diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000000..12812b5ca4 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "6.2.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index a709d6bbf4..eb83aeb16a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,7 @@ indent_size = 2 [*.cs] indent_style = space indent_size = 4 +dotnet_diagnostic.IDE0005.severity = error [*.cake] indent_style = space @@ -20,4 +21,14 @@ indent_size = 4 [*.js] indent_style = tab -indent_size = 2 \ No newline at end of file +indent_size = 2 + +# Verify settings +[*.{received,verified}.{txt,xml,json,cake}] +charset = "utf-8-bom" +end_of_line = lf +indent_size = unset +indent_style = unset +insert_final_newline = false +tab_width = unset +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..75b9c6b431 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,73 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto +*.sh text eol=lf + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain + +############################################################################### +# Verify files +############################################################################### + +*.verified.txt text eol=lf working-tree-encoding=UTF-8 +*.verified.xml text eol=lf working-tree-encoding=UTF-8 +*.verified.json text eol=lf working-tree-encoding=UTF-8 +*.verified.cake text eol=lf working-tree-encoding=UTF-8 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index a0c8ae48d7..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,67 +0,0 @@ - - -### What You Are Seeing? - -### What is Expected? - -### What version of Cake are you using? - -### Are you running on a 32 or 64 bit system? - -### What environment are you running on? Windows? Linux? Mac? - -### Are you running on a CI Server? If so, which one? - - - -### How Did You Get This To Happen? (Steps to Reproduce) - - - -### Output Log - - -GIST LINK - Please create a gist and link to that gist here - -OR - -~~~sh -PLACE LOG CONTENT HERE -~~~ - - diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..f6dfd77147 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,78 @@ +name: Bug report +description: Create a report to help us improve +body: +- type: checkboxes + attributes: + label: Prerequisites + options: + - label: I have written a descriptive issue title + required: true + - label: I have searched [issues](https://github.com/cake-build/cake/issues) to ensure it has not already been reported + required: true +- type: dropdown + attributes: + label: Cake runner + options: + - Cake .NET Tool + - Cake Frosting + - Cake SDK + multiple: true + validations: + required: true +- type: input + attributes: + label: Cake version + validations: + required: true +- type: dropdown + attributes: + label: Operating system + options: + - Linux + - Windows + - macOS + - FreeBSD + - N/A + multiple: true + validations: + required: true +- type: dropdown + attributes: + label: Operating system architecture + options: + - x64 + - x86 + - Arm64 + - N/A + validations: + required: true +- type: input + attributes: + label: CI Server + description: If you're running on a build server (GitHub Actions, Azure DevOps, etc) + validations: + required: false +- type: textarea + attributes: + label: What are you seeing? + description: Describe the issue you are seeing + validations: + required: true +- type: textarea + attributes: + label: What is expected? + description: Describe what you would expect + validations: + required: true +- type: textarea + attributes: + label: Steps to Reproduce + description: List of steps or sample project to reproduce the issue + validations: + required: true +- type: textarea + attributes: + label: Output log + description: Log messages you receive when running with --verbosity=diagnostic. Make sure there is no sensitive data shared and that you place a stack trace inside a code (```) block to avoid formatting issues. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..e3826d7bd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask for help or share ideas + url: https://github.com/cake-build/cake/discussions/category_choices + about: Ask the community for help or share ideas for new features. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..8118f81940 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,67 @@ +name: Build +on: + pull_request: + push: + branches: + - main + - develop + - hotfix/* +jobs: + prepare: + name: Prepare integration tests + runs-on: ubuntu-latest + steps: + - run: echo "Cake Integration Tests" > cake-integration-tests.txt + - uses: actions/upload-artifact@v4 + with: + name: cake-integration-tests + path: cake-integration-tests.txt + + build: + name: Build + needs: prepare + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-15-intel, ubuntu-latest, macos-latest] + steps: + - name: Get the sources + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Install .NET SDK 8.0.x - 9.0.x + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0.x + 9.0.x + + - name: Install .NET SDK (global.json) + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Run Cake script + id: build-cake + uses: cake-build/cake-action@v3.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AZURE_DEVOPS_NUGET_API_KEY: ${{ secrets.AZURE_DEVOPS_NUGET_API_KEY }} + AZURE_DEVOPS_NUGET_API_URL: ${{ secrets.AZURE_DEVOPS_NUGET_API_URL }} + with: + target: GitHubActions + cake-version: tool-manifest + + - name: Validate Integration Tests + id: validate-cake + uses: cake-build/cake-action@v3.0.1 + with: + script-path: tests/integration/Cake.Common/Build/GitHubActions/ValidateGitHubActionsProvider.cake + cake-version: tool-manifest + arguments: | + CAKE_NETCOREAPP_8_0_VERSION_OS: ${{ steps.build-cake.outputs.CAKE_NETCOREAPP_8_0_VERSION_OS }} + CAKE_NETCOREAPP_9_0_VERSION_OS: ${{ steps.build-cake.outputs.CAKE_NETCOREAPP_9_0_VERSION_OS }} + CAKE_NETCOREAPP_10_0_VERSION_OS: ${{ steps.build-cake.outputs.CAKE_NETCOREAPP_10_0_VERSION_OS }} + ValidateGitHubActionsProvider: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..748739a61d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: "CodeQL" + +on: + push: + branches: [ develop, main] + pull_request: + branches: [ develop ] + schedule: + - cron: '41 21 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - language: 'csharp' + build-mode: 'autobuild' + + steps: + + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Install .NET SDK 8.0.x - 9.0.x + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0.x + 9.0.x + + - name: Install .NET SDK (global.json) + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + env: + CAKE_INSTALL_SUPPORTED_SDKS: true + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..c9197da9fa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release + +on: + push: + tags: + - v* + +permissions: + id-token: write + contents: write + packages: write + issues: write + + +jobs: + deployment: + runs-on: windows-latest + environment: Production + steps: + - name: Get the sources + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Azure login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Install .NET SDK 8.0.x - 9.0.x + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0.x + 9.0.x + + - name: Install .NET SDK (global.json) + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: NuGet login (OIDC → temp API key) + uses: NuGet/login@v1 + id: login + with: + user: ${{secrets.NUGET_USER}} + + + - name: Build & Release Cake + uses: cake-build/cake-action@v3.0.1 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CAKE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AZURE_DEVOPS_NUGET_API_KEY: ${{ secrets.AZURE_DEVOPS_NUGET_API_KEY }} + AZURE_DEVOPS_NUGET_API_URL: ${{ secrets.AZURE_DEVOPS_NUGET_API_URL }} + NUGET_API_KEY: ${{steps.login.outputs.NUGET_API_KEY}} + NUGET_API_URL: ${{ secrets.NUGET_API_URL }} + SIGN_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + SIGN_CLIENT_SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + SIGN_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + SIGN_KEYVAULT_URL: ${{ secrets.SIGN_KEYVAULT_URL }} + SIGN_KEYVAULT_CERTIFICATE: ${{ secrets.SIGN_KEYVAULT_CERTIFICATE }} + with: + cake-version: tool-manifest + target: GitHubActions-Release \ No newline at end of file diff --git a/.gitignore b/.gitignore index 16fbe87b05..98ea8ea147 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,24 @@ [Tt]emp/ [Ll]ib/ [Pp]ackages/ -/[Bb]uild/ /[Aa]rtifacts/ +/[Tt]ools/ *.sln.ide/ +# .NET CLI +/.dotnet/ +dotnet-install.sh* +/.packages/ + +# Visual Studio +.vs/ +.vscode/ +launchSettings.json +project.lock.json + +# Rider +.idea/ + # Build related build-results/ tools/Cake/ @@ -17,6 +31,7 @@ tools/nuget.exe tools/gitreleasemanager/ tools/GitVersion.CommandLine/ tools/Addins/ +tools/packages.config.md5sum # mstest test results TestResults @@ -74,3 +89,12 @@ packages # Windows Thumbs.db + +# Mac +.DS_Store + +# Generated Assembly info +AssemblyInfo.Generated.cs + +# Verify +*.received \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..c6428caa5a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,9 @@ +image: cakebuild/cake:sdk-7.0 +stages: +- build + +build_job: + stage: build + script: + - git fetch --unshallow || true + - ./build.sh --target="Run-Unit-Tests" diff --git a/.rwx/cake.yml b/.rwx/cake.yml new file mode 100644 index 0000000000..0a06cf3138 --- /dev/null +++ b/.rwx/cake.yml @@ -0,0 +1,80 @@ +on: + github: + pull_request: + init: + commit-sha: ${{ event.git.sha }} + push: + - if: ${{ event.git.branch == 'develop' || event.git.branch == 'main' || starts-with(event.git.branch, 'hotfix/') }} + init: + commit-sha: ${{ event.git.sha }} + cli: + init: + commit-sha: ${{ event.git.sha }} + +base: + image: ubuntu:26.04 + config: rwx/base 1.1.1 + +tasks: + - key: code + call: git/clone 2.0.7 + with: + repository: https://github.com/cake-build/cake.git + ref: ${{ init.commit-sha }} + fetch-full-depth: true + preserve-git-dir: true + + - key: install-dotnet + use: code + call: dotnet/install 1.0.0 + with: + dotnet-channels: '["8.0", "9.0"]' + global-json-file: global.json + filter: + - global.json + + - key: build + use: [code, install-dotnet] + run: | + # We clone at a commit SHA, so HEAD is detached; GitVersion needs a named branch ref. + # Use plumbing commands so the working tree (including any patched files) is untouched. + git branch -f develop HEAD + git symbolic-ref HEAD refs/heads/develop + + dotnet tool restore + dotnet cake --target=Rwx --integration-tests-target=Cake.Common.Build.RwxProvider + env: + CAKE_INSTALL_SUPPORTED_SDKS: "true" + NuGetAudit: "false" + + - key: validate-output-artifact + use: [code, build] + run: | + CONTENTS=$(cat "$ARTIFACT_PATH") + if [ "$CONTENTS" != "cake integration test" ]; then + echo "Integration tests artifact contents are incorrect: '$CONTENTS'" + exit 1 + fi + echo "Integration tests artifact contents are correct: $CONTENTS" + env: + ARTIFACT_PATH: ${{ tasks.build.artifacts['cake-rwx-artifact.txt'] }} + + - key: validate-output-value + use: [code, build] + run: | + if [ "$OUTPUT_VALUE" != "ok" ]; then + echo "Output value contents are incorrect: '$CONTENTS'" + exit 1 + fi + echo "Output value contents are correct: $CONTENTS" + env: + OUTPUT_VALUE: ${{ tasks.build.values.cake_integration_test }} + + - key: validate-environment-variable + use: [code, build] + run: | + if [ "$CAKE_INTEGRATION_TEST" != "ok" ]; then + echo "Environment variable contents are incorrect: '$CAKE_INTEGRATION_TEST'" + exit 1 + fi + echo "Environment variable contents are correct: $CAKE_INTEGRATION_TEST" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b20c228969..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: csharp -script: - - ./build.sh -os: - - linux - - osx -cache: - directories: - - src/packages - - tools diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000000..6d0a474ffc --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,13 @@ +when: + - event: push + branch: develop + - event: push + branch: main + - event: manual + +steps: + - name: build + image: mcr.microsoft.com/dotnet/sdk:9.0 + commands: + - git fetch --unshallow || true + - ./build.sh --target="Run-Integration-Tests" \ No newline at end of file diff --git a/CODEOFCONDUCT.md b/CODEOFCONDUCT.md deleted file mode 100644 index 92fcabf88a..0000000000 --- a/CODEOFCONDUCT.md +++ /dev/null @@ -1,24 +0,0 @@ -# Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. - -We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery -- Personal attacks -- Trolling or insulting/derogatory comments -- Public or private harassment -- Publishing other's private information, such as physical or electronic addresses, without explicit permission -- Other unethical or unprofessional conduct - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [cake-build@outlook.com](mailto:cake-build@outlook.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. - -This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..021c03b84d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery +- Personal attacks +- Trolling or insulting/derogatory comments +- Public or private harassment +- Publishing other's private information, such as physical or electronic addresses, without explicit permission +- Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [caketeam@cakebuild.net](mailto:caketeam@cakebuild.net). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b8dd52499..fd179be432 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ By contributing to Cake, you assert that: * You have the right to assign the copyright for the work (it is not owned by your employer, or you have been given copyright assignment in writing). * You [license](https://github.com/cake-build/cake/blob/develop/LICENSE) the contribution under the terms applied to the rest of the Cake project. -* You agree to follow the [code of conduct](https://github.com/cake-build/cake/blob/develop/CODEOFCONDUCT.md). +* You agree to follow the [code of conduct](https://github.com/cake-build/cake/blob/develop/CODE_OF_CONDUCT.md). ## Definition of trivial contributions It's hard to define what is a trivial contribution. Sometimes even a 1 character change can be considered significant. @@ -64,11 +64,11 @@ Any new code should also have reasonable unit test coverage. ## Contributing process ### Get buyoff or find open community issues or features - * Through GitHub, or through the [Gitter chat](https://gitter.im/cake-build/cake) (preferred), + * Through GitHub, or through the [GitHub discussions](https://github.com/cake-build/cake/discussions) (preferred), you talk about a feature you would like to see (or a bug), and why it should be in Cake. - * If approved through the Gitter chat, ensure an accompanying GitHub issue is created with + * If approved through the GitHub discussions, ensure an accompanying GitHub issue is created with information and a link back to the discussion. - * Once you get a nod from one of the [Cake Team](https://github.com/cake-build?tab=members), you can start on the feature. + * Once you get a nod from one of the [Cake Team](https://github.com/orgs/cake-build/people), you can start on the feature. * Alternatively, if a feature is on the issues list with the [Up For Grabs](https://github.com/cake-build/cake/labels/up-for-grabs) label, it is open for a community member (contributor) to patch. You should comment that you are signing up for it on diff --git a/GitReleaseManager.yaml b/GitReleaseManager.yaml index e9bd8c17bc..d564200490 100644 --- a/GitReleaseManager.yaml +++ b/GitReleaseManager.yaml @@ -1,8 +1,29 @@ issue-labels-include: -- Bug +- Breaking change - Feature - Improvement +- Bug - Documentation -- Breaking change issue-labels-exclude: -- Build \ No newline at end of file +- Build +issue-labels-alias: + - name: Documentation + header: Documentation + plural: Documentation +create: + include-sha-section: true + sha-section-heading: "SHA256 Hashes of the release artifacts" + sha-section-line-format: "- `{1}\t{0}`" + include-contributors: true +close: + use-issue-comments: true + set-due-date: true + issue-comment: |- + :tada: This issue has been resolved in version {milestone} :tada: + + The release is available on: + + - [GitHub Release](https://github.com/{owner}/{repository}/releases/tag/{milestone}) + - [.NET Tool](https://www.nuget.org/packages/Cake.Tool/{milestone}) + + Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package::rocket: \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000000..6122b6e5f5 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,9 @@ +workflow: GitFlow/v1 +next-version: 6.0.0 +branches: + main: + label: beta +ignore: + sha: + - 2a4757b270f7946122ba6622e3d2e72b2b2808a7 + - 3e91c23637b97bc4e4c3234f93ffd03e6af70e8c diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml deleted file mode 100644 index 34aeb5f0a2..0000000000 --- a/GitVersionConfig.yaml +++ /dev/null @@ -1,7 +0,0 @@ -branches: - main: - mode: ContinuousDelivery - tag: - increment: Patch - prevent-increment-of-merged-branch-version: true - track-merge-target: false \ No newline at end of file diff --git a/README.md b/README.md index a854e4d280..5023d26f27 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,95 @@ -#Cake - -[![NuGet](https://img.shields.io/nuget/v/Cake.svg)](https://www.nuget.org/packages/Cake) [![MyGet](https://img.shields.io/myget/cake/v/Cake.svg?label=myget)](https://www.myget.org/gallery/cake) [![Chocolatey](https://img.shields.io/chocolatey/v/Cake.portable.svg)](https://chocolatey.org/packages/cake.portable) -[![homebrew](https://img.shields.io/homebrew/v/cake.svg)](http://braumeister.org/formula/cake) +# Cake Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. -| Build server | Platform | Status | -|-----------------------------|--------------|---------------------------------------------------------------------------------------------------------------------------| -| AppVeyor | Windows | [![AppVeyor branch](https://img.shields.io/appveyor/ci/cakebuild/cake/develop.svg)](https://ci.appveyor.com/project/cakebuild/cake/branch/develop) | -| Travis | Linux / OS X | [![Travis build status](https://travis-ci.org/cake-build/cake.svg?branch=develop)](https://travis-ci.org/cake-build/cake) | -| TeamCity | Windows | [![TeamCity Build Status](http://img.shields.io/teamcity/codebetter/Cake_CakeMaster.svg)](http://teamcity.codebetter.com/viewType.html?buildTypeId=Cake_CakeMaster) | -| Bitrise | OS X | ![Bitrise Build Status](https://www.bitrise.io/app/7a9d707b00881436.svg?token=m8zsF3tNONLaF03eHU-Ftg&branch=develop) | -| Bitrise | Linux | ![Bitrise Build Status](https://www.bitrise.io/app/b811c91a26b1ea80.svg?token=zdwab0niOTRF4p3HcFYaxQ&branch=develop) | -| Jenkins | Windows | [![Jenkins](https://img.shields.io/jenkins/s/https/cake-jenkins.azurewebsites.net/Cake.svg)](http://cake-jenkins.azurewebsites.net/job/Cake/lastStableBuild/) | -| Bamboo | Windows | [![Bamboo Build Status](https://bambooshield.azurewebsites.net/planstatus/Flat/CAKE-CAKE.svg)](https://cakebuild.atlassian.net/builds/browse/CAKE-CAKE) | -| Visual Studio Team Services | Windows | ![VSTS Build Status](https://img.shields.io/vso/build/cake-build/af63183c-ac1f-4dbb-93bc-4fa862ea5809/1.svg) | -| MyGet Build Services | Windows | [![cake-myget-build-service MyGet Build Status](https://www.myget.org/BuildSource/Badge/cake-myget-build-service?identifier=53513546-050e-45de-9500-f161c99df6e2)](https://www.myget.org/) | - -## Table of contents - -1. [Documentation](https://github.com/cake-build/cake#documentation) -2. [Example](https://github.com/cake-build/cake#example) - - [Install the Cake bootstrapper](https://github.com/cake-build/cake#1-install-the-cake-bootstrapper) - - [Create a Cake script](https://github.com/cake-build/cake#2-create-a-cake-script) - - [Run it!](https://github.com/cake-build/cake#3-run-it) -3. [Contributing](https://github.com/cake-build/cake#contributing) -4. [Get in touch](https://github.com/cake-build/cake#get-in-touch) -5. [License](https://github.com/cake-build/cake#license) - -## Documentation - -You can read the latest documentation at [http://cakebuild.net/](http://cakebuild.net/). - -## Example - -This example dowloads the Cake bootstrapper and executes a simple build script. -The bootstrapper is used to bootstrap Cake in a simple way and is not in -required in any way to execute build scripts. If you prefer to invoke the Cake -executable yourself, [take a look at the command line usage](http://cakebuild.net/docs/cli/usage). - -This example is also available on our homepage: -[http://cakebuild.net/docs/tutorials/setting-up-a-new-project](http://cakebuild.net/docs/tutorials/setting-up-a-new-project) - -### 1. Install the Cake bootstrapper - -The bootstrapper is used to download Cake and the tools required by the -build script. - -##### Windows - -```powershell -Invoke-WebRequest http://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1 -``` - -##### Linux +| Runner | Latest Released | Latest Develop | +|----------------|-----------------|----------------| +| Cake .NET Tool | [![NuGet](https://img.shields.io/nuget/v/Cake.Tool.svg)](https://www.nuget.org/packages/Cake.Tool) | [![Azure Artifacts](https://azpkgsshield.azurevoodoo.net/cake-build/Cake/cake/cake.tool)](https://dev.azure.com/cake-build/Cake/_packaging?_a=package&feed=cake&package=Cake.Tool&protocolType=NuGet) | +| Cake Frosting | [![NuGet](https://img.shields.io/nuget/v/Cake.Frosting.svg)](https://www.nuget.org/packages/Cake.Frosting) | [![Azure Artifacts](https://azpkgsshield.azurevoodoo.net/cake-build/Cake/cake/Cake.Frosting)](https://dev.azure.com/cake-build/Cake/_packaging?_a=package&feed=cake&package=Cake.Frosting&protocolType=NuGet) | +| Cake SDK | [![NuGet](https://img.shields.io/nuget/v/Cake.Sdk.svg)](https://www.nuget.org/packages/Cake.Sdk) | [![Azure Artifacts](https://azpkgsshield.azurevoodoo.net/cake-build/Cake/cake/Cake.Sdk)](https://dev.azure.com/cake-build/Cake/_packaging?_a=package&feed=cake&package=Cake.Sdk&protocolType=NuGet) | -```console -curl -Lsfo build.sh http://cakebuild.net/download/bootstrapper/linux -``` -##### OS X +## Continuous integration -```console -curl -Lsfo build.sh http://cakebuild.net/download/bootstrapper/osx -``` +| Build server | Platform | Build status | Integration tests | +|-----------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Azure Pipelines | MacOS | [![Azure Pipelines Mac Build status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Mac?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=4) | | +| Azure Pipelines | Windows | [![Azure Pipelines Windows Build status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Windows?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=1) | | +| Azure Pipelines | Debian | [![Azure Pipelines Debian Build status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Debian%20Stretch?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=7) | | +| Azure Pipelines | Fedora | [![Azure Pipelines Fedora Build status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Fedora%2028?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=6) | | +| Azure Pipelines | Centos | [![Azure Pipelines Cake Centos status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Centos%207?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=5) | | +| Azure Pipelines | Ubuntu | [![Azure Pipelines Ubuntu Build status](https://dev.azure.com/cake-build/Cake/_apis/build/status/Azure%20Pipelines%20-%20Build%20Cake%20Ubuntu?&branchName=develop)](https://dev.azure.com/cake-build/Cake/_build/latest?definitionId=3) | | +| AppVeyor | Windows | [![AppVeyor branch](https://img.shields.io/appveyor/ci/cakebuild/cake/develop.svg)](https://ci.appveyor.com/project/cakebuild/cake/branch/develop) | [![AppVeyor branch](https://img.shields.io/appveyor/ci/cakebuild/cake-eijwj/develop.svg)](https://ci.appveyor.com/project/cakebuild/cake-eijwj) | +| TeamCity | Windows | [![TeamCity Build Status](https://img.shields.io/teamcity/build/s/Cake_CakeMaster?server=https://teamcity.jetbrains.com)](https://teamcity.jetbrains.com/viewType.html?buildTypeId=Cake_CakeMaster) | | +| Bitrise | MacOS | [![Build Status](https://app.bitrise.io/app/42eaef77e8db4a5c/status.svg?token=EDjHGK5njNJ-MrhSbvKM1w&branch=develop)](https://app.bitrise.io/app/42eaef77e8db4a5c) | ![Build Status](https://app.bitrise.io/app/804b431c1f27e0a0/status.svg?token=qKosHEaJAJEqzZcq4s5WRg&branch=develop) | +| Bitrise | Debian | [![Build Status](https://app.bitrise.io/app/ea0c6b3c61eb1e79/status.svg?token=KJqOWXllYXz3WYqcB861Uw&branch=develop)](https://app.bitrise.io/app/ea0c6b3c61eb1e79) | ![Build Status](https://app.bitrise.io/app/5a406f34f22113c6/status.svg?token=TQPbsmA9yP-iJOhzunIP4w&branch=develop) | +| Bitbucket Pipelines | Debian | [![Build Status](https://bitbucketpipelinesshield.azurevoodoo.net/status/cakebuild/cake-integration-tests/develop)](https://bitbucketpipelinesshield.azurevoodoo.net/url/cakebuild/cake-integration-tests/develop) | | +| GitLab | Debian | [![pipeline status](https://gitlab.com/cake-build/cake/badges/develop/pipeline.svg)](https://gitlab.com/cake-build/cake/commits/develop) | | +| GitHub | Windows / Ubuntu/ macOS | [![Build Status](https://github.com/cake-build/cake/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/cake-build/cake/actions/workflows/build.yml) | | +| Woodpecker CI | Debian | [![Woodpecker CI status](https://ci.codeberg.org/api/badges/15091/status.svg)](https://ci.codeberg.org/repos/15091) | [![Woodpecker CI Status](https://ci.codeberg.org/api/badges/15091/status.svg)](https://ci.codeberg.org/repos/15091) | +| RWX | Ubuntu | [![RWX Build Status](https://cloud.rwx.com/status_badges/cake.svg?branch=develop&definition=.rwx%2Fcake.yml&repo=cake)](https://cloud.rwx.com/runs/latest/cake?branch=develop&definition=.rwx%2Fcake.yml&repo=cake) | [![RWX Build Status](https://cloud.rwx.com/status_badges/cake.svg?branch=develop&definition=.rwx%2Fcake.yml&repo=cake)](https://cloud.rwx.com/runs/latest/cake?branch=develop&definition=.rwx%2Fcake.yml&repo=cake) | -### 2. Create a Cake script +## Code Coverage -Add a cake script called `build.cake` to the same location as the -bootstrapper script that you downloaded. +[![Coverage Status](https://coveralls.io/repos/github/cake-build/cake/badge.svg?branch=develop)](https://coveralls.io/github/cake-build/cake?branch=develop) -```csharp -var target = Argument("target", "Default"); +## Table of Contents -Task("Default") - .Does(() => -{ - Information("Hello World!"); -}); - -RunTarget(target); -``` - -### 3. Run it! - -##### Windows - -```powershell -# Execute the bootstrapper script. -./build.ps1 -``` +1. [Documentation](https://github.com/cake-build/cake#documentation) +2. [Contributing](https://github.com/cake-build/cake#contributing) +3. [Get in touch](https://github.com/cake-build/cake#get-in-touch) +4. [License](https://github.com/cake-build/cake#license) -##### Linux / OS X +## Documentation -```console -# Adjust the permissions for the bootstrapper script. -chmod +x build.sh +You can read the latest documentation at [https://cakebuild.net/](https://cakebuild.net/). -# Execute the bootstrapper script. -./build.sh -``` +For a simple example to get started see [Setting up a new project](https://cakebuild.net/docs/getting-started/setting-up-a-new-scripting-project). ## Contributing So you’re thinking about contributing to Cake? Great! It’s **really** appreciated. -Make sure you've read the [contribution guidelines](http://cakebuild.net/contribute/contribution-guidelines/) before sending that epic pull request. +Make sure you've read the [contribution guidelines](https://cakebuild.net/docs/contributing/contribution-guidelines) before sending that epic pull request. You'll also need to sign the [contribution license agreement](https://cla.dotnetfoundation.org/cake-build/cake) (CLA) for anything other than a trivial change. **NOTE:** The .NET Foundation CLA Bot will provide a link to this CLA within the PR that you submit if it is deemed as required. * Fork the repository. +* Create a branch to work in. * Make your feature addition or bug fix. * Don't forget the unit tests. * Send a pull request. ## Get in touch -[![Follow @cakebuildnet](https://img.shields.io/badge/Twitter-Follow%20%40cakebuildnet-blue.svg)](https://twitter.com/intent/follow?screen_name=cakebuildnet) +[![Follow @cakebuild on Mastodon](https://img.shields.io/badge/Mastodon-Follow%20%40cakebuild-6364FF?style=flat&logo=mastodon&logoColor=white)](https://dotnet.social/@cakebuild) +[![Follow @cakebuild.net on Bluesky](https://img.shields.io/badge/Bluesky-Follow%20%40cakebuild.net-0085FF?style=flat&logo=bluesky&logoColor=white)](https://bsky.app/profile/cakebuild.net) -[![Join the chat at https://gitter.im/cake-build/cake](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cake-build/cake?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://github.com/cake-build/cake/discussions](https://img.shields.io/badge/discussions-join%20chat-brightgreen?style=flat&logo=github&logoColor=white)](https://github.com/cake-build/cake/discussions?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## License -Copyright © Patrik Svensson, Mattias Karlsson, Gary Ewan Park and contributors. +Copyright © .NET Foundation, Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors. + Cake is provided as-is under the MIT license. For more information see [LICENSE](https://github.com/cake-build/cake/blob/develop/LICENSE). * For Roslyn, see https://github.com/dotnet/roslyn/blob/master/License.txt -* For Mono.CSharp, see https://github.com/mono/mono/blob/master/mcs/LICENSE * For Autofac, see https://github.com/autofac/Autofac/blob/master/LICENSE -* For NuGet.Core, see https://nuget.codeplex.com/license +* For NuGet.Core, see https://github.com/NuGet/Home/blob/dev/LICENSE.txt ## Thanks -A big thank you has to go to [JetBrains](https://www.jetbrains.com) who provide each of the Cake Developers with an [Open Source License](https://www.jetbrains.com/support/community/#section=open-source) for [ReSharper](https://www.jetbrains.com/resharper/) that helps with the development of Cake. +A big thank you has to go to [JetBrains](https://www.jetbrains.com) who provide each of the Cake Developers with an [Open Source License](https://www.jetbrains.com/community/opensource/#support) for [ReSharper](https://www.jetbrains.com/resharper/) that helps with the development of Cake. + +### Sponsors + +Our wonderful sponsors: + +[![Sponsors](https://opencollective.com/cake/sponsors.svg)](https://opencollective.com/cake) + +### Backers + +Our wonderful backers: + +[![Backers](https://opencollective.com/cake/backers.svg)](https://opencollective.com/cake) ## Code of Conduct @@ -138,6 +97,10 @@ This project has adopted the code of conduct defined by the [Contributor Covenan to clarify expected behavior in our community. For more information see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). +## Contribution License Agreement + +By signing the [CLA](https://github.com/dotnet-foundation/.github/blob/main/CLA/dotnetfoundation.yml), the community is free to use your contribution to .NET Foundation projects. + ## .NET Foundation This project is supported by the [.NET Foundation](http://www.dotnetfoundation.org). diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 1fecea2da6..1afc54e7f2 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,1493 @@ -### New on 0.13.0 (Released 2016/06/07) +### New in 6.2.0 (Released 2026/05/22) + +* #4840 Add RWX as a build provider. +* #4832 Update Microsoft.Extensions.DependencyInjection to 9.0.16 (net9.0) & 10.0.8 (net10.0). +* #4830 Update Microsoft.IdentityModel.JsonWebTokens to 8.18.0. +* #4822 Update Spectre.Console to 0.55.2. +* #4820 Update Basic.Reference.Assemblies.* to 1.8.8. +* #4794 Update NuGet.* packages dependencies to avoid transitive package vulnerable warnings. +* #4784 Update Spectre.Console.* to 0.55.0. +* #4776 Microsoft.IdentityModel.JsonWebTokens to 8.17.0. +* #4771 Update System.Security.Cryptography.Pkcs to 9.0.14 & 10.0.5 (net9.0&net10.0). +* #4769 Update Microsoft.Extensions.DependencyInjection to 9.0.14 & 10.0.5 (net9.0&net10.0). +* #4765 Update Autofac to 9.1.0. +* #4763 Update Microsoft.CodeAnalysis.CSharp.Scripting to 5.3.0. +* #4442 Frosting DirectoryPath + ConvertableFilePath combines strings instead of paths. +* #4158 Cannot specify empty properties when using DotNetBuild. +* #2101 CakeReportPrinter should be disabled in Verbosity = Quiet. +* #4834 .NET Tool installer switch from --add-source to --source for .NET 9 & 10. +* #4456 DotNetMSBuild alias generates extra verbosity argument. +* #4454 Impossible to retrieve package versions list with DotNetSearchPackage. +* #4324 When running multiple targets, common dependent tasks are executed twice. +* #4066 SetupContext.TasksToExecute only lists tasks related to first target when calling RunTargets. +* #2666 GetFiles() using Glob curly braces gives empty result. + +### New in 6.1.0 (Released 2026/03/01) + +* #4656 Gitlab CI_PIPELINE_ID has exceeded the int limits on gitlab.com and the variable should be changed to a long. +* #4698 Add FormattableString Support to Logging Methods. +* #4520 add slnx support. +* #4071 Unable to set multiple of the same attribute CustomAttributes in AssemblyInfo creator. +* #3517 NuGet Pack Should support new readme file. +* #2028 In-Process NuGet doesn't support authentication. +* #4740 Add CakeModule assemmbly attribute to NuGetModule. +* #4737 Update Microsoft.Extensions.DependencyInjection to 9.0.13 & 10.0.3 (net9.0&net10.0). +* #4735 Update Microsoft.IdentityModel.JsonWebTokens to 8.16.0. +* #4733 Update NuGet.* to 7.3.0. +* #4715 Update System.Security.Cryptography.Pkcs to 9.0.12 & 10.0.2 (net9.0&net10.0). +* #4713 Update Microsoft.Extensions.DependencyInjection to 9.0.12 & 10.0.2 (net9.0&net10.0). +* #4694 Update Autofac to 9.0.0. +* #4692 Update System.Security.Cryptography.Pkcs to 10.0.1 for net10.0. +* #4688 Update Microsoft.IdentityModel.JsonWebTokens to 8.15.0. +* #4685 Update Microsoft.Extensions.DependencyInjection to 10.0.1 for net10.0. +* #4683 Update Microsoft.CodeAnalysis.CSharp.Scripting to 5.0.0. +* #4679 Update Basic.Reference.Assemblies.* to 1.8.4. +* #4677 Update NuGet.* to 7.0.1. +* #4675 Update Spectre.Console & Spectre.Console.Cli to 0.54.0 & 0.53.1. +* #4658 Add support for MSBuild 18 and VS2026. +* #4635 DotCover no longer works with version 2025.2+ of JetBrains.dotCover.CommandLineTools. +* #4720 Use non-boolean self-contained arguments for dotnet publish. +* #4706 Add support for .slnx files in dotnet test PathType autodetection. +* #4687 Cake Frosting FrostingConfiguration Command Line parameters are not parsed correctly. +* #4667 DotNetSlnList hardcodes English output. +* #4662 Colorization of console log output. + +### New in 6.0.0 (Released 2025/11/11) + +* #4639 Make CakeSpectreReportPrinter the default ICakeReportPrinter. +* #4638 Update Spectre.Console to 0.53.0. +* #4593 Update LatestPotentialBreakingChange to 6.0.0. +* #4534 Azure Pipleines Environement Agent Info uses FilePath instead of DirectoryPath. +* #4592 C# 14 Scripting Support. +* #4576 Add .NET 10 (net10.0) TFM. +* #4642 Display Delegated/Executed status as Succeded in Cake task summary. +* #4636 Enhance file system abstraction with timestamps, Unix modes, and performance improvements. +* #4627 .NET Test Platform Requires --project `` / --solution `` to be specified. +* #4624 Update System.* to 9.0.10. +* #4623 Update Microsoft.Extensions.DependencyInjection to 9.0.10. +* #4622 Update Spectre.Console to 0.52.0. +* #4616 Replace using with await using for IAsyncDisposable resources. +* #4615 Replace manual null checks with ArgumentNullException.ThrowIfNull(). +* #4614 Modernize Enum.GetName calls to use generic overload. +* #4613 Use char literals instead of string literals for single characters in StreamWriter.Write. +* #4612 Replace StringBuilder.Append(string.Join()) with StringBuilder.AppendJoin() for better performance. +* #4611 Use char literals instead of string literals for single characters in StringBuilder.Append. +* #4610 Replace string.Join with string.Concat for empty separator cases. +* #4609 Optimize string.Join calls to use char separators instead of string separators. +* #4608 Modernize dictionary access patterns with TryGetValue and deconstruction. +* #4603 Fix .NET framework version detection. + +### New in 5.1.0 (Released 2025/10/04) + +* #4539 Add Woodpecker CI Support. +* #4436 Add support for capturing MSBuild properties, items and target results. +* #4431 Add property for GitLab's "Pipeline Source" to GitLabCIBuildInfo. +* #4418 GitLab Server url should be available in GitLabCIServerInfo. +* #4416 Make Merge Request source and target branch available in GitLabCIPullRequestInfo. +* #4401 Add DotNetSlnRemove alias for dotnet sln remove command. +* #4584 Update Spectre.Console to 0.51.1. +* #4583 Update Newtonsoft.Json to 13.0.4. +* #4582 Update Microsoft.Extensions.DependencyInjection to 9.0.9. +* #4581 Update System.* to 9.0.9. +* #4549 Update Basic.Reference.Assemblies.Net90 to 1.8.3. +* #4548 Update Basic.Reference.Assemblies.Net80 to 1.8.3. +* #4547 Update Autofac to 8.4.0. +* #4546 Update Microsoft.IdentityModel.JsonWebTokens to 8.14.0. +* #4545 Update Microsoft.Extensions.DependencyInjection to 9.0.8. +* #4544 Update System.Reflection.Metadata to 9.0.8. +* #4543 Update System.Collections.Immutable to 9.0.8. +* #4542 Update System.Security.Cryptography.Pkcs to 9.0.8. +* #4529 Update System.Reflection.Metadata 9.0.7. +* #4528 Update System.Collections.Immutable to 9.0.7. +* #4527 Update System.Security.Cryptography.Pkcs to 9.0.7. +* #4526 Update Microsoft.Extensions.DependencyInjection to 9.0.7. +* #4523 Update Microsoft.IdentityModel.JsonWebTokens to 8.12.1. +* #4510 Update Microsoft.IdentityModel.JsonWebTokens to 8.12.0. +* #4509 Update Basic.Reference.Assemblies.Net* to 1.8.2. +* #4505 Update Basic.Reference.Assemblies.Net* to 1.8.1. +* #4497 Cake.Testing.Xunit.v3 should not reference xunit.v3. +* #4493 Update Spectre.Console to 0.50.0. +* #4492 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.14.0. +* #4491 Update System.Reflection.Metadata to 9.0.5. +* #4490 Update System.Collections.Immutable to 9.0.5. +* #4489 Update Autofac to 8.3.0. +* #4488 Update Microsoft.Extensions.DependencyInjection to 9.0.5. +* #4483 Update System.Security.Cryptography.Pkcs to 9.0.5. +* #4482 Update Microsoft.IdentityModel.JsonWebTokens to 8.11.0. +* #4481 Update NuGet.* to 6.14.0. +* #4471 Improve script caching behavior. +* #4440 Update Basic.Reference.Assemblies.Net80 to 1.8.0. +* #4426 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.12.0. +* #4425 Update NuGet.* to 6.12.1. +* #4421 Update Microsoft.IdentityModel.JsonWebTokens to 8.3.0. +* #4420 Update Autofac to 8.2.0. +* #4412 NuGet 6.9 renamed no-cache to no-http-cache and deprecated the old setting name. +* #4407 SignTool missing the /kc and /csp parameters. . +* #4399 Refactor DotNetAliases: Extract methods into a separate partial class. +* #4478 Failed to install addin when running from Windows powershell.exe. +* #4475 Script execution does not always show a report summary. +* #4410 AzurePipelinesPullRequestInfo fails to detect if build is running in the context of a GitHub Pull Request. +* #4286 DotNetToolRunner doesn't support empty space at tool path. + +### New in 5.0.0 (Released 2024/11/13) + +* #4384 Remove obsolete members / methods. +* #4383 Update LatestPotentialBreakingChange to 5.0.0. +* #4346 Remove Unsupported TFMS .NET 6 & 7 (net6.0 & net7.0). +* #4396 Add DotNetSlnAdd alias for dotnet sln add command. +* #4393 Add DotNetSlnList alias for dotnet sln list command. +* #4379 C# 13 Scripting Support. +* #4345 Add .NET 9 (net9.0) TFM. +* #4310 Update Cake.Tool to support running on FreeBSD. +* #4391 DownloadArtifacts errors in latest Cake 4.2.0 with 404 error. + +### New in 4.2.0 (Released 2024/10/23) + +* #4374 Argument 'foo' was not set" after update to 4.1 in Cake Frosting. + +### New in 4.1.0 (Released 2024/10/22) + +* 4353 Add DotNetListReference alias for dotnet list reference command. +* 4352 Add DotNetRemoveReference alias for dotnet remove reference command . +* 4334 Add DotNetSearchPackage Alias for dotnet package search. +* 4282 Add DotNetAddReference alias for dotnet add reference command. +* 4224 Add DotNetListPackage alias for dotnet list package command. +* 4363 Update System.Reflection.Metadata to 8.0.1. +* 4364 Update Microsoft.Extensions.DependencyInjection to 8.0.1. +* 4362 Update NuGet.* to 6.11.1. +* 4359 Update Microsoft.IdentityModel.JsonWebTokens to 8.1.2. +* 4358 Update Autofac to 8.1.1. +* 4355 Different target argument values depending on specifying --target or --Target. +* 4351 Use NuGet Client built-in Settings Utility to Get Enabled Sources. +* 4349 GitHubActionCommand DownloadArtifact is not using the latest @v4 Version. +* 4343 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.11.0. +* 4342 Update Basic.Reference.Assemblies.* to 1.7.9. +* 4341 Update Autofac to 8.1.0. +* 4340 Update NuGet.* to 6.11.0. +* 4335 Add ReportGenerator missing markdown report types. +* 4331 GitHubActionCommand UploadArtifact is not using the latest @v4 Version. +* 4322 FakeFileSystem throws on linux, when it is created on a Windows FakeEnvironment. +* 4306 Update Autofac to 8.0.0. +* 4305 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.9.2. +* 4304 Update Basic.Reference.Assemblies.* to 1.5.0. +* 4303 Update Microsoft.NET.Test.Sdk to 17.9.0. +* 4302 Update Verify.Xunit to 23.5.2. +* 4301 Update xunit to 2.7.0. +* 4300 Update NuGet.* to 6.9.1. +* 4299 Update NuGet.Packaging to 6.8.1. +* 4278 Update Spectre.Console to 0.49.1. +* 4276 Update NuGet.* to 6.8.0. +* 4274 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.8.0. + +### New in 4.0.0 (Released 2023/11/18) + +* 4266 Update LatestPotentialBreakingChange to 4.0.0. +* 4132 Add File APIs for setting timestamps (creation time, last write time, last access time). +* 4250 Update System.Collections.Immutable to 8.0.0. +* 4260 Unzip alias should support overwrite files. +* 4251 Update System.Reflection.Metadata 8.0.0. +* 4249 Update Microsoft.Extensions.DependencyInjection to 8.0.0. +* 4197 Execution of Cake script fails if an addin defines an alias that uses nullable reference types in its signature. +* 4150 Cake script is contributing unactionable diagnostics in VS Code Problems pane. + +### New in 3.2.0 (Released 2023/11/10) + +* 4225 Add DotNetRemovePackage alias for dotnet remove package command. +* 4187 Add DotNetAddPackage alias for dotnet add package command. +* 4221 Add Azure Pipelines group logging commands. +* 4219 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.7.0. +* 4217 Update NuGet.* to 6.7.0. +* 4215 Update Autofac to 7.1.0. +* 4157 Upgrading to spectre.console 0.47.0 breaks the cake build. +* 4144 DotNetMSBuildSettings is missing NodeReuse. +* 3996 Error: Bad IL format with Cake MacOSX (2.3.0 - 3.1.0). + +### New in 3.1.0 (Released 2023/07/09) + +* 4122 Call multiple tasks from CLI in Frosting. +* 4092 Add support for getting the user's home directory in the Cake Environment. +* 4184 Update Autofac to 7.0.1. +* 4183 Update System.Reflection.Metadata to 7.0.2. +* 4182 Update Basic.Reference.Assemblies.Net60/Net70 to 1.4.2. +* 4181 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.6.0. +* 4170 Update NuGet.* to v6.6.1. +* 4138 Upgrade Spectre.Console to v0.46.0. +* 4109 Add PublishReadyToRun to DotNetRestoreSettings. +* 4107 DotNetPublishSettings is missing a way to set the --os option. +* 4090 Update Microsoft.CodeAnalysis.CSharp.Scripting to v4.4.0. +* 4087 Update Newtonsoft.Json to v13.0.2. +* 4086 Update Autofac to v6.5.0. +* 4085 Update NuGet.* to v6.4.0. +* 1317 CleanDirectory does not clean readonly files. +* 4095 Fix broken link to Cake Team on CONTRIBUTING.md. +* 4128 Inconsistent determination of positional Argument when using context.Arguments versus context.Argument. + +### New in 3.0.0 (Released 2022/11/08) + +* 4046 Add typed data context CakeTaskBuilder.Finally overload. +* 4000 Remove obsolete CakeEngine Setup/Teardown events. +* 3997 Remove obsolete Logger property from DotNetTestSettings. +* 3991 Remove obsolete DotNetCore aliases. +* 3972 Remove .NET Core 3.1 TFM. +* 3969 Update Spectre.Console to 0.45.0. +* 3949 Remove .NET 5 TFM. +* 3946 Access to ITaskSetupContext in Frosting. +* 3867 Rename WindowsFact to WindowsFactAttribute to follow best practices. +* 3722 DotNetCore -> DotNet Rename Missed Objects. +* 4047 Add typed CakeTaskBuilder / TaskOf() to easier work with typed data context. +* 4028 Add GitLab CI SetEnvironmentVariable Command. +* 4019 Add support for TeamCity build status message. +* 4018 Add Support For TeamCity Statistics. +* 4011 Add GitHub Actions SetStepSummary Command. +* 4009 Add GitHub Actions SetOutputParameter Command. +* 3950 Add .NET 7 Support. +* 3328 Frosting: Support criteria description. +* 2863 Add support for GitHub Action workflow commands. +* 2470 Call multiple tasks from CLI and pass them to RunTarget. +* 1146 Add OutputDirectory property for Chocolatey Aliases. +* 4060 Update Microsoft.NETCore.Platforms to 7.0.0. +* 4059 Update System.Reflection.Metadata to 7.0.0. +* 4058 Update System.Collections.Immutable to 7.0.0. +* 4057 Update Microsoft.Extensions.DependencyInjection to 7.0.0. +* 4055 Add column to summary to include skip reason. +* 4052 Update Basic.Reference.Assemblies.* to 1.4.1. +* 4050 Overhaul Chocolatey Cake aliases. +* 4044 Overload missing for IsDependeeOf accepting a CakeTaskBuilder object. +* 4038 Add missing MSBuildSettings to DotNetRunSettings. +* 4036 Add missing MSBuildSettings to DotNetTestSettings. +* 4032 Add additional settings for ILMerge. +* 4020 Update Basic.Reference.Assemblies.* to 1.4.0. +* 4016 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.4.0-4.final. +* 4006 Add missing GitVersion command line options. +* 3124 EscapedBranchName is not supported by GitVersion.CommandLine. +* 4023 Update supported versions of Cake. +* 4034 DotNetMSBuildSettings ArgumentCustomization is not called with all DotNet* aliases. +* 4030 GitLab CI runner tags are not split correctly. + +### New in 2.3.0 (Released 2022/10/14) + +* 3947 Easier Way to Read Process Output?. +* 3916 GitVersion: Add ShortSha property. +* 3487 Add alias for dotnet workload update command. +* 3486 Add alias for dotnet workload uninstall command. +* 3484 Add alias for dotnet workload restore command. +* 3483 Add alias for dotnet workload repair command. +* 3482 Add alias for dotnet workload list command. +* 3978 Microsoft.Extensions.DependencyInjection to 6.0.1. +* 3976 Update NuGet.* to 6.3.1. +* 3970 Update Basic.Reference.Assemblies.* to 1.3.0. +* 3965 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.3.1. +* 3956 Extensibility issue - CakeEngineActions is internal. +* 3933 Update NuGet.* to 6.3.0. +* 3920 Update Microsoft.NETCore.Platforms to 6.0.5. +* 3909 Update Autofac to 6.4.0. +* 3901 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.2.0. +* 3899 Microsoft.NETCore.Platforms to 6.0.4. +* 3897 Update NuGet.* to 6.2.1. +* 3890 Update NuGet.* to 6.2.0. +* 3880 Better support global script cache. +* 2953 Allow setting MSBuild target via MSBuildSettings using a string. +* 2591 Extensibility issue - CakeTaskBuilder is sealed and CakeTaskBuilder(CakeTask task) is internal. . +* 3931 Cake fails to load native libraries on Ubuntu 22.04. +* 3894 Guard against null Console instance on InfoFeature. +* 3879 Build script caching throws after running dry-run on non-changed Cake script. +* 3878 OpenCover filters should be case sensitive. +* 1852 Incorrect escaping of semi-colon in property values for MS Build. + +### New in 2.2.0 (Released 2022/04/15) + +* 3821 PostAction is not setable on DotNetSettings. +* 3485 Add alias for dotnet workload search command. +* 2099 Cache compiled script on disk. +* 3866 Update Microsoft.NETCore.Platforms to 6.0.3. +* 3854 Update Spectre.Console to 0.44.0. +* 3851 Update System.Reflection.Metadata to 6.0.1. +* 3846 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.1.0. +* 3844 Update Microsoft.NETCore.Platforms to 6.0.2. +* 3843 Update NuGet.* to 6.1.0. +* 2763 Provide property to return parent directory on DirectoryPath. +* 2431 UploadFile should support option of username/password. +* 3819 Update Git Release Manager Comment template to remove Cake NuGet package and Chocolatey portable. +* 3859 PathCollapser.Collapse breaks UNC paths. +* 3858 PathCollapser.Collapse shows wrong output for if .. is the second segment in the path. +* 3823 Executing a cake script leads to System.IO.FileNotFoundException for several System.(...) assemblies. +* 3735 Incorrect warnings in diagnostic logs. + +### New in 2.1.0 (Released 2022/02/19) + +* 2524 XmlTransform support for xsl arguments +* 3479 Add alias for dotnet format command +* 3480 Add alias for dotnet sdk check command +* 3771 Add support for the Chocolatey Export command +* 2746 Add duration of a task +* 3733 Show relative path of addin assemblies that are being loaded +* 3756 Update NuGet.* to 6.0.0 +* 3758 Update Autofac to 6.3.0 +* 3760 Update Spectre.Console to 0.43.0 +* 3764 Add missing GitHub Actions environment info +* 3769 Update Microsoft.NETCore.Platforms to 6.0.1 +* 3776 Introduce IPath interface for easier code reuse +* 3777 GitHub Actions UploadArtifact command should accept relative paths +* 3778 Add GitHub Actions DownloadArtifact command +* 3743 SemVersion class crashes if compared to `null` +* 3772 VSTest Alias does not work when only VS 2022 Preview is installed +* 3794 VS2022 BuildTools are not found by the logic introduced in #3775 + +### New in 2.0.0 (Released 2021/11/30) + +* 3714 Use Basic.Reference.Assemblies.* to ensure all standard reference assemblies are available for Roslyn. +* 3654 IsRunningOnAzurePipelines should ignore agent type. +* 3631 Refactor GitHub Actions Paths. +* 3610 Remove TFBuildProvider. +* 3590 Directories in AzurePipelinesBuildInfo are FilePaths - FilePath.GetDirectory then inconsistent. +* 3581 Stop shipping Cake.Portable Chocolatey package and Cake Homebrew formulae. +* 3579 Stop shipping Cake runner for .NET Framework and Cake runner for .NET Core. +* 3577 Remove ReverseDependencyAttribute. +* 3572 Only build for TargetFrameworks netcoreapp3.1, net5.0 and net6.0. +* 3282 GitVersion Tool: Rename verbosity values to match GitVersion values. +* 3222 Add Xamarin.iOS platform targets to MSBuildSettings PlatformTarget enumeration. +* 3151 Add support for Engine event hooks after execution as well as before. +* 3003 Remove DependencyAttribute. +* 2872 Bump eol target frameworks. +* 2788 Tool:OpenCover - the register-setting should be an option, rather than a string. +* 1111 DotNetCoreRestore: dotnet restore no longer supports globbing. +* 3630 Add GitHub Actions Environment properties. +* 3629 Add GitHub Actions UploadArtifact Command. +* 3628 Add GitHub Actions SetEnvironmentVariable Command. +* 3627 Add GitHub Actions AddPath Command. +* 3341 Epic: Introduce DotNet aliases (synonyms to DotNetCore aliases). +* 3709 Arguments alias should support ICollection as default value. +* 3691 Update Microsoft.NETCore.Platforms to 6.0.0. +* 3690 Update Microsoft.Extensions.DependencyInjection to 6.0.0. +* 3689 Update System.Reflection.Metadata to 6.0.0. +* 3688 Update System.Collections.Immutable to 6.0.0. +* 3681 ScriptAssemblyResolver logging should be at debug/diagnostic level. +* 3662 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-6.final. +* 3647 Display message of criteria when task fails to run due to criteria not being met. +* 3644 Add DotNetNuGetUpdateSource aliases (synonym to DotNetCoreNuGetUpdateSource). +* 3643 Add DotNetNuGetRemoveSource aliases (synonym to DotNetCoreNuGetRemoveSource). +* 3642 Add DotNetNuGetListSourceSettings (derived from to DotNetNuGetSource). +* 3641 Add DotNetNuGetHasSource aliases (synonym to DotNetCoreNuGetHasSource). +* 3640 Add DotNetNuGetEnableSource aliases (synonym to DotNetCoreNuGetEnableSource). +* 3639 Add DotNetNuGetDisableSource aliases (synonym to DotNetCoreNuGetDisableSource). +* 3607 Add EnableCompressionInSingleFile to DotNetCorePublishSettings. +* 3599 Add VS2022 to default MSBuild Resolver. +* 3598 Remove Preview from VS2022 MSBuild Resolver. +* 3595 Update Autofac to 6.3.0. +* 3593 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-5.final. +* 3591 Update Microsoft.NETCore.Platforms to 6.0.0-rc.2.21480.5. +* 3555 Add DotNetNuGetAddSource aliases (synonym to DotNetCoreNuGetAddSource). +* 3554 Add DotNetNuGetDelete aliases (synonym to DotNetCoreNuGetDelete). +* 3553 Add DotNetNuGetPush aliases (synonym to DotNetCoreNuGetPush). +* 3552 Add DotNetPack alias (synonym to DotNetCorePack). +* 3551 Add DotNetPublish alias (synonym to DotNetCorePublish). +* 3550 Add DotNetVSTest alias (synonym to DotNetCoreVSTest). +* 3549 Add DotNetTest alias (synonym to DotNetCoreTest). +* 3548 Add DotNetBuildServer alias (synonym to DotNetCoreBuildServer). +* 3547 Add DotNetBuild alias (synonym to DotNetCoreBuild). +* 3546 Add DotNetRestore alias (synonym to DotNetCoreRestore). +* 3545 Add DotNetClean alias (synonym to DotNetCoreClean). +* 3544 Add DotNetExecute alias (synonym to DotNetCoreExecute). +* 3543 Add DotNetRun alias (synonym to DotNetCoreRun). +* 3542 Add DotNetTool alias (synonym to DotNetCoreTool). +* 3523 Add DotNetMSBuild alias (synonym to DotNetCoreMSBuild). +* 3215 Add RunCommand with postAction parameter to DotNetCoreTool. +* 3075 Make FilePath and DirectoryPath comparable by value. +* 2571 OctopusDeploy DeployTo property to take collection of string to specify multiple environments. +* 2075 Add overloads for DotNetCore*() methods taking FilePath instead of string. +* 1794 Private is missing from ProjectReference. +* 1616 Error message on circular references leads to poor developer experience. +* 3701 Add cake-module tag to Cake.DotNetTool.Module NuGet package. +* 3602 Switch to Cake.Tool as primary package in REAME.md. +* 3711 SemanticVersion missing equals/not equals operator, prerelease sorted wrong. +* 3697 Error: The requested service 'Cake.Commands.DefaultCommandSettings' has not been registered. +* 3693 Core suffix is still used in some settings classes. +* 3683 Use DotNetMSBuildSettings instead of DotNetCoreMSBuildSettings on new dotnet aliases settings. +* 3671 VS2022: msbuild can not be located, only Build Tools are installed. +* 2665 C* 8 Using Statement produces compile error. +* 2443 Erroneous "Target path must be an absolute path" when preserveFolderStructure is used with CopyFiles. +* 1669 Release notes does not tolerate prerelease versions. + +### New in 2.0.0-rc0002 (Released 2021/11/26) + +* 3714 Use Basic.Reference.Assemblies.* to ensure all standard reference assemblies are available for Roslyn +* 3654 IsRunningOnAzurePipelines should ignore agent type +* 3631 Refactor GitHub Actions Paths +* 3610 Remove TFBuildProvider +* 3590 Directories in AzurePipelinesBuildInfo are FilePaths - FilePath.GetDirectory then inconsistent +* 3581 Stop shipping Cake.Portable Chocolatey package and Cake Homebrew formulae +* 3579 Stop shipping Cake runner for .NET Framework and Cake runner for .NET Core +* 3577 Remove ReverseDependencyAttribute +* 3572 Only build for TargetFrameworks netcoreapp3.1, net5.0 and net6.0 +* 3282 GitVersion Tool: Rename verbosity values to match GitVersion values +* 3222 Add Xamarin.iOS platform targets to MSBuildSettings PlatformTarget enumeration +* 3151 Add support for Engine event hooks after execution as well as before +* 3003 Remove DependencyAttribute +* 2872 Bump eol target frameworks +* 2788 Tool:OpenCover - the register-setting should be an option, rather than a string +* 1111 DotNetCoreRestore: dotnet restore no longer supports globbing +* 3630 Add GitHub Actions Environment properties +* 3629 Add GitHub Actions UploadArtifact Command +* 3628 Add GitHub Actions SetEnvironmentVariable Command +* 3627 Add GitHub Actions AddPath Command +* 3341 Epic: Introduce DotNet aliases (synonyms to DotNetCore aliases) +* 3711 SemanticVersion missing equals/not equals operator, prerelease sorted wrong +* 3697 Error: The requested service 'Cake.Commands.DefaultCommandSettings' has not been registered +* 3693 `Core` suffix is still used in some settings classes +* 3683 Use DotNetMSBuildSettings instead of DotNetCoreMSBuildSettings on new dotnet aliases settings +* 3671 VS2022: msbuild can not be located, only Build Tools are installed +* 2443 Erroneous "Target path must be an absolute path" when preserveFolderStructure is used with CopyFiles +* 1669 Release notes does not tolerate prerelease versions +* 3709 Arguments alias should support ICollection as default value +* 3691 Update Microsoft.NETCore.Platforms to 6.0.0 +* 3690 Update Microsoft.Extensions.DependencyInjection to 6.0.0 +* 3689 Update System.Reflection.Metadata to 6.0.0 +* 3688 Update System.Collections.Immutable to 6.0.0 +* 3681 `ScriptAssemblyResolver` logging should be at debug/diagnostic level +* 3662 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-6.final +* 3647 Display message of criteria when task fails to run due to criteria not being met +* 3644 Add DotNetNuGetUpdateSource aliases (synonym to DotNetCoreNuGetUpdateSource) +* 3643 Add DotNetNuGetRemoveSource aliases (synonym to DotNetCoreNuGetRemoveSource) +* 3642 Add DotNetNuGetListSourceSettings (derived from to DotNetNuGetSource) +* 3641 Add DotNetNuGetHasSource aliases (synonym to DotNetCoreNuGetHasSource) +* 3640 Add DotNetNuGetEnableSource aliases (synonym to DotNetCoreNuGetEnableSource) +* 3639 Add DotNetNuGetDisableSource aliases (synonym to DotNetCoreNuGetDisableSource) +* 3607 Add `EnableCompressionInSingleFile` to `DotNetCorePublishSettings` +* 3599 Add VS2022 to default MSBuild Resolver +* 3598 Remove Preview from VS2022 MSBuild Resolver +* 3595 Update Autofac to 6.3.0 +* 3593 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-5.final +* 3591 Update Microsoft.NETCore.Platforms to 6.0.0-rc.2.21480.5 +* 3555 Add DotNetNuGetAddSource aliases (synonym to DotNetCoreNuGetAddSource) +* 3554 Add DotNetNuGetDelete aliases (synonym to DotNetCoreNuGetDelete) +* 3553 Add DotNetNuGetPush aliases (synonym to DotNetCoreNuGetPush) +* 3552 Add DotNetPack alias (synonym to DotNetCorePack) +* 3551 Add DotNetPublish alias (synonym to DotNetCorePublish) +* 3550 Add DotNetVSTest alias (synonym to DotNetCoreVSTest) +* 3549 Add DotNetTest alias (synonym to DotNetCoreTest) +* 3548 Add DotNetBuildServer alias (synonym to DotNetCoreBuildServer) +* 3547 Add DotNetBuild alias (synonym to DotNetCoreBuild) +* 3546 Add DotNetRestore alias (synonym to DotNetCoreRestore) +* 3545 Add DotNetClean alias (synonym to DotNetCoreClean) +* 3544 Add DotNetExecute alias (synonym to DotNetCoreExecute) +* 3543 Add DotNetRun alias (synonym to DotNetCoreRun) +* 3542 Add DotNetTool alias (synonym to DotNetCoreTool) +* 3523 Add DotNetMSBuild alias (synonym to DotNetCoreMSBuild) +* 3215 Add RunCommand with postAction parameter to DotNetCoreTool +* 3075 Make FilePath and DirectoryPath comparable by value +* 2571 OctopusDeploy DeployTo property to take collection of string to specify multiple environments +* 2075 Add overloads for DotNetCore*() methods taking FilePath instead of string +* 1794 Private is missing from ProjectReference +* 1616 Error message on circular references leads to poor developer experience +* 3701 Add cake-module tag to Cake.DotNetTool.Module NuGet package +* 3602 Switch to Cake.Tool as primary package in REAME.md + +### New in 2.0.0-rc0001 (Released 2021/11/07) + +* 3654 IsRunningOnAzurePipelines should ignore agent type +* 3631 Refactor GitHub Actions Paths +* 3610 Remove TFBuildProvider +* 3590 Directories in AzurePipelinesBuildInfo are FilePaths - FilePath.GetDirectory then inconsistent +* 3581 Stop shipping Cake.Portable Chocolatey package and Cake Homebrew formulae +* 3579 Stop shipping Cake runner for .NET Framework and Cake runner for .NET Core +* 3577 Remove ReverseDependencyAttribute +* 3572 Only build for TargetFrameworks netcoreapp3.1, net5.0 and net6.0 +* 3282 GitVersion Tool: Rename verbosity values to match GitVersion values +* 3222 Add Xamarin.iOS platform targets to MSBuildSettings PlatformTarget enumeration +* 3151 Add support for Engine event hooks after execution as well as before +* 3003 Remove DependencyAttribute +* 2872 Bump eol target frameworks +* 2788 Tool:OpenCover - the register-setting should be an option, rather than a string +* 1111 DotNetCoreRestore: dotnet restore no longer supports globbing +* 3341 Introduce DotNet aliases (synonyms to DotNetCore aliases +* 3627 Add GitHub Actions AddPath Command +* 3628 Add GitHub Actions SetEnvironmentVariable Command +* 3629 Add GitHub Actions UploadArtifact Command +* 3630 Add GitHub Actions Environment properties +* 3662 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-6.final +* 3647 Display message of criteria when task fails to run due to criteria not being met +* 3644 Add DotNetNuGetUpdateSource aliases (synonym to DotNetCoreNuGetUpdateSource) +* 3643 Add DotNetNuGetRemoveSource aliases (synonym to DotNetCoreNuGetRemoveSource) +* 3642 Add DotNetNuGetListSourceSettings (derived from to DotNetNuGetSource) +* 3641 Add DotNetNuGetHasSource aliases (synonym to DotNetCoreNuGetHasSource) +* 3640 Add DotNetNuGetEnableSource aliases (synonym to DotNetCoreNuGetEnableSource) +* 3639 Add DotNetNuGetDisableSource aliases (synonym to DotNetCoreNuGetDisableSource) +* 3607 Add EnableCompressionInSingleFile to DotNetCorePublishSettings +* 3599 Add VS2022 to default MSBuild Resolver +* 3598 Remove Preview from VS2022 MSBuild Resolver +* 3595 Update Autofac to 6.3.0 +* 3593 Update Microsoft.CodeAnalysis.CSharp.Scripting to 4.0.0-5.final +* 3591 Update Microsoft.NETCore.Platforms to 6.0.0-rc.2.21480.5 +* 3555 Add DotNetNuGetAddSource aliases (synonym to DotNetCoreNuGetAddSource) +* 3554 Add DotNetNuGetDelete aliases (synonym to DotNetCoreNuGetDelete) +* 3553 Add DotNetNuGetPush aliases (synonym to DotNetCoreNuGetPush) +* 3552 Add DotNetPack alias (synonym to DotNetCorePack) +* 3551 Add DotNetPublish alias (synonym to DotNetCorePublish) +* 3550 Add DotNetVSTest alias (synonym to DotNetCoreVSTest) +* 3549 Add DotNetTest alias (synonym to DotNetCoreTest) +* 3548 Add DotNetBuildServer alias (synonym to DotNetCoreBuildServer) +* 3547 Add DotNetBuild alias (synonym to DotNetCoreBuild) +* 3546 Add DotNetRestore alias (synonym to DotNetCoreRestore) +* 3545 Add DotNetClean alias (synonym to DotNetCoreClean) +* 3544 Add DotNetExecute alias (synonym to DotNetCoreExecute) +* 3543 Add DotNetRun alias (synonym to DotNetCoreRun) +* 3542 Add DotNetTool alias (synonym to DotNetCoreTool) +* 3523 Add DotNetMSBuild alias (synonym to DotNetCoreMSBuild) +* 3215 Add RunCommand with postAction parameter to DotNetCoreTool +* 3075 Make FilePath and DirectoryPath comparable by value +* 2571 OctopusDeploy DeployTo property to take collection of string to specify multiple environments +* 2075 Add overloads for DotNetCore*() methods taking FilePath instead of string +* 1794 Private is missing from ProjectReference +* 1616 Error message on circular references leads to poor developer experience +* 1669 Release notes does not tolerate prerelease versions +* 2443 Erroneous "Target path must be an absolute path" when preserveFolderStructure is used with CopyFiles +* 3602 Switch to Cake.Tool as primary package in REAME.md + +### New in 1.3.0 (Released 2021/10/07) + +* 3469 Add support for .NET 6 +* 3493 .NET CLI Build Binary log filenames aren't quoted correctly +* 3477 parsing of solution files with absolute paths to projects throws exception +* 3455 NuGet Resolver native dependencies fails on latest macOS +* 3352 Cake Frosting Parent DirectoryPath Fails To Combine with Slash +* 3291 Unable to retrieve target argument with Frosting +* 2048 DotNetCoreToolSettings.WorkingDirectory is not respected when running DotNetCoreTool +* 3521 Update Microsoft.NETCore.Platforms to 6.0.0-rc.1.21451.13 +* 3519 Update Spectre.Console to 0.42.0 +* 3503 Add NuGet Sources argument to DotNetCoreTestSettings +* 3502 Add NuGet Sources argument to DotNetCoreRunSettings +* 3501 Add NuGet Sources argument to DotNetCorePackSettings +* 3464 Support MSBuild version 17 +* 3452 Missing option in InspectCodeSettings: `--build` and `--no-build` flags +* 3449 Add Version, AssemblyVersion, FileVersion, and AssemblyInformationalVersion properties to DotNetCoreMSBuildSettings +* 3447 Add ContinuousIntegrationBuild to DotNetCoreMSBuildSettings +* 3445 Highlight failed tasks on summary when Error handler is defined +* 3237 Allow setting MSBuildToolVersion using custom string - Part 1 +* 3065 Add DOTNET_ROLL_FORWARD setting to DotNetCoreSettings +* 2165 DotNetCore Build misses Sources settings +* 2104 Make possibility to set Process Exit Code +* 1882 DeleteDirectory throws exception if directory doesn't exist +* 3515 Add a simple README to the packages to be shown on NuGet.org +* 3466 Fix two typos in GitReleaseManagerAliases documentation + +### New in 1.2.0 (Released 2021/08/29) + +* 2690 Consider adding some kind of "GetArguments()" alias, similar to the EnvironmentVariables() one. +* 2578 Feature request: nuget version ranges support. +* 2362 Add Support for New snupkg Symbol Packages. +* 3429 Microsoft.Extensions.DependencyInjection to 5.0.2. +* 3427 Update Microsoft.CodeAnalysis.CSharp.Scripting to 3.11.0. +* 3425 Update NuGet Client libraries to 5.11.0. +* 3423 Update Spectre.Console to 0.41.0. +* 3337 Suppress compilation warnings CS1701, CS1702, and CS1705. +* 3316 Bump NuGet client libraries to 5.9.1. +* 3314 Bump .NET SDK to 5.0.202. +* 3294 Clean up task builder extensions. +* 3281 GitVersion Tool: Remap existing verbosity values to valid GitVersion values. +* 3255 Update NuGet client libraries to 5.9.0. +* 3253 Update Microsoft.CodeAnalysis.CSharp.Scripting to 3.9.0 stable. +* 3246 Update Spectre.Console to 0.38.0. +* 3223 Feature request: Environment variable substitution in cake.config. +* 2654 NUnit3Settings should support TestParam. +* 2168 TypeConverter to enable Argument(...). +* 2030 NuGetHasSource is case sensitive. +* 3365 Typo in documentation of NuGetAdd alias. +* 3355 VSTest alias documentation contains holdover from 11.0.2. +* 2601 Update Microsoft.CodeAnalysis.CSharp.Scripting to 3.2.1. +* 2599 Update to Autofac 4.9.4. +* 2585 Cake.Tool - How in the world do I run a specific task?. +* 2590 Update confusing GitVersionVerbosity docs. +* 2610 Aliases of type 'dynamic' cannot be accessed directly. +* 2608 TFBuildProvider.IsHostedAgent returns wrong value when running on 2nd build agent. + +### New in 0.34.1 (Released 2019/07/16) + +* 2575 v0.34.0 fails on scripts using the dynamic keyword + +### New in 0.34.0 (Released 2019/07/16) + +* 2519 Not able to build project with ToolsVersion="15.0" +* 2553 cake 0.33.0 compilation is failing for System.Net.Http.HttpClient on Mono 5.20.1.19 +* 2535 OctoPack doesn't work on Linux +* 2161 If [Nuget] ConfigFile directive in cake configuration file has no folder — error rises +* 2157 NuGetPack with nuspec that contains contentFiles becomes invalid +* 2560 Runtime property is missing for 'dotnet pack', 'dotnet run' and 'dotnet clean' +* 2556 DotNetCoreTestSettings: Missing RunTime Property which is needed for RID builds +* 2551 Call MSBuild without specifying a target does not use DefaultTarget +* 2536 Additional formatting options on XmlPoke +* 2531 Update to NuGet client libraries to v5 +* 2530 Remove dependency on NuGet.PackageManagement +* 2521 Update to Roslyn 3.0.0 +* 2499 NuGet Pack with assembly references support +* 2156 Add newer nuspec properties to NuGetPackSettings +* 1618 Support different Git servers in TeamCityPullRequestInfo + +### New in 0.33.0 (Released 2019/04/01) + +* 2514 Add additional report types for ReportGenerator +* 2130 Add exceptions thrown to TaskTeardownContext +* 2456 Add logging aliases to override the log verbosity +* 2453 Unify pull request status across providers +* 2440 Add EnvironmentVariable alias +* 2400 Add globber pattern support to the #load directive +* 2504 Update .NET Core SDK 2.1.505 +* 2487 Warn and skip code gen for duplicate aliases +* 2481 FilePath and DirectoryPath implicit conversions should return null when passed null +* 2473 ParseAssemblyInfo does not support .NET Core generated assembly info +* 2468 DotNet commands do not respect the verbosity +* 2439 HtmlInline_AzurePipelines and MHtml shares the same numeric value +* 2432 Azure Pipelines build system not recognized with non-Windows jobs +* 2088 VSWhere -requires and -products argument values are quoted but VSWhere doesn't support multiple values in quotes +* 2507 Cake.CoreCLR can't handle whitespace in path +* 2491 Add additional Azure DevOps (TFBuild) properties +* 2484 Octopus Deploy 2019.1 and Spaces feature +* 2478 Lock file arguments for NuGet and dotnet restore +* 2474 TeamCityProvider.BuildProblem method should conform to TeamCity API +* 2472 Expose ICakeConfiguration (or specific values like tools path) on context +* 2465 Roundhouse dotnet tool does not run +* 2463 DoesForEach don't support data context for items functions +* 2462 Added unit tests for Cake.Core +* 2459 Add MSBuildPath to NuGetRestoreSettings +* 2449 ARM64 missing from MSBuild target platform +* 2445 Add OnError +* 2433 NugetRestore still using msbuild 15 +* 2429 Add provider name to BuildSystem +* 2415 Add support for MSBuild options to enable RestoreLockedMode +* 2393 MethodAliasGenerator doesn't generate parameter attributes +* 2345 Allow NuGetRestoreSettings to opt out of setting -NonInteractive +* 2270 Allow to listen and modify redirected standard output of a process +* 2141 Add Verbosity property to GitVersionSettings +* 2124 Add Support for IEnumerable tokens on TextTransformationExtensions +* 2087 Include more detailed exception information when Exception is AggregateException +* 2026 Support for additional SignTool flags +* 2019 Clean up some parser tests +* 1384 Enhancement: Add support for filtering files in Globbing alias +* 820 Log tools command-line at higher log level (preferably default) +* 2512 TFBuildPublishCodeCoverageData xml comments minor typo +* 2025 The tool path for MSpec needs to be changed in the documentation + +### New in 0.32.1 (Released 2019/01/04) + +* 2426 Chocolatey pack regression in Cake 0.32.0 + +### New in 0.32.0 (Released 2019/01/04) + +* 2420 Add new label alias for GitReleaseManager +* 2419 Extend GitReleaseManager aliases to use token parameter +* 2424 Support computer cert store with SignTool +* 2417 Extend GetToolExecutableNames for GitReleaseManager +* 2412 TFBuildCommand PublishCodeCoverage API Changes +* 2410 Add Global Tool and new arguments support in TextTemplatingAliases +* 2398 Support MsBuild version (16) +* 2381 Zip should behave by default like standard Zip utilities +* 2379 Add an Encoding parameter to TextTransformation.Save +* 2327 Missing report types for ReportGenerator +* 2294 Add fluent API to enable MSBuild binary logger +* 2249 Unhelpful error when * loading a missing nuget package +* 2243 Missing ResultsDirectory when using DotNetCoreVSTest +* 1973 Add Products prop to VSWhereSettings +* 2397 Fix missing parenthesis and missing setting. + +### New in 0.31.0 (Released 2018/12/13) + +* 2320 Alias for ScriptCallerInfo +* 2286 Add .NET build server shutdown alias "DotNetCoreBuildServerShutdown" +* 2277 Add basic implementation of info command +* 2201 Extend supported globber patterns +* 2200 Support UNC paths +* 2198 Add GlobberSettings +* 2197 Don't rely on System.IO namespace for FilePath/DirectoryPath +* 1976 Add MSBuildSettings.NoLogo +* 1383 Add command line option to display build target graph +* 2342 Provide value for self-contained to support succeeding parameters +* 2310 Cake.Testing.Xunit RuntimeFact and RuntimeTheory doesn't work for .NET Core App +* 2252 Cake fails to start on posix systems if script / current directory is root ( / ) +* 2391 Upgrade to NuGet 4.9.2 +* 2387 Extend GetTooolExecutableNames for GitVersion +* 2384 Add homebrew fallback path for MSBuild tool resolver +* 2369 Update Roslyn to 2.10.0 +* 2350 In-process NuGet client should reuse package sources as specified in NuGet.Config if available +* 2341 Add support for JUnit Output Format +* 2332 TFBuild UploadArtifact commands should support directories +* 2312 Add method to expand environment variables to FilePath/DirectoryPath +* 2308 Use Mono for full framework executables if running on Unix & .NET Core +* 2306 Add VSTestReportPath to DotNetCoreTestSettings +* 2300 Make DotNetCoreTool alias project path optional add overloads without. +* 2297 NUnit3Settings does not provide an option to specify the configuration file to load +* 2284 --version should only return sem/nuget version +* 2272 Update in-process NuGet client to support offline environments +* 2268 Add .NET Core tool support for Octopus aliases +* 2265 Update Roslyn to 2.9.0 +* 2257 NuGetPack should have a version suffix setting +* 2255 Show warning when referenced package is missing version number +* 2246 Add NuGet projectUrl to nuspec/csproj packages +* 2245 Add symbols for Cake.Tool package +* 2061 NuGetPack overwrites developmentDependency and requireLicenseAcceptance from nuspec. +* 1875 Folder structure of tools and addins can cause too long paths on Windows +* 2385 Typo in BuildSystem.TeamCity property example +* 2365 Fixed typos +* 2267 Fix more 'occured' and 'occuring' typos + +### New in 0.30.0 (Released 2018/08/22) + +* 2067 Publish as .NET Core Global Tool. +* 2238 Add repository metadata to NuGet packages. +* 2234 Remove mono argument from Argument Parser. +* 2211 DotNetCorePublishSettings doesn't contain --no-build flag support introduced in .NET Core SDK 2.1. +* 2146 Enabling initializer syntax for all collection properties. +* 1401 Support for dotCover configuration file. +* 2233 Add bootstrap argument to Help Command. +* 2232 Add exclusive argument to Help Command. +* 2220 Incorrect documentation for InnoSetup Alias. +* 2228 CakeTaskExtensions are no longer accessible. +* 2224 Add option for ProcessSettings to opt out of working directory magic. +* 2214 Cake.CoreCLR can't handle whitespace in path. +* 2208 WithCriteria does not work with 'DryRun' (WhatIf flag). +* 2207 NuGet hang due to bug in NuGet 4.6.0. + +### New in 0.29.0 (Released 2018/07/06) + +* 2140 DotNetCorePublish does not respect SelfContained DotNetCorePublishSettings property. +* 2203 Add Octopus Deploy Promote release support. +* 2095 Add "--skipnontestassemblies" funcionality to Cake's NUnit3Settings as it exists in original NUnit 3 test runner. +* 2094 Add support for executing a single task without dependencies. +* 2196 NuGet Repository information not settable in NuGet Pack. +* 2185 Try to find vswhere.exe on the system if the tool is not registered. +* 2154 Problem with loading abolute path scripts with #load preprocessor. +* 2152 try resolve vstest.console.exe before guessing it. +* 1609 Add additional VSTS actions. +* 2195 Updated the WiX tool documentation. +* 2193 Add Pascal and Dave to all required places. +* 2188 The CLA link in readme seems invalid or broken. + +### New in 0.28.1 (Released 2018/06/18) + +* 2176 Skipped tasks show up multiple times in report +* 2190 Suppress NuGet dependency warnings related to Cake.Core + +### New in 0.28.0 (Released 2018/05/31) + +* 2008 Allow defining a typed context to be used throughout a Cake script. +* 1772 Provide access to the run target and ordered list of tasks +* 1594 Add overload to WithCriteria which prints a message +* 2174 Support multiple Support / Teardown +* 2171 Add potential breaking change warning +* 2163 Update to Roslyn 2.8.x packages, adding support for C# 7.3 + +### New in 0.27.2 (Released 2018/05/15) + +* 2137 Dependency loading errors with Cake 0.27.1 and Cake.Powershell 0.4.5 +* 2134 Assembly conflicts during compilation + +### New in 0.27.1 (Released 2018/04/21) + +* 2132 Problems with loading certain assemblies (0.27.0) + +### New in 0.27.0 (Released 2018/04/19) + +* 2078 Support expand environment variables in script pre-processor directives +* 2047 Specify version during NuGet Updating +* 2005 Add entries for Setup/Teardown in report +* 1908 Octopus Deploy tool does not support list-deployments call for octo.exe +* 2116 Loading Newtonsoft.Json in Cake.CoreCLR throws during assembly loading +* 2084 Cake does not load dependencies in correct order +* 2082 Investigate NuGet local V3 cache +* 2081 Possibility to override default NuGet sources +* 2079 Default sources not loaded if nuget_source is empty +* 2119 DotNetCore Publish misses Force / Self contained / Sources settings +* 2113 Error when loading tools without internet connection +* 2106 Remove NUnit3Settings.ErrorOutputFile property +* 2092 Unable to set 'no-build' and 'no-restore' when executing DotNetCoreRun +* 2051 Add support for msbuild.exe /restore option +* 2039 XUnit2Runner doesn't respect ParallelismOption.None +* 2036 Don't output usage when an error occured. +* 2031 Simplify setting FileVersion and InformationalVersion +* 2029 Investigate in-process NuGet dependency resolution +* 2014 In-process NuGet don’t support multiple feeds through config +* 2003 Add possibility for AssemblyMetadata collection in CreateAssemblyInfo +* 1887 DotNetCoreRestoreSettings: support option --force +* 1557 Add support for MSBuild /consoleloggerparameters +* 2062 Fixed typo 'need to' +* 2035 Fix typo in README +* 1213 NuGetPushSettings.Source: incorrect documentation + +### New in 0.26.1 (Released 2018/03/03) + +* 2063 Cake running on Mono can't load netstandard 2.0 assembly + +### New in 0.26.0 (Released 2018/02/26) + +* 1781 Update to .NET Core 2 + +### New in 0.25.0 (Released 2018/01/17) + +* 1995 Make In-proc NuGet addin/tool installation default +* 1994 Get MSBuild Verbosity enum from string +* 1988 TeamCity writing start and end progress contains invalid messages property +* 1974 ToDictionary on Mono causes "The type 'Dictionary<,>' is defined in an assembly that is not referenced" +* 1998 Some .NET Core commands missing no dependencies/restore +* 1997 Add the --trace option to the NUnit3Settings class. +* 1992 Update to .NET Runtime 1.0.9 because security issues +* 1989 Path unnecessarily trims backslash in already normalized string +* 1987 Confusing Error from Bad Format String to Information() +* 1937 UseInProcessClient=true is slow +* 1982 CodeTriage - Get more Open Source Helpers +* 1689 ChocolateyDownload should be documented to only work in paid edition + +### New in 0.24.0 (Released 2017/12/29) + +* 1950 Allow Cake modules to be bootstrapped by Cake in a pre-processing phase +* 1833 NUnit: Add support for /labels +* 1653 Add Before and After options to NUnit3Labels enum +* 74 MSpec support +* 1957 Use working directory instead if initial script path for resolving tools directory in NuGetLoadDirectiveProvider +* 1939 Bug - TypeExtensions.GetFullName doesn't handle nested types correctly +* 1933 NuGetPackSettings.Properties does not support whitespaces. +* 1930 The "out" parameters are not compiled properly. +* 1915 Only set working directory on process runner if set in settings +* 1889 XmlPoke ignores BOM encoding settings +* 1874 NuGet script load: Do not add include for all cake scripts when include already specified +* 1968 Add interface for AssemblyVerifier so that it can be mocked +* 1960 Update Roslyn to 2.6.1 +* 1955 ResultsDirectory is missing from DotNetCoreTestSettings +* 1952 Add support for class/namespace/method arguments for XUnit2 +* 1946 Add option to pack files into the NuGet tool directory +* 1943 Chocolatey package dependencies cannot be set using the ChocolateyPackSettings +* 1936 Move to signing service v2 +* 1931 Allow passing a nuget.config as environment variable or in cake.config +* 1924 Set UserAgent for in-process NuGet +* 1922 GitVersion is missing AssemblySemFileVer +* 1912 Support for DotCover Process Filter +* 1910 MSBuild property values should escape carriage return and line feed +* 1855 SignTool is not found with latest windows 10 SDK +* 1796 Obsolete DotNetBuild and ultimately remove it +* 1692 Log script compilation warnings and other diagnostics +* 1522 The MSTest tool doesn't pick up the mstest.exe from Visual Studio 2017 +* 1811 Add code sample to build system properties + +### New in 0.23.0 (Released 2017/10/11) + +* 1805 Change GitVersion settings to use nullable integer +* 1856 Support MSBuild warnaserror and warnasmessage arguments +* 1821 Missing Cake method for nuget list +* 1818 Support task dependees (reverse dependencies) +* 1766 Support for #define +* 1032 Support async callbacks +* 1853 The "using static" directive doesn't compile +* 1843 NuGetContentResolver should not return ref assemblies. +* 1842 Params in URI pre-processor directives are case sensitive +* 1838 Dependencies are installed but have no references added when using LoadDependencies=true with in process NuGet client +* 1831 CleanDirectories Throws NullReferenceException When Token Is Null +* 1815 Exception Message should be shown rather than "One or more errors occurred." +* 1404 MsBuildSettings.WithProperty does not escape values +* 1840 Fix Chocolatey Package +* 1804 Unable to execute when namespace-less assembly with CakeMethodAlias is referenced +* 1731 GitLabCI variable changes. +* 1632 Tasks with long names do not display nicely with showdescription +* 1607 ToolResolutionStrategy fails unexpectedly with Cake.LongPath.Module +* 1548 LogExtension colorizes output incorrectly +* 1547 Escaping curly braces in log messages +* 787 Reference NuGet dependencies installed via the #addin directive +* 1835 Fixed typo in installer scripts +* 1814 Fix typo: envrionment + +### New in 0.22.2 (Released 2017/09/17) + +* 1807 NuGetVersion and CommitDate are null with 0.22.1 + +### New in 0.22.1 (Released 2017/09/15) + +* 1798 GitVersion error on build on master branch with version 0.22.0 + +### New in 0.22.0 (Released 2017/09/13) + +* 1785 Bump LatestBreakingChange to 0.22.0 +* 1745 Change parameter for InstallTools and InstallAddins in IScriptProcessor +* 1720 ILRepackSettings.Libs should be List of DirectoryPath +* 1719 Jenkins BRANCH_NAME is missing +* 1714 Updated CakeRuntime.TargetVersion to net462. +* 1674 MSBuildFileLogger LogFile is a string and not a FilePath +* 1665 NUnit3Settings: Params and multiple results +* 1651 NUnit3Settings Verbose flag obsoleted by NUnit console runner +* 1614 Correct the class that TeamCityEnvironmentInfo inherits from +* 1597 CommitsSinceVersionSource and PreReleaseNumber as Integer +* 1564 DeleteDirectory cannot delete read-only files +* 1540 Upgrade to Roslyn 2.0 +* 1791 Add option to enable MSBuild binary logging +* 1771 Look for msbuild in default install path on Linux +* 1761 DoesForEach() extension method +* 1754 VSWhere not returning prerelease versions +* 1743 Implement functionality in Cake.NuGet for downloading packages +* 1734 Add GitLink 3 compatible aliases +* 1710 Add alias for simple sub-directory listing +* 1699 NuGetPackSettings missing language/locale ID for the package +* 1670 OpenCover is missing some commandline parameter (for example mergebyhash) +* 1667 Add support for choco download internalize-all-urls +* 1621 Add overload for StartProcess which also returns redircted error output +* 1775 Strange usage of Cake.Core.dll when executing cake sub process +* 1773 NuGetHasSource call do not take care of ArgumentCustomization in NuGetSourcesSettings +* 1759 XmlPoke always writes the xmldeclaration even if the original file didn't have one +* 1742 Some unit tests are locale-sensitive +* 1739 NuGetContentResolver can't find assemblies if located in root +* 1738 NuGetInstaller can't resolve files if package contains dependencies +* 1697 CakeContextAdapter do not implement ICakeContext +* 1694 Addin directive shouldn't attempt to load native assemblies +* 1693 Possible bug when setting process environment variable +* 1625 Comma in msbuild commands are not escaped +* 1602 MSBuildFileLogger Verbosity does not accept Verbosity.Verbose +* 1537 XmlPeek not working correctly for element nodes +* 1422 Error: Unkown token when directory contains multibyte characters +* 1752 Extend DownloadFile to allow AcceptEncoding gzip +* 1746 ScriptAnalyzer.Analyze() should not throw - instead return list of errors +* 1704 Move CakeConsole & CakeBuildLog to Cake.Core and made CakeConfiguration public +* 1512 Please support C* 7 and Roslyn v2 +* 753 Tool Versioning +* 1787 Add opt-out config information to assembly version verification error message +* 1780 Fix typo in version.cake +* 1727 Incorrect documentation for XmlPeek Alias +* 1700 Update NuGet license url +* 1525 Updated examples for DotNetCoreTest + +### New in 0.21.1 (Released 2017/07/15) + +* 1685 Add DotNetCoreTool alias overload that takes DotNetCoreToolSettings parameter +* 1686 AssemblyLoadContext root path is relative + +### New in 0.21.0 (Released 2017/07/14) + +* 1533 Update DotNetCore Aliases to match tooling 1.0 +* 1554 Updated DotNetCoreTest inline with Tooling v1.0 +* 1553 Updated DotNetCorePack inline with Tooling v1.0 +* 1552 Updated DotNetCorePublish inline with Tooling v1.0 +* 1551 Updated DotNetCoreExecute inline with Tooling v1.0 +* 1550 Updated DotNetCoreBuild inline with Tooling v1.0 +* 1534 Updated DotNetCoreRestore inline with Tooling v1.0 +* 1599 Added support for dotnet msbuild inline with Tooling v1.0 +* 1591 Add support for choco download +* 1581 Added support for dotnet nuget push inline with Tooling v1.0 +* 1577 .NET Core CLI tools - Surfacing additional commands with a new alias: DotNetCoreTool +* 1565 Added support for dotnet nuget delete inline with Tooling v1.0 +* 1555 Added support for dotnet clean inline with Tooling v1.0 +* 1549 Common changes for DotNetCore Alias +* 1679 Cake on dotnet core doesn't load reference dll correctly if referenced from a subdirectory +* 1673 The xunit.runners package was deprecated +* 1654 Broken Documentation link for ReportUnit + +### New in 0.20.0 (Released 2017/06/12) + +* 1539 Update solution to Visual Studio 2017 +* 1640 Fetch version from solutioninfo & remove newtonsoft dependency +* 1638 Unix Integration tests fail post new SDK +* 1635 Non Nuspec assemblies not packaged after VS2017 upgrade +* 1603 Push Cake.NuGet to MyGet/NuGet +* 1538 Update DotNetInstallerUri to https://dot.net/v1/dotnet-install.ps1 +* 1620 Improve documentation for RedirectStandardError and RedirectStandardOutput +* 1613 Added documentation link to NUnit3Settings.Where +* 1605 Fix the contribution-guidelines link again +* 1604 Fix the contribution guidelines link in the README +* 1595 Add Alistair and Martin names to all required places + +### New in 0.19.5 (Released 2017/05/04) + +* 1587 Arguments missing for Octopus Deploy tool + +### New in 0.19.4 (Released 2017/04/19) + +* 1556 TeamCity BuildNumber is a string +* 1566 Generic alias methods with type constraints fail compilation + +### New in 0.19.3 (Released 2017/04/03) + +* 1544 Windows 10 SDK path is not being resolved + +### New in 0.19.2 (Released 2017/04/01) + +* 1546 MSBuild Logger Path are not correctly quoted + +### New in 0.19.1 (Released 2017/03/24) + +* 1543 VSWhere aliases should return Directory Paths and not File Paths + +### New in 0.19.0 (Released 2017/03/23) + +* Add VSWhere support +* Error: SignTool SIGN: Password is required with Certificate path but not specified. +* MSBuild on Mac/Linux +* Categorize logging aliases by level + +### New in 0.18.0 (Released 2017/03/07) + +* Remove obsoleted DNU aliases +* WiXHeat misleading signature- no mode operates on file list +* Add "build tools" path for MSBuild 2017 to MSBuildResolver +* Add ChocolateyNew Alias +* Add support for NuGet Init and Add commands +* NUnitSettings does not have X86 property +* Enhance TeamCity provider +* Support for TF Build Commands +* Provide ability to add Custom attributes when creating AssemblyInfo +* Support for uninstall packages using Chocolatey +* Provide ability to specify name for xunit report +* MSBuild support for Visual Studio 2017 (aka "15") +* Add support for importing namespaces at the assembly level +* Add DotCover Merge +* Proposal: Allow modules to listen for script lifecycle events +* Support optional parameters on alias methods +* Support downloadable .cake script directive +* Extending the Sign command +* Fix ParseAssemblyInfo does not work .vb +* Duplicate depedencies references in project.json for Cake.Testing.XUnit +* Cake.Testing package depends on xunit.core package +* Optional parameter codegen not invariant +* XBuildRunner#GetToolExecutableNames returning wrong executables +* Space in Reference Preprocessor Directive Throws Illegal characters in path +* Spaces in #load path will cause an Illegal characters in path error. +* Add CakeNamespaceImport for BuildSystem Aliases +* HeatSettings.OutputGroup is unusable +* OctoPack not passing --format to octo.exe +* Error: Unknown Token when directory contains @ character. +* Using reserved name for parameter name causes a parser failure +* signtool.exe should be called only once when signing multiple files +* Missing MSBuild15 on enum NuGetMSBuildVersion for VS 2017 +* Add ChocoPush alias for an IEnumerable +* Add ChocoPack alias for an IEnumerable +* Usage of -NoCache on installing tools and addins +* Mac OSX is not properly detected when running on Mono +* NuGet Tool Locator system paths on mac need updating +* Logging throws exception when there are curly braces in the string +* CopyDirectory - Missing Log information +* Teach XmlPeek to silence warnings, if needed +* Http call in unit test +* Add optional Go.CD Server URL Parameter to GetHistory +* Add RedirectStandardError to ProcessRunner +* Cake's default tools / addins / modules paths are not so default as they seem. +* ArgumentException with illegal character information +* Add mechanism to validate addins +* Support XUnit's x86 .exe runner +* Add Gitter and Twitter Notifications +* DownloadFile typo in docs +* Typo in SignTool docs +* Fix typos in GitVersion documentation +* Correct issue with GitLink Alias Category +* Fix commented example for DotNetCoreTest +* Fix doc comments in InnoSetupAliases +* Fix typo in comment +* Fixed Spelling Mistake. + +### New in 0.17.0 (Released 2016/11/09) + +* Allow custom loggers in the VSTestSettings +* Add support for InnoSetup +* Add a "Prepend" extension for the ProcessArgumentBuilder +* Add Support for the Go.CD build provider +* Add GitLab CI build system support +* Add VSTS build system support +* Wait for AppVeyor process to exit +* Add Ability to Redirect Standard Error on IProcess +* Add option to keep the autogenerated NuSpec file +* IsDependentOn with CakeTaskBuilder parameter +* CopyFiles doesn't respect source directory structure +* Add DotCover Report +* Support OctoPack +* Add support for moving directories +* Typo in VSTestSettings extension method name +* Globber exception when using a path with an exclamation +* Error: An item with the same key has already been added while running Cake from commit hooks +* System time separator is used when Octo DeployAt argument is converted to string +* Unquoted VSTest settings file path +* Globber exception when glob contains % +* GetEntryAssembly can return null, leading to NullReferenceException +* NuGetPack fails if no Files have been specified +* Add support fort Specifying Dependencies for Multi Target package +* Support DefaultCredentials usage for Http Downloads +* Add additional parameters to MSBuild runner +* Add Go.CD build history API call +* Some properties for RoundhouseSettings in Cake.Common.Tools.Roundhouse are not working properly +* Add user agent for DownloadFile +* Guard against invalid path environment variables +* Adding all current parameters for VSTest +* OctoCreateRelease is missing channel option +* Option to deploy an existing release in OctopusDeploy +* Get return code from intercepted process in SpecFlow TestExecutionReport +* Add parameter LogFile to DotCover commands +* Can't specify hash algorithm for the Sign command +* MSBuild add log file support +* Support for SHA256 code signing +* Fixed typos 'occured' and 'occuring' +* Add CLA link to README. +* Removed erroneous apostrophes +* Corrects the grammar "do/does" in exception messages and tests +* Adds default CPU count behavior to MSBuild settings documentation + +### New in 0.16.2 (Released 2016/10/11) + +* Fixed CakeExecuteScript getting access denied errors on mono/m + +### New in 0.16.1 (Released 2016/09/25) + +* Issue with debugging in v0.16.0 (.NET Core) +* Add missing assembly properties + +### New in 0.16.0 (Released 2016/09/15) + +* Change API for registering dependencies with Cake +* Add include & exlude parameters to #tool directive +* Allow passing username and password to DownloadFile alias +* Port to .NET Core +* Publish symbol files +* Add missing MergeOutput Option for OpenCover +* ICakeContainerRegistry missing constraint +* NugetV2Resolver doesn't support new netstandard +* Implement custom logger support for MSBuild +* Support MSBuild logger switches + +### New in 0.15.2 (Released 2016/07/29) + +* Ensured that WiX candle definitions are enclosed in quotes +* Corrected issue with WixHeat HarvestType Out parameter + +### New in 0.15.1 (Released 2016/07/28) + +* Corrected Issues found with 0.15.0 AppVeyor updates + +### New in 0.15.0 (Released 2016/07/26) + +* Add support for adding messages to the AppVeyor build log +* Add environment variable support for Process & Tool +* Add ITeardownContext to Teardown method +* Add OutputPath to ProjectParserResult +* Extend SolutionParserResult with solution folders information +* Add Atlassian Bitbucket Pipelines build system support +* Set ICakeRuntime.TargetFramework to constant +* Do not set /m parameter for MSBuild by default +* AppVeyor.UploadTestResults failing silently +* GetFullName in TypeExtensions.cs not handling arrays correctly +* Allow paths with spaces for OutputDirectory on DotNetCore Aliases +* The GetFiles overload with a predicate doesn't work properly +* Added ability to call SetParameter method on TeamCity Provider +* CakeExecuteScript tool resolution fails if script in parent path +* UploadArtifact support via the AppVeyor provider is incomplete +* Added raw version string property in ReleaseNotes class +* Marked Quiet property on DotNetCoreRestoreSettings as obsolete +* Added -oldstyle support for OpenCover tool +* Added -nofetch support for GitVersion tool +* Add an explicit NuGet source for NuGetPush +* Add parsing of references and project references to the project file parser +* Add configuration for default NuGet source +* Add MD5 checking of packages.config to bootstrappper +* Support results file for MSTest runner +* Support NUnit output format for XUnit2 runner +* NUnit runner: Handle specific non-zero exit codes +* Added examples for Fixie +* Corrected spelling mistake in README.md +* Improved documentation for ProcessSettings Timeout property +* Added documentation for multiple arguments for ToolSettings +* Corrected documentation for GitReleaseManager +* Corrected code example for DotNetCorePackSettings +* Add example documentation to aliases + +### New in 0.14.0 (Released 2016/07/11) + +* Remove obsoleted XmlPoke Aliases +* ToolSettings should allow should support of exit codes other than 0 +* Add support for skipautoprops flag OpenCover Alias +* Support Octopus Deploy Push (octo.exe push) +* Add WiX heat support +* Cake looks for configuration file in the wrong place +* Wrong platform "Any CPU" for project file (expects "AnyCPU") +* Change parameter names passed by GitVersion Alias +* Improve logging with NuGet Install Alias +* Additional null checks for module support +* Suppress obsolete warnings on Mono +* Add known parameters to CakeOptions +* Add working directory to ToolSettings class +* Refactor ICakeEnvironment +* Allow setting `/testsettings:` file for MSTest runner +* Corrected documentation for Directory Alias +* Corrected documentation for DotNetBuild Alias + + +### New in 0.13.0 (Released 2016/06/07) * DotNetCoreTest() alias calls DotNetCoreRun() * Fix DotNet CLI multi-arguments @@ -23,7 +1512,7 @@ * Add Summary Documentation for all aliases -### New on 0.12.0 (Released 2016/05/25) +### New in 0.12.0 (Released 2016/05/25) * Fix globalization & white space issue * New Setup(Action) fails on mono @@ -50,7 +1539,7 @@ * How to get ILRepack executable? -### New on 0.11.0 (Released 2016/05/01) +### New in 0.11.0 (Released 2016/05/01) * Regression: ProcessArgumentListExtensions was renamed * DNU usage of multi arguments changed @@ -80,7 +1569,7 @@ * Added Chocolatey Package Badge -### New on 0.10.1 (Released 2016/04/07) +### New in 0.10.1 (Released 2016/04/07) * Exception running InspectCode and then directly after TeamCity.ImportData * Ensure Cake Assemblies are stamped with current version number @@ -89,14 +1578,14 @@ ### New in 0.10.0 (Released 2016/03/16) * XUnit command line bug -* Cake does not find it's own nuget.exe on Linux +* Cake does not find its own nuget.exe on Linux * Sanitization in TeamCity Provider places extra apostrophe if '[' is used. * Path segment bug (or test bug, choose your own adventure!) * Add support for importing coverage to TeamCity * Add DotCover Cover support * Add SpecFlow support * Add Jenkins CI build system support -* Use V3 Nuget in bootstrapper +* Use V3 NuGet in bootstrapper * Remove logging from task setup/teardown. * Update ReleaseNotes.md * Removed year from © in readme @@ -123,7 +1612,7 @@ * Replace #if !UNIX with [WindowsFact] * Don't show delegating tasks in summary * Task Summary should include skipped tasks -* Support for nuget packing of project files +* Support for NuGet packing of project files * Add method to get relative paths (for paths) * Full Build/Publish Automation for Cake * Add GitVersion into build.cake @@ -183,7 +1672,7 @@ * Got support for .cake files in GitHub. * Created a Visual Studio Code extension for Cake. * Created a VSTS extension for Cake. -* Fixed issue with external nugets used directly via #addin directive. +* Fixed issue with external NuGet packages used directly via #addin directive. * DupFinder: Added ability to fail the build on detected issues. * InspectCode: Added ability to fail the build on detected issues. * TextTransform now handles Regex special characters. @@ -271,7 +1760,7 @@ * Added filter predicate to globber and clean directory methods. * Added Unzip alias. * Added DownloadFile alias. -* Added method to retrieve filename without it's extension. +* Added method to retrieve filename without its extension. * Added support for InternalsVisibleToAttribute when generating assembly info. * Added extension methods to ProcessSettings. * Fixed formatting in build report. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..caf7d1c36d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,53 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| --------- | ------------------ | +| 6.x.x | :white_check_mark: | +| 5.x.x | :x: | +| 4.x.x | :x: | +| 3.x.x | :x: | +| 2.x.x | :x: | +| 1.0.x | :x: | +| 0.38.5 | :x: | +| <= 0.38.4 | :x: | + +## Reporting Security Issues + +⚠ **Please do not report security vulnerabilities through public GitHub issues.** ⚠ + +Instead, either encrypted through Keybase chat + - [keybase.io/cakebuild](https://keybase.io/cakebuild) + +Or report them to us using NuGet.org *Contact owners* + - [Cake.Tool](https://www.nuget.org/packages/Cake.Tool) + - [Cake.Frosting](https://www.nuget.org/packages/Cake.Frosting) + - [Cake.Core](https://www.nuget.org/packages/Cake.Core) + - [Cake.Common](https://www.nuget.org/packages/Cake.Common) + - [Cake.Testing](https://www.nuget.org/packages/Cake.Testing) + - [Cake.Testing.Xunit](https://www.nuget.org/packages/Cake.Testing.Xunit) + - [Cake.Testing.Xunit.v3](https://www.nuget.org/packages/Cake.Testing.Xunit.v3) + - [Cake.NuGet](https://www.nuget.org/packages/Cake.NuGet) + - [Cake.Cli](https://www.nuget.org/packages/Cake.Cli) + - [Cake.DotNetTool.Module](https://www.nuget.org/packages/Cake.DotNetTool.Module) + +If you prefer to submit without logging in, send an email to [security@cakebuild.net](mailto:security@cakebuild.net). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. diff --git a/appveyor.yml b/appveyor.yml index e1782551d8..a72a0232b2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,12 @@ # Build script +image: Visual Studio 2017 init: - git config --global core.autocrlf true # Build script build_script: - - ps: .\build.ps1 -Target "AppVeyor" + - ps: $env:CAKE_INSTALL_SUPPORTED_SDKS='true' + - ps: .\build.ps1 --target="AppVeyor" # Tests test: off @@ -18,8 +20,9 @@ branches: - /r/.*/ - /release/.*/ - /hotfix/.*/ + - /feature/.*/ # Build cache cache: -- src\packages -> src\**\packages.config -- tools -> tools\packages.config +- tools -> build.cake +- packages -> build.cake diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000000..d93d9c45b6 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,10 @@ +# Cake Bitbucket Pipeline +image: cakebuild/cake:sdk-7.0 +clone: + depth: full + +pipelines: + default: + - step: + script: + - ./build.sh --target="Run-Unit-Tests" diff --git a/build.cake b/build.cake index 897dada7da..557c60f121 100644 --- a/build.cake +++ b/build.cake @@ -1,65 +1,88 @@ // Install addins. -#addin "nuget:?package=Polly&version=4.2.0" +#addin "nuget:https://api.nuget.org/v3/index.json?package=Cake.Twitter&version=6.0.0" + +// Install .NET Core Global tools. +#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=6.7.0" +#tool "dotnet:https://api.nuget.org/v3/index.json?package=GitReleaseManager.Tool&version=0.20.0" -// Install tools. -#tool "nuget:?package=xunit.runner.console&version=2.1.0" -#tool "nuget:?package=gitreleasemanager&version=0.4.0" -#tool "nuget:?package=GitVersion.CommandLine&version=3.4.1" // Load other scripts. #load "./build/parameters.cake" -// Using statements -using Polly; - -////////////////////////////////////////////////////////////////////// -// PARAMETERS -////////////////////////////////////////////////////////////////////// - -BuildParameters parameters = BuildParameters.GetParameters(Context, BuildSystem); -bool publishingError = false; - /////////////////////////////////////////////////////////////////////////////// // SETUP / TEARDOWN /////////////////////////////////////////////////////////////////////////////// - -Setup(context => +Setup(context => { - if(parameters.IsMainCakeBranch && (context.Log.Verbosity != Verbosity.Diagnostic)) { + var parameters = new BuildParameters(context); + + // Increase verbosity? + if (parameters.IsMainCakeBranch && (context.Log.Verbosity != Verbosity.Diagnostic)) { Information("Increasing verbosity to diagnostic."); context.Log.Verbosity = Verbosity.Diagnostic; } - parameters.SetBuildVersion( - BuildVersion.CalculatingSemanticVersion( - context: Context, - parameters: parameters - ) - ); - - parameters.SetBuildPaths( - BuildPaths.GetPaths( - context: Context, - configuration: parameters.Configuration, - semVersion: parameters.Version.SemVersion - ) - ); - - parameters.SetBuildPackages( - BuildPackages.GetPackages( - nugetRooPath: parameters.Paths.Directories.NugetRoot, - semVersion: parameters.Version.SemVersion, - packageIds: new [] { "Cake", "Cake.Core", "Cake.Common", "Cake.Testing" }, - chocolateyPackageIds: new [] { "cake.portable" } - ) - ); - - Information("Building version {0} of Cake ({1}, {2}) using version {3} of Cake. (IsTagged: {4})", + Information("Building version {0} of Cake ({1}, {2}) using version {3} of Cake.\r\n\t(IsTagged: {4}, IsMainCakeRepo: {5}, IsMainCakeBranch: {6}, IsDevelopCakeBranch: {7})\r\n\t(ShouldPublish: {8}, ShouldPublishToAzureDevOps: {9}, ShouldSignPackages: {10}, IsPullRequest: {11})", parameters.Version.SemVersion, parameters.Configuration, parameters.Target, parameters.Version.CakeVersion, - parameters.IsTagged); + parameters.IsTagged, + parameters.IsMainCakeRepo, + parameters.IsMainCakeBranch, + parameters.IsDevelopCakeBranch, + parameters.ShouldPublish, + parameters.ShouldPublishToAzureDevOps, + parameters.ShouldSignPackages, + parameters.IsPullRequest + ); + + + if (parameters.ShouldSignPackages) + { + if (!parameters.CodeSigning.HasCredentials) + { + throw new CakeException("Code signing credentials are missing."); + } + + context.Command( + parameters.SignCommandSettings, + out var standardOutput, + new ProcessArgumentBuilder() + .Append("--version") + ); + + Information("Sign tool version: {0}", standardOutput); + } + + foreach (var assemblyInfo in GetFiles("./src/**/AssemblyInfo.cs")) + { + CreateAssemblyInfo( + assemblyInfo.ChangeExtension(".Generated.cs"), + new AssemblyInfoSettings { Description = parameters.Version.SemVersion }); + } + + return parameters; +}); + +Teardown((context, parameters) => +{ + Information("Starting Teardown..."); + + if (context.Successful) + { + if (parameters.ShouldPublish) + { + var message = $"Version {parameters.Version.SemVersion} of Cake has just been released, https://www.nuget.org/packages/Cake.Tool/{parameters.Version.SemVersion} 🎉"; + + if (parameters.CanPostToTwitter) + { + TwitterSendTweet(parameters.Twitter.ConsumerKey, parameters.Twitter.ConsumerSecret, parameters.Twitter.AccessToken, parameters.Twitter.AccessTokenSecret, message); + } + } + } + + Information("Finished running tasks."); }); ////////////////////////////////////////////////////////////////////// @@ -67,319 +90,400 @@ Setup(context => ////////////////////////////////////////////////////////////////////// Task("Clean") - .Does(() => CleanDirectories(parameters.Paths.Directories.ToClean)); + .Does((context, parameters) => +{ + CleanDirectories("./src/**/bin/" + parameters.Configuration); + CleanDirectories("./src/**/obj"); + CleanDirectories(parameters.Paths.Directories.ToClean); +}); Task("Restore-NuGet-Packages") .IsDependentOn("Clean") - .Does(() => + .Does((context, parameters) => { - var maxRetryCount = 5; - var toolTimeout = 1d; - Policy - .Handle() - .Retry(maxRetryCount, (exception, retryCount, context) => { - if (retryCount == maxRetryCount) - { - throw exception; - } - else - { - Verbose("{0}", exception); - toolTimeout+=0.5; - }}) - .Execute(()=> { - NuGetRestore("./src/Cake.sln", new NuGetRestoreSettings { - Source = new List { - "https://api.nuget.org/v3/index.json", - "https://www.myget.org/F/roslyn-nightly/api/v3/index.json" - }, - ToolTimeout = TimeSpan.FromMinutes(toolTimeout) - }); - }); + DotNetRestore("./src/Cake.slnx", new DotNetRestoreSettings + { + Verbosity = DotNetVerbosity.Minimal, + Sources = new [] { "https://api.nuget.org/v3/index.json" }, + MSBuildSettings = parameters.MSBuildSettings + }); }); Task("Build") .IsDependentOn("Restore-NuGet-Packages") - .Does(() => + .Does((context, parameters) => { - if(parameters.IsRunningOnUnix) + // Build the solution. + var path = MakeAbsolute(new DirectoryPath("./src/Cake.slnx")); + DotNetBuild(path.FullPath, new DotNetBuildSettings { - XBuild("./src/Cake.sln", new XBuildSettings() - .SetConfiguration(parameters.Configuration) - .WithProperty("POSIX", "True") - .WithProperty("TreatWarningsAsErrors", "True") - .SetVerbosity(Verbosity.Minimal) - ); - } - else - { - MSBuild("./src/Cake.sln", new MSBuildSettings() - .SetConfiguration(parameters.Configuration) - .WithProperty("Windows", "True") - .WithProperty("TreatWarningsAsErrors", "True") - .UseToolVersion(MSBuildToolVersion.NET45) - .SetVerbosity(Verbosity.Minimal) - .SetNodeReuse(false)); - } + Configuration = parameters.Configuration, + NoRestore = true, + MSBuildSettings = parameters.MSBuildSettings + }); }); Task("Run-Unit-Tests") .IsDependentOn("Build") - .Does(() => -{ - XUnit2("./src/**/bin/" + parameters.Configuration + "/*.Tests.dll", new XUnit2Settings { - OutputDirectory = parameters.Paths.Directories.TestResults, - XmlReportV1 = true, - NoAppDomain = true - }); -}); - - -Task("Copy-Files") - .IsDependentOn("Run-Unit-Tests") - .Does(() => + .DoesForEach( + () => GetFiles("./src/**/*.Tests.csproj"), + (parameters, project, context) => { - CopyFiles( - parameters.Paths.Files.ArtifactsSourcePaths, - parameters.Paths.Directories.ArtifactsBin - ); -}); - -Task("Zip-Files") - .IsDependentOn("Copy-Files") - .Does(() => -{ - var files = GetFiles( parameters.Paths.Directories.ArtifactsBin.FullPath + "/*") - - GetFiles(parameters.Paths.Directories.ArtifactsBin.FullPath + "/*.Testing.*"); - - Zip(parameters.Paths.Directories.ArtifactsBin, parameters.Paths.Files.ZipArtifactPath, files); -}); - -Task("Create-Chocolatey-Packages") - .IsDependentOn("Copy-Files") - .IsDependentOn("Package") - .WithCriteria(() => parameters.IsRunningOnWindows) - .Does(() => -{ - foreach(var package in parameters.Packages.Chocolatey) + foreach (var framework in new[] { "net8.0", "net9.0", "net10.0" }) { - // Create package. - ChocolateyPack(package.NuspecPath, new ChocolateyPackSettings { - Version = parameters.Version.SemVersion, - ReleaseNotes = parameters.ReleaseNotes.Notes.ToArray(), - OutputDirectory = parameters.Paths.Directories.NugetRoot, - Files = parameters.Paths.ChocolateyFiles + var trxFileName = $"{project.GetFilenameWithoutExtension()}_{framework}_TestResults.trx"; + + DotNetTest(project.FullPath, new DotNetTestSettings + { + Framework = framework, + PathType = DotNetTestPathType.Project, + NoBuild = true, + NoRestore = true, + Configuration = parameters.Configuration, + ResultsDirectory = parameters.Paths.Directories.TestResults, + ArgumentCustomization = args => args + .Append("--report-trx") + .Append("--report-trx-filename") + .AppendQuoted(trxFileName) }); } }); Task("Create-NuGet-Packages") - .IsDependentOn("Copy-Files") - .Does(() => + .IsDependentOn("Run-Unit-Tests") + .Does((context, parameters) => { - foreach(var package in parameters.Packages.Nuget) + // Build libraries + var projects = GetFiles("./src/*/*.csproj"); + foreach (var project in projects) { - // Create package. - NuGetPack(package.NuspecPath, new NuGetPackSettings { - Version = parameters.Version.SemVersion, - ReleaseNotes = parameters.ReleaseNotes.Notes.ToArray(), - BasePath = parameters.Paths.Directories.ArtifactsBin, - OutputDirectory = parameters.Paths.Directories.NugetRoot, - Symbols = false, - NoPackageAnalysis = true + var name = project.GetDirectory().FullPath; + if (name.EndsWith("Tests") || name.EndsWith("Example")) + { + continue; + } + + DotNetPack(project.FullPath, new DotNetPackSettings { + Configuration = parameters.Configuration, + OutputDirectory = parameters.Paths.Directories.NuGetRoot, + NoBuild = true, + NoRestore = true, + MSBuildSettings = parameters.MSBuildSettings }); } }); -Task("Upload-AppVeyor-Artifacts") - .IsDependentOn("Create-Chocolatey-Packages") - .WithCriteria(() => parameters.IsRunningOnAppVeyor) - .Does(() => +Task("Sign-Binaries") + .IsDependentOn("Create-NuGet-Packages") + .WithCriteria(static (context, parameters) => parameters.ShouldSignPackages) + .Does(static (context, parameters) => { - AppVeyor.UploadArtifact(parameters.Paths.Files.ZipArtifactPath); + // Get the files to sign. + var files = context.GetFiles(string.Concat(parameters.Paths.Directories.NuGetRoot, "/", "*.nupkg")); + + Parallel.ForEach( + files, + file => { + context.Information("Signing {0}...", file.FullPath); + + // Build the argument list. + var arguments = new ProcessArgumentBuilder() + .Append("code") + .Append("azure-key-vault") + .AppendQuoted(file.FullPath) + .AppendSwitchQuoted("--file-list", parameters.Paths.SignFilterPath.FullPath) + .AppendSwitchQuoted("--publisher-name", "Cake") + .AppendSwitchQuoted("--description", "Cake (C# Make) is a cross platform build automation system.") + .AppendSwitchQuoted("--description-url", "https://cakebuild.net") + .AppendSwitchQuoted("--azure-credential-type", "azure-cli") + .AppendSwitchQuotedSecret("--azure-key-vault-certificate", parameters.CodeSigning.SignKeyVaultCertificate) + .AppendSwitchQuotedSecret("--azure-key-vault-url", parameters.CodeSigning.SignKeyVaultUrl); + + context.Command( + parameters.SignCommandSettings, + arguments + ); - foreach(var package in GetFiles(parameters.Paths.Directories.NugetRoot + "/*")) + context.Information("Done signing {0}.", file.FullPath); + }); +}); + +Task("Upload-AppVeyor-Artifacts") + .IsDependentOn("Package") + .WithCriteria((context, parameters) => parameters.IsRunningOnAppVeyor) + .Does((context, parameters) => +{ + foreach (var package in GetFiles(parameters.Paths.Directories.NuGetRoot + "/*")) { AppVeyor.UploadArtifact(package); } }); -Task("Publish-MyGet") +Task("Upload-GitHubActions-Artifacts") .IsDependentOn("Package") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainCakeRepo) - .WithCriteria(() => parameters.IsTagged || !parameters.IsMainCakeBranch) - .Does(() => + .WithCriteria(BuildSystem.IsRunningOnGitHubActions, nameof(BuildSystem.IsRunningOnGitHubActions)) + .Does( + static (context, parameters) => context + .GitHubActions() is var gh && gh != null + ? gh.Commands + .UploadArtifact(parameters.Paths.Directories.NuGetRoot, $"Artifact_{gh.Environment.Runner.ImageOS ?? gh.Environment.Runner.OS}_{gh.Environment.Runner.Architecture}_{context.Environment.Runtime.BuiltFramework.Identifier}_{context.Environment.Runtime.BuiltFramework.Version}") + : throw new Exception("GitHubActions not available") + ); + +Task("Publish-AzureDevOps") + .IsDependentOn("Sign-Binaries") + .IsDependentOn("Package") + .WithCriteria((context, parameters) => parameters.ShouldPublishToAzureDevOps) + .Does((context, parameters) => { - // Resolve the API key. - var apiKey = EnvironmentVariable("MYGET_API_KEY"); - if(string.IsNullOrEmpty(apiKey)) { - throw new InvalidOperationException("Could not resolve MyGet API key."); + string + apiKey = "AzureDevOps", + sourceName = "AzureDevOps", + userName = "AzureDevOps"; + + // Resolve the password . + var password = EnvironmentVariable("AZURE_DEVOPS_NUGET_API_KEY"); + if (string.IsNullOrEmpty(password)) { + throw new InvalidOperationException("Could not resolve AzureDevOps password."); } // Resolve the API url. - var apiUrl = EnvironmentVariable("MYGET_API_URL"); - if(string.IsNullOrEmpty(apiUrl)) { - throw new InvalidOperationException("Could not resolve MyGet API url."); + var apiUrl = EnvironmentVariable("AZURE_DEVOPS_NUGET_API_URL"); + if (string.IsNullOrEmpty(apiUrl)) { + throw new InvalidOperationException("Could not resolve AzureDevOps API url."); } - foreach(var package in parameters.Packages.All) + DotNetNuGetAddSource( + sourceName, + new DotNetNuGetAddSourceSettings + { + UserName = userName, + Password = password, + Source = apiUrl + }); + + foreach (var package in parameters.Packages.NuGet) { // Push the package. - NuGetPush(package.PackagePath, new NuGetPushSettings { - Source = apiUrl, - ApiKey = apiKey - }); + var settings = new DotNetNuGetPushSettings { + ApiKey = apiKey, + Source = apiUrl + }; + + DotNetNuGetPush(package.PackagePath.FullPath, settings); } }) -.OnError(exception => +.OnError((exception, parameters) => { - Information("Publish-MyGet Task failed, but continuing with next Task..."); - publishingError = true; + // Azure Artifacts not critical + Information("Publish-AzureDevOps Task failed, but continuing with next Task..."); }); Task("Publish-NuGet") - .IsDependentOn("Create-NuGet-Packages") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainCakeRepo) - .WithCriteria(() => parameters.IsMainCakeBranch) - .WithCriteria(() => parameters.IsTagged) - .Does(() => + .IsDependentOn("Sign-Binaries") + .IsDependentOn("Package") + .WithCriteria((context, parameters) => parameters.ShouldPublish) + .Does((context, parameters) => { // Resolve the API key. var apiKey = EnvironmentVariable("NUGET_API_KEY"); - if(string.IsNullOrEmpty(apiKey)) { + if (string.IsNullOrEmpty(apiKey)) { throw new InvalidOperationException("Could not resolve NuGet API key."); } // Resolve the API url. var apiUrl = EnvironmentVariable("NUGET_API_URL"); - if(string.IsNullOrEmpty(apiUrl)) { + if (string.IsNullOrEmpty(apiUrl)) { throw new InvalidOperationException("Could not resolve NuGet API url."); } - foreach(var package in parameters.Packages.Nuget) + foreach (var package in parameters.Packages.NuGet) { // Push the package. - NuGetPush(package.PackagePath, new NuGetPushSettings { - ApiKey = apiKey, - Source = apiUrl - }); + var settings = new DotNetNuGetPushSettings { + ApiKey = apiKey, + Source = apiUrl + }; + + DotNetNuGetPush(package.PackagePath.FullPath, settings); } }) -.OnError(exception => +.OnError((exception, parameters) => { Information("Publish-NuGet Task failed, but continuing with next Task..."); - publishingError = true; + parameters.PublishingError = true; }); -Task("Publish-Chocolatey") - .IsDependentOn("Create-Chocolatey-Packages") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainCakeRepo) - .WithCriteria(() => parameters.IsMainCakeBranch) - .WithCriteria(() => parameters.IsTagged) - .Does(() => +Task("Publish-GitHub-Release") + .WithCriteria((context, parameters) => parameters.ShouldPublish) + .Does((context, parameters) => { - // Resolve the API key. - var apiKey = EnvironmentVariable("CHOCOLATEY_API_KEY"); - if(string.IsNullOrEmpty(apiKey)) { - throw new InvalidOperationException("Could not resolve Chocolatey API key."); - } - - // Resolve the API url. - var apiUrl = EnvironmentVariable("CHOCOLATEY_API_URL"); - if(string.IsNullOrEmpty(apiUrl)) { - throw new InvalidOperationException("Could not resolve Chocolatey API url."); - } - - foreach(var package in parameters.Packages.Chocolatey) + foreach (var package in GetFiles(parameters.Paths.Directories.NuGetRoot + "/*")) { - // Push the package. - ChocolateyPush(package.PackagePath, new ChocolateyPushSettings { - ApiKey = apiKey, - Source = apiUrl - }); + GitReleaseManagerAddAssets(parameters.GitHub.Token, "cake-build", "cake", parameters.Version.Milestone, package.FullPath); } -}) -.OnError(exception => -{ - Information("Publish-Chocolatey Task failed, but continuing with next Task..."); - publishingError = true; -}); - -Task("Publish-HomeBrew") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainCakeRepo) - .WithCriteria(() => parameters.IsMainCakeBranch) - .WithCriteria(() => parameters.IsTagged) - .IsDependentOn("Zip-Files") - .Does(() => -{ - var hash = CalculateFileHash(parameters.Paths.Files.ZipArtifactPath).ToHex(); - - Information("Hash for creating HomeBrew PullRequest: {0}", hash); -}) -.OnError(exception => -{ - Information("Publish-HomeBrew Task failed, but continuing with next Task..."); - publishingError = true; -}); - -Task("Publish-GitHub-Release") - .WithCriteria(() => !parameters.IsLocalBuild) - .WithCriteria(() => !parameters.IsPullRequest) - .WithCriteria(() => parameters.IsMainCakeRepo) - .WithCriteria(() => parameters.IsMainCakeBranch) - .WithCriteria(() => parameters.IsTagged) - .Does(() => -{ - GitReleaseManagerAddAssets(parameters.GitHub.UserName, parameters.GitHub.Password, "cake-build", "cake", parameters.Version.Milestone, parameters.Paths.Files.ZipArtifactPath.ToString()); - GitReleaseManagerClose(parameters.GitHub.UserName, parameters.GitHub.Password, "cake-build", "cake", parameters.Version.Milestone); + GitReleaseManagerClose(parameters.GitHub.Token, "cake-build", "cake", parameters.Version.Milestone); }) -.OnError(exception => +.OnError((exception, parameters) => { Information("Publish-GitHub-Release Task failed, but continuing with next Task..."); - publishingError = true; + parameters.PublishingError = true; }); Task("Create-Release-Notes") - .Does(() => + .Does((context, parameters) => { - GitReleaseManagerCreate(parameters.GitHub.UserName, parameters.GitHub.Password, "cake-build", "cake", new GitReleaseManagerCreateSettings { + GitReleaseManagerCreate(parameters.GitHub.Token, "cake-build", "cake", new GitReleaseManagerCreateSettings { Milestone = parameters.Version.Milestone, Name = parameters.Version.Milestone, - Prerelease = true, + Prerelease = false, TargetCommitish = "main" }); }); +Task("Prepare-Integration-Tests") + .IsDependentOn("Create-NuGet-Packages") + .Does((context, parameters) => +{ + Unzip(parameters.Paths.Directories.NuGetRoot.CombineWithFilePath($"Cake.Tool.{parameters.Version.SemVersion}.nupkg"), + parameters.Paths.Directories.IntegrationTestsBinTool); +}); + +Task("Frosting-Integration-Tests") + .DeferOnError() + .DoesForEach( + (parameters, context) => { + var project = context.MakeAbsolute( + new FilePath("tests/integration/Cake.Frosting/build/Build.csproj") + ); + + DotNetBuild(project.FullPath, + new DotNetBuildSettings + { + Verbosity = DotNetVerbosity.Quiet, + Configuration = parameters.Configuration, + MSBuildSettings = parameters.MSBuildSettings + }); + + context.Verbose("Peeking into {0}...", project); + + var targetFrameworks = context.XmlPeek( + project, + "/Project/PropertyGroup/TargetFrameworks" + ); + + return targetFrameworks?.Split(';') + .Select(targetFramework => (targetFramework, project)) + .ToArray(); + }, + (parameters, test, context) => +{ + try + { + Information("Testing: {0}", test.Framework); + + DotNetRun(test.Project.FullPath, + new ProcessArgumentBuilder() + .AppendSwitchQuoted("--verbosity", "=", Argument("integration-tests-verbosity", "quiet")) + .AppendSwitchQuoted("--name", "=", "World") + .AppendSwitchQuoted("--IntegrationTest_Argument", "=", bool.TrueString), + new DotNetRunSettings + { + Configuration = parameters.Configuration, + Framework = test.Framework, + NoRestore = true, + NoBuild = true, + EnvironmentVariables = new Dictionary + { + ["CAKE_INTEGRATIONTEST_ENVIRONMENT"] = bool.TrueString, + } + }); + } + catch(Exception ex) + { + Error("While testing: {0}\r\n{1}", test.Framework, ex); + throw new Exception($"Exception while testing: {test.Framework}", ex); + } + finally + { + Information("Done testing: {0}", test.Framework); + } +}); +Task("Run-Integration-Tests") + .IsDependentOn("Prepare-Integration-Tests") + .IsDependentOn("Frosting-Integration-Tests") + .DeferOnError() + .DoesForEach( + parameters => new[] { + GetFiles($"{parameters.Paths.Directories.IntegrationTestsBinTool.FullPath}/**/net8.0/**/Cake.dll").Single(), + GetFiles($"{parameters.Paths.Directories.IntegrationTestsBinTool.FullPath}/**/net9.0/**/Cake.dll").Single(), + GetFiles($"{parameters.Paths.Directories.IntegrationTestsBinTool.FullPath}/**/net10.0/**/Cake.dll").Single() + }, + (parameters, cakeAssembly, context) => +{ + try + { + Information("Testing: {0}", cakeAssembly); + CakeExecuteScript("./tests/integration/build.cake", + new CakeSettings { + ToolPath = cakeAssembly, + EnvironmentVariables = { + ["MyEnvironmentVariable"] = "Hello World", + ["CAKE_INTEGRATION_TEST_ROOT"] = "../.." + }, + ArgumentCustomization = args => args + .AppendSwitchQuoted("--target", " ", Argument("integration-tests-target", "Run-All-Tests")) + .AppendSwitchQuoted("--verbosity", " ", Argument("integration-tests-verbosity", "quiet")) + .AppendSwitchQuoted("--platform", " ", parameters.IsRunningOnWindows ? "windows" : "posix") + .AppendSwitchQuoted("--customarg", " ", "hello") + .AppendSwitchQuoted("--multipleargs", "=", "a") + .AppendSwitchQuoted("--multipleargs", "=", "b") + .AppendSwitchQuoted("--testAssemblyDirectoryPath", "=", cakeAssembly.GetDirectory().FullPath) + .AppendSwitchQuoted("--testAssemblyFilePath", "=", cakeAssembly.FullPath) + .AppendSwitchQuoted("--testDotNetVerbosity", "=", "Diagnostic") + .AppendSwitchQuoted("--testDotNetRollForward", "=", "LatestMajor") + }); + } + catch(Exception ex) + { + Error("While testing: {0}\r\n{1}", cakeAssembly, ex); + throw new Exception($"Exception while testing: {cakeAssembly}", ex); + } + finally + { + Information("Done testing: {0}", cakeAssembly); + } +}); + + ////////////////////////////////////////////////////////////////////// // TASK TARGETS ////////////////////////////////////////////////////////////////////// Task("Package") - .IsDependentOn("Zip-Files") .IsDependentOn("Create-NuGet-Packages"); Task("Default") .IsDependentOn("Package"); Task("AppVeyor") - .IsDependentOn("Upload-AppVeyor-Artifacts") - .IsDependentOn("Publish-MyGet") + .IsDependentOn("Upload-AppVeyor-Artifacts"); + + +Task("GitHubActions") + .IsDependentOn("Upload-GitHubActions-Artifacts") + .IsDependentOn("Run-Integration-Tests") + .IsDependentOn("Publish-AzureDevOps"); + +Task("GitHubActions-Release") + .IsDependentOn("Upload-GitHubActions-Artifacts") + .IsDependentOn("Publish-AzureDevOps") .IsDependentOn("Publish-NuGet") - .IsDependentOn("Publish-Chocolatey") - .IsDependentOn("Publish-HomeBrew") .IsDependentOn("Publish-GitHub-Release") - .Finally(() => + .Does((context, parameters) => { - if(publishingError) + if (parameters.PublishingError) { throw new Exception("An error occurred during the publishing of Cake. All publishing tasks have been attempted."); } @@ -388,11 +492,17 @@ Task("AppVeyor") Task("Travis") .IsDependentOn("Run-Unit-Tests"); +Task("Rwx") + .IsDependentOn("Run-Integration-Tests"); + Task("ReleaseNotes") .IsDependentOn("Create-Release-Notes"); +Task("AzureDevOps") + .IsDependentOn("Package"); + ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// -RunTarget(parameters.Target); \ No newline at end of file +RunTarget(Argument("target", "Default")); diff --git a/build.ps1 b/build.ps1 old mode 100644 new mode 100755 index 184f22d585..96db12352e --- a/build.ps1 +++ b/build.ps1 @@ -1,69 +1,152 @@ -Param( - [string]$Script = "build.cake", - [string]$Target = "Default", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity = "Verbose", - [switch]$Experimental, - [switch]$WhatIf, - [switch]$Mono, - [switch]$SkipToolPackageRestore, - [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] - [string[]]$ScriptArgs -) - -$PSScriptRoot = split-path -parent $MyInvocation.MyCommand.Definition; -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" -$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - -# Should we use experimental build of Roslyn? -$UseExperimental = ""; -if($Experimental.IsPresent) { - $UseExperimental = "-experimental" -} +#!/usr/bin/env pwsh +$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; +$DotNetUnixInstallerUri = 'https://dot.net/v1/dotnet-install.sh' +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -# Is this a dry run? -$UseDryRun = ""; -if($WhatIf.IsPresent) { - $UseDryRun = "-dryrun" +# Make sure tools folder exists +$ToolPath = Join-Path $PSScriptRoot "tools" +if (!(Test-Path $ToolPath)) { + Write-Verbose "Creating tools directory..." + New-Item -Path $ToolPath -Type Directory -Force | out-null } -# Should we use mono? -$UseMono = ""; -if($Mono.IsPresent) { - $UseMono = "-mono" + +if ($PSVersionTable.PSEdition -ne 'Core') { + # Attempt to set highest encryption available for SecurityProtocol. + # PowerShell will not set this by default (until maybe .NET 4.6.x). This + # will typically produce a message for PowerShell v2 (just an info + # message though) + try { + # Set TLS 1.3 (12288), then TLS 1.2 (3072) + # Use integers because the enumeration values for TLS 1.3 and TLS 1.2 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.6.2+ is + # installed (.NET 4.6.2 is an in-place upgrade). + [System.Net.ServicePointManager]::SecurityProtocol = 12288 -bor 3072 + } catch { + Write-Output 'Unable to set PowerShell to use TLS 1.3 and TLS 1.2 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.6.2+ and PowerShell v3' + } } -# Try download NuGet.exe if do not exist. -if (!(Test-Path $NUGET_EXE)) { - (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT=1 +$env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 + + +Function Remove-PathVariable([string]$VariableToRemove) +{ + $SplitChar = ';' + if ($IsMacOS -or $IsLinux) { + $SplitChar = ':' + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "User") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "User") + } + + $path = [Environment]::GetEnvironmentVariable("PATH", "Process") + if ($path -ne $null) + { + $newItems = $path.Split($SplitChar, [StringSplitOptions]::RemoveEmptyEntries) | Where-Object { "$($_)" -inotlike $VariableToRemove } + [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join($SplitChar, $newItems), "Process") + } } -# Make sure NuGet exists where we expect it. -if (!(Test-Path $NUGET_EXE)) { - Throw "Could not find NuGet.exe" +$InstallPath = Join-Path $PSScriptRoot ".dotnet" +$GlobalJsonPath = Join-Path $PSScriptRoot "global.json" +if (!(Test-Path $InstallPath)) { + New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null; } -# Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) -{ - Push-Location - Set-Location $TOOLS_DIR - Invoke-Expression "$NUGET_EXE install -ExcludeVersion" - Pop-Location - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE +[bool]$InstallSupportedSdks = $env:CAKE_INSTALL_SUPPORTED_SDKS -eq "true" -or [string]::IsNullOrWhiteSpace($env:TEAMCITY_VERSION) -eq $false +[string]$InstallSdkVersion = $env:CAKE_INSTALL_SDK_VERSION +[bool]$InstallSpecificSdkVersion = [string]::IsNullOrWhiteSpace($InstallSdkVersion) -eq $false + +if ($IsMacOS -or $IsLinux) { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.sh' + (New-Object System.Net.WebClient).DownloadFile($DotNetUnixInstallerUri, $ScriptPath); + + # Install additional SDK channels if CAKE_INSTALL_SUPPORTED_SDKS is set to true + if ($InstallSupportedSdks) { + Write-Host "Installing additional supported SDK channels..." + + # Install .NET 8.0 SDK + Write-Host "Installing .NET 8.0 SDK..." + & bash $ScriptPath --channel 8.0 --install-dir "$InstallPath" --no-path + + # Install .NET 9.0 SDK + Write-Host "Installing .NET 9.0 SDK..." + & bash $ScriptPath --channel 9.0 --install-dir "$InstallPath" --no-path --skip-non-versioned-files + + # Install .NET 10.0 SDK (preview quality) + Write-Host "Installing .NET 10.0 SDK (preview)..." + & bash $ScriptPath --channel 10.0 --quality preview --install-dir "$InstallPath" --no-path --skip-non-versioned-files + } + + # Install SDK from global.json + & bash $ScriptPath --jsonfile "$GlobalJsonPath" --install-dir "$InstallPath" --no-path + + # Install specific SDK version if CAKE_INSTALL_SDK_VERSION is set + if ($InstallSpecificSdkVersion) { + Write-Host "Installing .NET $InstallSdkVersion SDK..." + & bash $ScriptPath --version $InstallSdkVersion --install-dir "$InstallPath" --no-path } + + Remove-PathVariable "$InstallPath" + $env:PATH = "$($InstallPath):$env:PATH" } +else { + $ScriptPath = Join-Path $InstallPath 'dotnet-install.ps1' + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath); + + # Install additional SDK channels if CAKE_INSTALL_SUPPORTED_SDKS is set to true + if ($InstallSupportedSdks) { + Write-Host "Installing additional supported SDK channels..." + + # Install .NET 8.0 SDK + Write-Host "Installing .NET 8.0 SDK..." + & $ScriptPath -Channel 8.0 -InstallDir $InstallPath -NoPath + + # Install .NET 9.0 SDK + Write-Host "Installing .NET 9.0 SDK..." + & $ScriptPath -Channel 9.0 -InstallDir $InstallPath -NoPath -SkipNonVersionedFiles + + # Install .NET 10.0 SDK (preview quality) + Write-Host "Installing .NET 10.0 SDK (preview)..." + & $ScriptPath -Channel 10.0 -Quality preview -InstallDir $InstallPath -NoPath -SkipNonVersionedFiles + } + + # Install SDK from global.json + & $ScriptPath -JSonFile $GlobalJsonPath -InstallDir $InstallPath; -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe" + # Install specific SDK version if CAKE_INSTALL_SDK_VERSION is set + if ($InstallSpecificSdkVersion) { + Write-Host "Installing .NET $InstallSdkVersion SDK..." + & $ScriptPath -Version $InstallSdkVersion -InstallDir $InstallPath -NoPath + } + + Remove-PathVariable "$InstallPath" + $env:PATH = "$InstallPath;$env:PATH" } +$env:DOTNET_ROOT=$InstallPath + +& dotnet --info + +########################################################################### +# INSTALL CAKE +########################################################################### + +& dotnet tool restore + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### +& dotnet cake ./build.cake $args -# Start Cake -Invoke-Expression "$CAKE_EXE `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE +exit $LASTEXITCODE \ No newline at end of file diff --git a/build.sh b/build.sh index 3d74a70fb5..47f48801ab 100755 --- a/build.sh +++ b/build.sh @@ -1,82 +1,67 @@ #!/usr/bin/env bash -############################################################### -# This is the Cake bootstrapper script that is responsible for -# downloading Cake and all specified tools from NuGet. -############################################################### - -# Define directories. +# Define varibles SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) TOOLS_DIR=$SCRIPT_DIR/tools -NUGET_EXE=$TOOLS_DIR/nuget.exe -CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe - -# Define default arguments. -SCRIPT="build.cake" -TARGET="Travis" -CONFIGURATION="Release" -VERBOSITY="verbose" -DRYRUN= -SHOW_VERSION=false -SCRIPT_ARGUMENTS=() -# Parse arguments. -for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - -t|--target) TARGET="$2"; shift ;; - -c|--configuration) CONFIGURATION="$2"; shift ;; - -v|--verbosity) VERBOSITY="$2"; shift ;; - -d|--dryrun) DRYRUN="-dryrun" ;; - --version) SHOW_VERSION=true ;; - --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; - *) SCRIPT_ARGUMENTS+=("$1") ;; - esac - shift -done # Make sure the tools folder exist. -if [ ! -d $TOOLS_DIR ]; then - mkdir $TOOLS_DIR +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" fi -# Make sure that packages.config exist. -if [ ! -f $TOOLS_DIR/packages.config ]; then - echo "Downloading packages.config..." - curl -Lsfo $TOOLS_DIR/packages.config http://cakebuild.net/download/bootstrapper/packages - if [ $? -ne 0 ]; then - echo "An error occured while downloading packages.config." - exit 1 - fi -fi +########################################################################### +# INSTALL .NET CORE CLI +########################################################################### -# Download NuGet if it does not exist. -if [ ! -f $NUGET_EXE ]; then - echo "Downloading NuGet..." - curl -Lsfo $NUGET_EXE https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - if [ $? -ne 0 ]; then - echo "An error occured while downloading nuget.exe." - exit 1 - fi -fi +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 +export DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 -# Restore tools from NuGet. -pushd $TOOLS_DIR >/dev/null -mono $NUGET_EXE install -ExcludeVersion -if [ $? -ne 0 ]; then - echo "Could not restore NuGet packages." - exit 1 +if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then + mkdir "$SCRIPT_DIR/.dotnet" fi -popd >/dev/null +curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh -# Make sure that Cake has been installed. -if [ ! -f $CAKE_EXE ]; then - echo "Could not find Cake.exe at '$CAKE_EXE'." - exit 1 +# Install additional SDK channels if CAKE_INSTALL_SUPPORTED_SDKS is set to true +if [ "$CAKE_INSTALL_SUPPORTED_SDKS" = "true" ]; then + echo "Installing additional supported SDK channels..." + + # Install .NET 8.0 SDK + echo "Installing .NET 8.0 SDK..." + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --channel 8.0 --install-dir .dotnet --no-path + + # Install .NET 9.0 SDK + echo "Installing .NET 9.0 SDK..." + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --channel 9.0 --install-dir .dotnet --no-path --skip-non-versioned-files + + # Install .NET 10.0 SDK (preview quality) + echo "Installing .NET 10.0 SDK (preview)..." + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --channel 10.0 --quality preview --install-dir .dotnet --no-path --skip-non-versioned-files fi -# Start Cake -if $SHOW_VERSION; then - exec mono $CAKE_EXE -version -else - exec mono $CAKE_EXE $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" +# Install SDK from global.json +bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --jsonfile ./global.json --install-dir .dotnet --no-path + +# Install specific SDK version if CAKE_INSTALL_SDK_VERSION is set +if [ "$CAKE_INSTALL_SDK_VERSION" ]; then + echo "Installing .NET $CAKE_INSTALL_SDK_VERSION SDK..." + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --version $CAKE_INSTALL_SDK_VERSION --install-dir .dotnet --no-path fi + +export PATH="$SCRIPT_DIR/.dotnet":$PATH +export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" + +dotnet --info + +########################################################################### +# INSTALL CAKE +########################################################################### + +dotnet tool restore + +########################################################################### +# RUN BUILD SCRIPT +########################################################################### + +dotnet cake "$@" \ No newline at end of file diff --git a/build/credentials.cake b/build/credentials.cake new file mode 100644 index 0000000000..14522362f8 --- /dev/null +++ b/build/credentials.cake @@ -0,0 +1,57 @@ +public record CodeSigningCredentials( + string SignTenantId, + string SignClientId, + string SignClientSubsription, + string SignKeyVaultCertificate, + string SignKeyVaultUrl +) +{ + public bool HasCredentials + { + get + { + return + !string.IsNullOrEmpty(SignTenantId) && + !string.IsNullOrEmpty(SignClientId) && + !string.IsNullOrEmpty(SignClientSubsription) && + !string.IsNullOrEmpty(SignKeyVaultCertificate) && + !string.IsNullOrEmpty(SignKeyVaultUrl); + } + } + + public static CodeSigningCredentials GetCodeSigningCredentials(ICakeContext context) + { + return new CodeSigningCredentials( + SignTenantId: context.EnvironmentVariable("SIGN_TENANT_ID"), + SignClientId: context.EnvironmentVariable("SIGN_CLIENT_ID"), + SignClientSubsription: context.EnvironmentVariable("SIGN_CLIENT_SUBSCRIPTION"), + SignKeyVaultCertificate: context.EnvironmentVariable("SIGN_KEYVAULT_CERTIFICATE"), + SignKeyVaultUrl: context.EnvironmentVariable("SIGN_KEYVAULT_URL")); + } +} + +public record BuildCredentials(string Token) +{ + public static BuildCredentials GetGitHubCredentials(ICakeContext context) + { + return new BuildCredentials( + context.EnvironmentVariable("CAKE_GITHUB_TOKEN")); + } +} + +public record TwitterCredentials( + string ConsumerKey, + string ConsumerSecret, + string AccessToken, + string AccessTokenSecret +) +{ + public static TwitterCredentials GetTwitterCredentials(ICakeContext context) + { + return new TwitterCredentials( + context.EnvironmentVariable("TWITTER_CONSUMER_KEY"), + context.EnvironmentVariable("TWITTER_CONSUMER_SECRET"), + context.EnvironmentVariable("TWITTER_ACCESS_TOKEN"), + context.EnvironmentVariable("TWITTER_ACCESS_TOKEN_SECRET")); + } +} diff --git a/build/packages.cake b/build/packages.cake index 26d42170a9..0f5e8485f3 100644 --- a/build/packages.cake +++ b/build/packages.cake @@ -1,56 +1,30 @@ -public class BuildPackages +public record BuildPackages( + ICollection NuGet +) { - public ICollection All { get; private set; } - public ICollection Nuget { get; private set; } - public ICollection Chocolatey { get; private set; } - public static BuildPackages GetPackages( DirectoryPath nugetRooPath, string semVersion, - string[] packageIds, - string[] chocolateyPackageIds - ) + string[] packageIds) { - var toNugetPackage = BuildPackage(nugetRooPath, semVersion); - var toChocolateyPackage = BuildPackage(nugetRooPath, semVersion, isChocolateyPackage: true); - var nugetPackages = packageIds.Select(toNugetPackage).ToArray(); - var chocolateyPackages = chocolateyPackageIds.Select(toChocolateyPackage).ToArray(); - return new BuildPackages { - All = nugetPackages.Union(chocolateyPackages).ToArray(), - Nuget = nugetPackages, - Chocolatey = chocolateyPackages - }; + var toNuGetPackage = BuildPackage(nugetRooPath, semVersion); + var nugetPackages = packageIds.Select(toNuGetPackage).ToArray(); + + return new BuildPackages(nugetPackages); } - private static Func BuildPackage(DirectoryPath nugetRooPath, string semVersion, - bool isChocolateyPackage = false) + private static Func BuildPackage( + DirectoryPath nugetRooPath, + string semVersion) { return package => new BuildPackage( - id: package, - nuspecPath: string.Concat("./nuspec/", package, ".nuspec"), - packagePath: nugetRooPath.CombineWithFilePath(string.Concat(package, ".", semVersion, ".nupkg")), - isChocolateyPackage: isChocolateyPackage + Id: package, + PackagePath: nugetRooPath.CombineWithFilePath(string.Concat(package, ".", semVersion, ".nupkg")) ); } } -public class BuildPackage -{ - public string Id { get; private set; } - public FilePath NuspecPath { get; private set; } - public FilePath PackagePath { get; private set; } - public bool IsChocolateyPackage { get; private set; } - - public BuildPackage( - string id, - FilePath nuspecPath, - FilePath packagePath, - bool isChocolateyPackage - ) - { - Id = id; - NuspecPath = nuspecPath; - PackagePath = packagePath; - IsChocolateyPackage = isChocolateyPackage; - } -} \ No newline at end of file +public record BuildPackage( + string Id, + FilePath PackagePath +); \ No newline at end of file diff --git a/build/parameters.cake b/build/parameters.cake index 8c03ffdfa9..3cff9f9d1a 100644 --- a/build/parameters.cake +++ b/build/parameters.cake @@ -1,103 +1,170 @@ #load "./paths.cake" #load "./packages.cake" #load "./version.cake" +#load "./credentials.cake" public class BuildParameters { - public string Target { get; private set; } - public string Configuration { get; private set; } - public bool IsLocalBuild { get; private set; } - public bool IsRunningOnUnix { get; private set; } - public bool IsRunningOnWindows { get; private set; } - public bool IsRunningOnAppVeyor { get; private set; } - public bool IsPullRequest { get; private set; } - public bool IsMainCakeRepo { get; private set; } - public bool IsMainCakeBranch { get; private set; } - public bool IsTagged { get; private set; } - public bool IsPublishBuild { get; private set; } - public bool IsReleaseBuild { get; private set; } - public bool SkipGitVersion { get; private set; } - public BuildCredentials GitHub { get; private set; } - public ReleaseNotes ReleaseNotes { get; private set; } - public BuildVersion Version { get; private set; } - public BuildPaths Paths { get; private set; } - public BuildPackages Packages { get; private set; } - - public void SetBuildVersion(BuildVersion version) + public string Target { get; } + public string Configuration { get; } + public bool IsLocalBuild { get; } + public bool IsRunningOnUnix { get; } + public bool IsRunningOnWindows { get; } + public bool IsRunningOnAppVeyor { get; } + public bool IsRunningOnGitHubActions { get; } + public bool IsPullRequest { get; } + public bool IsMainCakeRepo { get; } + public bool IsMainCakeBranch { get; } + public bool IsDevelopCakeBranch { get; } + public bool IsTagged { get; } + public bool IsPublishBuild { get; } + public bool IsReleaseBuild { get; } + public bool SkipGitVersion { get; } + public bool SkipSigning { get; } + public BuildCredentials GitHub { get; } + public TwitterCredentials Twitter { get; } + public ReleaseNotes ReleaseNotes { get; } + public BuildVersion Version { get; set; } + public BuildPaths Paths { get; } + public BuildPackages Packages { get; } + public bool PublishingError { get; set; } + public DotNetMSBuildSettings MSBuildSettings { get; } + public CodeSigningCredentials CodeSigning { get; } + public CommandSettings SignCommandSettings { get; } + + public bool ShouldPublish { - Version = version; + get + { + return IsRunningOnGitHubActions + && IsRunningOnWindows + && !IsLocalBuild + && !IsPullRequest + && IsMainCakeRepo + && IsTagged; + } } - public void SetBuildPaths(BuildPaths paths) + public bool ShouldPublishToAzureDevOps { - Paths = paths; + get + { + return IsRunningOnGitHubActions + && IsRunningOnWindows + && !IsLocalBuild + && !IsPullRequest + && IsMainCakeRepo + && (IsTagged || IsDevelopCakeBranch); + } } - public void SetBuildPackages(BuildPackages packages) + + public bool ShouldSignPackages { get; } + public bool CanPostToTwitter { - Packages = packages; + get + { + return !string.IsNullOrEmpty(Twitter.ConsumerKey) && + !string.IsNullOrEmpty(Twitter.ConsumerSecret) && + !string.IsNullOrEmpty(Twitter.AccessToken) && + !string.IsNullOrEmpty(Twitter.AccessTokenSecret); + } } - public static BuildParameters GetParameters( - ICakeContext context, - BuildSystem buildSystem - ) + public BuildParameters (ISetupContext context) { if (context == null) { throw new ArgumentNullException("context"); } - var target = context.Argument("target", "Default"); - - return new BuildParameters { - Target = target, - Configuration = context.Argument("configuration", "Release"), - IsLocalBuild = buildSystem.IsLocalBuild, - IsRunningOnUnix = context.IsRunningOnUnix(), - IsRunningOnWindows = context.IsRunningOnWindows(), - IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor, - IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest, - IsMainCakeRepo = StringComparer.OrdinalIgnoreCase.Equals("cake-build/cake", buildSystem.AppVeyor.Environment.Repository.Name), - IsMainCakeBranch = StringComparer.OrdinalIgnoreCase.Equals("main", buildSystem.AppVeyor.Environment.Repository.Branch), - IsTagged = ( - buildSystem.AppVeyor.Environment.Repository.Tag.IsTag && - !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name) - ), - GitHub = new BuildCredentials ( - userName: context.EnvironmentVariable("CAKE_GITHUB_USERNAME"), - password: context.EnvironmentVariable("CAKE_GITHUB_PASSWORD") - ), - ReleaseNotes = context.ParseReleaseNotes("./ReleaseNotes.md"), - IsPublishBuild = new [] { - "ReleaseNotes", - "Create-Release-Notes" - }.Any( - releaseTarget => StringComparer.OrdinalIgnoreCase.Equals(releaseTarget, target) - ), - IsReleaseBuild = new [] { - "Publish", - "Publish-NuGet", - "Publish-Chocolatey", - "Publish-HomeBrew", - "Publish-GitHub-Release" - }.Any( - publishTarget => StringComparer.OrdinalIgnoreCase.Equals(publishTarget, target) - ), - SkipGitVersion = StringComparer.OrdinalIgnoreCase.Equals("True", context.EnvironmentVariable("CAKE_SKIP_GITVERSION")) + var buildSystem = context.BuildSystem(); + + Target = context.TargetTask.Name; + Configuration = context.Argument("configuration", "Release"); + IsLocalBuild = buildSystem.IsLocalBuild; + IsRunningOnUnix = context.IsRunningOnUnix(); + IsRunningOnWindows = context.IsRunningOnWindows(); + IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor; + IsRunningOnGitHubActions = buildSystem.GitHubActions.IsRunningOnGitHubActions; + IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest || buildSystem.GitHubActions.Environment.PullRequest.IsPullRequest; + IsTagged = IsBuildTagged(buildSystem); + GitHub = BuildCredentials.GetGitHubCredentials(context); + Twitter = TwitterCredentials.GetTwitterCredentials(context); + CodeSigning = CodeSigningCredentials.GetCodeSigningCredentials(context); + ReleaseNotes = context.ParseReleaseNotes("./ReleaseNotes.md"); + IsPublishBuild = IsPublishing(context.TargetTask.Name); + IsReleaseBuild = IsReleasing(context.TargetTask.Name); + SkipSigning = StringComparer.OrdinalIgnoreCase.Equals("True", context.Argument("skipsigning", "False")); + SkipGitVersion = StringComparer.OrdinalIgnoreCase.Equals("True", context.EnvironmentVariable("CAKE_SKIP_GITVERSION")); + Version = BuildVersion.Calculate(context, this); + IsMainCakeBranch = StringComparer.OrdinalIgnoreCase.Equals("main", buildSystem.AppVeyor.Environment.Repository.Branch) || StringComparer.OrdinalIgnoreCase.Equals("main", Version.BranchName); + IsDevelopCakeBranch = StringComparer.OrdinalIgnoreCase.Equals("develop", buildSystem.AppVeyor.Environment.Repository.Branch) || StringComparer.OrdinalIgnoreCase.Equals("develop", Version.BranchName); + IsMainCakeRepo = StringComparer.OrdinalIgnoreCase.Equals("cake-build/cake", buildSystem.AppVeyor.Environment.Repository.Name) || StringComparer.OrdinalIgnoreCase.Equals("cake-build", buildSystem.GitHubActions.Environment.Workflow.RepositoryOwner); + Paths = BuildPaths.GetPaths(context, Configuration, Version.SemVersion); + SignCommandSettings = new CommandSettings{ + ToolExecutableNames = new [] { "sign", "sign.exe" }, + ToolName = "sign", + ToolPath = Paths.SignClientPath?.FullPath }; + Packages = BuildPackages.GetPackages( + Paths.Directories.NuGetRoot, + Version.SemVersion, + new [] { + "Cake.Core", + "Cake.Common", + "Cake.Testing", + "Cake.Testing.Xunit", + "Cake.Testing.Xunit.v3", + "Cake.NuGet", + "Cake.Tool", + "Cake.Frosting", + "Cake.Frosting.Template", + "Cake.Cli", + "Cake.DotNetTool.Module" + }); + + var releaseNotes = string.Join( + System.Environment.NewLine, + ReleaseNotes.Notes.ToArray() + ); + + MSBuildSettings = new DotNetMSBuildSettings { + Version = Version.SemVersion, + AssemblyVersion = Version.Version, + FileVersion = Version.Version, + PackageReleaseNotes = releaseNotes + }; + + if (!IsLocalBuild) + { + MSBuildSettings.WithProperty("TemplateVersion", Version.SemVersion); + } + + + ShouldSignPackages = (!SkipSigning && ShouldPublish) + || + StringComparer.OrdinalIgnoreCase.Equals( + context.EnvironmentVariable("SIGNING_TEST"), + "True" + ); } -} -public class BuildCredentials -{ - public string UserName { get; private set; } - public string Password { get; private set; } + private static bool IsBuildTagged(BuildSystem buildSystem) + { + return buildSystem.GitHubActions.IsRunningOnGitHubActions && buildSystem.GitHubActions.Environment.Workflow.RefType == GitHubActionsRefType.Tag; + } + + private static bool IsReleasing(string target) + { + var targets = new [] { "Publish", "Publish-NuGet", "Publish-GitHub-Release" }; + return targets.Any(t => StringComparer.OrdinalIgnoreCase.Equals(t, target)); + } - public BuildCredentials(string userName, string password) + private static bool IsPublishing(string target) { - UserName = userName; - Password = password; + var targets = new [] { "ReleaseNotes", "Create-Release-Notes" }; + return targets.Any(t => StringComparer.OrdinalIgnoreCase.Equals(t, target)); } } diff --git a/build/paths.cake b/build/paths.cake index 48db28a9b6..7661a26066 100644 --- a/build/paths.cake +++ b/build/paths.cake @@ -1,9 +1,9 @@ -public class BuildPaths +public record BuildPaths( + BuildDirectories Directories, + FilePath SignClientPath, + FilePath SignFilterPath +) { - public BuildFiles Files { get; private set; } - public BuildDirectories Directories { get; private set; } - public ICollection ChocolateyFiles { get; private set; } - public static BuildPaths GetPaths( ICakeContext context, string configuration, @@ -14,148 +14,76 @@ public class BuildPaths { throw new ArgumentNullException("context"); } - if (string.IsNullOrEmpty(configuration)) { throw new ArgumentNullException("configuration"); } - if (string.IsNullOrEmpty(semVersion)) { throw new ArgumentNullException("semVersion"); } - var buildDir = context.Directory("./src/Cake/bin") + context.Directory(configuration); var artifactsDir = (DirectoryPath)(context.Directory("./artifacts") + context.Directory("v" + semVersion)); - var artifactsBinDir = artifactsDir.Combine("bin"); var testResultsDir = artifactsDir.Combine("test-results"); var nugetRoot = artifactsDir.Combine("nuget"); - var testingDir = context.Directory("./src/Cake.Testing/bin") + context.Directory(configuration); - - var cakeAssemblyPaths = new FilePath[] { - buildDir + context.File("Cake.exe"), - buildDir + context.File("Cake.pdb"), - buildDir + context.File("Cake.Core.dll"), - buildDir + context.File("Cake.Core.pdb"), - buildDir + context.File("Cake.Core.xml"), - buildDir + context.File("Cake.NuGet.dll"), - buildDir + context.File("Cake.NuGet.pdb"), - buildDir + context.File("Cake.NuGet.xml"), - buildDir + context.File("Cake.Common.dll"), - buildDir + context.File("Cake.Common.pdb"), - buildDir + context.File("Cake.Common.xml"), - buildDir + context.File("Mono.CSharp.dll"), - buildDir + context.File("Autofac.dll"), - buildDir + context.File("NuGet.Core.dll") - }; - - var testingAssemblyPaths = new FilePath[] { - testingDir + context.File("Cake.Testing.dll"), - testingDir + context.File("Cake.Testing.pdb"), - testingDir + context.File("Cake.Testing.xml") - }; - var repoFilesPaths = new FilePath[] { - "LICENSE", - "README.md", - "ReleaseNotes.md" - }; + var testCoverageOutputFilePath = testResultsDir.CombineWithFilePath("OpenCover.xml"); - var artifactSourcePaths = cakeAssemblyPaths.Concat(testingAssemblyPaths.Concat(repoFilesPaths)).ToArray(); - - var chocolateyFiles = cakeAssemblyPaths.Concat(new FilePath[] {"LICENSE"}) - .Select( - file => new ChocolateyNuSpecContent {Source = string.Concat("./../", file.FullPath)} - ).ToArray(); - - var zipArtifactPath = artifactsDir.CombineWithFilePath("Cake-bin-v" + semVersion + ".zip"); + var integrationTestsBin = context.MakeAbsolute(context.Directory("./tests/integration/tools")); + var integrationTestsBinTool = integrationTestsBin.Combine("Cake.Tool"); + // Directories var buildDirectories = new BuildDirectories( artifactsDir, testResultsDir, nugetRoot, - artifactsBinDir - ); + integrationTestsBinTool); - var buildFiles = new BuildFiles( - context, - cakeAssemblyPaths, - testingAssemblyPaths, - repoFilesPaths, - artifactSourcePaths, - zipArtifactPath - ); - - return new BuildPaths + FilePath signClientPath = null; + if (context.IsRunningOnWindows()) { - Files = buildFiles, - Directories = buildDirectories, - ChocolateyFiles = chocolateyFiles - }; - } -} - -public class BuildFiles -{ - public ICollection CakeAssemblyPaths { get; private set; } - public ICollection TestingAssemblyPaths { get; private set; } - public ICollection RepoFilesPaths { get; private set; } - public ICollection ArtifactsSourcePaths { get; private set; } - public FilePath ZipArtifactPath { get; private set; } - - public BuildFiles( - ICakeContext context, - FilePath[] cakeAssemblyPaths, - FilePath[] testingAssemblyPaths, - FilePath[] repoFilesPaths, - FilePath[] artifactsSourcePaths, - FilePath zipArtifactPath - ) - { - CakeAssemblyPaths = Filter(context, cakeAssemblyPaths); - TestingAssemblyPaths = Filter(context, testingAssemblyPaths); - RepoFilesPaths = Filter(context, repoFilesPaths); - ArtifactsSourcePaths = Filter(context, artifactsSourcePaths); - ZipArtifactPath = zipArtifactPath; - } + signClientPath = context.Tools.Resolve("sign.exe"); + + if (signClientPath == null) + { + context.Information("Installing sign tool..."); + context.CakeExecuteScript( + "./build/signtool.cake", + new CakeSettings { + Verbosity = Verbosity.Quiet, + EnvironmentVariables = { + ["CAKE_PATHS_TOOLS"] = context.MakeAbsolute(context.Directory("./tools")).FullPath + } + } + ); + + signClientPath = context.Tools.Resolve("sign.exe") + ?? throw new Exception("Failed to locate sign tool"); + + } + } - private static FilePath[] Filter(ICakeContext context, FilePath[] files) - { - // Not a perfect solution, but we need to filter PDB files - // when building on an OS that's not Windows (since they don't exist there). + var signFilterPath = context.MakeAbsolute(context.File("./build/signclient.filter")); - if(!context.IsRunningOnWindows()) - { - return files.Where(f => !f.FullPath.EndsWith("pdb")).ToArray(); - } - return files; + return new BuildPaths( + Directories: buildDirectories, + SignClientPath: signClientPath, + SignFilterPath: signFilterPath + ); } } -public class BuildDirectories -{ - public DirectoryPath Artifacts { get; private set; } - public DirectoryPath TestResults { get; private set; } - public DirectoryPath NugetRoot { get; private set; } - public DirectoryPath ArtifactsBin { get; private set; } - public ICollection ToClean { get; private set; } - - public BuildDirectories( - DirectoryPath artifactsDir, - DirectoryPath testResultsDir, - DirectoryPath nugetRoot, - DirectoryPath artifactsBinDir +public record BuildDirectories( + DirectoryPath Artifacts, + DirectoryPath TestResults, + DirectoryPath NuGetRoot, + DirectoryPath IntegrationTestsBinTool ) - { - Artifacts = artifactsDir; - TestResults = testResultsDir; - NugetRoot = nugetRoot; - ArtifactsBin = artifactsBinDir; - ToClean = new[] { +{ + public ICollection ToClean { get; } = new[] { Artifacts, TestResults, - NugetRoot, - ArtifactsBin + NuGetRoot, + IntegrationTestsBinTool }; - } } \ No newline at end of file diff --git a/build/signclient.filter b/build/signclient.filter new file mode 100644 index 0000000000..e1a5fdfb24 --- /dev/null +++ b/build/signclient.filter @@ -0,0 +1 @@ +**/Cake.* \ No newline at end of file diff --git a/build/signtool.cake b/build/signtool.cake new file mode 100644 index 0000000000..7f0bac955a --- /dev/null +++ b/build/signtool.cake @@ -0,0 +1 @@ +#tool "dotnet:https://api.nuget.org/v3/index.json?package=sign&version=0.9.1-beta.26102.1&prerelease" \ No newline at end of file diff --git a/build/version.cake b/build/version.cake index 00c8939f84..42cf8e6340 100644 --- a/build/version.cake +++ b/build/version.cake @@ -1,14 +1,13 @@ -public class BuildVersion +public record BuildVersion( + string Version, + string SemVersion, + string DotNetAsterix, + string Milestone, + string CakeVersion, + string BranchName +) { - public string Version { get; private set; } - public string SemVersion { get; private set; } - public string Milestone { get; private set; } - public string CakeVersion { get; private set; } - - public static BuildVersion CalculatingSemanticVersion( - ICakeContext context, - BuildParameters parameters - ) + public static BuildVersion Calculate(ICakeContext context, BuildParameters parameters) { if (context == null) { @@ -18,52 +17,112 @@ public class BuildVersion string version = null; string semVersion = null; string milestone = null; + string branchName = null; - if (context.IsRunningOnWindows() && !parameters.SkipGitVersion) + if (!parameters.SkipGitVersion) { + // Temp Workaround GitVersion Azure Pipelines + var azurePipelines = context.AzurePipelines(); + string sourceBranch = string.Empty; + if (azurePipelines.IsRunningOnAzurePipelines && azurePipelines.Environment.PullRequest.Number > 0) + { + sourceBranch = $"PullRequest{azurePipelines.Environment.PullRequest.Number}"; + context.Information("Overriding Azure Pipelines branch name with: {0}", sourceBranch); + } + context.Information("Calculating Semantic Version"); if (!parameters.IsLocalBuild || parameters.IsPublishBuild || parameters.IsReleaseBuild) { context.GitVersion(new GitVersionSettings{ UpdateAssemblyInfoFilePath = "./src/SolutionInfo.cs", UpdateAssemblyInfo = true, - OutputType = GitVersionOutput.BuildServer + OutputType = GitVersionOutput.BuildServer, + EnvironmentVariables = { + { "BUILD_SOURCEBRANCH", sourceBranch }, + { "SYSTEM_PULLREQUEST_SOURCEBRANCH", sourceBranch } + } }); version = context.EnvironmentVariable("GitVersion_MajorMinorPatch"); - semVersion = context.EnvironmentVariable("GitVersion_LegacySemVerPadded"); + var preReleaseNumberStr = context.EnvironmentVariable("GitVersion_PreReleaseNumber"); + semVersion = GetLegacySemVerPadded( + context.EnvironmentVariable("GitVersion_MajorMinorPatch"), + context.EnvironmentVariable("GitVersion_PreReleaseLabel"), + !string.IsNullOrEmpty(preReleaseNumberStr) && int.TryParse(preReleaseNumberStr, out int num) ? num : (int?)null); milestone = string.Concat("v", version); } GitVersion assertedVersions = context.GitVersion(new GitVersionSettings { OutputType = GitVersionOutput.Json, + EnvironmentVariables = { + { "BUILD_SOURCEBRANCH", sourceBranch }, + { "SYSTEM_PULLREQUEST_SOURCEBRANCH", sourceBranch } + } }); version = assertedVersions.MajorMinorPatch; - semVersion = assertedVersions.LegacySemVerPadded; + semVersion = GetLegacySemVerPadded( + assertedVersions.MajorMinorPatch, + assertedVersions.PreReleaseLabel, + assertedVersions.PreReleaseNumber); milestone = string.Concat("v", version); + branchName = assertedVersions.BranchName; - context.Information("Calculated Semantic Version: {0}", semVersion); + context.Information("Calculated Semantic Version: {0} (Version: {1}, Milestone: {2})", semVersion, version, milestone); } if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(semVersion)) { - context.Information("Fetching verson from SolutionInfo"); - var assemblyInfo = context.ParseAssemblyInfo("./src/SolutionInfo.cs"); - version = assemblyInfo.AssemblyVersion; - semVersion = assemblyInfo.AssemblyInformationalVersion; + context.Information("Fetching version from first SolutionInfo..."); + version = ReadSolutionInfoVersion(context); + semVersion = version; milestone = string.Concat("v", version); + + context.Information("Fetched Semantic Version: {0} (Version: {1}, Milestone: {2})", semVersion, version, milestone); } var cakeVersion = typeof(ICakeContext).Assembly.GetName().Version.ToString(); - return new BuildVersion + return new BuildVersion( + Version: version, + SemVersion: semVersion, + DotNetAsterix: semVersion.Substring(version.Length).TrimStart('-'), + Milestone: milestone, + CakeVersion: cakeVersion, + BranchName: branchName + ); + } + + /// + /// Constructs the LegacySemVerPadded format that was removed in GitVersion 6.0.0. + /// Format: {MajorMinorPatch}-{PreReleaseLabel}{PreReleaseNumber:D4} + /// Example: 6.1.0-alpha-0041 + /// + private static string GetLegacySemVerPadded(string majorMinorPatch, string preReleaseLabel, int? preReleaseNumber) + { + if (string.IsNullOrEmpty(majorMinorPatch)) + { + return string.Empty; + } + + // If no pre-release label or number, return just the version + if (string.IsNullOrEmpty(preReleaseLabel) || !preReleaseNumber.HasValue) { - Version = version, - SemVersion = semVersion, - Milestone = milestone, - CakeVersion = cakeVersion - }; + return majorMinorPatch; + } + + // Format with 4-digit padding + return $"{majorMinorPatch}-{preReleaseLabel}{preReleaseNumber.Value:D4}"; + } + + public static string ReadSolutionInfoVersion(ICakeContext context) + { + var solutionInfo = context.ParseAssemblyInfo("./src/SolutionInfo.cs"); + if (!string.IsNullOrEmpty(solutionInfo.AssemblyVersion)) + { + return solutionInfo.AssemblyVersion; + } + throw new CakeException("Could not parse version."); } -} \ No newline at end of file +} diff --git a/global.json b/global.json new file mode 100644 index 0000000000..5372a83167 --- /dev/null +++ b/global.json @@ -0,0 +1,12 @@ +{ + "projects": [ + "src" + ], + "sdk": { + "version": "10.0.300", + "rollForward": "latestFeature" + }, + "test": { + "runner": "Microsoft.Testing.Platform" + } +} \ No newline at end of file diff --git a/nuspec/Cake.Common.nuspec b/nuspec/Cake.Common.nuspec deleted file mode 100644 index c9251d74d7..0000000000 --- a/nuspec/Cake.Common.nuspec +++ /dev/null @@ -1,27 +0,0 @@ - - - - Cake.Common - 0.0.0 - Patrik Svensson - Patrik Svensson - Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. - Provides aliases (extension methods on Cake context) that support CI, build, unit tests, zip, signing, etc. for Cake. - https://github.com/cake-build/cake/blob/develop/LICENSE - https://github.com/cake-build/cake - https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-medium.png - false - Copyright (c) .NET Foundation and Contributors - Cake Script Build - true - - - - - - - - - - - diff --git a/nuspec/Cake.Core.nuspec b/nuspec/Cake.Core.nuspec deleted file mode 100644 index b0956166e3..0000000000 --- a/nuspec/Cake.Core.nuspec +++ /dev/null @@ -1,23 +0,0 @@ - - - - Cake.Core - 0.0.0 - Patrik Svensson - Patrik Svensson - The Cake core library. - Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. - https://github.com/cake-build/cake/blob/develop/LICENSE - https://github.com/cake-build/cake - https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-medium.png - false - Copyright (c) .NET Foundation and Contributors - Cake Script Build - - - - - - - - \ No newline at end of file diff --git a/nuspec/Cake.Portable.nuspec b/nuspec/Cake.Portable.nuspec deleted file mode 100644 index b7cb657b4e..0000000000 --- a/nuspec/Cake.Portable.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - - cake.portable - Cake.Portable - 0.0.0 - Patrik Svensson - Patrik Svensson - Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. - The Cake script runner. - http://cakebuild.net/ - https://github.com/cake-build/cake - https://github.com/cake-build/cake - http://cakebuild.net/docs - - https://github.com/cake-build/cake/issues - Cake Script Build - Copyright (c) .NET Foundation and Contributors - https://github.com/cake-build/cake/blob/develop/LICENSE - false - https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-medium.png - - - - - diff --git a/nuspec/Cake.Testing.nuspec b/nuspec/Cake.Testing.nuspec deleted file mode 100644 index 0a6b756e2e..0000000000 --- a/nuspec/Cake.Testing.nuspec +++ /dev/null @@ -1,26 +0,0 @@ - - - - Cake.Testing - 0.0.0 - Patrik Svensson - Patrik Svensson - Contains testing utilities for Cake. - Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. - https://github.com/cake-build/cake/blob/develop/LICENSE - https://github.com/cake-build/cake - https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-medium.png - false - Copyright (c) .NET Foundation and Contributors - Cake Script Build - - - - - - - - - - - diff --git a/nuspec/Cake.nuspec b/nuspec/Cake.nuspec deleted file mode 100644 index 1351fbd085..0000000000 --- a/nuspec/Cake.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - Cake - 0.0.0 - Patrik Svensson - Patrik Svensson - The Cake script runner. - Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. - https://github.com/cake-build/cake/blob/develop/LICENSE - https://github.com/cake-build/cake - https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-medium.png - false - Copyright (c) .NET Foundation and Contributors - Cake Script Build - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/nuspec/NuGet.org.md b/nuspec/NuGet.org.md new file mode 100644 index 0000000000..8ca7058580 --- /dev/null +++ b/nuspec/NuGet.org.md @@ -0,0 +1,75 @@ +# Cake + +Cake (C# Make) is a build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. + +## Table of Contents + +1. [Documentation](https://github.com/cake-build/cake#documentation) +2. [Contributing](https://github.com/cake-build/cake#contributing) +3. [Get in touch](https://github.com/cake-build/cake#get-in-touch) +4. [License](https://github.com/cake-build/cake#license) + +## Documentation + +You can read the latest documentation at [https://cakebuild.net/](https://cakebuild.net/). + +For a simple example to get started see [Setting up a new project](https://cakebuild.net/docs/getting-started/setting-up-a-new-project). + +## Contributing + +So you’re thinking about contributing to Cake? Great! It’s **really** appreciated. + +Make sure you've read the [contribution guidelines](https://cakebuild.net/docs/contributing/contribution-guidelines) before sending that epic pull request. You'll also need to sign the [contribution license agreement](https://cla.dotnetfoundation.org/cake-build/cake) (CLA) for anything other than a trivial change. **NOTE:** The .NET Foundation CLA Bot will provide a link to this CLA within the PR that you submit if it is deemed as required. + +* Fork the repository. +* Create a branch to work in. +* Make your feature addition or bug fix. +* Don't forget the unit tests. +* Send a pull request. + +## Get in touch + +[![Follow @cakebuild on Mastodon](https://img.shields.io/badge/Mastodon-Follow%20%40cakebuild-6364FF?style=flat&logo=mastodon&logoColor=white)](https://dotnet.social/@cakebuild) +[![Follow @cakebuild.net on Bluesky](https://img.shields.io/badge/Bluesky-Follow%20%40cakebuild.net-0085FF?style=flat&logo=bluesky&logoColor=white)](https://bsky.app/profile/cakebuild.net) + +[![Join the chat at https://github.com/cake-build/cake/discussions](https://img.shields.io/badge/discussions-join%20chat-brightgreen?style=flat&logo=github&logoColor=white)](https://github.com/cake-build/cake/discussions?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## License + +Copyright © .NET Foundation, Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors. + +Cake is provided as-is under the MIT license. For more information see [LICENSE](https://github.com/cake-build/cake/blob/develop/LICENSE). + +* For Roslyn, see https://github.com/dotnet/roslyn/blob/master/License.txt +* For Autofac, see https://github.com/autofac/Autofac/blob/master/LICENSE +* For NuGet.Core, see https://github.com/NuGet/Home/blob/dev/LICENSE.txt + +## Thanks + +A big thank you has to go to [JetBrains](https://www.jetbrains.com) who provide each of the Cake Developers with an [Open Source License](https://www.jetbrains.com/community/opensource/#support) for [ReSharper](https://www.jetbrains.com/resharper/) that helps with the development of Cake. + +### Sponsors + +Our wonderful sponsors: + +[![Sponsors](https://opencollective.com/cake/sponsors.svg)](https://opencollective.com/cake) + +### Backers + +Our wonderful backers: + +[![Backers](https://opencollective.com/cake/backers.svg)](https://opencollective.com/cake) + +## Code of Conduct + +This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/) +to clarify expected behavior in our community. +For more information see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). + +## Contribution License Agreement + +By signing the [CLA](https://cla.dotnetfoundation.org/cake-build/cake), the community is free to use your contribution to .NET Foundation projects. + +## .NET Foundation + +This project is supported by the [.NET Foundation](http://www.dotnetfoundation.org). diff --git a/nuspec/cake-medium.png b/nuspec/cake-medium.png new file mode 100644 index 0000000000..3093eb2621 Binary files /dev/null and b/nuspec/cake-medium.png differ diff --git a/src/Cake.Cli.Tests/Cake.Cli.Tests.csproj b/src/Cake.Cli.Tests/Cake.Cli.Tests.csproj new file mode 100644 index 0000000000..af7ee9aa74 --- /dev/null +++ b/src/Cake.Cli.Tests/Cake.Cli.Tests.csproj @@ -0,0 +1,45 @@ + + + Cake.Cli.Tests + true + true + enable + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..c1a67c69e2 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,4 @@ + +──────────────────────────────────────────────────────────────────────────────── + Setup +──────────────────────────────────────────────────────────────────────────────── diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..c1a67c69e2 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt @@ -0,0 +1,4 @@ + +──────────────────────────────────────────────────────────────────────────────── + Setup +──────────────────────────────────────────────────────────────────────────────── diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..c1a67c69e2 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt @@ -0,0 +1,4 @@ + +──────────────────────────────────────────────────────────────────────────────── + Setup +──────────────────────────────────────────────────────────────────────────────── diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..fec1d95d5d --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,4 @@ + +════════════════════════════════════════════════════════════════════════════════ + Skipped Step +════════════════════════════════════════════════════════════════════════════════ diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..fec1d95d5d --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt @@ -0,0 +1,4 @@ + +════════════════════════════════════════════════════════════════════════════════ + Skipped Step +════════════════════════════════════════════════════════════════════════════════ diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..2754237467 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,4 @@ + +════════════════════════════════════════════════════════════════════════════════ + Test Step +════════════════════════════════════════════════════════════════════════════════ diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Normal.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..2754237467 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Normal.verified.txt @@ -0,0 +1,4 @@ + +════════════════════════════════════════════════════════════════════════════════ + Test Step +════════════════════════════════════════════════════════════════════════════════ diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..c1b8d743e3 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +emptyString \ No newline at end of file diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..2754237467 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt @@ -0,0 +1,4 @@ + +════════════════════════════════════════════════════════════════════════════════ + Test Step +════════════════════════════════════════════════════════════════════════════════ diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=False.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=False.verified.txt new file mode 100644 index 0000000000..df431c82c7 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=False.verified.txt @@ -0,0 +1,18 @@ + + Task Duration Status +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + SetupExecuted 00:00:00.2220000 Succeeded + SetupSkipped - Skipped + SetupFailed 00:00:00.8880000 Failed + SetupDelegated 00:00:00.4440000 Succeeded + TaskExecuted 00:00:00.1110000 Succeeded + TaskSkipped - Skipped + TaskFailed 00:00:00.4440000 Failed + TaskDelegated 00:00:00.2220000 Succeeded + TeardownExecuted 00:00:00.3330000 Succeeded + TeardownSkipped - Skipped + TeardownFailed 00:00:01.3320000 Failed + TeardownDelegated 00:00:00.6660000 Succeeded +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Total: 00:00:06.6600000 + diff --git a/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=True.verified.txt b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=True.verified.txt new file mode 100644 index 0000000000..7b78366c24 --- /dev/null +++ b/src/Cake.Cli.Tests/Expectations/CakeSpectreReportPrinterTests.Write_includeReason=True.verified.txt @@ -0,0 +1,18 @@ + + Task Duration Status Skip Reason +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + SetupExecuted 00:00:00.2220000 Succeeded + SetupSkipped - Skipped Skipped reason + SetupFailed 00:00:00.8880000 Failed + SetupDelegated 00:00:00.4440000 Succeeded + TaskExecuted 00:00:00.1110000 Succeeded + TaskSkipped - Skipped Skipped reason + TaskFailed 00:00:00.4440000 Failed + TaskDelegated 00:00:00.2220000 Succeeded + TeardownExecuted 00:00:00.3330000 Succeeded + TeardownSkipped - Skipped Skipped reason + TeardownFailed 00:00:01.3320000 Failed + TeardownDelegated 00:00:00.6660000 Succeeded +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Total: 00:00:06.6600000 + diff --git a/src/Cake.Cli.Tests/Unit/CakeSpectreReportPrinterTests.cs b/src/Cake.Cli.Tests/Unit/CakeSpectreReportPrinterTests.cs new file mode 100644 index 0000000000..9b5066145c --- /dev/null +++ b/src/Cake.Cli.Tests/Unit/CakeSpectreReportPrinterTests.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.Diagnostics; +using Spectre.Console.Testing; + +namespace Cake.Cli.Tests.Unit; + +public class CakeSpectreReportPrinterTests +{ + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Console_Is_Null() + { + // Given + // When + var result = Record.Exception(() => new CakeSpectreReportPrinter(null)); + // Then + AssertEx.IsArgumentNullException(result, "console"); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Write(bool includeReason) + { + // Given + var console = new TestConsole(); + var reportPrinter = new CakeSpectreReportPrinter(console); + + var executionCategories = Enum + .GetValues() + .OrderBy( + category => category switch { + CakeReportEntryCategory.Setup => 0, + CakeReportEntryCategory.Task => 1, + CakeReportEntryCategory.Teardown => 9, + _ => 5 + }) + .ToArray(); + + var executionStatuses = Enum + .GetValues() + .OrderBy( + status => status switch { + CakeTaskExecutionStatus.Executed => 0, + CakeTaskExecutionStatus.Skipped => 1, + CakeTaskExecutionStatus.Failed => 2, + CakeTaskExecutionStatus.Delegated => 9, + _ => 5 + }) + .ToArray(); + + var report = executionCategories + .Aggregate( + new CakeReport(), + (report, category) => executionStatuses.Aggregate( + report, + (report, status) => + { + report.Add( + $"{category:F}{status:F}", + includeReason && status == CakeTaskExecutionStatus.Skipped ? $"{status:F} reason" : null, + category, + TimeSpan.FromMilliseconds(((int)category + 1) * ((int)status + 1) * 111), + status); + return report; + }, + report => report), + report => report); + + // When + reportPrinter.Write(report); + + // Then + await Verify(console.Output); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteStep(Verbosity verbosity) + { + // Given + var console = new TestConsole(); + var reportPrinter = new CakeSpectreReportPrinter(console); + + // When + reportPrinter.WriteStep("Test Step", verbosity); + + // Then + await Verify(console.Output); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteLifeCycleStep(Verbosity verbosity) + { + // Given + var console = new TestConsole(); + var reportPrinter = new CakeSpectreReportPrinter(console); + + // When + reportPrinter.WriteLifeCycleStep("Setup", verbosity); + + // Then + await Verify(console.Output); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteSkippedStep(Verbosity verbosity) + { + // Given + var console = new TestConsole(); + var reportPrinter = new CakeSpectreReportPrinter(console); + + // When + reportPrinter.WriteSkippedStep("Skipped Step", verbosity); + + // Then + await Verify(console.Output); + } +} \ No newline at end of file diff --git a/src/Cake.Cli.Tests/VerifyConfig.cs b/src/Cake.Cli.Tests/VerifyConfig.cs new file mode 100644 index 0000000000..9946ee2346 --- /dev/null +++ b/src/Cake.Cli.Tests/VerifyConfig.cs @@ -0,0 +1,23 @@ +using System.Runtime.CompilerServices; +using VerifyTests.DiffPlex; + +namespace Cake.Cli.Tests; + +/// +/// Configuration for Verify tests. +/// +public static class VerifyConfig +{ + /// + /// Initializes the Verify configuration. + /// + [ModuleInitializer] + public static void Init() + { + if (!VerifyDiffPlex.Initialized) + { + VerifyDiffPlex.Initialize(OutputType.Compact); + DerivePathInfo(Expectations.Initialize); + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Cake.Cli.csproj b/src/Cake.Cli/Cake.Cli.csproj new file mode 100644 index 0000000000..dadf5563c8 --- /dev/null +++ b/src/Cake.Cli/Cake.Cli.csproj @@ -0,0 +1,25 @@ + + + Cake.Cli + Library + AnyCpu + true + + + + + The Cake CLI library. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Cli/Features/InfoFeature.cs b/src/Cake.Cli/Features/InfoFeature.cs new file mode 100644 index 0000000000..86745139a5 --- /dev/null +++ b/src/Cake.Cli/Features/InfoFeature.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Cli +{ + /// + /// Represents a feature that writes information about Cake to the console. + /// + public interface ICakeInfoFeature + { + /// + /// Runs the feature. + /// + /// The console to write to. + void Run(IConsole console); + } + + /// + /// Writes information about Cake to the console. + /// + public sealed class InfoFeature : ICakeInfoFeature + { + private readonly IVersionResolver _resolver; + + /// + /// Initializes a new instance of the class. + /// + /// The version resolver. + public InfoFeature(IVersionResolver resolver) + { + _resolver = resolver; + } + + /// + public void Run(IConsole console) + { + ArgumentNullException.ThrowIfNull(console); + + var version = _resolver.GetVersion(); + var product = _resolver.GetProductVersion(); + + console.WriteLine(); + console.WriteLine(@" +## #;;'"); + console.WriteLine(@" #;;# .+;;;;+,"); + console.WriteLine(@" '+;;#;,+';;;;;'#."); + console.WriteLine(@" ++'''';;;;;;;;;;# ;#;"); + console.WriteLine(@" ##';;;;++'+#;;;;;'. `#:"); + console.WriteLine(@" ;# '+'';;;;;;;;;'#` #."); + console.WriteLine(@" `#, .'++;;;;;':..........#"); + console.WriteLine(@" '+ `.........';;;;':.........#"); + console.WriteLine(@" #..................+;;;;;':........#"); + console.WriteLine(@" #..................#';;;;;'+''''''.#"); + console.WriteLine(@" #.......,:;''''''''##';;;;;'+'''''#,"); + console.WriteLine(@" #''''''''''''''''''###';;;;;;+''''#"); + console.WriteLine(@" #''''''''''''''''''####';;;;;;#'''#"); + console.WriteLine(@" #''''''''''''''''''#####';;;;;;#''#"); + console.WriteLine(@" #''''''''''''''''''######';;;;;;#'#"); + console.WriteLine(@" #''''''''''''''''''#######';;;;;;##"); + console.WriteLine(@" #''''''''''''''''''########';;;;;;#"); + console.WriteLine(@" #''''''''''''++####+;#######';;;;;;#"); + console.WriteLine(@" #+####':,` ,#####';;;;;;'"); + console.WriteLine(@" +##'''''+."); + + console.ForegroundColor = System.ConsoleColor.Yellow; + console.WriteLine(@" ___ _ ___ _ _ _ "); + console.WriteLine(@" / __\__ _| | _____ / __\_ _(_) | __| |"); + console.WriteLine(@" / / / _` | |/ / _ \/__\// | | | | |/ _` |"); + console.WriteLine(@"/ /___ (_| | < __/ \/ \ |_| | | | (_| |"); + console.WriteLine(@"\____/\__,_|_|\_\___\_____/\__,_|_|_|\__,_|"); + console.ResetColor(); + + console.WriteLine(); + console.WriteLine(@"Version: {0}", version); + console.WriteLine(@"Details: {0}", string.Join("\n ", product.Split('/'))); + console.WriteLine(); + } + } +} diff --git a/src/Cake.Cli/Features/VersionFeature.cs b/src/Cake.Cli/Features/VersionFeature.cs new file mode 100644 index 0000000000..fc9f66426e --- /dev/null +++ b/src/Cake.Cli/Features/VersionFeature.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Cli +{ + /// + /// Represents a feature that writes the Cake version to the console. + /// + public interface ICakeVersionFeature + { + /// + /// Writes the Cake version to the console. + /// + /// The console to write to. + void Run(IConsole console); + } + + /// + /// Writes the Cake version to the console. + /// + public sealed class VersionFeature : ICakeVersionFeature + { + private readonly IVersionResolver _resolver; + + /// + /// Initializes a new instance of the class. + /// + /// The version resolver. + public VersionFeature(IVersionResolver resolver) + { + _resolver = resolver; + } + + /// + public void Run(IConsole console) + { + ArgumentNullException.ThrowIfNull(console); + + console.WriteLine(_resolver.GetVersion()); + } + } +} diff --git a/src/Cake.Cli/Features/VersionResolver.cs b/src/Cake.Cli/Features/VersionResolver.cs new file mode 100644 index 0000000000..cc91351749 --- /dev/null +++ b/src/Cake.Cli/Features/VersionResolver.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Reflection; + +namespace Cake.Cli +{ + /// + /// Represents a version resolver. + /// + public interface IVersionResolver + { + /// + /// Gets the version. + /// + /// The version. + string GetVersion(); + + /// + /// Gets the product version. + /// + /// The product version. + string GetProductVersion(); + } + + /// + /// The Cake version resolver. + /// + public sealed class VersionResolver : IVersionResolver + { + /// + public string GetVersion() + { + var assembly = Assembly.GetEntryAssembly(); + var version = FileVersionInfo.GetVersionInfo(assembly.Location).Comments; + + if (string.IsNullOrWhiteSpace(version)) + { + version = "Unknown"; + } + + return version; + } + + /// + public string GetProductVersion() + { + var assembly = Assembly.GetEntryAssembly(); + var version = FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion; + + if (string.IsNullOrWhiteSpace(version)) + { + version = "Unknown"; + } + + return version; + } + } +} diff --git a/src/Cake.Cli/Hosts/BuildScriptHost.cs b/src/Cake.Cli/Hosts/BuildScriptHost.cs new file mode 100644 index 0000000000..b86b808f36 --- /dev/null +++ b/src/Cake.Cli/Hosts/BuildScriptHost.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Cake.Cli.Infrastructure; +using Cake.Core; +using Cake.Core.Configuration; +using Cake.Core.Diagnostics; +using Cake.Core.Scripting; + +namespace Cake.Cli +{ + /// + /// The script host used to execute Cake scripts. + /// + public sealed class BuildScriptHost : BuildScriptHost + { + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The execution strategy. + /// The context. + /// The report printer. + /// The Cake Configuration. + /// The log. + public BuildScriptHost( + ICakeEngine engine, + IExecutionStrategy executionStrategy, + ICakeContext context, + ICakeReportPrinter reportPrinter, + ICakeConfiguration configuration, + ICakeLog log) : base(engine, executionStrategy, context, reportPrinter, configuration, log) + { + } + } + + /// + /// The script host used to execute Cake scripts. + /// + /// The context type. + public class BuildScriptHost : ScriptHost + where TContext : ICakeContext + { + private readonly ICakeReportPrinter _reportPrinter; + private readonly ICakeLog _log; + private readonly IExecutionStrategy _executionStrategy; + private readonly TContext _context; + private readonly ICakeConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The execution strategy. + /// The context. + /// The report printer. + /// The Cake Configuration. + /// The log. + public BuildScriptHost( + ICakeEngine engine, + IExecutionStrategy executionStrategy, + TContext context, + ICakeReportPrinter reportPrinter, + ICakeConfiguration configuration, + ICakeLog log) : base(engine, context) + { + _executionStrategy = executionStrategy; + _context = context; + _reportPrinter = reportPrinter; + _configuration = configuration; + _log = log; + } + + /// + public override async Task RunTargetAsync(string target) + { + Settings.SetTarget(target); + + return await internalRunTargetAsync(); + } + + /// + public override async Task RunTargetsAsync(IEnumerable targets) + { + Settings.SetTargets(targets); + + return await internalRunTargetAsync(); + } + + private async Task internalRunTargetAsync() + { + try + { + if (_configuration.GetBoolValue(Constants.Settings.UnifiedDependencyGraphForMultipleTargets)) + { + Settings.UseUnifiedDependencyGraphForMultipleTargets(true); + } + + var report = await Engine.RunTargetAsync(_context, _executionStrategy, Settings).ConfigureAwait(false); + + if (report != null && !report.IsEmpty && !_configuration.GetBoolValue(Constants.Settings.NoReport)) + { + _reportPrinter.Write(report); + } + + return report; + } + catch (CakeReportException cre) + { + if (cre.Report != null && !cre.Report.IsEmpty) + { + _reportPrinter.Write(cre.Report); + } + + throw; + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Hosts/DescriptionScriptHost.cs b/src/Cake.Cli/Hosts/DescriptionScriptHost.cs new file mode 100644 index 0000000000..408bdcb481 --- /dev/null +++ b/src/Cake.Cli/Hosts/DescriptionScriptHost.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cake.Core; +using Cake.Core.Scripting; + +namespace Cake.Cli +{ + /// + /// The script host used for showing task descriptions. + /// + public class DescriptionScriptHost : ScriptHost + { + private readonly IConsole _console; + private readonly Dictionary _descriptions; + + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The context. + /// The console. + public DescriptionScriptHost(ICakeEngine engine, ICakeContext context, IConsole console) + : base(engine, context) + { + _console = console ?? throw new ArgumentNullException(nameof(console)); + _descriptions = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + public override Task RunTargetAsync(string target) + { + PrintTaskDescriptions(); + + return System.Threading.Tasks.Task.FromResult(null); + } + + /// + public override Task RunTargetsAsync(IEnumerable targets) + { + PrintTaskDescriptions(); + + return System.Threading.Tasks.Task.FromResult(null); + } + + private void PrintTaskDescriptions() + { + var maxTaskNameLength = 29; + + foreach (var task in Tasks) + { + if (task.Name.Length > maxTaskNameLength) + { + maxTaskNameLength = task.Name.Length; + } + + _descriptions.Add(task.Name, task.Description); + } + + maxTaskNameLength++; + string lineFormat = "{0,-" + maxTaskNameLength + "}{1}"; + + _console.WriteLine(); + _console.WriteLine(lineFormat, "Task", "Description"); + _console.WriteLine(new String('=', maxTaskNameLength + 50)); + foreach (var (key, value) in _descriptions) + { + _console.WriteLine(lineFormat, key, value); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Hosts/DryRunExecutionStrategy.cs b/src/Cake.Cli/Hosts/DryRunExecutionStrategy.cs new file mode 100644 index 0000000000..0bfb9d689d --- /dev/null +++ b/src/Cake.Cli/Hosts/DryRunExecutionStrategy.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Cake.Core; +using Cake.Core.Diagnostics; + +namespace Cake.Cli +{ + internal sealed class DryRunExecutionStrategy : IExecutionStrategy + { + private readonly ICakeLog _log; + private int _counter; + + public DryRunExecutionStrategy(ICakeLog log) + { + _log = log ?? throw new ArgumentNullException(nameof(log)); + _counter = 1; + } + + public void PerformSetup(Action action, ISetupContext context) + { + action(context); + } + + public void PerformTeardown(Action action, ITeardownContext teardownContext) + { + action(teardownContext); + } + + public Task ExecuteAsync(CakeTask task, ICakeContext context) + { + if (task != null) + { + _log.Information("{0}. {1}", _counter, task.Name); + _counter++; + } + + return Task.CompletedTask; + } + + public void Skip(CakeTask task, CakeTaskCriteria critera) + { + } + + public Task ReportErrorsAsync(Func action, Exception exception) + { + return Task.CompletedTask; + } + + public Task HandleErrorsAsync(Func action, Exception exception, ICakeContext context) + { + return Task.CompletedTask; + } + + public Task InvokeFinallyAsync(Func action, ICakeContext context) + { + return Task.CompletedTask; + } + + public void PerformTaskSetup(Action action, ITaskSetupContext taskSetupContext) + { + } + + public void PerformTaskTeardown(Action action, ITaskTeardownContext taskTeardownContext) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Hosts/DryRunScriptHost.cs b/src/Cake.Cli/Hosts/DryRunScriptHost.cs new file mode 100644 index 0000000000..00545d1f75 --- /dev/null +++ b/src/Cake.Cli/Hosts/DryRunScriptHost.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.Scripting; + +namespace Cake.Cli +{ + /// + /// The script host used to dry run Cake scripts. + /// + public sealed class DryRunScriptHost : DryRunScriptHost + { + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The context. + /// The log. + public DryRunScriptHost(ICakeEngine engine, ICakeContext context, ICakeLog log) + : base(engine, context, log) + { + } + } + + /// + /// The script host used to dry run Cake scripts. + /// + /// The context. + public class DryRunScriptHost : ScriptHost + where TContext : ICakeContext + { + private readonly ICakeLog _log; + + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The context. + /// The log. + public DryRunScriptHost(ICakeEngine engine, TContext context, ICakeLog log) + : base(engine, context) + { + _log = log ?? throw new ArgumentNullException(nameof(log)); + } + + /// + public override async Task RunTargetAsync(string target) + { + _log.Information("Performing dry run..."); + _log.Information("Target is: {0}", target); + _log.Information(string.Empty); + + Settings.SetTarget(target); + + var strategy = new DryRunExecutionStrategy(_log); + var result = await Engine.RunTargetAsync(Context, strategy, Settings).ConfigureAwait(false); + + _log.Information(string.Empty); + _log.Information("This was a dry run."); + _log.Information("No tasks were actually executed."); + + return result; + } + + /// + public override async Task RunTargetsAsync(IEnumerable targets) + { + _log.Information("Performing dry run..."); + _log.Information("Targets are: {0}", string.Join(", ", targets)); + _log.Information(string.Empty); + + Settings.SetTargets(targets); + + var strategy = new DryRunExecutionStrategy(_log); + var result = await Engine.RunTargetAsync(Context, strategy, Settings).ConfigureAwait(false); + + _log.Information(string.Empty); + _log.Information("This was a dry run."); + _log.Information("No tasks were actually executed."); + + return result; + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Hosts/TreeScriptHost.cs b/src/Cake.Cli/Hosts/TreeScriptHost.cs new file mode 100644 index 0000000000..f07a819623 --- /dev/null +++ b/src/Cake.Cli/Hosts/TreeScriptHost.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Cake.Core; +using Cake.Core.Graph; +using Cake.Core.Scripting; + +namespace Cake.Cli +{ + /// + /// The script host used for showing task descriptions. + /// + public sealed class TreeScriptHost : ScriptHost + { + private const int _maxDepth = 0; + private const string _cross = "├─"; + private const string _corner = "└─"; + private const string _vertical = "│ "; + private readonly IConsole _console; + + /// + /// Initializes a new instance of the class. + /// + /// The engine. + /// The context. + /// The console. + public TreeScriptHost(ICakeEngine engine, ICakeContext context, IConsole console) + : base(engine, context) + { + _console = console ?? throw new ArgumentNullException(nameof(console)); + } + + /// + public override Task RunTargetAsync(string target) + { + PrintTaskTree(); + + return System.Threading.Tasks.Task.FromResult(null); + } + + /// + public override Task RunTargetsAsync(IEnumerable targets) + { + PrintTaskTree(); + + return System.Threading.Tasks.Task.FromResult(null); + } + + private void PrintTaskTree() + { + var topLevelTasks = GetTopLevelTasks(); + _console.WriteLine(); + + foreach (ICakeTaskInfo task in topLevelTasks) + { + PrintTask(task, string.Empty, false, 0); + _console.WriteLine(); + } + } + + private List GetTopLevelTasks() + { + // Display "Default" first, then alphabetical + var graph = CakeGraphBuilder.Build(Tasks); + return Tasks.Where(task => !graph.Edges.Any( + edge => edge.Start.Equals(task.Name, StringComparison.OrdinalIgnoreCase))) + .OrderByDescending(task => task.Name.Equals("Default", StringComparison.OrdinalIgnoreCase)) + .ThenBy(task => task.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private void PrintTask(ICakeTaskInfo task, string indent, bool isLast, int depth) + { + // Builds ASCII graph + _console.Write(indent); + if (isLast) + { + _console.Write(_corner); + indent += " "; + } + else if (depth > 0) + { + _console.Write(_cross); + indent += _vertical; + } + + PrintName(task, depth); + + if ((_maxDepth > 0) && (depth >= _maxDepth)) + { + return; + } + + for (var i = 0; i < task.Dependencies.Count; i++) + { + // First() is safe as CakeGraphBuilder has already validated graph is valid + var childTask = Tasks + .Where(x => x.Name.Equals(task.Dependencies[i].Name, StringComparison.OrdinalIgnoreCase)) + .First(); + + PrintTask(childTask, indent, i == (task.Dependencies.Count - 1), depth + 1); + } + } + + private void PrintName(ICakeTaskInfo task, int depth) + { + var originalColor = _console.ForegroundColor; + + if (depth == 0) + { + _console.ForegroundColor = ConsoleColor.Cyan; + } + else if (task is CakeTask cakeTask && + (cakeTask.Actions.Any() || cakeTask.DelayedActions.Any())) + { + _console.ForegroundColor = ConsoleColor.Green; + } + else + { + _console.ForegroundColor = ConsoleColor.Gray; + } + + _console.WriteLine(task.Name); + _console.ForegroundColor = originalColor; + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs b/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs new file mode 100644 index 0000000000..dc7e4304eb --- /dev/null +++ b/src/Cake.Cli/Infrastructure/CakeSpectreReportPrinter.cs @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Cake.Core; +using Cake.Core.Diagnostics; +using Spectre.Console; + +namespace Cake.Cli +{ + /// + /// The default report printer. + /// + public sealed class CakeSpectreReportPrinter : ICakeReportPrinter + { + private readonly IAnsiConsole _console; + + /// + /// Initializes a new instance of the class. + /// + /// The console. + public CakeSpectreReportPrinter(IAnsiConsole console) + { + ArgumentNullException.ThrowIfNull(console); + _console = console; + } + + /// + public void Write(CakeReport report) + { + // Create a table + var table = new Table().Border(TableBorder.SimpleHeavy); + table.Width(100); + table.BorderStyle(default(Style).Foreground(ConsoleColor.Green)); + + var includeSkippedReasonColumn = report.Any(r => !string.IsNullOrEmpty(r.SkippedMessage)); + var rowStyle = new Style(ConsoleColor.Green); + + // Add some columns + table.AddColumn(new TableColumn(new Text("Task", rowStyle)).Footer(new Text("Total:", rowStyle)).PadRight(10)); + table.AddColumn( + new TableColumn( + new Text("Duration", rowStyle)).Footer( + new Text(FormatTime(GetTotalTime(report)).EscapeMarkup(), rowStyle))); + + table.AddColumn( + new TableColumn( + new Text("Status", rowStyle))); + + if (includeSkippedReasonColumn) + { + table.AddColumn(new TableColumn(new Text("Skip Reason", rowStyle))); + } + + foreach (var item in report) + { + var itemStyle = GetItemStyle(item); + + if (includeSkippedReasonColumn) + { + table.AddRow(new Markup(item.TaskName.EscapeMarkup(), itemStyle), + new Markup(FormatDuration(item).EscapeMarkup(), itemStyle), + new Markup(item.ExecutionStatus.ToReportStatus().EscapeMarkup(), itemStyle), + new Markup(item.SkippedMessage.EscapeMarkup(), itemStyle)); + } + else + { + table.AddRow(new Markup(item.TaskName.EscapeMarkup(), itemStyle), + new Markup(FormatDuration(item).EscapeMarkup(), itemStyle), + new Markup(item.ExecutionStatus.ToReportStatus().EscapeMarkup(), itemStyle)); + } + } + + // Render the table to the console + _console.Write(table); + } + + /// + public void WriteStep(string name, Verbosity verbosity) + { + if (verbosity < Verbosity.Normal) + { + return; + } + + var table = new Table().Border(DoubleBorder.Shared); + table.Width(100); + table.AddColumn(name.EscapeMarkup()); + _console.Write(new Padder(table).Padding(0, 1, 0, 0)); + } + + /// + public void WriteLifeCycleStep(string name, Verbosity verbosity) + { + if (verbosity < Verbosity.Normal) + { + return; + } + + _console.WriteLine(); + + var table = new Table().Border(SingleBorder.Shared); + table.Width(100); + table.AddColumn(name.EscapeMarkup()); + _console.Write(table); + } + + /// + public void WriteSkippedStep(string name, Verbosity verbosity) + { + if (verbosity < Verbosity.Verbose) + { + return; + } + + _console.WriteLine(); + + var table = new Table() + .Border(DoubleBorder.Shared) + .BorderStyle(new Style(ConsoleColor.Gray)); + + table.Width(100); + table.AddColumn(name.EscapeMarkup()); + _console.Write(table); + } + + private static string FormatDuration(CakeReportEntry item) + { + if (item.ExecutionStatus == CakeTaskExecutionStatus.Skipped) + { + return "-"; + } + + return FormatTime(item.Duration); + } + + private static Style GetItemStyle(CakeReportEntry item) + { + if (item.Category == CakeReportEntryCategory.Setup || item.Category == CakeReportEntryCategory.Teardown) + { + return new Style(ConsoleColor.Cyan); + } + + if (item.ExecutionStatus == CakeTaskExecutionStatus.Failed) + { + return new Style(ConsoleColor.Red); + } + + if (item.ExecutionStatus == CakeTaskExecutionStatus.Executed) + { + return new Style(ConsoleColor.Green); + } + + return new Style(ConsoleColor.Gray); + } + + private static string FormatTime(TimeSpan time) + { + return time.ToString("c", CultureInfo.InvariantCulture); + } + + private static TimeSpan GetTotalTime(IEnumerable entries) + { + return entries.Select(i => i.Duration) + .Aggregate(TimeSpan.Zero, (t1, t2) => t1 + t2); + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Infrastructure/Constants.cs b/src/Cake.Cli/Infrastructure/Constants.cs new file mode 100644 index 0000000000..e58b2ae0a7 --- /dev/null +++ b/src/Cake.Cli/Infrastructure/Constants.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Cli.Infrastructure +{ + /// + /// Constants used by the Cake CLI. + /// + internal static class Constants + { + /// + /// Configuration key names for CLI settings. + /// + public static class Settings + { + /// + /// Configuration key for disabling the build report. + /// + public const string NoReport = "Settings_NoReport"; + + /// + /// Configuration key for using a unified dependency graph when running multiple targets. + /// + public const string UnifiedDependencyGraphForMultipleTargets = "Settings_UnifiedDependencyGraphForMultipleTargets"; + } + } +} diff --git a/src/Cake.Cli/Infrastructure/DoubleBorder.cs b/src/Cake.Cli/Infrastructure/DoubleBorder.cs new file mode 100644 index 0000000000..a3514b7bcc --- /dev/null +++ b/src/Cake.Cli/Infrastructure/DoubleBorder.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#nullable enable + +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace Cake.Cli +{ + /// + /// A custom Spectre.Console border class, used for outputting information about steps. + /// + public class DoubleBorder : TableBorder + { + /// + /// Gets a single instance of the DoubleBorder class. + /// + public static TableBorder Shared { get; } = new DoubleBorder(); + + /// + public override TableBorder? SafeBorder => new Safe(); + + /// + /// Get information about the custom border. + /// + /// The part that needs a border applied to it. + /// A simple double border character. + public override string GetPart(TableBorderPart part) + { + return part switch + { + TableBorderPart.HeaderTopLeft => "═", + TableBorderPart.HeaderTop => "═", + TableBorderPart.HeaderTopRight => "═", + TableBorderPart.FooterBottomLeft => "═", + TableBorderPart.FooterBottom => "═", + TableBorderPart.FooterBottomRight => "═", + _ => string.Empty, + }; + } + + private sealed class Safe : TableBorder + { + public override string GetPart(TableBorderPart part) + { + return part switch + { + TableBorderPart.HeaderTopLeft => "=", + TableBorderPart.HeaderTop => "=", + TableBorderPart.HeaderTopRight => "=", + TableBorderPart.FooterBottomLeft => "=", + TableBorderPart.FooterBottom => "=", + TableBorderPart.FooterBottomRight => "=", + _ => string.Empty, + }; + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Infrastructure/ExceptionLogger.cs b/src/Cake.Cli/Infrastructure/ExceptionLogger.cs new file mode 100644 index 0000000000..b72d3b32d2 --- /dev/null +++ b/src/Cake.Cli/Infrastructure/ExceptionLogger.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.Diagnostics; + +namespace Cake.Cli +{ + /// + /// Exception logging extension methods for the ICakeLog. + /// + public static class ExceptionLogger + { + /// + /// Logs exception and returns exit code if available in exception. + /// + /// The exception type. + /// The log. + /// The exception. + /// 1 or exit code provided by . + public static int LogException(this ICakeLog log, T ex) + where T : Exception + { + log = log ?? new CakeBuildLog( + new CakeConsole(new CakeEnvironment(new CakePlatform(), new CakeRuntime()))); + + if (log.Verbosity == Verbosity.Diagnostic) + { + log.Error("Error: {0}", ex); + } + else + { + log.Error("Error: {0}", ex.Message); + if (ex is AggregateException aex) + { + foreach (var exception in aex.Flatten().InnerExceptions) + { + log.Error("\t{0}", exception.Message); + } + } + } + + var exitCode = ex switch + { + CakeException cex => cex.ExitCode, + AggregateException aex => aex + .InnerExceptions + .OfType() + .Select(cex => cex.ExitCode as int?) + .FirstOrDefault() ?? 1, + _ => 1 + }; + + return exitCode; + } + } +} diff --git a/src/Cake.Cli/Infrastructure/FilePathConverter.cs b/src/Cake.Cli/Infrastructure/FilePathConverter.cs new file mode 100644 index 0000000000..f8932244f2 --- /dev/null +++ b/src/Cake.Cli/Infrastructure/FilePathConverter.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Globalization; +using Cake.Core.IO; + +namespace Cake.Cli +{ + /// + /// A type converter for . + /// + public sealed class FilePathConverter : TypeConverter + { + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + return new FilePath(stringValue); + } + + throw new NotSupportedException("Can't convert value to file path."); + } + } + + /// + /// A type converter for . + /// + public sealed class DirectoryPathConverter : TypeConverter + { + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + return new DirectoryPath(stringValue); + } + + throw new NotSupportedException("Can't convert value to file path."); + } + } +} diff --git a/src/Cake.Cli/Infrastructure/RemainingArgsParser.cs b/src/Cake.Cli/Infrastructure/RemainingArgsParser.cs new file mode 100644 index 0000000000..58466b859f --- /dev/null +++ b/src/Cake.Cli/Infrastructure/RemainingArgsParser.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Spectre.Console.Cli; +using static Cake.Cli.Infrastructure.Constants; + +namespace Cake.Cli.Infrastructure +{ + /// + /// Spectre.Console extensions. + /// + public static class IRemainingArgumentsExtensions + { + /// + /// Parses Spectre.Console to . + /// + /// The remainingArguments. + /// The optional targets, i.e. if specified by command. + /// The optional pre-process arguments. + /// . + public static CakeArguments ToCakeArguments( + this IRemainingArguments remainingArguments, + string[] targets = null, + Action>> preProcessArgs = null) + => remainingArguments.ToCakeArguments( + noReport: false, + targets: targets, + preProcessArgs: preProcessArgs); + + /// + /// Parses Spectre.Console to . + /// + /// The remainingArguments. + /// No report argument supplied. + /// The optional targets, i.e. if specified by command. + /// The optional pre-process arguments. + /// . + public static CakeArguments ToCakeArguments( + this IRemainingArguments remainingArguments, + bool noReport, + string[] targets = null, + Action>> preProcessArgs = null) + { + var arguments = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + // Keep the actual remaining arguments in the cake arguments + foreach (var group in remainingArguments.Parsed) + { + arguments.TryAdd( + group.Key.TrimStart('-'), + [.. group]); + } + + // Fixes #3291, We have to add arguments manually which are defined within the DefaultCommandSettings type. Those are not considered "as remaining" because they could be parsed + const string targetArgumentName = "target"; + if (!arguments.TryGetValue(targetArgumentName, out List value)) + { + arguments[targetArgumentName] = value = + []; + } + + if (targets != null) + { + foreach (var target in targets) + { + value.Add(target); + } + } + + if (noReport) + { + arguments.TryAdd( + Settings.NoReport, + [bool.TrueString]); + } + + preProcessArgs?.Invoke(arguments); + + var argumentLookUp = arguments.SelectMany(a => a.Value, Tuple.Create).ToLookup(a => a.Item1.Key, a => a.Item2); + return new CakeArguments(argumentLookUp); + } + } +} diff --git a/src/Cake.Cli/Infrastructure/SingleBorder.cs b/src/Cake.Cli/Infrastructure/SingleBorder.cs new file mode 100644 index 0000000000..72ea200a7a --- /dev/null +++ b/src/Cake.Cli/Infrastructure/SingleBorder.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace Cake.Cli +{ + /// + /// A custom Spectre.Console border class, used for outputting information about steps. + /// + public class SingleBorder : TableBorder + { + /// + /// Gets a single instance of the SingleBorder class. + /// + public static SingleBorder Shared { get; } = new SingleBorder(); + + /// + public override TableBorder SafeBorder { get; } = new Safe(); + + /// + /// Get information about the custom border. + /// + /// The part that needs a border applied to it. + /// A simple single border character. + public override string GetPart(TableBorderPart part) + { + return part switch + { + TableBorderPart.HeaderTopLeft => "─", + TableBorderPart.HeaderTop => "─", + TableBorderPart.HeaderTopRight => "─", + TableBorderPart.FooterBottomLeft => "─", + TableBorderPart.FooterBottom => "─", + TableBorderPart.FooterBottomRight => "─", + _ => string.Empty, + }; + } + + private sealed class Safe : TableBorder + { + public override string GetPart(TableBorderPart part) + { + return part switch + { + TableBorderPart.HeaderTopLeft => "-", + TableBorderPart.HeaderTop => "-", + TableBorderPart.HeaderTopRight => "-", + TableBorderPart.FooterBottomLeft => "-", + TableBorderPart.FooterBottom => "-", + TableBorderPart.FooterBottomRight => "-", + _ => string.Empty, + }; + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Cli/Infrastructure/VerbosityConverter.cs b/src/Cake.Cli/Infrastructure/VerbosityConverter.cs new file mode 100644 index 0000000000..7ee38940c6 --- /dev/null +++ b/src/Cake.Cli/Infrastructure/VerbosityConverter.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using Cake.Core; +using Cake.Core.Diagnostics; + +namespace Cake.Cli +{ + /// + /// A type converter for . + /// + public sealed class VerbosityConverter : TypeConverter + { + private readonly Dictionary _lookup; + + /// + /// Initializes a new instance of the class. + /// + public VerbosityConverter() + { + _lookup = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "q", Verbosity.Quiet }, + { "quiet", Verbosity.Quiet }, + { "m", Verbosity.Minimal }, + { "minimal", Verbosity.Minimal }, + { "n", Verbosity.Normal }, + { "normal", Verbosity.Normal }, + { "v", Verbosity.Verbose }, + { "verbose", Verbosity.Verbose }, + { "d", Verbosity.Diagnostic }, + { "diagnostic", Verbosity.Diagnostic } + }; + } + + /// + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string stringValue) + { + var result = _lookup.TryGetValue(stringValue, out var verbosity); + if (!result) + { + const string format = "The value '{0}' is not a valid verbosity."; + var message = string.Format(CultureInfo.InvariantCulture, format, value); + throw new CakeException(message); + } + return verbosity; + } + throw new NotSupportedException("Can't convert value to verbosity."); + } + } +} diff --git a/src/Cake.Common.Tests/Cake.Common.Tests.csproj b/src/Cake.Common.Tests/Cake.Common.Tests.csproj index f207005d19..a50a8cc52a 100644 --- a/src/Cake.Common.Tests/Cake.Common.Tests.csproj +++ b/src/Cake.Common.Tests/Cake.Common.Tests.csproj @@ -1,382 +1,65 @@ - - - - - Debug - AnyCPU - {9FF2440F-1ADC-4263-831A-A687543383AD} - Library - Properties - Cake.Common.Tests - Cake.Common.Tests - v4.5 - 512 - 8e548008 - 5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - ..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll - - - - - - - - - - - - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.extensibility.core.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties\SolutionInfo.cs - - - - - - - - - - {ABC3F1CB-F84E-43ED-A120-0CCFE344D250} - Cake.Common - - - {8074B833-11B8-459F-BB98-BFBA2BC5C698} - Cake.Core - - - {5AF751D1-BB54-4268-9E42-3A898B034B06} - Cake.Testing.Xunit - - - {5572610D-D857-450A-9CC9-F3E08B0E1449} - Cake.Testing - - - - - Designer - - - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - + + + Cake.Common.Tests + true + true + enable + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + PreserveNewest + + + + + + True + True + Resources.resx + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Common.Tests/CrossCutting/ToolSettingsTests.cs b/src/Cake.Common.Tests/CrossCutting/ToolSettingsTests.cs new file mode 100644 index 0000000000..28563d1bc6 --- /dev/null +++ b/src/Cake.Common.Tests/CrossCutting/ToolSettingsTests.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Cake.Core.Tooling; +using Cake.Testing; +using Cake.Testing.Extensions; +using Xunit; + +namespace Cake.Common.Tests.CrossCutting +{ + public static class ToolSettingsTests + { + // Ensures that C# initializer syntax will not throw NullReferenceException when collection properties are used. + [Theory] + [MemberData(nameof(ToolSettingsTypes))] + public static void Tool_settings_collection_properties_must_be_initialized(MemberTestInfo toolSettingsProperty) + { + Assert.NotNull(toolSettingsProperty.Member.GetValue(toolSettingsProperty.Instance)); + } + + public static IEnumerable ToolSettingsTypes => + from type in MemberTestingUtils.GetMembersToTest(typeof(ToolSettings), type => + type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(property => !property.PropertyType.IsArray + && property.PropertyType.SatisfiesInterfaceDefinition(typeof(ICollection<>)))) + select new object[] { type }; + } +} diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Folders.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Folders.verified.txt new file mode 100644 index 0000000000..06946dbfe5 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Folders.verified.txt @@ -0,0 +1,72 @@ +{ + Version: Format Version 12.00, + VisualStudioVersion: 14.0.25123.0, + MinimumVisualStudioVersion: 10.0.40219.1, + Projects: [ + { + Items: [ + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2 + } + ], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + }, + { + Items: [ + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2 + } + ], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + }, + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_7, + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_2, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects.verified.txt new file mode 100644 index 0000000000..06946dbfe5 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects.verified.txt @@ -0,0 +1,72 @@ +{ + Version: Format Version 12.00, + VisualStudioVersion: 14.0.25123.0, + MinimumVisualStudioVersion: 10.0.40219.1, + Projects: [ + { + Items: [ + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2 + } + ], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + }, + { + Items: [ + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2 + } + ], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + }, + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_7, + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_2, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt new file mode 100644 index 0000000000..68e19a45fc --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt @@ -0,0 +1,14 @@ +{ + Version: Format Version 12.00, + VisualStudioVersion: 16.0.31702.278, + MinimumVisualStudioVersion: 10.0.40219.1, + Projects: [ + { + Id: Guid_1, + Name: dummy, + Path: C:/project/dummy/src/dummy/dummy.csproj, + Type: Guid_2, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt new file mode 100644 index 0000000000..06946dbfe5 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt @@ -0,0 +1,72 @@ +{ + Version: Format Version 12.00, + VisualStudioVersion: 14.0.25123.0, + MinimumVisualStudioVersion: 10.0.40219.1, + Projects: [ + { + Items: [ + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2 + } + ], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + }, + { + Items: [ + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2 + } + ], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + }, + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_7, + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_2, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt new file mode 100644 index 0000000000..06946dbfe5 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSln.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt @@ -0,0 +1,72 @@ +{ + Version: Format Version 12.00, + VisualStudioVersion: 14.0.25123.0, + MinimumVisualStudioVersion: 10.0.40219.1, + Projects: [ + { + Items: [ + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2 + } + ], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + }, + { + Items: [ + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2 + } + ], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + }, + { + Id: Guid_1, + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_3, + Name: src, + Path: /Working/src, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_5, + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: Guid_6, + Name: test, + Path: /Working/test, + Type: Guid_4, + Parent: null + } + }, + { + Id: Guid_7, + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_2, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Folders.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Folders.verified.txt new file mode 100644 index 0000000000..53421f1f50 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Folders.verified.txt @@ -0,0 +1,72 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Items: [ + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1 + } + ], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + } + }, + { + Items: [ + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1 + } + ], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + } + }, + { + Id: , + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects.verified.txt new file mode 100644 index 0000000000..53421f1f50 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects.verified.txt @@ -0,0 +1,72 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Items: [ + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1 + } + ], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + } + }, + { + Items: [ + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1 + } + ], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + } + }, + { + Id: , + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt new file mode 100644 index 0000000000..1c24bdfc2f --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Absolute_Path.verified.txt @@ -0,0 +1,14 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Id: , + Name: dummy, + Path: C:/project/dummy/src/dummy/dummy.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Different_Type_Id.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Different_Type_Id.verified.txt new file mode 100644 index 0000000000..d5050fbcdd --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Different_Type_Id.verified.txt @@ -0,0 +1,14 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Id: , + Name: WebApplication, + Path: /Working/WebApplication/WebApplication.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt new file mode 100644 index 0000000000..53421f1f50 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Projects_With_Empty_Lines.verified.txt @@ -0,0 +1,72 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Items: [ + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1 + } + ], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + } + }, + { + Items: [ + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1 + } + ], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + } + }, + { + Id: , + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt new file mode 100644 index 0000000000..53421f1f50 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Relation_Between_Project_And_Folder.verified.txt @@ -0,0 +1,72 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Items: [ + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1 + } + ], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy, + Path: /Working/src/dummy/dummy.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: src, + Path: /Working/src, + Type: Guid_2, + Parent: null + } + }, + { + Items: [ + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1 + } + ], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + }, + { + Id: , + Name: dummy.Tests, + Path: /Working/test/dummy.Tests/dummy.Tests.csproj, + Type: Guid_1, + Parent: { + Items: [], + Id: , + Name: test, + Path: /Working/test, + Type: Guid_2, + Parent: null + } + }, + { + Id: , + Name: executable, + Path: /Working/executable/executable.csproj, + Type: Guid_1, + Parent: null + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Solution_With_Nested_Solution_Folders.verified.txt b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Solution_With_Nested_Solution_Folders.verified.txt new file mode 100644 index 0000000000..bdd23783d5 --- /dev/null +++ b/src/Cake.Common.Tests/Expectations/SolutionParserTests.TheParseMethodForSlnx.Should_Properly_Parse_Solution_With_Nested_Solution_Folders.verified.txt @@ -0,0 +1,44 @@ +{ + Version: , + VisualStudioVersion: , + MinimumVisualStudioVersion: , + Projects: [ + { + Items: [], + Id: , + Name: SolutionFolder1, + Path: /Working/SolutionFolder1, + Type: Guid_1, + Parent: null + }, + { + Items: [ + { + Id: , + Name: ClassLibraryNestedSolutionFolder, + Path: /Working/ClassLibraryNestedSolutionFolder/ClassLibraryNestedSolutionFolder.csproj, + Type: Guid_2 + } + ], + Id: , + Name: NestedSolutionFolder, + Path: /Working/SolutionFolder1/NestedSolutionFolder, + Type: Guid_1, + Parent: null + }, + { + Id: , + Name: ClassLibraryNestedSolutionFolder, + Path: /Working/ClassLibraryNestedSolutionFolder/ClassLibraryNestedSolutionFolder.csproj, + Type: Guid_2, + Parent: { + Items: [], + Id: , + Name: NestedSolutionFolder, + Path: /Working/SolutionFolder1/NestedSolutionFolder, + Type: Guid_1, + Parent: null + } + } + ] +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fakes/FakeBuildSystemServiceMessageWriter.cs b/src/Cake.Common.Tests/Fakes/FakeBuildSystemServiceMessageWriter.cs new file mode 100644 index 0000000000..c8bfcf15ea --- /dev/null +++ b/src/Cake.Common.Tests/Fakes/FakeBuildSystemServiceMessageWriter.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Cake.Common.Build; + +namespace Cake.Common.Tests.Fakes +{ + public sealed class FakeBuildSystemServiceMessageWriter : IBuildSystemServiceMessageWriter + { + private readonly StringWriter _writer; + + public List Entries { get; } + + public FakeBuildSystemServiceMessageWriter() + { + _writer = new StringWriter(); + Entries = new List(); + } + + public void Write(string format, params object[] args) + { + _writer.WriteLine(format, args); + Entries.Add(string.Format(format, args)); + } + + public string GetOutput() + { + return _writer.ToString(); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture.cs index 5f01264871..51f2eeaf20 100644 --- a/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.IO; using Cake.Common.Solution.Project.Properties; using Cake.Core; using Cake.Core.Diagnostics; @@ -8,7 +10,6 @@ using Cake.Testing; using NSubstitute; using Xunit; -using System.IO; namespace Cake.Common.Tests.Fixtures { @@ -47,4 +48,4 @@ public string CreateAndReturnContent() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture_VB.cs b/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture_VB.cs new file mode 100644 index 0000000000..203cea4896 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/AssemblyInfoFixture_VB.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using Cake.Common.Solution.Project.Properties; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Fixtures +{ + internal sealed class AssemblyInfoFixture_VB + { + public FakeFileSystem FileSystem { get; set; } + public ICakeLog Log { get; set; } + public ICakeEnvironment Environment { get; set; } + + public AssemblyInfoSettings Settings { get; set; } + public FilePath Path { get; set; } + + public AssemblyInfoFixture_VB() + { + Environment = Substitute.For(); + Environment.WorkingDirectory.Returns(new DirectoryPath("/Working")); + + FileSystem = new FakeFileSystem(Environment); + FileSystem.CreateDirectory(Environment.WorkingDirectory); + + Log = Substitute.For(); + Settings = new AssemblyInfoSettings(); + Path = "AssemblyInfo.vb"; + } + + public string CreateAndReturnContent() + { + var creator = new AssemblyInfoCreator(FileSystem, Environment, Log); + creator.Create(Path, Settings); + + var file = FileSystem.GetFile(Path.MakeAbsolute(Environment)); + Assert.True(file.Exists, "File was not created."); + using (var reader = new StreamReader(file.OpenRead())) + { + return reader.ReadToEnd(); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture.cs b/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture.cs index bbf70bded9..300a6a2d8d 100644 --- a/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Solution.Project.Properties; using Cake.Core; @@ -26,6 +27,7 @@ internal sealed class AssemblyInfoParserFixture public string Guid { get; set; } public string InformationalVersion { get; set; } public List InternalsVisibleTo { get; set; } + public List SupportedOSPlatform { get; set; } public string Product { get; set; } public string Title { get; set; } public string Trademark { get; set; } @@ -33,6 +35,7 @@ internal sealed class AssemblyInfoParserFixture public FilePath Path { get; set; } public bool CreateAssemblyInfo { get; set; } + public bool ExtraWhiteSpaces { get; set; } public AssemblyInfoParserFixture() { @@ -99,6 +102,10 @@ private void CreateAssemblyInfoOnDisk(FilePath path) { settings.InternalsVisibleTo = InternalsVisibleTo; } + if (SupportedOSPlatform != null) + { + settings.SupportedOSPlatform = SupportedOSPlatform; + } if (Product != null) { settings.Product = Product; @@ -117,7 +124,16 @@ private void CreateAssemblyInfoOnDisk(FilePath path) } var creator = new AssemblyInfoCreator(FileSystem, Environment, Substitute.For()); - creator.Create(path, settings); + if (ExtraWhiteSpaces) + { + creator.Create(path, settings, "[assembly: {0} ]", + "[assembly: {0}( {1} )]", + "[assembly: {0}( {1}, {2} )]"); + } + else + { + creator.Create(path, settings); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture_VB.cs b/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture_VB.cs new file mode 100644 index 0000000000..8df2b1114e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/AssemblyInfoParserFixture_VB.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Solution.Project.Properties; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures +{ + internal sealed class AssemblyInfoParserFixture_VB + { + public FakeFileSystem FileSystem { get; set; } + public ICakeEnvironment Environment { get; set; } + + public bool? ClsCompliant { get; set; } + public bool? ComVisible { get; set; } + public string Company { get; set; } + public string Configuration { get; set; } + public string Copyright { get; set; } + public string Description { get; set; } + public string FileVersion { get; set; } + public string Guid { get; set; } + public string InformationalVersion { get; set; } + public List InternalsVisibleTo { get; set; } + public List SupportedOSPlatform { get; set; } + public string Product { get; set; } + public string Title { get; set; } + public string Trademark { get; set; } + public string Version { get; set; } + + public FilePath Path { get; set; } + public bool CreateAssemblyInfo { get; set; } + public bool ExtraWhiteSpaces { get; set; } + + public AssemblyInfoParserFixture_VB() + { + Environment = Substitute.For(); + Environment.WorkingDirectory.Returns("/Working"); + FileSystem = new FakeFileSystem(Environment); + FileSystem.CreateDirectory(Environment.WorkingDirectory); + + // Set fixture values. + Path = new FilePath("./output.vb"); + CreateAssemblyInfo = true; + } + + public void WithAssemblyInfoContents(string assemblyInfoContents) + { + FileSystem.CreateFile("/Working/output.vb").SetContent(assemblyInfoContents); + } + + public AssemblyInfoParseResult Parse() + { + if (CreateAssemblyInfo && Path != null) + { + CreateAssemblyInfoOnDisk(Path); + } + var parser = new AssemblyInfoParser(FileSystem, Environment); + return parser.Parse(Path); + } + + private void CreateAssemblyInfoOnDisk(FilePath path) + { + var settings = new AssemblyInfoSettings(); + settings.CLSCompliant = ClsCompliant; + settings.ComVisible = ComVisible; + + if (Company != null) + { + settings.Company = Company; + } + if (Configuration != null) + { + settings.Configuration = Configuration; + } + if (Copyright != null) + { + settings.Copyright = Copyright; + } + if (Description != null) + { + settings.Description = Description; + } + if (FileVersion != null) + { + settings.FileVersion = FileVersion; + } + if (Guid != null) + { + settings.Guid = Guid; + } + if (InformationalVersion != null) + { + settings.InformationalVersion = InformationalVersion; + } + if (InternalsVisibleTo != null) + { + settings.InternalsVisibleTo = InternalsVisibleTo; + } + if (SupportedOSPlatform != null) + { + settings.SupportedOSPlatform = SupportedOSPlatform; + } + if (Product != null) + { + settings.Product = Product; + } + if (Title != null) + { + settings.Title = Title; + } + if (Trademark != null) + { + settings.Trademark = Trademark; + } + if (Version != null) + { + settings.Version = Version; + } + + var creator = new AssemblyInfoCreator(FileSystem, Environment, Substitute.For()); + if (ExtraWhiteSpaces) + { + creator.Create(path, settings, vbAttributeFormat: "", + vbAttributeWithValueFormat: "", + vbAttributeWithKeyValueFormat: ""); + } + else + { + creator.Create(path, settings); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/AppVeyorFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/AppVeyorFixture.cs index 982e273015..477f3a4e8c 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/AppVeyorFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/AppVeyorFixture.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.AppVeyor; using Cake.Core; using Cake.Core.IO; +using Cake.Testing; using NSubstitute; namespace Cake.Common.Tests.Fixtures.Build @@ -13,6 +15,8 @@ internal sealed class AppVeyorFixture public ICakeEnvironment Environment { get; set; } public IProcessRunner ProcessRunner { get; set; } + public FakeLog CakeLog { get; set; } + public AppVeyorFixture() { Environment = Substitute.For(); @@ -20,6 +24,7 @@ public AppVeyorFixture() Environment.GetEnvironmentVariable("APPVEYOR").Returns((string)null); ProcessRunner = Substitute.For(); + CakeLog = new FakeLog(); } public void IsRunningOnAppVeyor() @@ -29,7 +34,7 @@ public void IsRunningOnAppVeyor() public AppVeyorProvider CreateAppVeyorService() { - return new AppVeyorProvider(Environment, ProcessRunner); + return new AppVeyorProvider(Environment, ProcessRunner, CakeLog); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/AppVeyorInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/AppVeyorInfoFixture.cs index 177c3c3f6f..3144d127e5 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/AppVeyorInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/AppVeyorInfoFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.AppVeyor.Data; using Cake.Core; using NSubstitute; @@ -87,4 +88,4 @@ public AppVeyorEnvironmentInfo CreateEnvironmentInfo() return new AppVeyorEnvironmentInfo(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesFixture.cs new file mode 100644 index 0000000000..dacc1cc813 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesFixture.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.AzurePipelines; +using Cake.Common.Tests.Fakes; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class AzurePipelinesFixture + { + public ICakeEnvironment Environment { get; set; } + + public FakeBuildSystemServiceMessageWriter Writer { get; set; } + + public AzurePipelinesFixture() + { + Environment = Substitute.For(); + Environment.WorkingDirectory.Returns("C:\\build\\CAKE-CAKE-JOB1"); + Environment.GetEnvironmentVariable("TF_BUILD").Returns((string)null); + Environment.Platform.Family.Returns(PlatformFamily.Windows); + Writer = new FakeBuildSystemServiceMessageWriter(); + } + + public void IsRunningOnAzurePipelines() + { + Environment.GetEnvironmentVariable("TF_BUILD").Returns("True"); + } + + public AzurePipelinesProvider CreateAzurePipelinesService() => new AzurePipelinesProvider(Environment, Writer); + + public AzurePipelinesProvider CreateAzurePipelinesService(PlatformFamily platformFamily, DirectoryPath workingDirectory) + { + Environment.Platform.Family.Returns(platformFamily); + Environment.WorkingDirectory.Returns(workingDirectory); + return CreateAzurePipelinesService(); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesInfoFixture.cs new file mode 100644 index 0000000000..c9af8a282c --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/AzurePipelinesInfoFixture.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + public sealed class AzurePipelinesInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public AzurePipelinesInfoFixture() + { + Environment = Substitute.For(); + + // TFBuild RepositoryInfo + Environment.GetEnvironmentVariable("BUILD_SOURCEVERSION").Returns("4efbc1ffb993dfbcf024e6a9202865cc0b6d9c50"); + Environment.GetEnvironmentVariable("BUILD_SOURCETFVCSHELVESET").Returns("Shelveset1"); + Environment.GetEnvironmentVariable("BUILD_REPOSITORY_NAME").Returns("cake"); + Environment.GetEnvironmentVariable("BUILD_REPOSITORY_PROVIDER").Returns("GitHub"); + Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCHNAME").Returns("develop"); + Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH").Returns("refs/heads/develop"); + Environment.GetEnvironmentVariable("BUILD_SOURCEVERSIONMESSAGE").Returns("A commit message"); + Environment.GetEnvironmentVariable("BUILD_REPOSITORY_GIT_SUBMODULECHECKOUT").Returns("A commit message"); + + // TFBuild AgentInfo + Environment.GetEnvironmentVariable("AGENT_BUILDDIRECTORY").Returns(@"c:\agent\_work\1"); + Environment.GetEnvironmentVariable("AGENT_HOMEDIRECTORY").Returns(@"c:\agent"); + Environment.GetEnvironmentVariable("AGENT_WORKFOLDER").Returns(@"c:\agent\_work"); + Environment.GetEnvironmentVariable("AGENT_ID").Returns("71"); + Environment.GetEnvironmentVariable("AGENT_NAME").Returns("Agent-1"); + Environment.GetEnvironmentVariable("AGENT_MACHINENAME").Returns("BuildServer"); + Environment.GetEnvironmentVariable("AGENT_JOBNAME").Returns("Job"); + Environment.GetEnvironmentVariable("AGENT_JOBSTATUS").Returns("SucceededWithIssues"); + Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY").Returns(@"C:/hostedtoolcache/windows"); + + // TFBuild BuildInfo + Environment.GetEnvironmentVariable("SYSTEM_ACCESSTOKEN").Returns("f662dbe218144c86bdecb1e9b2eb336c"); + Environment.GetEnvironmentVariable("SYSTEM_DEBUG").Returns("true"); + Environment.GetEnvironmentVariable("BUILD_BUILDID").Returns("100234"); + Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER").Returns("Build-20160927.1"); + Environment.GetEnvironmentVariable("BUILD_BUILDURI").Returns("vstfs:///Build/Build/1430"); + Environment.GetEnvironmentVariable("BUILD_QUEUEDBY") + .Returns(@"[DefaultCollection]\Project Collection Service Accounts"); + Environment.GetEnvironmentVariable("BUILD_REQUESTEDFOR").Returns("Alistair Chapman"); + Environment.GetEnvironmentVariable("BUILD_REQUESTEDFOREMAIL").Returns("author@mail.com"); + Environment.GetEnvironmentVariable("BUILD_ARTIFACTSTAGINGDIRECTORY").Returns(@"c:\agent\_work\1\a"); + Environment.GetEnvironmentVariable("BUILD_BINARIESDIRECTORY").Returns(@"c:\agent\_work\1\b"); + Environment.GetEnvironmentVariable("BUILD_REASON").Returns("PullRequest"); + Environment.GetEnvironmentVariable("BUILD_SOURCESDIRECTORY").Returns(@"c:\agent\_work\1\s"); + Environment.GetEnvironmentVariable("BUILD_STAGINGDIRECTORY").Returns(@"c:\agent\_work\1\a"); + Environment.GetEnvironmentVariable("COMMON_TESTRESULTSDIRECTORY").Returns(@"c:\agent\_work\1\TestResults"); + + // VSTS Build TriggeredBy + Environment.GetEnvironmentVariable("BUILD_TRIGGEREDBY_BUILDID").Returns(@"1"); + Environment.GetEnvironmentVariable("BUILD_TRIGGEREDBY_DEFINITIONID").Returns(@"1"); + Environment.GetEnvironmentVariable("BUILD_TRIGGEREDBY_DEFINITIONNAME").Returns(@"Build"); + Environment.GetEnvironmentVariable("BUILD_TRIGGEREDBY_BUILDNUMBER").Returns(@"123"); + Environment.GetEnvironmentVariable("BUILD_TRIGGEREDBY_PROJECTID").Returns(@"456"); + + // TFBuild PullRequestInfo + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTID").Returns("1"); + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTNUMBER").Returns("1"); + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_ISFORK").Returns(@"False"); + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_SOURCEBRANCH").Returns(@"refs/heads/FeatureBranch"); + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI").Returns(@"https://fabrikamfiber.visualstudio.com/Project/_git/ProjectRepo"); + Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_TARGETBRANCH").Returns(@"refs/heads/master"); + + // TFBuild DefinitionInfo + Environment.GetEnvironmentVariable("SYSTEM_DEFINITIONID").Returns("1855"); + Environment.GetEnvironmentVariable("BUILD_DEFINITIONNAME").Returns("Cake-CI"); + Environment.GetEnvironmentVariable("BUILD_DEFINITIONVERSION").Returns("47"); + + // TFBuild TeamProjectInfo + Environment.GetEnvironmentVariable("SYSTEM_TEAMPROJECT").Returns("TeamProject"); + Environment.GetEnvironmentVariable("SYSTEM_TEAMPROJECTID").Returns("D0A3B6B8-499B-4D4B-BD46-DB70C19E6D33"); + Environment.GetEnvironmentVariable("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + .Returns("https://fabrikamfiber.visualstudio.com/"); + } + + public AzurePipelinesEnvironmentInfo CreateEnvironmentInfo() + { + return new AzurePipelinesEnvironmentInfo(Environment); + } + + public AzurePipelinesRepositoryInfo CreateRepositoryInfo() + { + return new AzurePipelinesRepositoryInfo(Environment); + } + + public AzurePipelinesRepositoryInfo CreateRepositoryInfo(string repoType) + { + Environment.GetEnvironmentVariable("BUILD_REPOSITORY_PROVIDER").Returns(repoType); + return CreateRepositoryInfo(); + } + + public AzurePipelinesAgentInfo CreateAgentInfo() + { + return new AzurePipelinesAgentInfo(Environment); + } + + public AzurePipelinesAgentInfo CreateHostedAgentInfo() + { + Environment.GetEnvironmentVariable("AGENT_NAME").Returns("Hosted Agent"); + return new AzurePipelinesAgentInfo(Environment); + } + + public AzurePipelinesBuildInfo CreateBuildInfo() + { + return new AzurePipelinesBuildInfo(Environment); + } + + public AzurePipelinesPullRequestInfo CreatePullRequestInfo() + { + return new AzurePipelinesPullRequestInfo(Environment); + } + + public AzurePipelinesDefinitionInfo CreateDefinitionInfo() + { + return new AzurePipelinesDefinitionInfo(Environment); + } + + public AzurePipelinesTeamProjectInfo CreateTeamProjectInfo() + { + return new AzurePipelinesTeamProjectInfo(Environment); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/BambooFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/BambooFixture.cs index 480ba9c2dd..851cba48a8 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/BambooFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/BambooFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bamboo; using Cake.Core; using NSubstitute; @@ -28,4 +29,4 @@ public BambooProvider CreateBambooService() return new BambooProvider(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/BambooInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/BambooInfoFixture.cs index 942066fa0f..43bc225008 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/BambooInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/BambooInfoFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bamboo.Data; using Cake.Core; using NSubstitute; @@ -15,7 +16,7 @@ public BambooInfoFixture() { Environment = Substitute.For(); - //BambooBuildInfo + // BambooBuildInfo Environment.GetEnvironmentVariable("bamboo_build_working_directory").Returns("C:\\build\\CAKE-CAKE-JOB1"); Environment.GetEnvironmentVariable("bamboo_buildNumber").Returns("28"); Environment.GetEnvironmentVariable("bamboo_buildKey").Returns("CAKE-CAKE-JOB1"); @@ -23,21 +24,21 @@ public BambooInfoFixture() Environment.GetEnvironmentVariable("bamboo_buildResultsUrl").Returns("https://cakebuild.atlassian.net/builds/browse/CAKE-CAKE-JOB1-28"); Environment.GetEnvironmentVariable("bamboo_buildTimeStamp").Returns("2015-12-15T22:53:37.847+01:00"); - //BambooCustomBuildInfo + // BambooCustomBuildInfo Environment.GetEnvironmentVariable("bamboo_customRevision").Returns("Cake with Iceing"); - //BambooCommitInfo + // BambooCommitInfo Environment.GetEnvironmentVariable("bamboo_planRepository_revision").Returns("d4a3a4cb304548450e3cab2ff735f778ffe58d03"); - //BambooPlanInfo + // BambooPlanInfo Environment.GetEnvironmentVariable("bamboo_planKey").Returns("CAKE-CAKE"); - Environment.GetEnvironmentVariable ("bamboo_planName").Returns ("cake-bamboo - dev"); + Environment.GetEnvironmentVariable("bamboo_planName").Returns("cake-bamboo - dev"); Environment.GetEnvironmentVariable("bamboo_shortJobKey").Returns("JOB1"); Environment.GetEnvironmentVariable("bamboo_shortJobName").Returns("Build Cake"); Environment.GetEnvironmentVariable("bamboo_shortPlanKey").Returns("CAKE"); Environment.GetEnvironmentVariable("bamboo_shortPlanName").Returns("Cake"); - //BambooRepositoryInfo + // BambooRepositoryInfo Environment.GetEnvironmentVariable("bamboo_planRepository_type").Returns("git"); Environment.GetEnvironmentVariable("bamboo_repository_name").Returns("Cake/Develop"); Environment.GetEnvironmentVariable("bamboo_planRepository_branch").Returns("develop"); @@ -73,4 +74,4 @@ public BambooEnvironmentInfo CreateEnvironmentInfo() return new BambooEnvironmentInfo(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/BitbucketPipelinesInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/BitbucketPipelinesInfoFixture.cs new file mode 100644 index 0000000000..0ab8c28a88 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/BitbucketPipelinesInfoFixture.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.BitbucketPipelines.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + public class BitbucketPipelinesInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public BitbucketPipelinesInfoFixture() + { + Environment = Substitute.For(); + + // BitbucketPipelines RepositoryInfo + Environment.GetEnvironmentVariable("BITBUCKET_COMMIT").Returns("4efbc1ffb993dfbcf024e6a9202865cc0b6d9c50"); + Environment.GetEnvironmentVariable("BITBUCKET_REPO_SLUG").Returns("cake"); + Environment.GetEnvironmentVariable("BITBUCKET_REPO_OWNER").Returns("cakebuild"); + Environment.GetEnvironmentVariable("BITBUCKET_BRANCH").Returns("develop"); + Environment.GetEnvironmentVariable("BITBUCKET_TAG").Returns("BitbucketPipelines"); + + // BitbucketPipelines PullRequestInfo + Environment.GetEnvironmentVariable("BITBUCKET_PR_ID").Returns("1"); + } + + public BitbucketPipelinesEnvironmentInfo CreateEnvironmentInfo() + { + return new BitbucketPipelinesEnvironmentInfo(Environment); + } + + public BitbucketPipelinesRepositoryInfo CreateRepositoryInfo() + { + return new BitbucketPipelinesRepositoryInfo(Environment); + } + + public BitbucketPipelinesPullRequestInfo CreatePullRequestInfo() + { + return new BitbucketPipelinesPullRequestInfo(Environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/BitriseFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/BitriseFixture.cs index 6734af118a..294698d709 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/BitriseFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/BitriseFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bitrise; using Cake.Core; using Cake.Core.IO; @@ -26,7 +27,7 @@ public void IsRunningOnBitrise() public BitriseProvider CreateBitriseService() { - return new BitriseProvider(Environment); + return new BitriseProvider(Environment, ProcessRunner); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/BitriseInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/BitriseInfoFixture.cs index bcc3b4512c..3d2fe7c535 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/BitriseInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/BitriseInfoFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bitrise.Data; using Cake.Core; using NSubstitute; @@ -27,6 +28,9 @@ public BitriseInfoFixture() Environment.GetEnvironmentVariable("BITRISE_BUILD_TRIGGER_TIMESTAMP").Returns("2016-03-12 23:49:26"); Environment.GetEnvironmentVariable("BITRISE_BUILD_STATUS").Returns("true"); + // Bitrise PullRequestInfo + Environment.GetEnvironmentVariable("BITRISE_PULL_REQUEST").Returns("1"); + // Bitrise DirectoryInfo Environment.GetEnvironmentVariable("BITRISE_SOURCE_DIR").Returns("/Users/vagrant/git"); Environment.GetEnvironmentVariable("BITRISE_DEPLOY_DIR").Returns("/Users/vagrant/deploy"); @@ -41,13 +45,17 @@ public BitriseInfoFixture() Environment.GetEnvironmentVariable("BITRISE_GIT_BRANCH").Returns("cake-branch"); Environment.GetEnvironmentVariable("BITRISE_GIT_TAG").Returns("v0.0.1"); Environment.GetEnvironmentVariable("BITRISE_GIT_COMMIT").Returns("63dd7b"); - Environment.GetEnvironmentVariable("BITRISE_PULL_REQUEST").Returns("[WIP] Bitrise cake support #000"); // Bitrise WorkflowInfo Environment.GetEnvironmentVariable("BITRISE_TRIGGERED_WORKFLOW_ID").Returns("Build & Test Cake on BitRise"); Environment.GetEnvironmentVariable("BITRISE_TRIGGERED_WORKFLOW_TITLE").Returns("Build & Test Cake on BitRise"); } + public BitriseEnvironmentInfo CreateEnvironmentInfo() + { + return new BitriseEnvironmentInfo(Environment); + } + public BitriseApplicationInfo CreateApplicationInfo() { return new BitriseApplicationInfo(Environment); @@ -58,6 +66,11 @@ public BitriseBuildInfo CreateBuildInfo() return new BitriseBuildInfo(Environment); } + public BitrisePullRequestInfo CreatePullRequestInfo() + { + return new BitrisePullRequestInfo(Environment); + } + public BitriseDirectoryInfo CreateDirectoryInfo() { return new BitriseDirectoryInfo(Environment); @@ -78,4 +91,4 @@ public BitriseWorkflowInfo CreateWorkflowInfo() return new BitriseWorkflowInfo(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIFixture.cs index 505f1c8ab4..fe705f8c32 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIFixture.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.ContinuaCI; +using Cake.Common.Tests.Fakes; using Cake.Core; using NSubstitute; @@ -10,12 +12,14 @@ namespace Cake.Common.Tests.Fixtures.Build internal sealed class ContinuaCIFixture { public ICakeEnvironment Environment { get; set; } + public FakeBuildSystemServiceMessageWriter Writer { get; set; } public ContinuaCIFixture() { Environment = Substitute.For(); Environment.WorkingDirectory.Returns("C:\\build\\CAKE-CAKE-JOB1"); Environment.GetEnvironmentVariable("ContinuaCI.Version").Returns((string)null); + Writer = new FakeBuildSystemServiceMessageWriter(); } public void IsRunningOnContinuaCI() @@ -25,7 +29,7 @@ public void IsRunningOnContinuaCI() public ContinuaCIProvider CreateContinuaCIService() { - return new ContinuaCIProvider(Environment); + return new ContinuaCIProvider(Environment, Writer); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIInfoFixture.cs index 3c90762523..428d465702 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/ContinuaCIInfoFixture.cs @@ -1,98 +1,98 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using Cake.Common.Build.ContinuaCI.Data; using Cake.Core; using NSubstitute; -using System; -using System.Collections.Generic; namespace Cake.Common.Tests.Fixtures.Build { - internal sealed class ContinuaCIInfoFixture - { - public ICakeEnvironment Environment { get; set; } - - public ContinuaCIInfoFixture() - { - Environment = Substitute.For(); + internal sealed class ContinuaCIInfoFixture + { + public ICakeEnvironment Environment { get; set; } - //ContinuaCIBuildInfo - Environment.GetEnvironmentVariable("ContinuaCI.Build.Id").Returns("99"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.Version").Returns("v1.2.3"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.StartedBy").Returns("TestTrigger"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.IsFeatureBranchBuild").Returns("true"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.BuildNumber").Returns("999"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.Started").Returns("2015-12-15T22:53:37.847+01:00"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.UsesDefaultBranch").Returns("false"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.HasNewChanges").Returns("true"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetCount").Returns("6"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.IssueCount").Returns("3"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.Elapsed").Returns(TimeSpan.FromMinutes(5).ToString()); - Environment.GetEnvironmentVariable("ContinuaCI.Build.TimeOnQueue").Returns("7777"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.Repositories").Returns("Repo1,Repo2,Repo3"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.RepositoryBranches").Returns("Branch1,Branch2,Branch3"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.TriggeringBranch").Returns("Branch2"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetRevisions").Returns("6,8,65"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetUserNames").Returns("george,bill"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetTagNames").Returns("tag1,tag2,tag 3"); + public ContinuaCIInfoFixture() + { + Environment = Substitute.For(); - //ContinuaCIChangesetInfo - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Revision").Returns("55"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Branch").Returns("master"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Created").Returns("2016-01-02T12:00:16.666+11:00"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.FileCount").Returns("77"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.UserName").Returns("georgedawes"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.TagCount").Returns("2"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.IssueCount").Returns("3"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.TagNames").Returns("the tag,the other tag"); - Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.IssueNames").Returns("an important issue,another more important issue,a not so important issue"); + // ContinuaCIBuildInfo + Environment.GetEnvironmentVariable("ContinuaCI.Build.Id").Returns("99"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.Version").Returns("v1.2.3"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.StartedBy").Returns("TestTrigger"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.IsFeatureBranchBuild").Returns("true"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.BuildNumber").Returns("999"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.Started").Returns("2015-12-15T22:53:37.847+01:00"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.UsesDefaultBranch").Returns("false"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.HasNewChanges").Returns("true"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetCount").Returns("6"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.IssueCount").Returns("3"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.Elapsed").Returns(TimeSpan.FromMinutes(5).ToString()); + Environment.GetEnvironmentVariable("ContinuaCI.Build.TimeOnQueue").Returns("7777"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.Repositories").Returns("Repo1,Repo2,Repo3"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.RepositoryBranches").Returns("Branch1,Branch2,Branch3"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.TriggeringBranch").Returns("Branch2"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetRevisions").Returns("6,8,65"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetUserNames").Returns("george,bill"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.ChangesetTagNames").Returns("tag1,tag2,tag 3"); - //ContinuaCIProjectInfo - Environment.GetEnvironmentVariable("ContinuaCI.Project.Name").Returns("the project from hell"); + // ContinuaCIChangesetInfo + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Revision").Returns("55"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Branch").Returns("master"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.Created").Returns("2016-01-02T12:00:16.666+11:00"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.FileCount").Returns("77"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.UserName").Returns("georgedawes"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.TagCount").Returns("2"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.IssueCount").Returns("3"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.TagNames").Returns("the tag,the other tag"); + Environment.GetEnvironmentVariable("ContinuaCI.Build.LatestChangeset.IssueNames").Returns("an important issue,another more important issue,a not so important issue"); - //ContinuaCIConfigurationInfo - Environment.GetEnvironmentVariable("ContinuaCI.Configuration.Name").Returns("The configuration from the end of the universe"); + // ContinuaCIProjectInfo + Environment.GetEnvironmentVariable("ContinuaCI.Project.Name").Returns("the project from hell"); - //ContinuaCIEnvironmentInfo - Environment.GetEnvironmentVariables().Returns(new Dictionary() - { - { "ContinuaCI.Variable.TestVar1", "gorgonzola" }, - { "ContinuaCI.Variable.TestVar2", "is" }, - { "ContinuaCI.Variable.TestVarX", "tasty" }, - { "This.Is.A.Dummy", "Init?" }, - { "ContinuaCI.AgentProperty.DotNet.4.0.FrameworkPathX64", @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319" }, - { "ContinuaCI.AgentProperty.MSBuild.4.0.PathX86", @"C:\Windows\Microsoft.NET\Framework\v4.0.30319" }, - { "ContinuaCI.AgentProperty.ServerFileTransport.UNCAvailable", "True" } - }); + // ContinuaCIConfigurationInfo + Environment.GetEnvironmentVariable("ContinuaCI.Configuration.Name").Returns("The configuration from the end of the universe"); + // ContinuaCIEnvironmentInfo + Environment.GetEnvironmentVariables().Returns(new Dictionary() + { + { "ContinuaCI.Variable.TestVar1", "gorgonzola" }, + { "ContinuaCI.Variable.TestVar2", "is" }, + { "ContinuaCI.Variable.TestVarX", "tasty" }, + { "This.Is.A.Dummy", "Init?" }, + { "ContinuaCI.AgentProperty.DotNet.4.0.FrameworkPathX64", @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319" }, + { "ContinuaCI.AgentProperty.MSBuild.4.0.PathX86", @"C:\Windows\Microsoft.NET\Framework\v4.0.30319" }, + { "ContinuaCI.AgentProperty.ServerFileTransport.UNCAvailable", "True" } + }); - Environment.GetEnvironmentVariable("ContinuaCI.Version").Returns("v1.6.6.6"); - } + Environment.GetEnvironmentVariable("ContinuaCI.Version").Returns("v1.6.6.6"); + } - public ContinuaCIBuildInfo CreateBuildInfo() - { - return new ContinuaCIBuildInfo(Environment, "ContinuaCI.Build"); - } + public ContinuaCIBuildInfo CreateBuildInfo() + { + return new ContinuaCIBuildInfo(Environment, "ContinuaCI.Build"); + } - public ContinuaCIEnvironmentInfo CreateEnvironmentInfo() - { - return new ContinuaCIEnvironmentInfo(Environment); - } + public ContinuaCIEnvironmentInfo CreateEnvironmentInfo() + { + return new ContinuaCIEnvironmentInfo(Environment); + } - public ContinuaCIProjectInfo CreateProjectInfo() - { - return new ContinuaCIProjectInfo(Environment, "ContinuaCI.Project"); - } + public ContinuaCIProjectInfo CreateProjectInfo() + { + return new ContinuaCIProjectInfo(Environment, "ContinuaCI.Project"); + } - public ContinuaCIConfigurationInfo CreateConfigurationInfo() - { - return new ContinuaCIConfigurationInfo(Environment, "ContinuaCI.Configuration"); - } + public ContinuaCIConfigurationInfo CreateConfigurationInfo() + { + return new ContinuaCIConfigurationInfo(Environment, "ContinuaCI.Configuration"); + } - public ContinuaCIChangesetInfo CreateChangesetInfo() - { - return new ContinuaCIChangesetInfo(Environment, "ContinuaCI.Build.LatestChangeset"); - } - } -} + public ContinuaCIChangesetInfo CreateChangesetInfo() + { + return new ContinuaCIChangesetInfo(Environment, "ContinuaCI.Build.LatestChangeset"); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs new file mode 100644 index 0000000000..23e083bcc2 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsCommandsFixture.cs @@ -0,0 +1,265 @@ +using System; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Cake.Common.Build.GitHubActions.Commands; +using Cake.Common.Build.GitHubActions.Commands.Artifact; +using Cake.Common.Tests.Fakes; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GitHubActionsCommandsFixture : HttpMessageHandler + { + private const string AcceptHeaderResults = "application/json"; + private const string ArtifactUrl = GitHubActionsInfoFixture.ActionResultsUrl + "twirp/github.actions.results.api.v1.ArtifactService/"; + private const string CreateArtifactUrl = ArtifactUrl + "CreateArtifact"; + private const string FinalizeArtifactUrl = ArtifactUrl + "FinalizeArtifact"; + private const string GetSignedArtifactURLUrl = ArtifactUrl + "GetSignedArtifactURL"; + private const string ListArtifactsUrl = ArtifactUrl + "ListArtifacts"; + private const string UploadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=upload"; + private const string DownloadFileUrl = "https://cake.build.net/actions-results/a9d82106-d5d5-4310-8f60-0bfac035cf02/workflow-job-run-1d849a45-2f30-5fbb-3226-b730a17a93af/artifacts/91e64594182918fa8012cdbf7d1a4f801fa0c35f485c3277268aad8e3f45377c.zip?sig=download"; + private const string CreateArtifactResponse = + $$""" + { + "ok": true, + "signed_upload_url": "{{UploadFileUrl}}" + } + """; + private const string FinalizeArtifactResponse = + """ + { + "ok": true, + "artifact_id": "1991105334" + } + """; + private const string GetSignedArtifactURLResponse = + $$""" + { + "name": "artifact", + "signed_url": "{{DownloadFileUrl}}" + } + """; + private const string ListArtifactsResponse = + $$""" + { + "artifacts": [ + { + "workflow_run_backend_id": "b9e28153-ca20-4b86-91dd-09e8f644efdf", + "workflow_job_run_backend_id": "1d849a45-2f30-5fbb-3226-b730a17a93af", + "database_id": "1", + "name": "artifact", + "created_at": "2024-11-09T21:53:00.7110204+00:00" + } + ] + } + """; + + private GitHubActionsInfoFixture GitHubActionsInfoFixture { get; } + private ICakeEnvironment Environment { get; } + public FakeFileSystem FileSystem { get; } + public FakeBuildSystemServiceMessageWriter Writer { get; } + + public GitHubActionsCommandsFixture() + { + GitHubActionsInfoFixture = new GitHubActionsInfoFixture(); + FileSystem = new FakeFileSystem(GitHubActionsInfoFixture.Environment); + FileSystem.CreateDirectory("/opt"); + Environment = GitHubActionsInfoFixture.Environment; + Writer = new FakeBuildSystemServiceMessageWriter(); + } + + public GitHubActionsCommands CreateGitHubActionsCommands() + { + return new GitHubActionsCommands(Environment, FileSystem, Writer, GitHubActionsInfoFixture.CreateEnvironmentInfo(), CreateClient); + } + + public GitHubActionsCommandsFixture WithWorkingDirectory(DirectoryPath workingDirectory) + { + Environment.WorkingDirectory = workingDirectory; + return this; + } + + public GitHubActionsCommandsFixture WithNoGitHubEnv() + { + Environment.GetEnvironmentVariable("GITHUB_ENV").Returns(null as string); + return this; + } + + public GitHubActionsCommandsFixture WithNoGitHubOutput() + { + Environment.GetEnvironmentVariable("GITHUB_OUTPUT").Returns(null as string); + return this; + } + + public GitHubActionsCommandsFixture WithNoGitHubStepSummary() + { + Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY").Returns(null as string); + return this; + } + + public GitHubActionsCommandsFixture WithNoGitHubPath() + { + Environment.GetEnvironmentVariable("GITHUB_PATH").Returns(null as string); + return this; + } + + private HttpClient CreateClient(string name) => new HttpClient(this); + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.RequestUri.AbsoluteUri == DownloadFileUrl) + { + } + else if (request.RequestUri.AbsoluteUri == UploadFileUrl) + { + if ( + !request.Content.Headers.TryGetValues("x-ms-blob-content-type", out var contentTypes) + || !contentTypes.Contains("application/zip") + || !request.Content.Headers.TryGetValues("x-ms-blob-type", out var blobTypes) + || !blobTypes.Contains("BlockBlob")) + { + return new HttpResponseMessage + { + StatusCode = HttpStatusCode.Unauthorized + }; + } + } + else if (request.Headers.Authorization is null || request.Headers.Authorization.Scheme != "Bearer" || request.Headers.Authorization.Parameter != GitHubActionsInfoFixture.ActionRuntimeToken) + { + return new HttpResponseMessage + { + StatusCode = HttpStatusCode.Unauthorized + }; + } + + switch (request) + { +#pragma warning disable SA1013 + // Get Signed Artifact Url + case + { + RequestUri: { AbsoluteUri: GetSignedArtifactURLUrl }, + Method: { Method: "POST" }, + }: + { + using var getSignedArtifactURLRequestStream = await request.Content.ReadAsStreamAsync(cancellationToken); + var getSignedArtifactURLRequest = await System.Text.Json.JsonSerializer.DeserializeAsync(getSignedArtifactURLRequestStream, cancellationToken: cancellationToken); + return getSignedArtifactURLRequest switch + { + { Name: { Length: >0}, WorkflowJobRunBackendId: { Length: >0}, WorkflowRunBackendId: { Length: >0 } } => Ok(new StringContent(GetSignedArtifactURLResponse)), + _ => new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest + } + }; + } + + // Create Artifact + case + { + RequestUri: { AbsoluteUri: CreateArtifactUrl }, + Method: { Method: "POST" }, + }: + { + using var createArtifactRequestStream = await request.Content.ReadAsStreamAsync(cancellationToken); + var createArtifactRequest = await System.Text.Json.JsonSerializer.DeserializeAsync(createArtifactRequestStream, cancellationToken: cancellationToken); + + return createArtifactRequest switch + { + { Version: 4, Name: "artifact", } => Ok(new StringContent(CreateArtifactResponse)), + { Version: 4, Name: "artifacts", } => Ok(new StringContent(CreateArtifactResponse)), + _ => new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest + } + }; + } + // Finalize Artifact + case + { + RequestUri: { AbsoluteUri: FinalizeArtifactUrl }, + Method: { Method: "POST" }, + }: + { + using var createArtifactRequestStream = await request.Content.ReadAsStreamAsync(cancellationToken); + var finalizeArtifactRequest = await System.Text.Json.JsonSerializer.DeserializeAsync(createArtifactRequestStream, cancellationToken: cancellationToken); + + return finalizeArtifactRequest switch + { + { Hash: { Length: > 0}, Size: >0, Name: "artifact", } => Ok(new StringContent(FinalizeArtifactResponse)), + { Hash: { Length: > 0 }, Size: > 0, Name: "artifacts", } => Ok(new StringContent(FinalizeArtifactResponse)), + _ => new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest + } + }; + } + + // Upload File + case + { + RequestUri: { AbsoluteUri: UploadFileUrl }, + Method: { Method: "PUT" } + }: + { + return Ok(); + } + + // List Artifacts + case + { + RequestUri: { AbsoluteUri: ListArtifactsUrl }, + Method: { Method: "POST" } + }: + { + return Ok(new StringContent(ListArtifactsResponse)); + } + + // Download File + case + { + RequestUri: { AbsoluteUri: DownloadFileUrl }, + Method: { Method: "GET" } + }: + { + await using var stream = new System.IO.MemoryStream(); + using (var zip = new ZipArchive(stream, ZipArchiveMode.Create, true)) + { + var entry = zip.CreateEntry("test.txt"); + using var entryStream = entry.Open(); + using var writer = new System.IO.StreamWriter(entryStream); + writer.Write("Cake"); + } + return Ok(new ByteArrayContent(stream.ToArray())); + } +#pragma warning restore SA1013 + + default: + { + await Task.Delay(1, cancellationToken); + return new HttpResponseMessage + { + StatusCode = HttpStatusCode.NotFound + }; + } + } + } + + private static HttpResponseMessage Ok(HttpContent content = null) + { + return new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + ReasonPhrase = "OK", + Content = content + }; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsFixture.cs new file mode 100644 index 0000000000..ad8e1fb2f4 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsFixture.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitHubActions; +using Cake.Common.Tests.Fakes; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GitHubActionsFixture + { + public ICakeEnvironment Environment { get; } + public IFileSystem FileSystem { get; } + public FakeBuildSystemServiceMessageWriter Writer { get; } + + public GitHubActionsFixture() + { + Environment = Substitute.For(); + Environment.GetEnvironmentVariable("GITHUB_ACTIONS").Returns((string)null); + Environment.WorkingDirectory.Returns("/home/cake"); + FileSystem = new FakeFileSystem(Environment); + Writer = new FakeBuildSystemServiceMessageWriter(); + } + + public void IsRunningOnGitHubActions() + { + Environment.GetEnvironmentVariable("GITHUB_ACTIONS").Returns("true"); + } + + public GitHubActionsProvider CreateGitHubActionsService() + { + return new GitHubActionsProvider(Environment, FileSystem, Writer); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsInfoFixture.cs new file mode 100644 index 0000000000..73a510fb16 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GitHubActionsInfoFixture.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.GitHubActions.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GitHubActionsInfoFixture + { + public const string ActionRuntimeToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ikh5cTROQVRBanNucUM3bWRydEFoaHJDUjJfUSJ9.eyJuYW1laWQiOiJkZGRkZGRkZC1kZGRkLWRkZGQtZGRkZC1kZGRkZGRkZGRkZGQiLCJzY3AiOiJBY3Rpb25zLkdlbmVyaWNSZWFkOjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCBBY3Rpb25zLlJlc3VsdHM6YjllMjgxNTMtY2EyMC00Yjg2LTkxZGQtMDllOGY2NDRlZmRmOjFkODQ5YTQ1LTJmMzAtNWZiYi0zMjI2LWI3MzBhMTdhOTNhZiBBY3Rpb25zLlVwbG9hZEFydGlmYWN0czowMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAvMTpCdWlsZC9CdWlsZC8xNiBMb2NhdGlvblNlcnZpY2UuQ29ubmVjdCBSZWFkQW5kVXBkYXRlQnVpbGRCeVVyaTowMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAvMTpCdWlsZC9CdWlsZC8xNiIsIklkZW50aXR5VHlwZUNsYWltIjoiU3lzdGVtOlNlcnZpY2VJZGVudGl0eSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3NpZCI6IkRERERERERELUREREQtRERERC1ERERELURERERERERERERERCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcHJpbWFyeXNpZCI6ImRkZGRkZGRkLWRkZGQtZGRkZC1kZGRkLWRkZGRkZGRkZGRkZCIsImF1aSI6ImUyMTI4OTY1LThlY2EtNDgxYy1hODhkLWJmOTFlZDg3Y2RiNSIsInNpZCI6ImMwNmVjY2E0LWY3ZjUtNGY4Mi1iM2IxLTJhYjM0M2Y4Mjg3NCIsImFjIjoiW3tcIlNjb3BlXCI6XCJyZWZzL2hlYWRzL21haW5cIixcIlBlcm1pc3Npb25cIjozfV0iLCJhY3NsIjoiMTAiLCJvcmNoaWQiOiJiOWUyODE1My1jYTIwLTRiODYtOTFkZC0wOWU4ZjY0NGVmZGYuYnVpbGQudWJ1bnR1LWxhdGVzdCIsImlzcyI6InZzdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20iLCJhdWQiOiJ2c3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tfHZzbzo0M2YwNTdkMC0wODAzLTRkOTEtOTRhMS1mOGViMTAzZGYxMWYiLCJuYmYiOjE3Mjc1NDQzOTIsImV4cCI6MTcyNzU2NzE5Mn0.sUTvwxD-NlbAhQJB7cIInovd9qDkFHWcwOiiQAlHCsjpRBCEUWb3tWfOmCEpn8It4FWkaSszjMd8oecBEMlyEUtk6Cm6l1AqCUnIT13B48c_2sjhjWz-UDNMt94nzYH2ulC8mBcV_kSEIHJUvOnFKrFMKEdg6axAjLCx4la9MOklVq2ehx6DC12qbUNpTELJGeWz_JvKHWexyfN1qJgUw3y4ritZDJF3HLTpb5IJS7sQmFZVB7F2P6DF-1iaCBX5hgA9KfiwWXw6oTkKd6aOEyJpcBe0b87V_-fVTivOUS-ABE5XN6TCLZSmt7X6qwTPeSoLKgQGx1h_tHwubGDjtQ"; + public const string ActionRuntimeUrl = "https://pipelines.actions.githubusercontent.com/ip0FyYnZXxdEOcOwPHkRsZJd2x6G5XoT486UsAb0/"; + public const string ActionResultsUrl = "https://results-receiver.actions.githubusercontent.com/"; + private readonly Version CakeTestVersion = new Version(1, 2, 3, 4); + + public ICakeEnvironment Environment { get; } + + public GitHubActionsInfoFixture() + { + Environment = Substitute.For(); + + Environment.GetEnvironmentVariable("GITHUB_ACTIONS").Returns("true"); + Environment.GetEnvironmentVariable("HOME").Returns("/home/runner"); + + Environment.GetEnvironmentVariable("RUNNER_NAME").Returns("RunnerName"); + Environment.GetEnvironmentVariable("RUNNER_OS").Returns("Linux"); + Environment.GetEnvironmentVariable("RUNNER_TEMP").Returns("/home/runner/work/_temp"); + Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE").Returns("/opt/hostedtoolcache"); + Environment.GetEnvironmentVariable("RUNNER_WORKSPACE").Returns("/home/runner/work/cake"); + Environment.GetEnvironmentVariable("ImageOS").Returns("ubuntu20"); + Environment.GetEnvironmentVariable("ImageVersion").Returns("20211209.3"); + Environment.GetEnvironmentVariable("RUNNER_USER").Returns("runner"); + + Environment.GetEnvironmentVariable("GITHUB_ACTION").Returns("run1"); + Environment.GetEnvironmentVariable("GITHUB_ACTION_PATH").Returns("/path/to/action"); + Environment.GetEnvironmentVariable("GITHUB_ACTOR").Returns("dependabot"); + Environment.GetEnvironmentVariable("GITHUB_API_URL").Returns("https://api.github.com"); + Environment.GetEnvironmentVariable("GITHUB_BASE_REF").Returns("master"); + Environment.GetEnvironmentVariable("GITHUB_EVENT_NAME").Returns("pull_request"); + Environment.GetEnvironmentVariable("GITHUB_EVENT_PATH").Returns("/home/runner/work/_temp/_github_workflow/event.json"); + Environment.GetEnvironmentVariable("GITHUB_GRAPHQL_URL").Returns("https://api.github.com/graphql"); + Environment.GetEnvironmentVariable("GITHUB_HEAD_REF").Returns("dependabot/nuget/Microsoft.SourceLink.GitHub-1.0.0"); + Environment.GetEnvironmentVariable("GITHUB_JOB").Returns("job"); + Environment.GetEnvironmentVariable("GITHUB_REF").Returns("refs/pull/1/merge"); + Environment.GetEnvironmentVariable("GITHUB_REPOSITORY").Returns("cake-build/cake"); + Environment.GetEnvironmentVariable("GITHUB_REPOSITORY_OWNER").Returns("cake-build"); + Environment.GetEnvironmentVariable("GITHUB_RUN_ID").Returns("34058136"); + Environment.GetEnvironmentVariable("GITHUB_RUN_NUMBER").Returns("60"); + Environment.GetEnvironmentVariable("GITHUB_SERVER_URL").Returns("https://github.com"); + Environment.GetEnvironmentVariable("GITHUB_SHA").Returns("d1e4f990f57349334368c8253382abc63be02d73"); + Environment.GetEnvironmentVariable("GITHUB_WORKFLOW").Returns("Build"); + Environment.GetEnvironmentVariable("GITHUB_WORKSPACE").Returns("/home/runner/work/cake/cake"); + Environment.GetEnvironmentVariable("GITHUB_RUN_ATTEMPT").Returns("2"); + Environment.GetEnvironmentVariable("GITHUB_REF_PROTECTED").Returns("true"); + Environment.GetEnvironmentVariable("GITHUB_REF_NAME").Returns("main"); + + Environment.GetEnvironmentVariable("ACTIONS_RUNTIME_TOKEN").Returns(ActionRuntimeToken); + Environment.GetEnvironmentVariable("ACTIONS_RUNTIME_URL").Returns(ActionRuntimeUrl); + Environment.GetEnvironmentVariable("ACTIONS_RESULTS_URL").Returns(ActionResultsUrl); + Environment.GetEnvironmentVariable("GITHUB_ENV").Returns("/opt/github.env"); + Environment.GetEnvironmentVariable("GITHUB_OUTPUT").Returns("/opt/github.output"); + Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY").Returns("/opt/github.stepsummary"); + Environment.GetEnvironmentVariable("GITHUB_PATH").Returns("/opt/github.path"); + Environment.WorkingDirectory.Returns("/home/runner/work/cake/cake"); + + Environment.GetSpecialPath(Core.IO.SpecialPath.LocalTemp).Returns("/tmp"); + Environment.Runtime.CakeVersion.Returns(CakeTestVersion); + } + + public GitHubActionsRunnerInfo CreateRunnerInfo(string architecture = null) + { + Environment.GetEnvironmentVariable("RUNNER_ARCH").Returns(architecture); + return new GitHubActionsRunnerInfo(Environment); + } + + public GitHubActionsWorkflowInfo CreateWorkflowInfo(string refType = null) + { + Environment.GetEnvironmentVariable("GITHUB_REF_TYPE").Returns(refType); + return new GitHubActionsWorkflowInfo(Environment); + } + + public GitHubActionsPullRequestInfo CreatePullRequestInfo() + { + return new GitHubActionsPullRequestInfo(Environment); + } + + public GitHubActionsEnvironmentInfo CreateEnvironmentInfo() + { + return new GitHubActionsEnvironmentInfo(Environment); + } + + public GitHubActionsRuntimeInfo CreateRuntimeInfo() + { + return new GitHubActionsRuntimeInfo(Environment); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitLabCIFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitLabCIFixture.cs new file mode 100644 index 0000000000..db34bb6ea8 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GitLabCIFixture.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Core; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GitLabCIFixture + { + public ICakeEnvironment Environment { get; } + public FakeFileSystem FileSystem { get; } + + public GitLabCIFixture() + { + Environment = Substitute.For(); + Environment.GetEnvironmentVariable("CI_SERVER").Returns((string)null); + Environment.WorkingDirectory.Returns("/runner/builds"); + FileSystem = new FakeFileSystem(Environment); + } + + public void IsRunningOnGitLabCI() + { + Environment.GetEnvironmentVariable("CI_SERVER").Returns("yes"); + } + + public GitLabCIProvider CreateGitLabCIService() + { + return new GitLabCIProvider(Environment, FileSystem); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/GitLabCIInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GitLabCIInfoFixture.cs new file mode 100644 index 0000000000..968b18a106 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GitLabCIInfoFixture.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GitLabCIInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public GitLabCIInfoFixture(bool versionNineOrNewer = false) + { + Environment = Substitute.For(); + + // Example values taken from https://docs.gitlab.com/ce/ci/variables/README.html + Environment.GetEnvironmentVariable("CI_SERVER").Returns("yes"); + if (versionNineOrNewer) + { + Environment.GetEnvironmentVariable("CI_JOB_ID").Returns("50"); + Environment.GetEnvironmentVariable("CI_COMMIT_SHA").Returns("1ecfd275763eff1d6b4844ea3168962458c9f27a"); + Environment.GetEnvironmentVariable("CI_COMMIT_REF_NAME").Returns("master"); + Environment.GetEnvironmentVariable("CI_REPOSITORY_URL").Returns("https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git"); + Environment.GetEnvironmentVariable("CI_COMMIT_TAG").Returns("1.0.0"); + Environment.GetEnvironmentVariable("CI_JOB_NAME").Returns("spec:other"); + Environment.GetEnvironmentVariable("CI_JOB_STAGE").Returns("test"); + Environment.GetEnvironmentVariable("CI_JOB_MANUAL").Returns("true"); + Environment.GetEnvironmentVariable("CI_PIPELINE_TRIGGERED").Returns("true"); + Environment.GetEnvironmentVariable("CI_JOB_TOKEN").Returns("abcde-1234ABCD5678ef"); + } + else + { + Environment.GetEnvironmentVariable("CI_BUILD_ID").Returns("50"); + Environment.GetEnvironmentVariable("CI_BUILD_REF").Returns("1ecfd275763eff1d6b4844ea3168962458c9f27a"); + Environment.GetEnvironmentVariable("CI_BUILD_REF_NAME").Returns("master"); + Environment.GetEnvironmentVariable("CI_BUILD_REPO").Returns("https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git"); + Environment.GetEnvironmentVariable("CI_BUILD_TAG").Returns("1.0.0"); + Environment.GetEnvironmentVariable("CI_BUILD_NAME").Returns("spec:other"); + Environment.GetEnvironmentVariable("CI_BUILD_STAGE").Returns("test"); + Environment.GetEnvironmentVariable("CI_BUILD_MANUAL").Returns("true"); + Environment.GetEnvironmentVariable("CI_BUILD_TRIGGERED").Returns("true"); + Environment.GetEnvironmentVariable("CI_BUILD_TOKEN").Returns("abcde-1234ABCD5678ef"); + } + Environment.GetEnvironmentVariable("CI_MERGE_REQUEST_ID").Returns("10"); + Environment.GetEnvironmentVariable("CI_MERGE_REQUEST_IID").Returns("1"); + Environment.GetEnvironmentVariable("CI_PIPELINE_ID").Returns("1000"); + Environment.GetEnvironmentVariable("CI_PIPELINE_IID").Returns("100"); + Environment.GetEnvironmentVariable("CI_PROJECT_ID").Returns("34"); + Environment.GetEnvironmentVariable("CI_PROJECT_DIR").Returns("/builds/gitlab-org/gitlab-ce"); + Environment.GetEnvironmentVariable("CI_PROJECT_NAME").Returns("gitlab-ce"); + Environment.GetEnvironmentVariable("CI_PROJECT_NAMESPACE").Returns("gitlab-org"); + Environment.GetEnvironmentVariable("CI_PROJECT_PATH").Returns("gitlab-org/gitlab-ce"); + Environment.GetEnvironmentVariable("CI_PROJECT_URL").Returns("https://gitlab.com/gitlab-org/gitlab-ce"); + Environment.GetEnvironmentVariable("CI_REGISTRY").Returns("registry.gitlab.com"); + Environment.GetEnvironmentVariable("CI_REGISTRY_IMAGE").Returns("registry.gitlab.com/gitlab-org/gitlab-ce"); + Environment.GetEnvironmentVariable("CI_RUNNER_ID").Returns("10"); + Environment.GetEnvironmentVariable("CI_RUNNER_DESCRIPTION").Returns("my runner"); + Environment.GetEnvironmentVariable("CI_RUNNER_TAGS").Returns("[\"docker\", \"linux\"]"); + Environment.GetEnvironmentVariable("CI_SERVER").Returns("yes"); + Environment.GetEnvironmentVariable("CI_SERVER_URL").Returns("https://gitlab.example.com:8080"); + Environment.GetEnvironmentVariable("CI_SERVER_NAME").Returns("GitLab"); + Environment.GetEnvironmentVariable("CI_SERVER_REVISION").Returns("70606bf"); + Environment.GetEnvironmentVariable("CI_SERVER_VERSION").Returns("8.9.0"); + Environment.GetEnvironmentVariable("GITLAB_USER_ID").Returns("42"); + Environment.GetEnvironmentVariable("GITLAB_USER_EMAIL").Returns("anthony@warwickcontrol.com"); + Environment.GetEnvironmentVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME").Returns("source-branch"); + Environment.GetEnvironmentVariable("CI_MERGE_REQUEST_TARGET_BRANCH_NAME").Returns("main"); + } + + public GitLabCIBuildInfo CreateBuildInfo() + { + return new GitLabCIBuildInfo(Environment); + } + + public GitLabCIPullRequestInfo CreatePullRequestInfo() + { + return new GitLabCIPullRequestInfo(Environment); + } + + public GitLabCIProjectInfo CreateProjectInfo() + { + return new GitLabCIProjectInfo(Environment); + } + + public GitLabCIRunnerInfo CreateRunnerInfo() + { + return new GitLabCIRunnerInfo(Environment); + } + + public GitLabCIServerInfo CreateServerInfo() + { + return new GitLabCIServerInfo(Environment); + } + + public GitLabCIEnvironmentInfo CreateEnvironmentInfo() + { + return new GitLabCIEnvironmentInfo(Environment); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/GoCDFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GoCDFixture.cs new file mode 100644 index 0000000000..eef6bca400 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GoCDFixture.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GoCD; +using Cake.Core; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GoCDFixture + { + public ICakeEnvironment Environment { get; set; } + + public FakeLog CakeLog { get; set; } + + public GoCDFixture() + { + Environment = Substitute.For(); + Environment.GetEnvironmentVariable("https://127.0.0.1:8154/go").Returns((string)null); + CakeLog = new FakeLog(); + } + + public void IsRunningOnGoCD() + { + Environment.GetEnvironmentVariable("GO_SERVER_URL").Returns("https://127.0.0.1:8154/go"); + } + + public GoCDProvider CreateGoCDService() + { + return new GoCDProvider(Environment, CakeLog); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/GoCDInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/GoCDInfoFixture.cs new file mode 100644 index 0000000000..79b8a35d07 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/GoCDInfoFixture.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GoCD.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class GoCDInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public GoCDInfoFixture() + { + Environment = Substitute.For(); + + // GoCDEnvironmentInfo + Environment.GetEnvironmentVariable("GO_SERVER_URL").Returns("https://127.0.0.1:8154/go"); + Environment.GetEnvironmentVariable("GO_ENVIRONMENT_NAME").Returns("Development"); + Environment.GetEnvironmentVariable("GO_JOB_NAME").Returns("linux-firefox"); + Environment.GetEnvironmentVariable("GO_TRIGGER_USER").Returns("changes"); + + // GoCDPipelineInfo + Environment.GetEnvironmentVariable("GO_PIPELINE_NAME").Returns("main"); + Environment.GetEnvironmentVariable("GO_PIPELINE_COUNTER").Returns("2345"); + Environment.GetEnvironmentVariable("GO_PIPELINE_LABEL").Returns("1.1.2345"); + + // GoCDCommitInfo + Environment.GetEnvironmentVariable("bamboo_planRepository_revision").Returns("d4a3a4cb304548450e3cab2ff735f778ffe58d03"); + + // GoCDRepositoryInfo + Environment.GetEnvironmentVariable("GO_REVISION").Returns("123"); + Environment.GetEnvironmentVariable("GO_TO_REVISION").Returns("124"); + Environment.GetEnvironmentVariable("GO_FROM_REVISION").Returns("122"); + + // GoCDStageInfo + Environment.GetEnvironmentVariable("GO_STAGE_NAME").Returns("dev"); + Environment.GetEnvironmentVariable("GO_STAGE_COUNTER").Returns("1"); + } + + public GoCDEnvironmentInfo CreateEnvironmentInfo() + { + return new GoCDEnvironmentInfo(Environment); + } + + public GoCDPipelineInfo CreatePipelineInfo() + { + return new GoCDPipelineInfo(Environment); + } + + public GoCDRepositoryInfo CreateRepositoryInfo() + { + return new GoCDRepositoryInfo(Environment); + } + + public GoCDStageInfo CreateStageInfo() + { + return new GoCDStageInfo(Environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/JenkinsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/JenkinsFixture.cs index 1b7a531b63..5e4bb3b0f7 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/JenkinsFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/JenkinsFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Jenkins; using Cake.Core; using NSubstitute; @@ -27,4 +28,4 @@ public JenkinsProvider CreateJenkinsProvider() return new JenkinsProvider(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/JenkinsInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/JenkinsInfoFixture.cs index f121259b2d..aa2fa6b30e 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/JenkinsInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/JenkinsInfoFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Jenkins.Data; using Cake.Core; using NSubstitute; @@ -16,19 +17,20 @@ public JenkinsInfoFixture() Environment = Substitute.For(); // JenkinsEnvironmentInfo - Environment.GetEnvironmentVariable("WORKSPACE").Returns("C:\\Jenkins\\build\\456"); - Environment.GetEnvironmentVariable("EXECUTOR_NUMBER").Returns("2112"); Environment.GetEnvironmentVariable("JENKINS_HOME").Returns("C:\\Jenkins\\build"); Environment.GetEnvironmentVariable("JENKINS_URL").Returns("http://localhost:8080/jenkins/"); // JenkinsBuildInfo Environment.GetEnvironmentVariable("BUILD_NUMBER").Returns("456"); - Environment.GetEnvironmentVariable("BUILD_DISPLAY_NAME").Returns("#456"); Environment.GetEnvironmentVariable("BUILD_ID").Returns("456"); + Environment.GetEnvironmentVariable("BUILD_DISPLAY_NAME").Returns("#456"); Environment.GetEnvironmentVariable("BUILD_TAG").Returns("jenkins-JOB1-456"); Environment.GetEnvironmentVariable("BUILD_URL").Returns("http://localhost:8080/jenkins/job/cake/456/"); + Environment.GetEnvironmentVariable("EXECUTOR_NUMBER").Returns("2112"); + Environment.GetEnvironmentVariable("WORKSPACE").Returns("C:\\Jenkins\\build\\456"); // JenkinsRepositoryInfo + Environment.GetEnvironmentVariable("BRANCH_NAME").Returns("CAKE-BRANCH"); Environment.GetEnvironmentVariable("GIT_COMMIT").Returns("67d423d36dd15b191a53ab3ddb613dc4b95be8b3"); Environment.GetEnvironmentVariable("GIT_BRANCH").Returns("CAKE-BRANCH"); Environment.GetEnvironmentVariable("SVN_REVISION").Returns("REVISION-NUMBER"); @@ -41,7 +43,19 @@ public JenkinsInfoFixture() // JenkinsJobInfo Environment.GetEnvironmentVariable("JOB_NAME").Returns("JOB1"); + Environment.GetEnvironmentVariable("JOB_BASE_NAME").Returns("JOB1BASE"); Environment.GetEnvironmentVariable("JOB_URL").Returns("http://localhost:8080/jenkins/job/cake/"); + + // JenkinsChangeInfo + Environment.GetEnvironmentVariable("CHANGE_ID").Returns("42178"); + Environment.GetEnvironmentVariable("CHANGE_URL").Returns("http://changeurl"); + Environment.GetEnvironmentVariable("CHANGE_TITLE").Returns("Modified x"); + Environment.GetEnvironmentVariable("CHANGE_AUTHOR").Returns("cu"); + Environment.GetEnvironmentVariable("CHANGE_AUTHOR_DISPLAY_NAME").Returns("Cake User"); + Environment.GetEnvironmentVariable("CHANGE_AUTHOR_EMAIL").Returns("cake@cakebuild.net"); + Environment.GetEnvironmentVariable("CHANGE_TARGET").Returns("develop"); + Environment.GetEnvironmentVariable("CHANGE_BRANCH").Returns("feature/feature1"); + Environment.GetEnvironmentVariable("CHANGE_FORK").Returns("fork1"); } public JenkinsBuildInfo CreateBuildInfo() @@ -69,4 +83,4 @@ public JenkinsEnvironmentInfo CreateEnvironmentInfo() return new JenkinsEnvironmentInfo(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs new file mode 100644 index 0000000000..4619d39380 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs @@ -0,0 +1,34 @@ +using Cake.Common.Build.MyGet; +using Cake.Common.Tests.Fakes; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class MyGetFixture + { + public ICakeEnvironment Environment { get; set; } + + public FakeBuildSystemServiceMessageWriter Writer { get; set; } + + public MyGetFixture() + { + Environment = Substitute.For(); + Environment.GetEnvironmentVariable("BuildRunner").Returns((string)null); + Writer = new FakeBuildSystemServiceMessageWriter(); + } + + public void IsRunningOnMyGet(bool? capitalCase = default) + { + var buildRunnerValue = "MyGet"; + if (capitalCase.HasValue) + { + buildRunnerValue = capitalCase.Value ? "MYGET" : "myget"; + } + + Environment.GetEnvironmentVariable("BuildRunner").Returns(buildRunnerValue); + } + + public MyGetProvider CreateMyGetProvider() => new MyGetProvider(Environment, Writer); + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/RwxCommandsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/RwxCommandsFixture.cs new file mode 100644 index 0000000000..118a8c0019 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/RwxCommandsFixture.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.Rwx.Commands; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class RwxCommandsFixture + { + public RwxInfoFixture InfoFixture { get; } + + public ICakeEnvironment Environment => InfoFixture.Environment; + + public FakeFileSystem FileSystem { get; } + + public RwxCommandsFixture() + { + InfoFixture = new RwxInfoFixture(); + Environment.WorkingDirectory.Returns("/work"); + FileSystem = new FakeFileSystem(Environment); + FileSystem.CreateDirectory("/rwx/values"); + FileSystem.CreateDirectory("/rwx/artifacts"); + FileSystem.CreateDirectory("/rwx/env"); + } + + public RwxCommands CreateRwxCommands() + { + return new RwxCommands(Environment, FileSystem, InfoFixture.CreateEnvironmentInfo()); + } + + public RwxCommandsFixture WithNoRwxValues() + { + Environment.GetEnvironmentVariable("RWX_VALUES").Returns(null as string); + return this; + } + + public RwxCommandsFixture WithNoRwxArtifacts() + { + Environment.GetEnvironmentVariable("RWX_ARTIFACTS").Returns(null as string); + return this; + } + + public RwxCommandsFixture WithNoRwxEnv() + { + Environment.GetEnvironmentVariable("RWX_ENV").Returns(null as string); + return this; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/RwxFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/RwxFixture.cs new file mode 100644 index 0000000000..f93d9ee28b --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/RwxFixture.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.Rwx; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class RwxFixture + { + public ICakeEnvironment Environment { get; set; } + + public IFileSystem FileSystem { get; set; } + + public RwxFixture() + { + Environment = Substitute.For(); + FileSystem = Substitute.For(); + } + + public void IsRunningOnRwx() + { + Environment.GetEnvironmentVariable("RWX").Returns("true"); + } + + public RwxProvider CreateRwxProvider() + { + return new RwxProvider(Environment, FileSystem); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/RwxInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/RwxInfoFixture.cs new file mode 100644 index 0000000000..d998f7385e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/RwxInfoFixture.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.Rwx.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class RwxInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public RwxInfoFixture() + { + Environment = Substitute.For(); + + // RwxEnvironmentInfo + Environment.GetEnvironmentVariable("CI").Returns("true"); + Environment.GetEnvironmentVariable("RWX").Returns("true"); + + // RwxRunInfo + Environment.GetEnvironmentVariable("RWX_RUN_ID").Returns("01J9ABCDEFG"); + Environment.GetEnvironmentVariable("RWX_RUN_TITLE").Returns("Build and test"); + Environment.GetEnvironmentVariable("RWX_RUN_URL").Returns("https://cloud.rwx.com/runs/01J9ABCDEFG"); + + // RwxTaskInfo + Environment.GetEnvironmentVariable("RWX_TASK_ID").Returns("01J9TASKABC"); + Environment.GetEnvironmentVariable("RWX_TASK_URL").Returns("https://cloud.rwx.com/tasks/01J9TASKABC"); + Environment.GetEnvironmentVariable("RWX_TASK_ATTEMPT_NUMBER").Returns("2"); + + // RwxActorInfo + Environment.GetEnvironmentVariable("RWX_ACTOR_ID").Returns("usr_12345"); + Environment.GetEnvironmentVariable("RWX_ACTOR").Returns("octocat"); + + // RwxGitInfo + Environment.GetEnvironmentVariable("RWX_GIT_REPOSITORY_URL").Returns("https://github.com/cake-build/cake.git"); + Environment.GetEnvironmentVariable("RWX_GIT_REPOSITORY_NAME").Returns("cake-build/cake"); + Environment.GetEnvironmentVariable("RWX_GIT_COMMIT_SHA").Returns("0123456789abcdef0123456789abcdef01234567"); + Environment.GetEnvironmentVariable("RWX_GIT_COMMIT_MESSAGE").Returns("Add RWX build provider\n\nMore details here."); + Environment.GetEnvironmentVariable("RWX_GIT_COMMIT_SUMMARY").Returns("Add RWX build provider"); + Environment.GetEnvironmentVariable("RWX_GIT_COMMITTER_NAME").Returns("Octo Cat"); + Environment.GetEnvironmentVariable("RWX_GIT_COMMITTER_EMAIL").Returns("octocat@example.com"); + Environment.GetEnvironmentVariable("RWX_GIT_REF").Returns("refs/heads/main"); + Environment.GetEnvironmentVariable("RWX_GIT_REF_NAME").Returns("main"); + + // RwxRuntimeInfo + Environment.GetEnvironmentVariable("RWX_VALUES").Returns("/rwx/values"); + Environment.GetEnvironmentVariable("RWX_ARTIFACTS").Returns("/rwx/artifacts"); + Environment.GetEnvironmentVariable("RWX_ENV").Returns("/rwx/env"); + } + + public RwxRunInfo CreateRunInfo() + { + return new RwxRunInfo(Environment); + } + + public RwxTaskInfo CreateTaskInfo() + { + return new RwxTaskInfo(Environment); + } + + public RwxActorInfo CreateActorInfo() + { + return new RwxActorInfo(Environment); + } + + public RwxGitInfo CreateGitInfo() + { + return new RwxGitInfo(Environment); + } + + public RwxRuntimeInfo CreateRuntimeInfo() + { + return new RwxRuntimeInfo(Environment); + } + + public RwxEnvironmentInfo CreateEnvironmentInfo() + { + return new RwxEnvironmentInfo(Environment); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/TeamCityFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/TeamCityFixture.cs index 32bc80488e..6d4ea07887 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/TeamCityFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/TeamCityFixture.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.TeamCity; +using Cake.Common.Tests.Fakes; using Cake.Core; -using Cake.Testing; +using Cake.Core.IO; using NSubstitute; namespace Cake.Common.Tests.Fixtures.Build @@ -11,14 +13,16 @@ namespace Cake.Common.Tests.Fixtures.Build internal sealed class TeamCityFixture { public ICakeEnvironment Environment { get; set; } - public FakeLog Log { get; set; } + public IFileSystem FileSystem { get; set; } + public FakeBuildSystemServiceMessageWriter Writer { get; set; } public TeamCityFixture() { Environment = Substitute.For(); Environment.WorkingDirectory.Returns("C:\\build\\CAKE-CAKE-JOB1"); Environment.GetEnvironmentVariable("TEAMCITY_VERSION").Returns((string)null); - Log = new FakeLog(); + FileSystem = Substitute.For(); + Writer = new FakeBuildSystemServiceMessageWriter(); } public void IsRunningOnTeamCity() @@ -28,7 +32,7 @@ public void IsRunningOnTeamCity() public TeamCityProvider CreateTeamCityService() { - return new TeamCityProvider(Environment, Log); + return new TeamCityProvider(Environment, FileSystem, Writer); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/TeamCityInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/TeamCityInfoFixture.cs new file mode 100644 index 0000000000..18439f72ff --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/TeamCityInfoFixture.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.TeamCity.Data; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class TeamCityInfoFixture + { + public IFileSystem FileSystem { get; set; } + public ICakeEnvironment Environment { get; set; } + + public TeamCityInfoFixture() + { + Environment = FakeEnvironment.CreateUnixEnvironment(); + FileSystem = new FakeFileSystem(Environment); + ((FakeFileSystem)FileSystem).CreateDirectory("/Working"); + + ((FakeEnvironment)Environment).SetEnvironmentVariable("TEAMCITY_BUILDCONF_NAME", @"Cake Build"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("BUILD_NUMBER", "10-Foo"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("TEAMCITY_PROJECT_NAME", "Cake"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("TEAMCITY_BUILD_PROPERTIES_FILE", "/Working/teamcity.build.properties"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("Git_Branch", "refs/pull-requests/7/from"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("BUILD_START_DATE", "20200822"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("BUILD_START_TIME", "123456"); + } + + public void SetBuildPropertiesContent(string xml) + { + ((FakeFileSystem)FileSystem).GetFile("/Working/teamcity.build.properties.xml").SetContent(xml); + } + + public void SetConfigPropertiesContent(string xml) + { + ((FakeFileSystem)FileSystem).GetFile("/Working/teamcity.config.configuration.xml").SetContent(xml); + } + + public void SetRunnerPropertiesContent(string xml) + { + ((FakeFileSystem)FileSystem).GetFile("/Working/teamcity.runner.configuration.xml").SetContent(xml); + } + + public void SetGitBranch(string branch) + { + ((FakeEnvironment)Environment).SetEnvironmentVariable("Git_Branch", branch); + } + + public void SetBuildStartDate(string startDate) + { + ((FakeEnvironment)Environment).SetEnvironmentVariable("BUILD_START_DATE", startDate); + } + + public void SetBuildStartTime(string startTime) + { + ((FakeEnvironment)Environment).SetEnvironmentVariable("BUILD_START_TIME", startTime); + } + + public TeamCityPullRequestInfo CreatePullRequestInfo() + { + return new TeamCityPullRequestInfo(Environment, CreateBuildInfo()); + } + + public TeamCityEnvironmentInfo CreateEnvironmentInfo() + { + return new TeamCityEnvironmentInfo(Environment, FileSystem); + } + + public TeamCityBuildInfo CreateBuildInfo() + { + return new TeamCityBuildInfo(Environment, FileSystem); + } + + public TeamCityProjectInfo CreateProjectInfo() + { + return new TeamCityProjectInfo(Environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/TravisCIFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/TravisCIFixture.cs index be1660f11b..ea43086cdd 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/TravisCIFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/TravisCIFixture.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.TravisCI; +using Cake.Common.Tests.Fakes; using Cake.Core; using Cake.Testing; using NSubstitute; @@ -11,23 +13,23 @@ namespace Cake.Common.Tests.Fixtures.Build internal sealed class TravisCIFixture { public ICakeEnvironment Environment { get; set; } - public FakeLog Log { get; set; } + public FakeBuildSystemServiceMessageWriter Writer { get; set; } public TravisCIFixture() { Environment = Substitute.For(); Environment.WorkingDirectory.Returns("/home/travis/.local"); - Log = new FakeLog(); + Writer = new FakeBuildSystemServiceMessageWriter(); } - public void IsRunningOnTravicCI() + public void IsRunningOnTravisCI() { Environment.GetEnvironmentVariable("TRAVIS").Returns("true"); } public TravisCIProvider CreateTravisCIProvider() { - return new TravisCIProvider(Environment, Log); + return new TravisCIProvider(Environment, Writer); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/TravisCIInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/TravisCIInfoFixture.cs index 9fdc62870e..018a5485c0 100644 --- a/src/Cake.Common.Tests/Fixtures/Build/TravisCIInfoFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Build/TravisCIInfoFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.TravisCI.Data; using Cake.Core; using NSubstitute; @@ -28,6 +29,9 @@ public TravisCIInfoFixture() Environment.GetEnvironmentVariable("TRAVIS_TEST_RESULT").Returns("0"); Environment.GetEnvironmentVariable("TRAVIS_TAG").Returns("v0.10.0"); + // TravisCIPullRequestInfo + Environment.GetEnvironmentVariable("TRAVIS_PULL_REQUEST").Returns("1"); + // TravisCIJobInfo Environment.GetEnvironmentVariable("TRAVIS_JOB_ID").Returns("934"); Environment.GetEnvironmentVariable("TRAVIS_JOB_NUMBER").Returns("934.2"); @@ -37,7 +41,6 @@ public TravisCIInfoFixture() // TravisCIRepositoryInfo Environment.GetEnvironmentVariable("TRAVIS_COMMIT").Returns("6cbdbe8"); Environment.GetEnvironmentVariable("TRAVIS_COMMIT_RANGE").Returns("6cb4d6...5ba6dbe8"); - Environment.GetEnvironmentVariable("TRAVIS_PULL_REQUEST").Returns("#786 (GH742) Added TravisCI build system support"); Environment.GetEnvironmentVariable("TRAVIS_REPO_SLUG").Returns("4d65ba6"); } @@ -46,6 +49,11 @@ public TravisCIBuildInfo CreateBuildInfo() return new TravisCIBuildInfo(Environment); } + public TravisCIPullRequestInfo CreatePullRequestInfo() + { + return new TravisCIPullRequestInfo(Environment); + } + public TravisCIJobInfo CreateJobInfo() { return new TravisCIJobInfo(Environment); @@ -61,4 +69,4 @@ public TravisCIEnvironmentInfo CreateEnvironmentInfo() return new TravisCIEnvironmentInfo(Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCICommandsFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCICommandsFixture.cs new file mode 100644 index 0000000000..8baf1b185a --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCICommandsFixture.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Commands; +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class WoodpeckerCICommandsFixture + { + public ICakeEnvironment Environment { get; set; } + public IFileSystem FileSystem { get; set; } + public WoodpeckerCIEnvironmentInfo WoodpeckerEnvironment { get; set; } + + public WoodpeckerCICommandsFixture() + { + Environment = FakeEnvironment.CreateUnixEnvironment(); + FileSystem = new FakeFileSystem(Environment); + + // Set up the WoodpeckerCI environment variables + ((FakeEnvironment)Environment).SetEnvironmentVariable("CI", "woodpecker"); + ((FakeEnvironment)Environment).SetEnvironmentVariable("CI_WORKSPACE", "/woodpecker/src/git.example.com/john-doe/my-repo"); + + WoodpeckerEnvironment = new WoodpeckerCIEnvironmentInfo(Environment); + + // Create the .woodpecker/env file for testing + var envFile = WoodpeckerEnvironment.Workspace?.CombineWithFilePath(".woodpecker/env"); + if (envFile != null) + { + // Create the directory structure first + var directory = envFile.GetDirectory(); + ((FakeFileSystem)FileSystem).CreateDirectory(directory); + ((FakeFileSystem)FileSystem).CreateFile(envFile); + } + } + + public WoodpeckerCICommands CreateWoodpeckerCICommands() + { + return new WoodpeckerCICommands(Environment, FileSystem, WoodpeckerEnvironment); + } + + public WoodpeckerCICommandsFixture WithNoWoodpeckerEnv() + { + var envFile = Environment.WorkingDirectory.CombineWithFilePath(".woodpecker/env"); + if (FileSystem.Exist(envFile)) + { + FileSystem.GetFile(envFile).Delete(); + } + return this; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCIInfoFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCIInfoFixture.cs new file mode 100644 index 0000000000..d343c3d1c3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/WoodpeckerCIInfoFixture.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + public class WoodpeckerCIInfoFixture + { + public ICakeEnvironment Environment { get; set; } + + public WoodpeckerCIInfoFixture() + { + Environment = Substitute.For(); + + // WoodpeckerCI Environment + Environment.GetEnvironmentVariable("CI").Returns("woodpecker"); + Environment.GetEnvironmentVariable("CI_WORKSPACE").Returns("/woodpecker/src/git.example.com/john-doe/my-repo"); + + // WoodpeckerCI RepositoryInfo + Environment.GetEnvironmentVariable("CI_REPO").Returns("john-doe/my-repo"); + Environment.GetEnvironmentVariable("CI_REPO_OWNER").Returns("john-doe"); + Environment.GetEnvironmentVariable("CI_REPO_NAME").Returns("my-repo"); + Environment.GetEnvironmentVariable("CI_REPO_REMOTE_ID").Returns("82"); + Environment.GetEnvironmentVariable("CI_REPO_URL").Returns("https://git.example.com/john-doe/my-repo"); + Environment.GetEnvironmentVariable("CI_REPO_CLONE_URL").Returns("https://git.example.com/john-doe/my-repo.git"); + Environment.GetEnvironmentVariable("CI_REPO_CLONE_SSH_URL").Returns("git@git.example.com:john-doe/my-repo.git"); + Environment.GetEnvironmentVariable("CI_REPO_DEFAULT_BRANCH").Returns("main"); + Environment.GetEnvironmentVariable("CI_REPO_PRIVATE").Returns("true"); + Environment.GetEnvironmentVariable("CI_REPO_TRUSTED_NETWORK").Returns("false"); + Environment.GetEnvironmentVariable("CI_REPO_TRUSTED_VOLUMES").Returns("false"); + Environment.GetEnvironmentVariable("CI_REPO_TRUSTED_SECURITY").Returns("false"); + + // WoodpeckerCI CommitInfo + Environment.GetEnvironmentVariable("CI_COMMIT_SHA").Returns("eba09b46064473a1d345da7abf28b477468e8dbd"); + Environment.GetEnvironmentVariable("CI_COMMIT_REF").Returns("refs/heads/main"); + Environment.GetEnvironmentVariable("CI_COMMIT_REFSPEC").Returns("issue-branch:main"); + Environment.GetEnvironmentVariable("CI_COMMIT_BRANCH").Returns("main"); + Environment.GetEnvironmentVariable("CI_COMMIT_SOURCE_BRANCH").Returns("issue-branch"); + Environment.GetEnvironmentVariable("CI_COMMIT_TARGET_BRANCH").Returns("main"); + Environment.GetEnvironmentVariable("CI_COMMIT_TAG").Returns("v1.10.3"); + Environment.GetEnvironmentVariable("CI_COMMIT_PULL_REQUEST").Returns("1"); + Environment.GetEnvironmentVariable("CI_COMMIT_PULL_REQUEST_LABELS").Returns("server"); + Environment.GetEnvironmentVariable("CI_COMMIT_MESSAGE").Returns("Initial commit"); + Environment.GetEnvironmentVariable("CI_COMMIT_AUTHOR").Returns("john-doe"); + Environment.GetEnvironmentVariable("CI_COMMIT_AUTHOR_EMAIL").Returns("john-doe@example.com"); + Environment.GetEnvironmentVariable("CI_COMMIT_PRERELEASE").Returns("false"); + + // WoodpeckerCI PipelineInfo + Environment.GetEnvironmentVariable("CI_PIPELINE_NUMBER").Returns("8"); + Environment.GetEnvironmentVariable("CI_PIPELINE_PARENT").Returns("0"); + Environment.GetEnvironmentVariable("CI_PIPELINE_EVENT").Returns("push"); + Environment.GetEnvironmentVariable("CI_PIPELINE_URL").Returns("https://ci.example.com/repos/john-doe/my-repo/pipeline/123"); + Environment.GetEnvironmentVariable("CI_PIPELINE_FORGE_URL").Returns("https://git.example.com/john-doe/my-repo/commit/abc123"); + Environment.GetEnvironmentVariable("CI_PIPELINE_DEPLOY_TARGET").Returns("production"); + Environment.GetEnvironmentVariable("CI_PIPELINE_DEPLOY_TASK").Returns("migration"); + Environment.GetEnvironmentVariable("CI_PIPELINE_CREATED").Returns("1722617519"); + Environment.GetEnvironmentVariable("CI_PIPELINE_STARTED").Returns("1722617519"); + Environment.GetEnvironmentVariable("CI_PIPELINE_FILES").Returns("[\".woodpecker.yml\",\"README.md\"]"); + Environment.GetEnvironmentVariable("CI_PIPELINE_AUTHOR").Returns("octocat"); + Environment.GetEnvironmentVariable("CI_PIPELINE_AVATAR").Returns("https://git.example.com/avatars/john-doe"); + + // WoodpeckerCI WorkflowInfo + Environment.GetEnvironmentVariable("CI_WORKFLOW_NAME").Returns("release"); + + // WoodpeckerCI StepInfo + Environment.GetEnvironmentVariable("CI_STEP_NAME").Returns("build package"); + Environment.GetEnvironmentVariable("CI_STEP_NUMBER").Returns("0"); + Environment.GetEnvironmentVariable("CI_STEP_STARTED").Returns("1722617519"); + Environment.GetEnvironmentVariable("CI_STEP_URL").Returns("https://ci.example.com/repos/7/pipeline/8"); + + // WoodpeckerCI SystemInfo + Environment.GetEnvironmentVariable("CI_SYSTEM_NAME").Returns("woodpecker"); + Environment.GetEnvironmentVariable("CI_SYSTEM_URL").Returns("https://ci.example.com"); + Environment.GetEnvironmentVariable("CI_SYSTEM_HOST").Returns("ci.example.com"); + Environment.GetEnvironmentVariable("CI_SYSTEM_VERSION").Returns("2.7.0"); + + // WoodpeckerCI ForgeInfo + Environment.GetEnvironmentVariable("CI_FORGE_TYPE").Returns("github"); + Environment.GetEnvironmentVariable("CI_FORGE_URL").Returns("https://git.example.com"); + } + + public WoodpeckerCIEnvironmentInfo CreateEnvironmentInfo() + { + return new WoodpeckerCIEnvironmentInfo(Environment); + } + + public WoodpeckerCIRepositoryInfo CreateRepositoryInfo() + { + return new WoodpeckerCIRepositoryInfo(Environment); + } + + public WoodpeckerCICommitInfo CreateCommitInfo() + { + return new WoodpeckerCICommitInfo(Environment); + } + + public WoodpeckerCIPipelineInfo CreatePipelineInfo() + { + return new WoodpeckerCIPipelineInfo(Environment); + } + + public WoodpeckerCIWorkflowInfo CreateWorkflowInfo() + { + return new WoodpeckerCIWorkflowInfo(Environment); + } + + public WoodpeckerCIStepInfo CreateStepInfo() + { + return new WoodpeckerCIStepInfo(Environment); + } + + public WoodpeckerCISystemInfo CreateSystemInfo() + { + return new WoodpeckerCISystemInfo(Environment); + } + + public WoodpeckerCIForgeInfo CreateForgeInfo() + { + return new WoodpeckerCIForgeInfo(Environment); + } + + public WoodpeckerCIInfoFixture SetEnvironmentVariable(string name, string value) + { + Environment.GetEnvironmentVariable(name).Returns(value); + return this; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Diagnostics/LogActionFixture.cs b/src/Cake.Common.Tests/Fixtures/Diagnostics/LogActionFixture.cs index 4fd88faa09..bd8824704b 100644 --- a/src/Cake.Common.Tests/Fixtures/Diagnostics/LogActionFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Diagnostics/LogActionFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Diagnostics; @@ -10,8 +11,8 @@ namespace Cake.Common.Tests.Fixtures.Diagnostics { internal sealed class LogActionFixture { - public string Format { get; private set; } - public object[] Args { get; private set; } + public string Format { get; } + public object[] Args { get; } public ICakeContext Context { get; set; } public bool Evaluated { get; private set; } @@ -19,7 +20,7 @@ public void Log(LogActionEntry actionEntry) { if (Evaluated) { - throw new Exception("Message allready evaluated"); + throw new Exception("Message already evaluated"); } Evaluated = true; actionEntry(Format, Args); @@ -36,4 +37,4 @@ public LogActionFixture(string format = "Hello {0}!", object[] args = null, Verb Context = context; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/IO/FileCopierFixture.cs b/src/Cake.Common.Tests/Fixtures/IO/FileCopierFixture.cs new file mode 100644 index 0000000000..1c19381102 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/IO/FileCopierFixture.cs @@ -0,0 +1,49 @@ +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.IO +{ + internal sealed class FileCopierFixture + { + private readonly FakeEnvironment _environment; + private readonly FakeFileSystem _fileSystem; + private readonly IGlobber _globber; + + public FileCopierFixture() + { + _environment = FakeEnvironment.CreateUnixEnvironment(); + _environment.WorkingDirectory = "/working"; + _fileSystem = new FakeFileSystem(_environment); + _globber = new Globber(_fileSystem, _environment); + Context = new CakeContextFixture { Environment = _environment, FileSystem = _fileSystem, Globber = _globber }.CreateContext(); + } + + public CakeContext Context { get; } + + public bool ExistsFile(FilePath path) + { + var file = _fileSystem.GetFile(path.MakeAbsolute(Context.Environment)); + return file != null; + } + + public FilePath MakeAbsolute(FilePath inputPath) + { + return inputPath.MakeAbsolute(Context.Environment); + } + + public void EnsureFileExists(FilePath filePath) + { + var fullPath = filePath.MakeAbsolute(Context.Environment); + _fileSystem.CreateFile(fullPath).SetContent(Guid.NewGuid().ToString("D")); + } + + public void EnsureDirectoryExists(DirectoryPath directoryPath) + { + var fullPath = directoryPath.MakeAbsolute(Context.Environment); + _fileSystem.CreateDirectory(fullPath); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/IO/FileCopyFixture.cs b/src/Cake.Common.Tests/Fixtures/IO/FileCopyFixture.cs index 6e9e38c8b0..6679838b62 100644 --- a/src/Cake.Common.Tests/Fixtures/IO/FileCopyFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/IO/FileCopyFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; @@ -37,7 +38,7 @@ public FileCopyFixture() // Setup the globber to return all files for wild card. Globber = Substitute.For(); - Globber.Match("*").Returns(c => TargetFilePaths); + Globber.Match("*", Arg.Any()).Returns(c => TargetFilePaths); // Setup the file system to return correct directories when asked for. FileSystem = Substitute.For(); @@ -73,4 +74,4 @@ private void CreateTargetFile(FilePath sourcePath, FilePath targetPath) TargetFilePaths.Add(targetPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/IO/FileDeleteFixture.cs b/src/Cake.Common.Tests/Fixtures/IO/FileDeleteFixture.cs index a70e076a67..fae287513f 100644 --- a/src/Cake.Common.Tests/Fixtures/IO/FileDeleteFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/IO/FileDeleteFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; @@ -10,8 +11,6 @@ namespace Cake.Common.Tests.Fixtures.IO { internal sealed class FileDeleteFixture { - private readonly List _files; - private readonly List _paths; private readonly Dictionary _lookup; public IFileSystem FileSystem { get; set; } @@ -19,28 +18,22 @@ internal sealed class FileDeleteFixture public ICakeContext Context { get; set; } public IGlobber Globber { get; set; } - public List Files - { - get { return _files; } - } + public List Files { get; } - public List Paths - { - get { return _paths; } - } + public List Paths { get; } public FileDeleteFixture() { // Setup the files in the file system. - _paths = new List(); - _files = new List(); + Paths = new List(); + Files = new List(); _lookup = new Dictionary(); - CreatFile("./file1.txt", "/Working/file1.txt"); - CreatFile("./file2.txt", "/Working/file2.txt"); + CreateFile("./file1.txt", "/Working/file1.txt"); + CreateFile("./file2.txt", "/Working/file2.txt"); // Setup the globber to return all files for wild card. Globber = Substitute.For(); - Globber.Match("*").Returns(c => _paths); + Globber.Match("*", Arg.Any()).Returns(c => Paths); // Setup the file system to return correct files when asked for. FileSystem = Substitute.For(); @@ -58,7 +51,7 @@ public FileDeleteFixture() Context.Globber.Returns(Globber); } - private void CreatFile(FilePath relativePath, FilePath absolutePath) + private void CreateFile(FilePath relativePath, FilePath absolutePath) { // Create the target file. var file = Substitute.For(); @@ -66,9 +59,9 @@ private void CreatFile(FilePath relativePath, FilePath absolutePath) file.Path.Returns(absolutePath); // Add to collections. - _paths.Add(relativePath); - _files.Add(file); + Paths.Add(relativePath); + Files.Add(file); _lookup.Add(absolutePath.FullPath, file); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs b/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs index 4d700ccb32..62fe44bd30 100644 --- a/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/IO/FileSystemFixture.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.IO; using Cake.Core; using Cake.Core.IO; using Cake.Testing; @@ -31,6 +33,7 @@ private static FakeFileSystem CreateFileSystem(ICakeEnvironment environment) fileSystem.CreateDirectory("/Temp/Hello/World"); fileSystem.CreateDirectory("/Temp/Goodbye"); fileSystem.CreateDirectory("/Temp/Hello/Hidden").Hide(); + fileSystem.CreateDirectory("/HasReadonly"); fileSystem.CreateFile("/Presentation.ppt"); fileSystem.CreateFile("/Budget.xlsx"); fileSystem.CreateFile("/Text.txt"); @@ -42,7 +45,10 @@ private static FakeFileSystem CreateFileSystem(ICakeEnvironment environment) fileSystem.CreateFile("/Temp/Goodbye/OtherText.txt"); fileSystem.CreateFile("/Temp/Goodbye/OtherPicture.png"); fileSystem.CreateFile("/Temp/HasFiles/A.txt"); + fileSystem.CreateFile("/HasReadonly/Readonly.txt", FileAttributes.ReadOnly); + fileSystem.CreateFile("/HasReadonly/Not-Readonly.txt"); + fileSystem.CreateFile("/HasReadonly/Hidden.txt").Hide(); return fileSystem; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/ProcessFixture.cs b/src/Cake.Common.Tests/Fixtures/ProcessFixture.cs index eb2eb1a673..108e840485 100644 --- a/src/Cake.Common.Tests/Fixtures/ProcessFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/ProcessFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.IO; using NSubstitute; @@ -54,4 +55,4 @@ public IProcess StartNewProcess(string filename, ProcessSettings settings) return Context.StartAndReturnProcess(filename, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Solution/Project/ProjectParserFixture.cs b/src/Cake.Common.Tests/Fixtures/Solution/Project/ProjectParserFixture.cs index cfbdc89975..2cc837dcd7 100644 --- a/src/Cake.Common.Tests/Fixtures/Solution/Project/ProjectParserFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Solution/Project/ProjectParserFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Solution.Project; using Cake.Common.Tests.Properties; using Cake.Core; @@ -51,4 +52,4 @@ public ProjectParserResult ParseIncomplete() public ICakeLog Log { get; set; } public FilePath ProjFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Solution/SolutionParserFixture.cs b/src/Cake.Common.Tests/Fixtures/Solution/SolutionParserFixture.cs new file mode 100644 index 0000000000..b42294d100 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Solution/SolutionParserFixture.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.Solution +{ + internal sealed class SolutionParserFixture + { + public FakeEnvironment Environment { get; set; } + + public FakeFileSystem FileSystem { get; set; } + + public SolutionParserFixture() + { + var environment = FakeEnvironment.CreateUnixEnvironment(); + Environment = environment; + var fileSystem = new FakeFileSystem(environment); + FileSystem = fileSystem; + } + + public FilePath WithSolutionFile(string slnContent) + { + var file = FileSystem.CreateFile("/Working/dummySolution.sln").SetContent(slnContent); + return file.Path; + } + + public FilePath WithXmlSolutionFile(string slnxContent) + { + var file = FileSystem.CreateFile("/Working/dummySolution.slnx").SetContent(slnxContent); + return file.Path; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/TextTransformationFixture.cs b/src/Cake.Common.Tests/Fixtures/TextTransformationFixture.cs index 12ca721584..3276222a80 100644 --- a/src/Cake.Common.Tests/Fixtures/TextTransformationFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/TextTransformationFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Text; using Cake.Core; using Cake.Core.Text; @@ -32,4 +33,4 @@ public TextTransformation CreateTextTransformation( FileSystem, Environment, TransformationTemplate); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/CakeRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/CakeRunnerFixture.cs index 41a91a5c92..924cb55937 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/CakeRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/CakeRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Cake; using Cake.Core.IO; using Cake.Testing; @@ -10,7 +11,7 @@ namespace Cake.Common.Tests.Fixtures.Tools { internal sealed class CakeRunnerFixture : ToolFixture { - public FilePath ScriptPath { get; set;} + public FilePath ScriptPath { get; set; } public CakeRunnerFixture() : base("Cake.exe") @@ -34,4 +35,4 @@ protected override void RunTool() runner.ExecuteScript(ScriptPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/CandleFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/CandleFixture.cs index 2f9db169d0..8ecff56cce 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/CandleFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/CandleFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.WiX; using Cake.Core.IO; @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(SourceFiles, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterFixture.cs index ef222103f6..f08403fa71 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterFixture.cs @@ -1,25 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.ApiKey; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey { internal sealed class ChocolateyApiKeySetterFixture : ChocolateyFixture { - public string ApiKey { get; set; } public string Source { get; set; } public ChocolateyApiKeySetterFixture() { Source = "source1"; - ApiKey = "apikey1"; } protected override void RunTool() { var tool = new ChocolateyApiKeySetter(FileSystem, Environment, ProcessRunner, Tools, Resolver); - tool.Set(ApiKey, Source, Settings); + tool.Set(Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ChocolateyFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ChocolateyFixture.cs index e4a19ad118..712fecba36 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ChocolateyFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/ChocolateyFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -33,4 +34,4 @@ protected ChocolateyFixture() Log = Substitute.For(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Config/ChocolateyConfigSetterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Config/ChocolateyConfigSetterFixture.cs index 01f763257f..c44c512428 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Config/ChocolateyConfigSetterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Config/ChocolateyConfigSetterFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Config; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Config @@ -22,4 +23,4 @@ protected override void RunTool() tool.Set(Name, Value, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Download/ChocolateyDownloadFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Download/ChocolateyDownloadFixture.cs new file mode 100644 index 0000000000..31e90862a0 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Download/ChocolateyDownloadFixture.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.Chocolatey.Download; + +namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Download +{ + internal sealed class ChocolateyDownloadFixture : ChocolateyFixture + { + public string PackageId { get; set; } + + public ChocolateyDownloadFixture() + { + PackageId = "MyPackage"; + } + + protected override void RunTool() + { + var tool = new ChocolateyDownloader(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.Download(PackageId, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Export/ChocolateyExportFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Export/ChocolateyExportFixture.cs new file mode 100644 index 0000000000..dbadd8771a --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Export/ChocolateyExportFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.Chocolatey.Export; + +namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Export +{ + internal sealed class ChocolateyExportFixture : ChocolateyFixture + { + public ChocolateyExportFixture() + { + } + + protected override void RunTool() + { + var tool = new ChocolateyExporter(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.Export(Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyDisableFeatureFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyDisableFeatureFixture.cs index 9c9a85c622..286d295b99 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyDisableFeatureFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyDisableFeatureFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Features; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Features @@ -13,4 +14,4 @@ protected override void RunTool() tool.DisableFeature(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyEnableFeatureFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyEnableFeatureFixture.cs index f51f4b53e7..e139cc1639 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyEnableFeatureFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyEnableFeatureFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Features; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Features @@ -13,4 +14,4 @@ protected override void RunTool() tool.EnableFeature(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyFeatureTogglerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyFeatureTogglerFixture.cs index 414e6c79e8..62c4800b84 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyFeatureTogglerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Features/ChocolateyFeatureTogglerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Features; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Features @@ -14,4 +15,4 @@ protected ChocolateyFeatureTogglerFixture() Name = "checkSumFiles"; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFixture.cs index 674f066642..20a5416e4b 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Install; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Installer @@ -20,4 +21,4 @@ protected override void RunTool() tool.Install(PackageId, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFromConfigFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFromConfigFixture.cs index 41ca8e8736..20c3916f7e 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFromConfigFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Installer/ChocolateyInstallerFromConfigFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Install; using Cake.Core.IO; @@ -21,4 +22,4 @@ protected override void RunTool() tool.InstallFromConfig(PackageConfigPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/New/ChocolateyNewFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/New/ChocolateyNewFixture.cs new file mode 100644 index 0000000000..f397fdd3b6 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/New/ChocolateyNewFixture.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.Chocolatey.New; + +namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.New +{ + internal sealed class ChocolateyNewFixture : ChocolateyFixture + { + public string PackageId { get; set; } + + public ChocolateyNewFixture() + { + PackageId = "MyPackage"; + } + + protected override void RunTool() + { + var tool = new ChocolateyScaffolder(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.CreatePackage(PackageId, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixture.cs index ea3aa6df70..9d1d6a1568 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Pack; using Cake.Core.IO; using Cake.Testing; @@ -24,4 +25,4 @@ protected override ChocolateyPackerFixtureResult CreateResult(FilePath path, Pro return new ChocolateyPackerFixtureResult(FileSystem, path, process); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixtureResult.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixtureResult.cs index 624ee22fa0..2f2738b927 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixtureResult.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerFixtureResult.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Linq; using Cake.Core; @@ -12,26 +13,21 @@ namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Packer { internal sealed class ChocolateyPackerFixtureResult : ToolFixtureResult { - private readonly string _nuspecContent; - - public string NuspecContent - { - get { return _nuspecContent; } - } + public string NuspecContent { get; } public ChocolateyPackerFixtureResult(FakeFileSystem fileSystem, FilePath path, ProcessSettings process) : base(path, process) { - _nuspecContent = GetNuSpecContent(fileSystem, process); + NuspecContent = GetNuSpecContent(fileSystem, process); } private static string GetNuSpecContent(FakeFileSystem fileSystem, ProcessSettings process) { var args = process.Arguments.Render(); var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - var last = parts.Last(); - var file = fileSystem.GetFile(last.UnQuote()); + var nuspecFilePath = parts[1]; + var file = fileSystem.GetFile(nuspecFilePath.UnQuote()); return file.Exists ? file.GetTextContent() : null; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithNuSpecFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithNuSpecFixture.cs index 10299e9a51..f457bfa7a5 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithNuSpecFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithNuSpecFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Tools.Chocolatey.Pack; using Cake.Core.IO; @@ -24,4 +25,4 @@ protected override void RunTool() tool.Pack(NuSpecFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithoutNuSpecFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithoutNuSpecFixture.cs index 2de6f4de01..d496d6f6d9 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithoutNuSpecFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Packer/ChocolateyPackerWithoutNuSpecFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Pack; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Packer @@ -13,4 +14,4 @@ protected override void RunTool() tool.Pack(Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Pin/ChocolateyPinnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Pin/ChocolateyPinnerFixture.cs index 3e03c36101..9e90db8e99 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Pin/ChocolateyPinnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Pin/ChocolateyPinnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Pin; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Pin @@ -20,4 +21,4 @@ protected override void RunTool() tool.Pin(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Push/ChocolateyPusherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Push/ChocolateyPusherFixture.cs index 4f7f385dea..d954f18ed1 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Push/ChocolateyPusherFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Push/ChocolateyPusherFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Push; using Cake.Core.IO; @@ -21,4 +22,4 @@ protected override void RunTool() tool.Push(PackageFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyAddSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyAddSourceFixture.cs index 21b30fd9cc..fe874a7a43 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyAddSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyAddSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Sources; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources @@ -20,4 +21,4 @@ protected override void RunTool() tool.AddSource(Name, Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyDisableSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyDisableSourceFixture.cs index 63555b230d..a226d55a02 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyDisableSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyDisableSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Sources; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.DisableSource(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyEnableSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyEnableSourceFixture.cs index 8ad841f097..486a5f54ff 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyEnableSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyEnableSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Sources; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.EnableSource(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyRemoveSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyRemoveSourceFixture.cs index 6e7745345b..af268c4ab2 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyRemoveSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateyRemoveSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Sources; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.RemoveSource(Name, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateySourcesFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateySourcesFixture.cs index 9e97158d55..b730efe06e 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateySourcesFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Sources/ChocolateySourcesFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Sources; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources @@ -14,4 +15,4 @@ protected ChocolateySourcesFixture() Name = "name"; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Uninstaller/ChocolateyUninstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Uninstaller/ChocolateyUninstallerFixture.cs new file mode 100644 index 0000000000..939ce43218 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Uninstaller/ChocolateyUninstallerFixture.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.Chocolatey.Uninstall; + +namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Uninstaller +{ + internal sealed class ChocolateyUninstallerFixture : ChocolateyFixture + { + public IEnumerable PackageIds { get; set; } + + public ChocolateyUninstallerFixture() + { + PackageIds = new[] { "Cake" }; + } + + protected override void RunTool() + { + var tool = new ChocolateyUninstaller(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.Uninstall(PackageIds, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Upgrade/ChocolateyUpgraderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Upgrade/ChocolateyUpgraderFixture.cs index 94baf17483..204cbdbe76 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Upgrade/ChocolateyUpgraderFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/Chocolatey/Upgrade/ChocolateyUpgraderFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Upgrade; namespace Cake.Common.Tests.Fixtures.Tools.Chocolatey.Upgrade @@ -20,4 +21,4 @@ protected override void RunTool() tool.Upgrade(PackageId, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs new file mode 100644 index 0000000000..39dbe064cb --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerFixture.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.Command; +using Cake.Core.IO; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerFixture : ToolFixture + { + public ProcessArgumentBuilder Arguments { get; set; } + + public string ToolName + { + get => Settings.ToolName; + set => Settings.ToolName = value; + } + + public ICollection ToolExecutableNames + { + get => Settings.ToolExecutableNames; + set => Settings.ToolExecutableNames = value; + } + + public CommandRunnerFixture() + : base("dotnet.exe") + { + Arguments = new ProcessArgumentBuilder(); + Settings.ToolName = "dotnet"; + Settings.ToolExecutableNames = new[] { "dotnet.exe", "dotnet" }; + } + + protected override void RunTool() + { + GetRunner().RunCommand(Arguments); + } + + protected CommandRunner GetRunner() + => new CommandRunner( + Settings, + FileSystem, + Environment, + ProcessRunner, + Tools); + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs new file mode 100644 index 0000000000..15944337cd --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardErrorFixture.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerStandardErrorFixture : CommandRunnerStandardOutputFixture + { + public string StandardError { get; private set; } + + protected override void RunTool() + { + ExitCode = GetRunner().RunCommand(Arguments, out var standardOutput, out var standardError); + StandardOutput = standardOutput; + StandardError = standardError; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs new file mode 100644 index 0000000000..048501dbbd --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutFixture.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal class CommandRunnerStandardOutputFixture : CommandRunnerFixture + { + public int ExitCode { get; protected set; } + public string StandardOutput { get; protected set; } + + protected override void RunTool() + { + ExitCode = GetRunner().RunCommand(Arguments, out var standardOutput); + StandardOutput = standardOutput; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.cs b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.cs new file mode 100644 index 0000000000..edf6cdb1f4 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/Command/CommandRunnerStandardOutputFixtureExtentions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tests.Fixtures.Tools.Command +{ + internal static class CommandRunnerStandardOutputFixtureExtentions + { + public static T GivenStandardOutput(this T fixture, params string[] standardOutput) + where T : CommandRunnerStandardOutputFixture + { + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + return fixture; + } + + public static T GivenStandardError(this T fixture, params string[] standardError) + where T : CommandRunnerStandardOutputFixture + { + fixture.ProcessRunner.Process.SetStandardError(standardError); + return fixture; + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Build/DNUBuilderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DNU/Build/DNUBuilderFixture.cs deleted file mode 100644 index 5c138b823e..0000000000 --- a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Build/DNUBuilderFixture.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DNU.Build; - -namespace Cake.Common.Tests.Fixtures.Tools.DNU.Build -{ - internal sealed class DNUBuilderFixture : DNUFixture - { - public string Path { get; set; } - - protected override void RunTool() - { - var tool = new DNUBuilder(FileSystem, Environment, ProcessRunner, Tools); - tool.Build(Path, Settings); - } - } -} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DNU/DNUFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DNU/DNUFixture.cs deleted file mode 100644 index 915a88e31f..0000000000 --- a/src/Cake.Common.Tests/Fixtures/Tools/DNU/DNUFixture.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Core.IO; -using Cake.Core.Tooling; -using Cake.Testing.Fixtures; - -namespace Cake.Common.Tests.Fixtures.Tools.DNU -{ - internal abstract class DNUFixture : DNUFixture - where TSettings : ToolSettings, new() - { - protected override ToolFixtureResult CreateResult(FilePath path, ProcessSettings process) - { - return new ToolFixtureResult(path, process); - } - } - - internal abstract class DNUFixture : ToolFixture - where TSettings : ToolSettings, new() - where TFixtureResult : ToolFixtureResult - { - protected DNUFixture() - : base("dnu.cmd") - { - ProcessRunner.Process.SetStandardOutput(new string[] { }); - } - } -} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Pack/DNUPackerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DNU/Pack/DNUPackerFixture.cs deleted file mode 100644 index 0bb23c4970..0000000000 --- a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Pack/DNUPackerFixture.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DNU.Pack; - -namespace Cake.Common.Tests.Fixtures.Tools.DNU.Pack -{ - internal sealed class DNUPackerFixture : DNUFixture - { - public string Path { get; set; } - - protected override void RunTool() - { - var tool = new DNUPacker(FileSystem, Environment, ProcessRunner, Tools); - tool.Pack(Path, Settings); - } - } -} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Restorer/DNURestorerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DNU/Restorer/DNURestorerFixture.cs deleted file mode 100644 index 12dba96222..0000000000 --- a/src/Cake.Common.Tests/Fixtures/Tools/DNU/Restorer/DNURestorerFixture.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DNU.Restore; -using Cake.Core.IO; - -namespace Cake.Common.Tests.Fixtures.Tools.DNU.Restorer -{ - internal sealed class DNURestorerFixture : DNUFixture - { - public FilePath FilePath { get; set; } - - protected override void RunTool() - { - var tool = new DNURestorer(FileSystem, Environment, ProcessRunner, Tools); - tool.Restore(FilePath, Settings); - } - } -} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Analyse/DotCoverAnalyserFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Analyse/DotCoverAnalyserFixture.cs index 6bdfcbea4d..2c35c0d02a 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Analyse/DotCoverAnalyserFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Analyse/DotCoverAnalyserFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.DotCover.Analyse; using Cake.Core; @@ -50,4 +51,4 @@ protected override void RunTool() tool.Analyse(Context, Action, OutputPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Cover/DotCoverCovererFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Cover/DotCoverCovererFixture.cs index a335edc68f..32fbcb0fd2 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Cover/DotCoverCovererFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Cover/DotCoverCovererFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.DotCover.Cover; using Cake.Core; @@ -50,4 +51,4 @@ protected override void RunTool() tool.Cover(Context, Action, OutputPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/DotCoverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/DotCoverFixture.cs index 77d2ad4b83..632fda82d6 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/DotCoverFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/DotCoverFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; using Cake.Testing.Fixtures; @@ -26,4 +27,4 @@ protected DotCoverFixture() ProcessRunner.Process.SetStandardOutput(new string[] { }); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Merge/DotCoverMergerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Merge/DotCoverMergerFixture.cs new file mode 100644 index 0000000000..8054a9c9b3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Merge/DotCoverMergerFixture.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotCover.Merge; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotCover.Merge +{ + internal sealed class DotCoverMergerFixture : DotCoverFixture + { + public IEnumerable SourceFiles { get; set; } + public FilePath OutputFile { get; set; } + + public DotCoverMergerFixture() + { + // Set the source files. + SourceFiles = new[] + { + new FilePath("./result1.dcvr"), + new FilePath("./result2.dcvr"), + }; + + // Setup the output file. + OutputFile = new FilePath("./result.dcvr"); + } + + protected override void RunTool() + { + var tool = new DotCoverMerger(FileSystem, Environment, ProcessRunner, Tools); + tool.Merge(SourceFiles, OutputFile, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Report/DotCoverReporterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Report/DotCoverReporterFixture.cs new file mode 100644 index 0000000000..b075379c78 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotCover/Report/DotCoverReporterFixture.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotCover.Report; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Tools.DotCover.Report +{ + internal sealed class DotCoverReporterFixture : DotCoverFixture + { + public FilePath SourceFile { get; set; } + public FilePath OutputFile { get; set; } + + public DotCoverReporterFixture() + { + // Set the source file. + SourceFile = new FilePath("./result.dcvr"); + + // Setup the output file. + OutputFile = new FilePath("./result.xml"); + } + + protected override void RunTool() + { + var tool = new DotCoverReporter(FileSystem, Environment, ProcessRunner, Tools); + tool.Report(SourceFile, OutputFile, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/DotNetFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/DotNetFixture.cs new file mode 100644 index 0000000000..3fa46ee699 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/DotNetFixture.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet +{ + internal abstract class DotNetFixture : ToolFixture + where TSettings : DotNetSettings, new() + { + protected DotNetFixture() + : base("dotnet.exe") + { + ProcessRunner.Process.SetStandardOutput(new string[] { }); + } + + protected override ToolFixtureResult CreateResult(FilePath path, ProcessSettings process) + { + return new ToolFixtureResult(path, process); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Format/DotNetFormatterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Format/DotNetFormatterFixture.cs new file mode 100644 index 0000000000..f7d678036d --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Format/DotNetFormatterFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Format; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Format +{ + internal sealed class DotNetFormatterFixture : DotNetFixture + { + public string Root { get; set; } + + public string Subcommand { get; set; } + + protected override void RunTool() + { + var tool = new DotNetFormatter(FileSystem, Environment, ProcessRunner, Tools); + tool.Format(Root, Subcommand, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs new file mode 100644 index 0000000000..7b38b4a55c --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/MSBuild/DotNetCoreMSBuildBuilderFixture.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild +{ + internal sealed class DotNetMSBuildBuilderFixture : DotNetFixture + { + public string Project { get; set; } + public Action> StandardOutputAction { get; set; } + + protected override void RunTool() + { + var tool = new DotNetMSBuildBuilder(FileSystem, Environment, ProcessRunner, Tools); + tool.Build(Project, Settings, StandardOutputAction); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs new file mode 100644 index 0000000000..2c2b1fa842 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Add/DotNetPackageAdderFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Package.Add; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Add +{ + internal sealed class DotNetPackageAdderFixture : DotNetFixture + { + public string PackageName { get; set; } + + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetPackageAdder(FileSystem, Environment, ProcessRunner, Tools); + tool.Add(PackageName, Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/List/DotNetPackageListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/List/DotNetPackageListerFixture.cs new file mode 100644 index 0000000000..6f0cd378a4 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/List/DotNetPackageListerFixture.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Package.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Package.List +{ + internal sealed class DotNetPackageListerFixture : DotNetFixture + { + public string Project { get; set; } + public DotNetPackageList Result { get; set; } + + public void GivenPackgeListResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "{", + " \"version\": 1,", + " \"parameters\": \"\",", + " \"projects\": [", + " {", + " \"path\": \"src/lib/MyProject.csproj\",", + " \"frameworks\": [", + " {", + " \"framework\": \"netstandard2.0\",", + " \"topLevelPackages\": [", + " {", + " \"id\": \"NETStandard.Library\",", + " \"requestedVersion\": \"[2.0.3, )\",", + " \"resolvedVersion\": \"2.0.3\",", + " \"autoReferenced\": \"true\"", + " }", + " ]", + " }", + " ]", + " }", + " ]", + "}" + }); + } + + protected override void RunTool() + { + var tool = new DotNetPackageLister(FileSystem, Environment, ProcessRunner, Tools); + Result = tool.List(Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Remove/DotNetPackageRemoverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Remove/DotNetPackageRemoverFixture.cs new file mode 100644 index 0000000000..4d259b0ae9 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Remove/DotNetPackageRemoverFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Package.Remove; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Remove +{ + internal sealed class DotNetPackageRemoverFixture : DotNetFixture + { + public string PackageName { get; set; } + + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetPackageRemover(FileSystem, Environment, ProcessRunner, Tools); + tool.Remove(PackageName, Project); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Search/DotNetPackageSearcherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Search/DotNetPackageSearcherFixture.cs new file mode 100644 index 0000000000..c443ed10f3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Package/Search/DotNetPackageSearcherFixture.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Package.Search; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Search +{ + internal class DotNetPackageSearcherFixture : DotNetFixture + { + public string SearchTerm { get; set; } + + public IEnumerable Result { get; private set; } + + protected override void RunTool() + { + var tool = new DotNetPackageSearcher(FileSystem, Environment, ProcessRunner, Tools); + Result = tool.Search(SearchTerm, Settings); + } + + internal void GivenNormalPackageResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "{", + " \"version\": 2,", + " \"problems\": [],", + " \"searchResult\": [", + " {", + " \"sourceName\": \"nuget.org\",", + " \"packages\": [", + " {", + " \"id\": \"Cake\",", + " \"latestVersion\": \"0.22.2\"", + " },", + " {", + " \"id\": \"Cake.Core\",", + " \"latestVersion\": \"0.22.2\"", + " },", + " {", + " \"id\": \"Cake.CoreCLR\",", + " \"latestVersion\": \"0.22.2\"", + " }", + " ]", + " }", + " ]", + "}", + }); + } + + /// + /// Sets standard output to exact-match format (uses "version" instead of "latestVersion" per package). + /// + internal void GivenExactMatchPackageResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "{", + " \"version\": 2,", + " \"problems\": [],", + " \"searchResult\": [", + " {", + " \"sourceName\": \"nuget.org\",", + " \"packages\": [", + " {", + " \"id\": \"Refit.Newtonsoft.Json\",", + " \"version\": \"7.0.0\"", + " },", + " {", + " \"id\": \"Refit.Newtonsoft.Json\",", + " \"version\": \"6.3.0\"", + " }", + " ]", + " }", + " ]", + "}", + }); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Add/DotNetReferenceAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Add/DotNetReferenceAdderFixture.cs new file mode 100644 index 0000000000..374db52523 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Add/DotNetReferenceAdderFixture.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Reference.Add; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.Add +{ + internal sealed class DotNetReferenceAdderFixture : DotNetFixture + { + public string Project { get; set; } + + public IEnumerable ProjectReferences { get; set; } + + protected override void RunTool() + { + var tool = new DotNetReferenceAdder(FileSystem, Environment, ProcessRunner, Tools); + tool.Add(Project, ProjectReferences, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/List/DotNetReferenceListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/List/DotNetReferenceListerFixture.cs new file mode 100644 index 0000000000..f78b860699 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/List/DotNetReferenceListerFixture.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Reference.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.List +{ + internal sealed class DotNetReferenceListerFixture : DotNetFixture + { + public string Project { get; set; } + + public IEnumerable References { get; set; } + + public void GivenProjectReferencesResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Project reference(s)", + "--------------------", + "..\\..\\Common\\Common.AspNetCore\\Common.AspNetCore.csproj", + "..\\..\\Common\\Common.Messaging\\Common.Messaging.csproj", + "..\\..\\Common\\Common.Utilities\\Common.Utilities.csproj" + }); + } + + public void GivenEmptyProjectReferencesResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "There are no Project to Project references in project C:\\Cake\\Cake.Core\\." + }); + } + + protected override void RunTool() + { + var tool = new DotNetReferenceLister(FileSystem, Environment, ProcessRunner, Tools); + References = tool.List(Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverFixture.cs new file mode 100644 index 0000000000..a08933f278 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverFixture.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Reference.Remove; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.Remove +{ + internal sealed class DotNetReferenceRemoverFixture : DotNetFixture + { + public string Project { get; set; } + + public IEnumerable ProjectReferences { get; set; } + + protected override void RunTool() + { + var tool = new DotNetReferenceRemover(FileSystem, Environment, ProcessRunner, Tools); + tool.Remove(Project, ProjectReferences, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/SDKCheck/DotNetSDKCheckerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/SDKCheck/DotNetSDKCheckerFixture.cs new file mode 100644 index 0000000000..bcbff5010e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/SDKCheck/DotNetSDKCheckerFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.SDKCheck; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.SDKCheck +{ + internal sealed class DotNetSDKCheckerFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetSDKChecker(FileSystem, Environment, ProcessRunner, Tools); + tool.Check(); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs new file mode 100644 index 0000000000..ede36264de --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Add/DotNetSlnAdderFixture.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Sln.Add; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.Add +{ + internal sealed class DotNetSlnAdderFixture : DotNetFixture + { + public FilePath Solution { get; set; } + + public IEnumerable ProjectPath { get; set; } + + protected override void RunTool() + { + var tool = new DotNetSlnAdder(FileSystem, Environment, ProcessRunner, Tools); + tool.Add(Solution, ProjectPath, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs new file mode 100644 index 0000000000..0fbdbe7d85 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/List/DotNetSlnListerFixture.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Sln.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.List +{ + internal sealed class DotNetSlnListerFixture : DotNetFixture + { + public string Solution { get; set; } + + public string StandardError { get; set; } + + public IEnumerable Projects { get; private set; } + + public void GivenProjectsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Project(s)", + "--------------------", + "Common\\Common.AspNetCore\\Common.AspNetCore.csproj", + "Common\\Common.Messaging\\Common.Messaging.csproj", + "Common\\Common.Utilities\\Common.Utilities.csproj" + }); + } + + public void GivenErrorResult() + { + ProcessRunner.Process.SetStandardError(new string[] + { + StandardError + }); + } + + protected override void RunTool() + { + var tool = new DotNetSlnLister(FileSystem, Environment, ProcessRunner, Tools); + Projects = tool.List(Solution, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Remove/DotNetSlnRemoverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Remove/DotNetSlnRemoverFixture.cs new file mode 100644 index 0000000000..6a0473f016 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Sln/Remove/DotNetSlnRemoverFixture.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Sln.Remove; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.Remove +{ + internal sealed class DotNetSlnRemoverFixture : DotNetFixture + { + public FilePath Solution { get; set; } + + public IEnumerable ProjectPath { get; set; } + + protected override void RunTool() + { + var tool = new DotNetSlnRemover(FileSystem, Environment, ProcessRunner, Tools); + tool.Remove(Solution, ProjectPath, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.cs new file mode 100644 index 0000000000..8a541e9c05 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Install/DotNetWorkloadInstallerFixture.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Install; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Install +{ + internal sealed class DotNetWorkloadInstallerFixture : DotNetFixture + { + public IEnumerable WorkloadIds { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadInstaller(FileSystem, Environment, ProcessRunner, Tools); + tool.Install(WorkloadIds, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs new file mode 100644 index 0000000000..241002d69e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/List/DotNetWorkloadListerFixture.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.List; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.List +{ + internal sealed class DotNetWorkloadListerFixture : DotNetFixture + { + public IEnumerable Workloads { get; set; } + + public void GivenInstalledWorkloadsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Installed Workload Ids Manifest Version Installation Source", + "--------------------------------------------------------------------------------------", + "maui-ios 6.0.312/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "maui-windows 6.0.312/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "android 32.0.301/6.0.300 VS 17.3.32804.467, VS 17.4.32804.182", + "", + "Use `dotnet workload search` to find additional workloads to install." + }); + } + + public void GivenEmptyInstalledWorkloadsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Installed Workload Ids Manifest Version Installation Source", + "---------------------------------------------------------------------", + "", + "Use `dotnet workload search` to find additional workloads to install." + }); + } + + protected override void RunTool() + { + var tool = new DotNetWorkloadLister(FileSystem, Environment, ProcessRunner, Tools); + Workloads = tool.List(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.cs new file mode 100644 index 0000000000..86d25af11c --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairerFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Workload.Repair; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Repair +{ + internal sealed class DotNetWorkloadRepairerFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetWorkloadRepairer(FileSystem, Environment, ProcessRunner, Tools); + tool.Repair(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs new file mode 100644 index 0000000000..4e8ef88acc --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorerFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Workload.Restore; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Restore +{ + internal sealed class DotNetWorkloadRestorerFixture : DotNetFixture + { + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadRestorer(FileSystem, Environment, ProcessRunner, Tools); + tool.Restore(Project, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Search/DotNetWorkloadSearcherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Search/DotNetWorkloadSearcherFixture.cs new file mode 100644 index 0000000000..66cd3fbe82 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Search/DotNetWorkloadSearcherFixture.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Search; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Search +{ + internal sealed class DotNetWorkloadSearcherFixture : DotNetFixture + { + public string SearchString { get; set; } + public IEnumerable Workloads { get; set; } + + public void GivenAvailableWorkloadsResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Workload ID Description", + "-----------------------------------------------------", + "maui .NET MAUI SDK for all platforms", + "maui-desktop .NET MAUI SDK for Desktop", + "maui-mobile .NET MAUI SDK for Mobile" + }); + } + + protected override void RunTool() + { + var tool = new DotNetWorkloadSearcher(FileSystem, Environment, ProcessRunner, Tools); + Workloads = tool.Search(SearchString, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.cs new file mode 100644 index 0000000000..745b70853b --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallerFixture.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Uninstall; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Uninstall +{ + internal sealed class DotNetWorkloadUninstallerFixture : DotNetFixture + { + public IEnumerable WorkloadIds { get; set; } + + protected override void RunTool() + { + var tool = new DotNetWorkloadUninstaller(FileSystem, Environment, ProcessRunner, Tools); + tool.Uninstall(WorkloadIds); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.cs new file mode 100644 index 0000000000..3dc3f1cec3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNet/Workload/Update/DotNetWorkloadUpdaterFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Workload.Update; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Update +{ + internal sealed class DotNetWorkloadUpdaterFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetWorkloadUpdater(FileSystem, Environment, ProcessRunner, Tools); + tool.Update(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Build/DotNetCoreBuilderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Build/DotNetCoreBuilderFixture.cs index 0bcb4a091f..48a05a95c2 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Build/DotNetCoreBuilderFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Build/DotNetCoreBuilderFixture.cs @@ -1,18 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Build; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Build +using Cake.Common.Tools.DotNet.Build; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Build { - internal sealed class DotNetCoreBuilderFixture : DotNetCoreFixture + internal sealed class DotNetBuilderFixture : DotNetFixture { public string Project { get; set; } protected override void RunTool() { - var tool = new DotNetCoreBuilder(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetBuilder(FileSystem, Environment, ProcessRunner, Tools); tool.Build(Project, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/BuildServer/DotNetCoreBuildServerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/BuildServer/DotNetCoreBuildServerFixture.cs new file mode 100644 index 0000000000..f4953279f4 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/BuildServer/DotNetCoreBuildServerFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.BuildServer; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Build +{ + internal sealed class DotNetBuildServerFixture : DotNetFixture + { + protected override void RunTool() + { + var tool = new DotNetBuildServer(FileSystem, Environment, ProcessRunner, Tools); + tool.Shutdown(Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Clean/DotNetCoreCleanerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Clean/DotNetCoreCleanerFixture.cs new file mode 100644 index 0000000000..25b0ceffe9 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Clean/DotNetCoreCleanerFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Clean; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Clean +{ + internal sealed class DotNetCleanerFixture : DotNetFixture + { + public string Project { get; set; } + + protected override void RunTool() + { + var tool = new DotNetCleaner(FileSystem, Environment, ProcessRunner, Tools); + tool.Clean(Project, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/DotNetCoreFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/DotNetCoreFixture.cs index 47e3615a7f..d766f63a47 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/DotNetCoreFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/DotNetCoreFixture.cs @@ -1,24 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore; -using Cake.Core.IO; -using Cake.Testing.Fixtures; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore +using Cake.Common.Tests.Fixtures.Tools.DotNet; +using Cake.Common.Tools.DotNet; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet { - internal abstract class DotNetCoreFixture : ToolFixture - where TSettings : DotNetCoreSettings, new() + internal abstract class DotNetCoreFixture : DotNetFixture + where TSettings : DotNetSettings, new() { - protected DotNetCoreFixture() - : base("dotnet.exe") - { - ProcessRunner.Process.SetStandardOutput(new string[] { }); - } - - protected override ToolFixtureResult CreateResult(FilePath path, ProcessSettings process) - { - return new ToolFixtureResult(path, process); - } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Execute/DotNetCoreExecutorFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Execute/DotNetCoreExecutorFixture.cs index cf96a266e0..021490b9c0 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Execute/DotNetCoreExecutorFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Execute/DotNetCoreExecutorFixture.cs @@ -1,21 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Execute; + +using Cake.Common.Tools.DotNet.Execute; using Cake.Core.IO; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Execute +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Execute { - internal sealed class DotNetCoreExecutorFixture : DotNetCoreFixture + internal sealed class DotNetExecutorFixture : DotNetFixture { public FilePath AssemblyPath { get; set; } - public string Arguments { get; set; } + public ProcessArgumentBuilder Arguments { get; set; } protected override void RunTool() { - var tool = new DotNetCoreExecutor(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetExecutor(FileSystem, Environment, ProcessRunner, Tools); tool.Execute(AssemblyPath, Arguments, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Delete/DotNetCoreNuGetDeleterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Delete/DotNetCoreNuGetDeleterFixture.cs new file mode 100644 index 0000000000..3a7c628992 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Delete/DotNetCoreNuGetDeleterFixture.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Delete; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Delete +{ + internal sealed class DotNetNuGetDeleterFixture : DotNetFixture + { + public string PackageName { get; set; } + public string PackageVersion { get; set; } + + protected override void RunTool() + { + var tool = new DotNetNuGetDeleter(FileSystem, Environment, ProcessRunner, Tools); + tool.Delete(PackageName, PackageVersion, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Push/DotNetCoreNuGetPusherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Push/DotNetCoreNuGetPusherFixture.cs new file mode 100644 index 0000000000..d21aa0840b --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Push/DotNetCoreNuGetPusherFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Push; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Push +{ + internal sealed class DotNetNuGetPusherFixture : DotNetFixture + { + public string PackageName { get; set; } + + protected override void RunTool() + { + var tool = new DotNetNuGetPusher(FileSystem, Environment, ProcessRunner, Tools); + tool.Push(PackageName, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetAddSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetAddSourceFixture.cs new file mode 100644 index 0000000000..838e4952e1 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetAddSourceFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetAddSourceFixture : DotNetNuGetSourcerFixture + { + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.AddSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetDisableSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetDisableSourceFixture.cs new file mode 100644 index 0000000000..29af1633fe --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetDisableSourceFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetDisableSourceFixture : DotNetNuGetSourcerFixture + { + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.DisableSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetEnableSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetEnableSourceFixture.cs new file mode 100644 index 0000000000..595917b58a --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetEnableSourceFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetEnableSourceFixture : DotNetNuGetSourcerFixture + { + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.EnableSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetHasSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetHasSourceFixture.cs new file mode 100644 index 0000000000..1ae02308e7 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetHasSourceFixture.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetHasSourceFixture : DotNetNuGetSourcerFixture + { + public bool HasSource { get; set; } + + public void GivenProcessOutput(string[] output) + { + ProcessRunner.Process.SetStandardOutput(output); + } + + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + HasSource = tool.HasSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetListSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetListSourceFixture.cs new file mode 100644 index 0000000000..94fc2eb762 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetListSourceFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetListSourceFixture : DotNetNuGetSourcerFixture + { + public string Format { get; set; } + + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.ListSource(Format, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetRemoveSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetRemoveSourceFixture.cs new file mode 100644 index 0000000000..e143281f97 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetRemoveSourceFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetRemoveSourceFixture : DotNetNuGetSourcerFixture + { + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.RemoveSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetSourcerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetSourcerFixture.cs new file mode 100644 index 0000000000..294b7b5ba0 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetSourcerFixture.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal abstract class DotNetNuGetSourcerFixture : DotNetFixture + { + public string Name { get; set; } = "name"; + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetUpdateSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetUpdateSourceFixture.cs new file mode 100644 index 0000000000..0fe3243f98 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/NuGet/Source/DotNetCoreNuGetUpdateSourceFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.NuGet.Source; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source +{ + internal sealed class DotNetNuGetUpdateSourceFixture : DotNetNuGetSourcerFixture + { + protected override void RunTool() + { + var tool = new DotNetNuGetSourcer(FileSystem, Environment, ProcessRunner, Tools); + tool.UpdateSource(Name, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Pack/DotNetCorePackerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Pack/DotNetCorePackerFixture.cs index 89f9777b95..760d22269c 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Pack/DotNetCorePackerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Pack/DotNetCorePackerFixture.cs @@ -1,18 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Pack; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Pack +using Cake.Common.Tools.DotNet.Pack; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Pack { - internal sealed class DotNetCorePackFixture : DotNetCoreFixture + internal sealed class DotNetPackFixture : DotNetFixture { public string Project { get; set; } protected override void RunTool() { - var tool = new DotNetCorePacker(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetPacker(FileSystem, Environment, ProcessRunner, Tools); tool.Pack(Project, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Publish/DotNetCorePublisherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Publish/DotNetCorePublisherFixture.cs index a6e343377d..df8cf1103e 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Publish/DotNetCorePublisherFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Publish/DotNetCorePublisherFixture.cs @@ -1,18 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Publish; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Publish +using Cake.Common.Tools.DotNet.Publish; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Publish { - internal sealed class DotNetCorePublisherFixture : DotNetCoreFixture + internal sealed class DotNetPublisherFixture : DotNetFixture { public string Project { get; set; } protected override void RunTool() { - var tool = new DotNetCorePublisher(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetPublisher(FileSystem, Environment, ProcessRunner, Tools); tool.Publish(Project, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Restore/DotNetCoreRestorerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Restore/DotNetCoreRestorerFixture.cs index afb59df9ea..5a834e83ab 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Restore/DotNetCoreRestorerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Restore/DotNetCoreRestorerFixture.cs @@ -1,18 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Restore; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Restore +using Cake.Common.Tools.DotNet.Restore; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Restore { - internal sealed class DotNetCoreRestorerFixture : DotNetCoreFixture + internal sealed class DotNetRestorerFixture : DotNetFixture { public string Root { get; set; } protected override void RunTool() { - var tool = new DotNetCoreRestorer(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetRestorer(FileSystem, Environment, ProcessRunner, Tools, new FakeLog()); tool.Restore(Root, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Run/DotNetCoreRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Run/DotNetCoreRunnerFixture.cs index 27ee2fc057..576b995a05 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Run/DotNetCoreRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Run/DotNetCoreRunnerFixture.cs @@ -1,20 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Run; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Run +using Cake.Common.Tools.DotNet.Run; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Run { - internal sealed class DotNetCoreRunnerFixture : DotNetCoreFixture + internal sealed class DotNetRunnerFixture : DotNetFixture { public string Project { get; set; } - public string Arguments { get; set; } + public ProcessArgumentBuilder Arguments { get; set; } protected override void RunTool() { - var tool = new DotNetCoreRunner(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DotNetRunner(FileSystem, Environment, ProcessRunner, Tools); tool.Run(Project, Arguments, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Test/DotNetCoreTesterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Test/DotNetCoreTesterFixture.cs index 28d681e712..2a91cbb885 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Test/DotNetCoreTesterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Test/DotNetCoreTesterFixture.cs @@ -1,18 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tools.DotNetCore.Test; -namespace Cake.Common.Tests.Fixtures.Tools.DotNetCore.Test +using Cake.Common.Tools.DotNet.Test; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Test { - internal sealed class DotNetCoreTesterFixture : DotNetCoreFixture + internal sealed class DotNetTesterFixture : DotNetFixture { public string Project { get; set; } + public ProcessArgumentBuilder Arguments { get; set; } + protected override void RunTool() { - var tool = new DotNetCoreTester(FileSystem, Environment, ProcessRunner, Tools); - tool.Test(Project, Settings); + var tool = new DotNetTester(FileSystem, Environment, ProcessRunner, Tools); + tool.Test(Project, Arguments, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Tool/DotNetCoreToolFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Tool/DotNetCoreToolFixture.cs new file mode 100644 index 0000000000..5234a8df29 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/Tool/DotNetCoreToolFixture.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.Tool; +using Cake.Core.IO; +using Cake.Testing; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.Tool +{ + internal sealed class DotNetToolFixture : DotNetFixture + { + public FilePath ProjectPath { get; set; } + + public string Command { get; set; } + + public ProcessArgumentBuilder Arguments { get; set; } + + protected override void RunTool() + { + var tool = new DotNetToolRunner(FileSystem, Environment, ProcessRunner, Tools); + + tool.Execute(ProjectPath, Command, Arguments, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/VSTest/DotNetCoreVSTesterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/VSTest/DotNetCoreVSTesterFixture.cs new file mode 100644 index 0000000000..f66e769db8 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/DotNetCore/VSTest/DotNetCoreVSTesterFixture.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.VSTest; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.DotNet.VSTest +{ + internal sealed class DotNetVSTesterFixture : DotNetFixture + { + public ICollection TestFiles { get; set; } + + protected override void RunTool() + { + var tool = new DotNetVSTester(FileSystem, Environment, ProcessRunner, Tools); + tool.Test(TestFiles, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerConfigFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerConfigFixture.cs index 3aecab38e6..1730be2f1f 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerConfigFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerConfigFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.DupFinder; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -28,4 +29,4 @@ protected override void RunTool() tool.RunFromConfig(ConfigPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerFixture.cs index a1604c322d..9165d41289 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/DupFinder/DupFinderRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tests.Properties; using Cake.Common.Tools.DupFinder; @@ -36,4 +37,4 @@ protected override void RunTool() tool.Run(FilePaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/FixieRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/FixieRunnerFixture.cs index 3c7bcc3bea..781a0fd7b4 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/FixieRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/FixieRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.Fixie; using Cake.Core.IO; @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitLink3Fixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitLink3Fixture.cs new file mode 100644 index 0000000000..bfa76f7926 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitLink3Fixture.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.GitLink; +using Cake.Core.IO; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + internal sealed class GitLink3Fixture : ToolFixture + { + public FilePath PdbFilePath { get; set; } + + public GitLink3Fixture() + : base("gitlink.exe") + { + PdbFilePath = new FilePath("c:/temp/my.pdb"); + } + + protected override void RunTool() + { + var tool = new GitLink3Runner(FileSystem, Environment, ProcessRunner, Tools); + tool.Run(PdbFilePath, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitLinkFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitLinkFixture.cs index f026e87b04..d6e2e135b7 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitLinkFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitLinkFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitLink; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -23,4 +24,4 @@ protected override void RunTool() tool.Run(RepositoryRootPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerAssetsAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerAssetsAdderFixture.cs index d7c0c5a6d6..84f2d00811 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerAssetsAdderFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerAssetsAdderFixture.cs @@ -1,14 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseManager.AddAssets; namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager { internal sealed class GitReleaseManagerAssetsAdderFixture : GitReleaseManagerFixture { + private bool _useToken = false; + public string UserName { get; set; } public string Password { get; set; } + + public string Token { get; set; } public string Owner { get; set; } public string Repository { get; set; } public string TagName { get; set; } @@ -18,16 +23,30 @@ public GitReleaseManagerAssetsAdderFixture() { UserName = "bob"; Password = "password"; + Token = "token"; Owner = "repoOwner"; Repository = "repo"; TagName = "0.1.0"; Assets = @"/temp/asset1.txt"; } + public void UseToken() + { + _useToken = true; + } + protected override void RunTool() { var tool = new GitReleaseManagerAssetsAdder(FileSystem, Environment, ProcessRunner, Tools); - tool.AddAssets(UserName, Password, Owner, Repository, TagName, Assets, Settings); + + if (_useToken) + { + tool.AddAssets(Token, Owner, Repository, TagName, Assets, Settings); + } + else + { + tool.AddAssets(UserName, Password, Owner, Repository, TagName, Assets, Settings); + } } } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerCreatorFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerCreatorFixture.cs index 3e790818f3..323ac1b419 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerCreatorFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerCreatorFixture.cs @@ -1,14 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseManager.Create; namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager { internal sealed class GitReleaseManagerCreatorFixture : GitReleaseManagerFixture { + private bool _useToken = false; + public string UserName { get; set; } public string Password { get; set; } + public string Token { get; set; } public string Owner { get; set; } public string Repository { get; set; } @@ -16,14 +20,28 @@ public GitReleaseManagerCreatorFixture() { UserName = "bob"; Password = "password"; + Token = "token"; Owner = "repoOwner"; Repository = "repo"; } + public void UseToken() + { + _useToken = true; + } + protected override void RunTool() { var tool = new GitReleaseManagerCreator(FileSystem, Environment, ProcessRunner, Tools); - tool.Create(UserName, Password, Owner, Repository, Settings); + + if (_useToken) + { + tool.Create(Token, Owner, Repository, Settings); + } + else + { + tool.Create(UserName, Password, Owner, Repository, Settings); + } } } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerDiscarderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerDiscarderFixture.cs new file mode 100644 index 0000000000..89235b8c9a --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerDiscarderFixture.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.GitReleaseManager.Discard; + +namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager +{ + internal sealed class GitReleaseManagerDiscarderFixture : GitReleaseManagerFixture + { + public string Token { get; set; } + public string Owner { get; set; } + public string Repository { get; set; } + + public string Milestone { get; set; } + + public GitReleaseManagerDiscarderFixture() + { + Token = "token"; + Owner = "repoOwner"; + Repository = "repo"; + Milestone = "0.1.0"; + } + + protected override void RunTool() + { + var tool = new GitReleaseManagerDiscarder(FileSystem, Environment, ProcessRunner, Tools); + + tool.Discard(Token, Owner, Repository, Milestone, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerExporterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerExporterFixture.cs index 8c24b61140..370bbb543a 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerExporterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerExporterFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseManager.Export; using Cake.Core.IO; @@ -8,8 +9,11 @@ namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager { internal sealed class GitReleaseManagerExporterFixture : GitReleaseManagerFixture { + private bool _useToken = false; + public string UserName { get; set; } public string Password { get; set; } + public string Token { get; set; } public string Owner { get; set; } public string Repository { get; set; } public FilePath FileOutputPath { get; set; } @@ -18,15 +22,29 @@ public GitReleaseManagerExporterFixture() { UserName = "bob"; Password = "password"; + Token = "token"; Owner = "repoOwner"; Repository = "repo"; FileOutputPath = "/temp"; } + public void UseToken() + { + _useToken = true; + } + protected override void RunTool() { var tool = new GitReleaseManagerExporter(FileSystem, Environment, ProcessRunner, Tools); - tool.Export(UserName, Password, Owner, Repository, FileOutputPath, Settings); + + if (_useToken) + { + tool.Export(Token, Owner, Repository, FileOutputPath, Settings); + } + else + { + tool.Export(UserName, Password, Owner, Repository, FileOutputPath, Settings); + } } } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerLabellerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerLabellerFixture.cs new file mode 100644 index 0000000000..3d8a8e39c9 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerLabellerFixture.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.GitReleaseManager.Label; + +namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager +{ + internal sealed class GitReleaseManagerLabellerFixture : GitReleaseManagerFixture + { + private bool _useToken = false; + + public string UserName { get; set; } + public string Password { get; set; } + public string Token { get; set; } + public string Owner { get; set; } + public string Repository { get; set; } + + public GitReleaseManagerLabellerFixture() + { + UserName = "bob"; + Password = "password"; + Token = "token"; + Owner = "repoOwner"; + Repository = "repo"; + } + + public void UseToken() + { + _useToken = true; + } + + protected override void RunTool() + { + var tool = new GitReleaseManagerLabeller(FileSystem, Environment, ProcessRunner, Tools); + + if (_useToken) + { + tool.Label(Token, Owner, Repository, Settings); + } + else + { + tool.Label(UserName, Password, Owner, Repository, Settings); + } + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneCloserFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneCloserFixture.cs index 3b58bac5c7..eaf63d2bf0 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneCloserFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneCloserFixture.cs @@ -1,14 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseManager.Close; namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager { internal sealed class GitReleaseManagerMilestoneCloserFixture : GitReleaseManagerFixture { + private bool _useToken = false; + public string UserName { get; set; } public string Password { get; set; } + public string Token { get; set; } public string Owner { get; set; } public string Repository { get; set; } public string Milestone { get; set; } @@ -17,15 +21,29 @@ public GitReleaseManagerMilestoneCloserFixture() { UserName = "bob"; Password = "password"; + Token = "token"; Owner = "repoOwner"; Repository = "repo"; Milestone = "0.1.0"; } + public void UseToken() + { + _useToken = true; + } + protected override void RunTool() { var tool = new GitReleaseManagerMilestoneCloser(FileSystem, Environment, ProcessRunner, Tools); - tool.Close(UserName, Password, Owner, Repository, Milestone, Settings); + + if (_useToken) + { + tool.Close(Token, Owner, Repository, Milestone, Settings); + } + else + { + tool.Close(UserName, Password, Owner, Repository, Milestone, Settings); + } } } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneOpenerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneOpenerFixture.cs new file mode 100644 index 0000000000..b7494ec7f0 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerMilestoneOpenerFixture.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.GitReleaseManager.Open; + +namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager +{ + internal sealed class GitReleaseManagerMilestoneOpenerFixture : GitReleaseManagerFixture + { + public string Token { get; set; } + public string Owner { get; set; } + public string Repository { get; set; } + public string Milestone { get; set; } + + public GitReleaseManagerMilestoneOpenerFixture() + { + Token = "token"; + Owner = "repoOwner"; + Repository = "repo"; + Milestone = "0.1.0"; + } + + protected override void RunTool() + { + var tool = new GitReleaseManagerMilestoneOpener(FileSystem, Environment, ProcessRunner, Tools); + + tool.Open(Token, Owner, Repository, Milestone, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerPublisherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerPublisherFixture.cs index 064fd0d26c..5fdecdd353 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerPublisherFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManager/GitReleaseManagerPublisherFixture.cs @@ -1,14 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseManager.Publish; namespace Cake.Common.Tests.Fixtures.Tools.GitReleaseManager { internal sealed class GitReleaseManagerPublisherFixture : GitReleaseManagerFixture { + private bool _useToken = false; + public string UserName { get; set; } public string Password { get; set; } + public string Token { get; set; } public string Owner { get; set; } public string Repository { get; set; } public string TagName { get; set; } @@ -17,15 +21,29 @@ public GitReleaseManagerPublisherFixture() { UserName = "bob"; Password = "password"; + Token = "token"; Owner = "repoOwner"; Repository = "repo"; TagName = "0.1.0"; } + public void UseToken() + { + _useToken = true; + } + protected override void RunTool() { var tool = new GitReleaseManagerPublisher(FileSystem, Environment, ProcessRunner, Tools); - tool.Publish(UserName, Password, Owner, Repository, TagName, Settings); + + if (_useToken) + { + tool.Publish(Token, Owner, Repository, TagName, Settings); + } + else + { + tool.Publish(UserName, Password, Owner, Repository, TagName, Settings); + } } } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManagerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManagerFixture.cs index 94d332c69d..45c6dffed0 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManagerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseManagerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Diagnostics; using Cake.Core.Tooling; using Cake.Testing.Fixtures; @@ -19,4 +20,4 @@ protected GitReleaseManagerFixture() Log = Substitute.For(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseNotesRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseNotesRunnerFixture.cs index 2f4725a391..3ecdfc031a 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseNotesRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitReleaseNotesRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.GitReleaseNotes; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -9,7 +10,7 @@ namespace Cake.Common.Tests.Fixtures.Tools { internal sealed class GitReleaseNotesRunnerFixture : ToolFixture { - public FilePath OutputFile; + public FilePath OutputFile { get; set; } public GitReleaseNotesRunnerFixture() : base("GitReleaseNotes.exe") @@ -23,4 +24,4 @@ protected override void RunTool() tool.Run(OutputFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/GitVersionRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/GitVersionRunnerFixture.cs index 69108214fa..95a77fbafb 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/GitVersionRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/GitVersionRunnerFixture.cs @@ -1,13 +1,12 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; -using System.IO; using Cake.Common.Tools.GitVersion; using Cake.Core.Diagnostics; -using NSubstitute; -using System.Runtime.Serialization.Json; using Cake.Testing.Fixtures; +using NSubstitute; namespace Cake.Common.Tests.Fixtures.Tools { @@ -15,38 +14,43 @@ internal sealed class GitVersionRunnerFixture : ToolFixture { public ICakeLog Log { get; set; } - public GitVersionRunnerFixture() + public GitVersionRunnerFixture(ICollection standardOutput = null) : base("GitVersion.exe") { - var resultJson = new GitVersion + if (standardOutput == null) { - Major = 1, - Minor = 0, - Patch = 0 - }; - - var serializer = new DataContractJsonSerializer(typeof(GitVersion)); - using (var memoryStream = new MemoryStream()) - using (var reader = new StreamReader(memoryStream)) - { - serializer.WriteObject(memoryStream, resultJson); - memoryStream.Position = 0; - var output = new List(); - while (!reader.EndOfStream) - { - output.Add(reader.ReadLine()); - } - - ProcessRunner.Process.SetStandardOutput(output); + // Minimal GitVersion-style JSON (string values match GitVersion CLI output). + standardOutput = + [ + """ + { + "Major":"1", + "Minor":"0", + "Patch":"0" + } + """ + ]; } + ProcessRunner.Process.SetStandardOutput(standardOutput); Log = Substitute.For(); + Log.Verbosity = Verbosity.Normal; + } + + public void SetLogVerbosity(Verbosity verbosity) + { + Log.Verbosity = verbosity; } protected override void RunTool() + { + RunGitVersion(); + } + + public GitVersion RunGitVersion() { var tool = new GitVersionRunner(FileSystem, Environment, ProcessRunner, Tools, Log); - tool.Run(Settings); + return tool.Run(Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/ILMergeRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/ILMergeRunnerFixture.cs index 2c897cf400..be19d64ddb 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/ILMergeRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/ILMergeRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.ILMerge; using Cake.Core.IO; @@ -28,4 +29,4 @@ protected override void RunTool() runner.Merge(OutputAssemblyPath, PrimaryAssemblyPath, AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/ILRepackRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/ILRepackRunnerFixture.cs index ee02013470..60c16e4ae2 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/ILRepackRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/ILRepackRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.ILRepack; using Cake.Core.IO; @@ -28,4 +29,4 @@ protected override void RunTool() runner.Merge(OutputAssemblyPath, PrimaryAssemblyPath, AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/InnoSetupFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/InnoSetupFixture.cs new file mode 100644 index 0000000000..e3ba25a4bf --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/InnoSetupFixture.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.InnoSetup; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Fixtures; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + internal sealed class InnoSetupFixture : ToolFixture + { + public IRegistry Registry { get; set; } + + public FilePath ScriptPath { get; set; } + + public Dictionary InstalledToolPaths { get; private set; } + + public InnoSetupFixture() + : base("iscc.exe") + { + ScriptPath = new FilePath("./Test.iss"); + InstalledToolPaths = new Dictionary(); + Registry = Substitute.For(); + var hklm = Substitute.For(); + Registry.LocalMachine.Returns(hklm); + } + + public void GivenToolIsInstalled(bool is64Bit, InnoSetupVersion version) + { + Environment.Platform.Is64Bit = is64Bit; + ConfigureInstalledLocation(is64Bit, version); + } + + protected override void RunTool() + { + var tool = new InnoSetupRunner(FileSystem, Registry, Environment, ProcessRunner, Tools); + tool.Run(ScriptPath, Settings); + } + + private void ConfigureInstalledLocation(bool is64Bit, InnoSetupVersion version) + { + var registryKeyPath = GetRegistryKeyPath(is64Bit, version); + var installLocation = GetInstallLocation(is64Bit, version); + + var installedToolPath = installLocation.CombineWithFilePath("iscc.exe"); + InstalledToolPaths.Add(version, installLocation.CombineWithFilePath("iscc.exe")); + + var innoSetupKey = Substitute.For(); + innoSetupKey.GetValue("InstallLocation").Returns(installLocation.FullPath); + + Registry.LocalMachine.OpenKey(registryKeyPath).Returns(innoSetupKey); + + FileSystem.CreateDirectory(installLocation); + FileSystem.CreateFile(installedToolPath); + } + + private static string GetRegistryKeyPath(bool is64Bit, InnoSetupVersion version) + { + var softwareKeyPath = is64Bit ? @"SOFTWARE\Wow6432Node\" : @"SOFTWARE\"; + switch (version) + { + case InnoSetupVersion.InnoSetup6: + return $@"{softwareKeyPath}Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1"; + case InnoSetupVersion.InnoSetup5: + return $@"{softwareKeyPath}Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 5_is1"; + default: + return null; + } + } + + private static DirectoryPath GetInstallLocation(bool is64Bit, InnoSetupVersion version) + { + var programFiles = is64Bit ? "/Program Files (x86)/" : "/Program Files/"; + switch (version) + { + case InnoSetupVersion.InnoSetup6: + return $@"{programFiles}Inno Setup 6"; + case InnoSetupVersion.InnoSetup5: + return $@"{programFiles}Inno Setup 5"; + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeFixture.cs index 664a9dac0d..c8c5272783 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.InspectCode; using Cake.Testing.Fixtures; @@ -8,9 +9,9 @@ namespace Cake.Common.Tests.Fixtures.Tools.InspectCode { internal abstract class InspectCodeFixture : ToolFixture { - protected InspectCodeFixture() - : base("inspectcode.exe") + protected InspectCodeFixture(bool useX86) + : base(useX86 ? "inspectcode.x86.exe" : "inspectcode.exe") { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFixture.cs index 3ab765f9d5..e17d4b0ff9 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Tools.InspectCode; using Cake.Core; @@ -16,7 +17,7 @@ internal sealed class InspectCodeRunFixture : InspectCodeFixture public ICakeLog Log { get; set; } public FilePath Solution { get; set; } - public InspectCodeRunFixture() + public InspectCodeRunFixture(bool useX86 = false) : base(useX86) { Solution = new FilePath("./Test.sln"); @@ -32,4 +33,4 @@ protected override void RunTool() tool.Run(Solution, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFromConfigFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFromConfigFixture.cs index 72c6685f33..b48d468b2a 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFromConfigFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/InspectCode/InspectCodeRunFromConfigFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.InspectCode; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -13,7 +14,7 @@ internal sealed class InspectCodeRunFromConfigFixture : InspectCodeFixture public ICakeLog Log { get; set; } public FilePath Config { get; set; } - public InspectCodeRunFromConfigFixture() + public InspectCodeRunFromConfigFixture(bool useX86 = false) : base(useX86) { Log = Substitute.For(); } @@ -24,4 +25,4 @@ protected override void RunTool() tool.RunFromConfig(Config); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs index 72287d97c0..5a96cf326d 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/MSBuildRunnerFixture.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Common.Tools.MSBuild; +using Cake.Core; using Cake.Core.IO; using Cake.Testing; using Cake.Testing.Fixtures; @@ -11,24 +14,37 @@ namespace Cake.Common.Tests.Fixtures.Tools { internal sealed class MSBuildRunnerFixture : ToolFixture { - public HashSet KnownMSBuildPaths { get; private set; } + public HashSet KnownMSBuildPaths { get; } public FilePath Solution { get; set; } + public Action> StandardOutputAction { get; set; } - public MSBuildRunnerFixture(bool is64BitOperativeSystem) + public MSBuildRunnerFixture(bool is64BitOperativeSystem, PlatformFamily platformFamily) : base("MSBuild.exe") { // Create the list of all known MSBuild paths. - KnownMSBuildPaths = new HashSet(new PathComparer(false)); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework/v2.0.50727/MSBuild.exe"); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework64/v2.0.50727/MSBuild.exe"); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework/v3.5/MSBuild.exe"); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework64/v3.5/MSBuild.exe"); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe"); - KnownMSBuildPaths.Add("/Windows/Microsoft.NET/Framework64/v4.0.30319/MSBuild.exe"); - KnownMSBuildPaths.Add("/Program86/MSBuild/12.0/Bin/MSBuild.exe"); - KnownMSBuildPaths.Add("/Program86/MSBuild/12.0/Bin/amd64/MSBuild.exe"); - KnownMSBuildPaths.Add("/Program86/MSBuild/14.0/Bin/MSBuild.exe"); - KnownMSBuildPaths.Add("/Program86/MSBuild/14.0/Bin/amd64/MSBuild.exe"); + KnownMSBuildPaths = new HashSet(new PathComparer(false)) + { + "/Windows/Microsoft.NET/Framework/v2.0.50727/MSBuild.exe", + "/Windows/Microsoft.NET/Framework64/v2.0.50727/MSBuild.exe", + "/Windows/Microsoft.NET/Framework/v3.5/MSBuild.exe", + "/Windows/Microsoft.NET/Framework64/v3.5/MSBuild.exe", + "/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe", + "/Windows/Microsoft.NET/Framework64/v4.0.30319/MSBuild.exe", + "/Program86/MSBuild/12.0/Bin/MSBuild.exe", + "/Program86/MSBuild/12.0/Bin/amd64/MSBuild.exe", + "/Program86/MSBuild/14.0/Bin/MSBuild.exe", + "/Program86/MSBuild/14.0/Bin/amd64/MSBuild.exe", + "/Program86/Microsoft Visual Studio/2017/Enterprise/MSBuild/15.0/Bin/MSBuild.exe", + "/Program86/Microsoft Visual Studio/2017/Enterprise/MSBuild/15.0/Bin/amd64/MSBuild.exe", + "/Program86/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/MSBuild.exe", + "/Program86/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/amd64/MSBuild.exe", + "/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/MSBuild.exe", + "/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe", + "/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/MSBuild.exe", + "/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe", + "/usr/bin/msbuild", + "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild" + }; // Install all known MSBuild versions. foreach (var msBuildPath in KnownMSBuildPaths) @@ -38,8 +54,15 @@ public MSBuildRunnerFixture(bool is64BitOperativeSystem) // Prepare the environment. Environment.SetSpecialPath(SpecialPath.ProgramFilesX86, "/Program86"); + Environment.SetSpecialPath(SpecialPath.ProgramFiles, "/Program"); Environment.SetSpecialPath(SpecialPath.Windows, "/Windows"); Environment.ChangeOperativeSystemBitness(is64BitOperativeSystem); + Environment.ChangeOperatingSystemFamily(platformFamily); + if (platformFamily == PlatformFamily.Windows) + { + Environment.WorkingDirectory = new DirectoryPath("C:/Working"); + Environment.ApplicationRoot = Environment.WorkingDirectory.Combine("bin"); + } // Prepare the tool parameters. Solution = new FilePath("./src/Solution.sln"); @@ -58,12 +81,13 @@ public void GivenMSBuildIsNotInstalled() } FileSystem.GetDirectory("/Windows").Delete(true); FileSystem.GetDirectory("/Program86").Delete(true); + FileSystem.GetDirectory("/Program").Delete(true); } protected override void RunTool() { var runner = new MSBuildRunner(FileSystem, Environment, ProcessRunner, Tools); - runner.Run(Solution, Settings); + runner.Run(Solution, Settings, StandardOutputAction); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/MSTestRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/MSTestRunnerFixture.cs index 937f253be5..4d7bc77446 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/MSTestRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/MSTestRunnerFixture.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Common.Tools.MSTest; using Cake.Core.IO; using Cake.Testing.Fixtures; -using System.Collections.Generic; namespace Cake.Common.Tests.Fixtures.Tools { @@ -17,6 +18,7 @@ public MSTestRunnerFixture() { AssemblyPaths = new[] { new FilePath("./Test1.dll") }; Environment.SetSpecialPath(SpecialPath.ProgramFilesX86, "/ProgramFilesX86"); + Environment.SetSpecialPath(SpecialPath.ProgramFiles, "/ProgramFiles"); } protected override FilePath GetDefaultToolPath(string toolFilename) @@ -30,4 +32,4 @@ protected override void RunTool() tool.Run(AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/MSpecRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/MSpecRunnerFixture.cs new file mode 100644 index 0000000000..48ab686e50 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/MSpecRunnerFixture.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.MSpec; +using Cake.Core.IO; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + internal sealed class MSpecRunnerFixture : ToolFixture + { + public List Assemblies { get; set; } + + public MSpecRunnerFixture() + : base("mspec-clr4.exe") + { + Assemblies = new List(); + Assemblies.Add(new FilePath("./Test1.dll")); + } + + public MSpecRunnerFixture(string toolFileName) : base(toolFileName) + { + Assemblies = new List(); + Assemblies.Add(new FilePath("./Test1.dll")); + } + + protected override void RunTool() + { + var tool = new MSpecRunner(FileSystem, Environment, ProcessRunner, Tools); + tool.Run(Assemblies, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NSISFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NSISFixture.cs index a79466b07a..979b0aa852 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NSISFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NSISFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NSIS; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -24,4 +25,4 @@ protected override void RunTool() tool.Run(ScriptPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NUnit3RunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NUnit3RunnerFixture.cs index c7efecae8b..d4e671f298 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NUnit3RunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NUnit3RunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.NUnit; using Cake.Core.IO; @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(Assemblies, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NUnitRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NUnitRunnerFixture.cs index 1304f9a3f7..b25c2a33f5 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NUnitRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NUnitRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.NUnit; using Cake.Core.IO; @@ -13,7 +14,12 @@ internal sealed class NUnitRunnerFixture : ToolFixture public List Assemblies { get; set; } public NUnitRunnerFixture() - : base("nunit-console.exe") + : this("nunit-console.exe") + { + } + + public NUnitRunnerFixture(string toolFilename) + : base(toolFilename) { Assemblies = new List(); Assemblies.Add(new FilePath("./Test1.dll")); @@ -25,4 +31,4 @@ protected override void RunTool() tool.Run(Assemblies, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Add/NuGetAdderFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Add/NuGetAdderFixture.cs new file mode 100644 index 0000000000..e0f622f0c0 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Add/NuGetAdderFixture.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.NuGet.Add; + +namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Add +{ + internal sealed class NuGetAdderFixture : NuGetFixture + { + public string PackageId { get; set; } + + public NuGetAdderFixture() + { + PackageId = "Cake"; + Settings.Source = "/Working/NuGet/localfeed"; + } + + protected override void RunTool() + { + var tool = new NuGetAdder(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.Add(PackageId, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Deleter/NuGetDeleterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Deleter/NuGetDeleterFixture.cs new file mode 100644 index 0000000000..3203414a14 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Deleter/NuGetDeleterFixture.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.NuGet.Delete; +using Cake.Core.IO; + +namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Deleter +{ + internal sealed class NuGetDeleterFixture : NuGetFixture + { + public string PackageID { get; set; } + + public string PackageVersion { get; set; } + + public NuGetDeleterFixture() + { + PackageID = "existing"; + PackageVersion = "0.1.0"; + } + + protected override void RunTool() + { + var tool = new NuGetDeleter(FileSystem, Environment, ProcessRunner, Tools, Resolver, Log); + tool.Delete(PackageID, PackageVersion, Settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Init/NuGetIniterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Init/NuGetIniterFixture.cs new file mode 100644 index 0000000000..63d068c051 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Init/NuGetIniterFixture.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.NuGet.Add; +using Cake.Common.Tools.NuGet.Init; + +namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Init +{ + internal sealed class NuGetIniterFixture : NuGetFixture + { + public string Source { get; set; } + public string Destination { get; set; } + + public NuGetIniterFixture() + { + Source = "/Working/NuGet/localfeed"; + Destination = "/Working/NuGet/localfeed-destination"; + } + + protected override void RunTool() + { + var tool = new NuGetIniter(FileSystem, Environment, ProcessRunner, Tools, Resolver); + tool.Init(Source, Destination, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFixture.cs index 1f0d262455..28ce774db1 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Install; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Installer @@ -20,4 +21,4 @@ protected override void RunTool() tool.Install(PackageId, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFromConfigFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFromConfigFixture.cs index d75ac81658..4dc0850330 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFromConfigFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Installer/NuGetInstallerFromConfigFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Install; using Cake.Core.IO; @@ -21,4 +22,4 @@ protected override void RunTool() tool.InstallFromConfig(PackageConfigPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/List/NuGetListFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/List/NuGetListFixture.cs new file mode 100644 index 0000000000..907ca141ef --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/List/NuGetListFixture.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Cake.Common.Tools.NuGet.List; + +namespace Cake.Common.Tests.Fixtures.Tools.NuGet.List +{ + internal sealed class NuGetListFixture : NuGetFixture + { + public string PackageId { get; set; } + + public IEnumerable Result { get; set; } + + public NuGetListFixture() + { + PackageId = "Cake"; + } + + public void GivenNormalPackageResult() + { + ProcessRunner.Process.SetStandardOutput(new string[] + { + "Cake 0.22.2", + "Cake.Core 0.22.2", + "Cake.CoreCLR 0.22.2", + }); + } + + protected override void RunTool() + { + var tool = new NuGetList(FileSystem, Environment, ProcessRunner, Tools, Resolver); + Result = tool.List(PackageId, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/NuGetFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/NuGetFixture.cs index 5259c0b957..61affa1998 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/NuGetFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/NuGetFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.IO.NuGet; @@ -34,4 +35,4 @@ protected NuGetFixture() Log = Substitute.For(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixture.cs index f7a6d09acf..1f58934160 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Pack; using Cake.Core.IO; using Cake.Testing; @@ -24,4 +25,4 @@ protected override NuGetPackerFixtureResult CreateResult(FilePath path, ProcessS return new NuGetPackerFixtureResult(FileSystem, path, process); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixtureResult.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixtureResult.cs new file mode 100644 index 0000000000..fbca20098c --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerFixtureResult.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Packer +{ + internal sealed class NuGetPackerFixtureResult : ToolFixtureResult + { + public string NuspecContent { get; } + + public NuGetPackerFixtureResult(FakeFileSystem fileSystem, FilePath path, ProcessSettings process) + : base(path, process) + { + NuspecContent = GetNuSpecContent(fileSystem, process); + } + + private static string GetNuSpecContent(FakeFileSystem fileSystem, ProcessSettings process) + { + var args = process.Arguments.Render(); + var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var last = parts.Last(); + var file = fileSystem.GetFile(last.UnQuote()); + return file.Exists ? file.GetTextContent() : null; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithNuSpecFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithNuSpecFixture.cs index fb6ddf40a1..7c08dfe264 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithNuSpecFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithNuSpecFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Tools.NuGet.Pack; using Cake.Core.IO; @@ -24,4 +25,4 @@ protected override void RunTool() tool.Pack(NuSpecFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithProjectFileFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithProjectFileFixture.cs index 1ca0d573e7..a9e998b4f5 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithProjectFileFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithProjectFileFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Tools.NuGet.Pack; using Cake.Core.IO; @@ -24,4 +25,4 @@ protected override void RunTool() tool.Pack(ProjectFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithoutNuSpecFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithoutNuSpecFixture.cs index f06680ee1d..dd67e0f054 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithoutNuSpecFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NuGetPackerWithoutNuSpecFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Pack; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Packer @@ -13,4 +14,4 @@ protected override void RunTool() tool.Pack(Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NugetPackerFixtureResult.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NugetPackerFixtureResult.cs deleted file mode 100644 index 6e046a2958..0000000000 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Packer/NugetPackerFixtureResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Linq; -using Cake.Core; -using Cake.Core.IO; -using Cake.Testing; -using Cake.Testing.Fixtures; - -namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Packer -{ - internal sealed class NuGetPackerFixtureResult : ToolFixtureResult - { - private readonly string _nuspecContent; - - public string NuspecContent - { - get { return _nuspecContent; } - } - - public NuGetPackerFixtureResult(FakeFileSystem fileSystem, FilePath path, ProcessSettings process) - : base(path, process) - { - _nuspecContent = GetNuSpecContent(fileSystem, process); - } - - private static string GetNuSpecContent(FakeFileSystem fileSystem, ProcessSettings process) - { - var args = process.Arguments.Render(); - var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - var last = parts.Last(); - var file = fileSystem.GetFile(last.UnQuote()); - return file.Exists ? file.GetTextContent() : null; - } - } -} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Pusher/NuGetPusherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Pusher/NuGetPusherFixture.cs index da79049dfd..b9e5360118 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Pusher/NuGetPusherFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Pusher/NuGetPusherFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Push; using Cake.Core.IO; @@ -17,8 +18,8 @@ public NuGetPusherFixture() protected override void RunTool() { - var tool = new NuGetPusher(FileSystem, Environment, ProcessRunner, Tools, Resolver); + var tool = new NuGetPusher(FileSystem, Environment, ProcessRunner, Tools, Resolver, Log); tool.Push(PackageFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Restorer/NuGetRestorerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Restorer/NuGetRestorerFixture.cs index fa290f142d..9c6813e0fb 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Restorer/NuGetRestorerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Restorer/NuGetRestorerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Restore; using Cake.Core.IO; @@ -21,4 +22,4 @@ protected override void RunTool() tool.Restore(TargetFilePath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetApiKey/NuGetSetApiKeyFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetApiKey/NuGetSetApiKeyFixture.cs index bb1e65780d..0318e14d46 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetApiKey/NuGetSetApiKeyFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetApiKey/NuGetSetApiKeyFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.SetApiKey; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.SetApiKey @@ -14,16 +15,6 @@ public NuGetSetApiKeyFixture() { ApiKey = "SECRET"; Source = "http://a.com"; - - // Set the standard output. - ProcessRunner.Process.SetStandardOutput(new[] { - string.Concat("The API Key '", ApiKey, - "' was saved for '", Source, "'.")}); - } - - public void GivenUnexpectedOutput() - { - ProcessRunner.Process.SetStandardOutput(new string[] { }); } protected override void RunTool() @@ -32,4 +23,4 @@ protected override void RunTool() tool.SetApiKey(ApiKey, Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetProxy/NuGetSetProxyFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetProxy/NuGetSetProxyFixture.cs index d422380483..1cf5737353 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetProxy/NuGetSetProxyFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/SetProxy/NuGetSetProxyFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.SetProxy; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.SetProxy @@ -27,4 +28,4 @@ protected override void RunTool() tool.SetProxy(Url, Username, Password, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetAddSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetAddSourceFixture.cs index 4149c5be1a..64d2599118 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetAddSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetAddSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Sources; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.AddSource(Name, Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetHasSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetHasSourceFixture.cs index 8a70f31def..6ae6b1e8d6 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetHasSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetHasSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Sources; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.HasSource(Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetRemoveSourceFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetRemoveSourceFixture.cs index 6e30846503..b58d60b7f3 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetRemoveSourceFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetRemoveSourceFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Sources; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Sources @@ -13,4 +14,4 @@ protected override void RunTool() tool.RemoveSource(Name, Source, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetSourcesFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetSourcesFixture.cs index f7c91401af..cf012b87e4 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetSourcesFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Sources/NuGetSourcesFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Sources; namespace Cake.Common.Tests.Fixtures.Tools.NuGet.Sources @@ -18,11 +19,13 @@ protected NuGetSourcesFixture() public void GivenExistingSource() { - ProcessRunner.Process.SetStandardOutput(new[] { + ProcessRunner.Process.SetStandardOutput(new[] + { " 1. https://www.nuget.org/api/v2/ [Enabled]", " https://www.nuget.org/api/v2/", - string.Format(" 2. {0} [Enabled]", Name), - string.Format(" {0}", Source)}); + $" 2. {Name} [Enabled]", + $" {Source}" + }); } public void GivenSourceAlreadyHasBeenAdded() @@ -30,4 +33,4 @@ public void GivenSourceAlreadyHasBeenAdded() ProcessRunner.Process.SetStandardOutput(new[] { Source }); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Update/NuGetUpdateFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Update/NuGetUpdateFixture.cs index 929fb67152..686988de17 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Update/NuGetUpdateFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/NuGet/Update/NuGetUpdateFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Update; using Cake.Core.IO; @@ -21,4 +22,4 @@ protected override void RunTool() tool.Update(TargetFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPackerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPackerFixture.cs new file mode 100644 index 0000000000..3bcbc37c50 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPackerFixture.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.OctopusDeploy; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + internal sealed class OctopusDeployPackerFixture : ToolFixture + { + public string Id { get; set; } + + public OctopusDeployPackerFixture() + : base("Octo.exe") + { + } + + protected override void RunTool() + { + var tool = new OctopusDeployPacker(FileSystem, Environment, ProcessRunner, Tools); + tool.Pack(Id, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPusherFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPusherFixture.cs index 104058c621..f933eaebbf 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPusherFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployPusherFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.OctopusDeploy; using Cake.Core.IO; @@ -24,7 +25,6 @@ public OctopusDeployPusherFixture() "MyPackage.1.0.0.zip", "MyOtherPackage.1.0.1.nupkg" }; - Server = "http://octopus"; ApiKey = "API-12345"; } @@ -32,7 +32,7 @@ public OctopusDeployPusherFixture() protected override void RunTool() { var tool = new OctopusDeployPusher(FileSystem, Environment, ProcessRunner, Tools); - tool.PushPackage(Server, ApiKey, Packages == null ? null : Packages.ToArray(), Settings); + tool.PushPackage(Server, ApiKey, Packages?.ToArray(), Settings); } } -} \ No newline at end of file +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseCreatorFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseCreatorFixture.cs index 6d375c541d..00cbd34e21 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseCreatorFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseCreatorFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.OctopusDeploy; using Cake.Testing.Fixtures; diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseDeployerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseDeployerFixture.cs new file mode 100644 index 0000000000..5771ab48fd --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleaseDeployerFixture.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.OctopusDeploy; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + public sealed class OctopusDeployReleaseDeployerFixture : ToolFixture + { + internal string Server { get; set; } + + internal string ApiKey { get; set; } + + internal string Project { get; set; } + + internal string[] DeployTo { get; set; } + + internal string ReleaseNumber { get; set; } + + public OctopusDeployReleaseDeployerFixture() + : base("Octo.exe") + { + Server = "http://octopus"; + ApiKey = "API-12345"; + Project = "MyProject"; + DeployTo = new string[] { "Testing" }; + ReleaseNumber = "0.15.1"; + } + + protected override void RunTool() + { + var tool = new OctopusDeployReleaseDeployer(FileSystem, Environment, ProcessRunner, Tools); + tool.DeployRelease(Server, ApiKey, Project, DeployTo, ReleaseNumber, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleasePromoterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleasePromoterFixture.cs new file mode 100644 index 0000000000..91bf19b1c5 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeployReleasePromoterFixture.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.OctopusDeploy; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + public sealed class OctopusDeployReleasePromoterFixture : ToolFixture + { + internal string Server { get; set; } + + internal string ApiKey { get; set; } + + internal string Project { get; set; } + + internal string DeployFrom { get; set; } + + internal string DeployTo { get; set; } + + public OctopusDeployReleasePromoterFixture() + : base("Octo.exe") + { + Server = "http://octopus"; + ApiKey = "API-12345"; + Project = "MyProject"; + DeployFrom = "Testing"; + DeployTo = "Staging"; + } + + protected override void RunTool() + { + var tool = new OctopusDeployReleasePromoter(FileSystem, Environment, ProcessRunner, Tools); + tool.PromoteRelease(Server, ApiKey, Project, DeployFrom, DeployTo, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeploymentQuerierFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeploymentQuerierFixture.cs new file mode 100644 index 0000000000..379f8d77f9 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/OctopusDeploymentQuerierFixture.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.OctopusDeploy; +using Cake.Testing.Fixtures; + +namespace Cake.Common.Tests.Fixtures.Tools +{ + internal sealed class OctopusDeploymentQuerierFixture : ToolFixture + { + internal string Server { get; set; } + + internal string ApiKey { get; set; } + + public OctopusDeploymentQuerierFixture() + : base("Octo.exe") + { + Server = "http://octopus"; + ApiKey = "API-12345"; + } + + public DeploymentQueryResultParser Parser { get; set; } = new DeploymentQueryResultParser(); + + protected override void RunTool() + { + var tool = new OctopusDeployDeploymentQuerier(FileSystem, Environment, ProcessRunner, Tools); + var results = tool.QueryOctopusDeployments(Server, ApiKey, Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/OpenCoverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/OpenCoverFixture.cs index 7e8b440208..8d5de798a5 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/OpenCoverFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/OpenCoverFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.OpenCover; using Cake.Core; @@ -52,4 +53,4 @@ protected override void RunTool() tool.Run(Context, Action, OutputPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/ReportGeneratorRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/ReportGeneratorRunnerFixture.cs index 60049ba680..501ca297ef 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/ReportGeneratorRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/ReportGeneratorRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.ReportGenerator; using Cake.Core.IO; @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(Reports, TargetDir, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitDirectoryFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitDirectoryFixture.cs index 39cef6652d..7c550011c9 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitDirectoryFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitDirectoryFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.ReportUnit; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -9,8 +10,8 @@ namespace Cake.Common.Tests.Fixtures.Tools.ReportUnit { internal sealed class ReportUnitDirectoryFixture : ToolFixture { - public DirectoryPath InputFolder; - public DirectoryPath OutputFolder; + public DirectoryPath InputFolder { get; set; } + public DirectoryPath OutputFolder { get; set; } public ReportUnitDirectoryFixture() : base("ReportUnit.exe") @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(InputFolder, OutputFolder, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitFileFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitFileFixture.cs index 397abad71b..ce74d3abfe 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitFileFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/ReportUnit/ReportUnitFileFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.ReportUnit; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -9,8 +10,8 @@ namespace Cake.Common.Tests.Fixtures.Tools.ReportUnit { internal sealed class ReportUnitFileFixture : ToolFixture { - public FilePath InputFile; - public FilePath OutputFile; + public FilePath InputFile { get; set; } + public FilePath OutputFile { get; set; } public ReportUnitFileFixture() : base("ReportUnit.exe") @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(InputFile, OutputFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/RoundhouseRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/RoundhouseRunnerFixture.cs index 7c09c62880..62e9af2bab 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/RoundhouseRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/RoundhouseRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Roundhouse; using Cake.Testing.Fixtures; @@ -22,4 +23,4 @@ protected override void RunTool() tool.Run(Settings, Drop); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/SignToolResolverFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/SignToolResolverFixture.cs index 11c05ebce2..d34a97cea6 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/SignToolResolverFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/SignToolResolverFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.SignTool; using Cake.Core; using Cake.Core.IO; @@ -16,7 +17,6 @@ internal sealed class SignToolResolverFixture public ICakeEnvironment Environment { get; set; } public IRegistry Registry { get; set; } - public SignToolResolverFixture(bool is64Bit = true) { _is64Bit = is64Bit; @@ -25,7 +25,7 @@ public SignToolResolverFixture(bool is64Bit = true) Environment = Substitute.For(); Registry = Substitute.For(); - Environment.Is64BitOperativeSystem().Returns(_is64Bit); + Environment.Platform.Is64Bit.Returns(_is64Bit); Environment.GetSpecialPath(SpecialPath.ProgramFiles).Returns("/ProgramFiles"); Environment.GetSpecialPath(SpecialPath.ProgramFilesX86).Returns("/ProgramFilesX86"); } @@ -42,7 +42,31 @@ public void GivenThatToolExistInKnownPath() } } - public void GivenThatToolHasRegistryKey() + public void GivenThatToolExistInKnownPathWindows10() + { + if (_is64Bit) + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/ProgramFilesX86/Windows Kits/10/bin/x64/signtool.exe")).Returns(true); + } + else + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/ProgramFiles/Windows Kits/10/bin/x86/signtool.exe")).Returns(true); + } + } + + public void GivenThatToolExistInKnownPathAppCertificationKit() + { + if (_is64Bit) + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/ProgramFilesX86/Windows Kits/10/App Certification Kit/signtool.exe")).Returns(true); + } + else + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/ProgramFiles/Windows Kits/10/App Certification Kit/signtool.exe")).Returns(true); + } + } + + public void GivenThatToolHasRegistryKeyMicrosoftSdks() { var signToolKey = Substitute.For(); signToolKey.GetValue("InstallationFolder").Returns("/SignTool"); @@ -58,6 +82,54 @@ public void GivenThatToolHasRegistryKey() Registry.LocalMachine.Returns(localMachine); } + public void GivenThatToolHasRegistryKeyWindowsKits() + { + var signToolKey = Substitute.For(); + signToolKey.GetValue("KitsRoot").Returns("/SignTool"); + + var localMachine = Substitute.For(); + localMachine.OpenKey("Software\\Microsoft\\Windows Kits\\Installed Roots").Returns(signToolKey); + + if (_is64Bit) + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/SignTool/bin/x64/signtool.exe")).Returns(true); + } + else + { + FileSystem.Exist(Arg.Is(p => p.FullPath == "/SignTool/bin/x86/signtool.exe")).Returns(true); + } + + Registry.LocalMachine.Returns(localMachine); + } + + public void GivenThatToolHasRegistryKeyWindows10Kits() + { + var versions = new[] { "10.0.15063.0", "10.0.16299.0" }; + + var signToolKey = Substitute.For(); + signToolKey.GetValue("KitsRoot10").Returns("/SignTool"); + signToolKey.GetSubKeyNames().Returns(versions); + + var localMachine = Substitute.For(); + localMachine.OpenKey("Software\\Microsoft\\Windows Kits\\Installed Roots").Returns(signToolKey); + + foreach (string version in versions) + { + if (_is64Bit) + { + FileSystem.Exist(Arg.Is(p => p.FullPath == $"/SignTool/bin/{version}/x64/signtool.exe")) + .Returns(true); + } + else + { + FileSystem.Exist(Arg.Is(p => p.FullPath == $"/SignTool/bin/{version}/x86/signtool.exe")) + .Returns(true); + } + } + + Registry.LocalMachine.Returns(localMachine); + } + public void GivenThatNoSdkRegistryKeyExist() { var localMachine = Substitute.For(); @@ -71,4 +143,4 @@ public FilePath Resolve() return resolver.GetPath(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/SignToolSignRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/SignToolSignRunnerFixture.cs index a7cf4cbd6a..d596760d68 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/SignToolSignRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/SignToolSignRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.SignTool; using Cake.Core.IO; @@ -13,19 +14,17 @@ namespace Cake.Common.Tests.Fixtures.Tools internal sealed class SignToolSignRunnerFixture : ToolFixture { public ISignToolResolver Resolver { get; set; } - public IFile AssemblyFile { get; set; } public IFile CertificateFile { get; set; } - public FilePath AssemblyPath { get; set; } + public FilePath[] AssemblyPaths { get; set; } public SignToolSignRunnerFixture() : base("signtool.exe") { Settings.CertPath = "./cert.pfx"; Settings.Password = "secret"; - Settings.TimeStampUri = new Uri("https://t.com"); - AssemblyPath = new FilePath("./a.dll"); + AssemblyPaths = new[] { new FilePath("./a.dll") }; FileSystem.CreateFile("/Working/a.dll"); FileSystem.CreateFile("/Working/cert.pfx"); @@ -35,7 +34,7 @@ public SignToolSignRunnerFixture() protected override void RunTool() { var tool = new SignToolSignRunner(FileSystem, Environment, ProcessRunner, Tools, null, Resolver); - tool.Run(AssemblyPath, Settings); + tool.Run(AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/SpecFlowFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/SpecFlowFixture.cs index 0045734bac..c34a9856ca 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/SpecFlowFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/SpecFlowFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; using Cake.Testing.Fixtures; @@ -26,4 +27,4 @@ protected SpecFlowFixture() ProcessRunner.Process.SetStandardOutput(new string[] { }); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterFixture.cs index 6407a49b5c..1098cf6a0e 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.SpecFlow.StepDefinitionReport; using Cake.Core.IO; @@ -22,4 +23,4 @@ protected override void RunTool() tool.Run(ProjectFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterFixture.cs index a89970d427..3170b9ca08 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.SpecFlow.TestExecutionReport; using Cake.Core; @@ -50,4 +51,4 @@ protected override void RunTool() tool.Run(Context, Action, ProjectFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/TextTransform/TextTransformFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/TextTransform/TextTransformFixture.cs index 81c6054a2b..281f555ed9 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/TextTransform/TextTransformFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/TextTransform/TextTransformFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.TextTransform; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -22,4 +23,4 @@ protected override void RunTool() tool.Run(SourceFile, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSTestRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSTestRunnerFixture.cs index dc9e7d8824..2cd249edd0 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/VSTestRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSTestRunnerFixture.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Common.Tools.VSTest; using Cake.Core.IO; using Cake.Testing.Fixtures; -using System.Collections.Generic; namespace Cake.Common.Tests.Fixtures.Tools { @@ -17,6 +18,7 @@ public VSTestRunnerFixture() { AssemblyPaths = new[] { new FilePath("./Test1.dll") }; Environment.SetSpecialPath(SpecialPath.ProgramFilesX86, "/ProgramFilesX86"); + Environment.SetSpecialPath(SpecialPath.ProgramFiles, "/ProgramFiles"); } protected override FilePath GetDefaultToolPath(string toolFilename) @@ -30,4 +32,4 @@ protected override void RunTool() tool.Run(AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/All/VSWhereAllFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/All/VSWhereAllFixture.cs new file mode 100644 index 0000000000..4b0ca5683b --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/All/VSWhereAllFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere.All; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere.All +{ + internal sealed class VSWhereAllFixture : VSWhereFixture + { + protected override void RunTool() + { + var tool = new VSWhereAll(FileSystem, Environment, ProcessRunner, Tools); + tool.All(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Latest/VSWhereLatestFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Latest/VSWhereLatestFixture.cs new file mode 100644 index 0000000000..76e5d86be3 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Latest/VSWhereLatestFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere.Latest; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere.Latest +{ + internal sealed class VSWhereLatestFixture : VSWhereFixture + { + protected override void RunTool() + { + var tool = new VSWhereLatest(FileSystem, Environment, ProcessRunner, Tools); + tool.Latest(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Legacy/VSWhereLegacyFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Legacy/VSWhereLegacyFixture.cs new file mode 100644 index 0000000000..481b13b00e --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Legacy/VSWhereLegacyFixture.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere.Legacy; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere.Legacy +{ + internal sealed class VSWhereLegacyFixture : VSWhereFixture + { + protected override void RunTool() + { + var tool = new VSWhereLegacy(FileSystem, Environment, ProcessRunner, Tools); + tool.Legacy(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Product/VSWhereProductFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Product/VSWhereProductFixture.cs new file mode 100644 index 0000000000..ce6ad3f376 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/Product/VSWhereProductFixture.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere.Product; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere.Product +{ + internal sealed class VSWhereProductFixture : VSWhereFixture + { + public VSWhereProductFixture() + { + Settings.Products = "Microsoft.VisualStudio.Product.BuildTools"; + } + + protected override void RunTool() + { + var tool = new VSWhereProduct(FileSystem, Environment, ProcessRunner, Tools); + tool.Products(Settings); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereFixture.cs new file mode 100644 index 0000000000..d906aef624 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereFixture.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Core.Tooling; +using Cake.Testing.Fixtures; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere +{ + internal abstract class VSWhereFixture : VSWhereFixture + where TSettings : ToolSettings, new() + { + protected VSWhereFixture() + : base() + { + } + + protected VSWhereFixture(bool is64BitOperativeSystem) + : base(is64BitOperativeSystem) + { + } + + protected override ToolFixtureResult CreateResult(FilePath path, ProcessSettings process) + { + return new ToolFixtureResult(path, process); + } + } + + internal abstract class VSWhereFixture : ToolFixture + where TSettings : ToolSettings, new() + where TFixtureResult : ToolFixtureResult + { + public ICakeLog Log { get; set; } + + protected VSWhereFixture() + : this(true) + { + } + + protected VSWhereFixture(bool is64BitOperativeSystem) + : base("vswhere.exe") + { + ProcessRunner.Process.SetStandardOutput(new string[] { }); + Log = Substitute.For(); + + // Prepare the environment. + Environment.SetSpecialPath(SpecialPath.ProgramFilesX86, "/Program86"); + Environment.SetSpecialPath(SpecialPath.ProgramFiles, "/Program"); + Environment.ChangeOperativeSystemBitness(is64BitOperativeSystem); + } + + protected override FilePath GetDefaultToolPath(string toolFilename) + { + return Environment.Platform.Is64Bit + ? new FilePath("/Program86/Microsoft Visual Studio/Installer/vswhere.exe") + : new FilePath("/Program/Microsoft Visual Studio/Installer/vswhere.exe"); + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereToolFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereToolFixture.cs new file mode 100644 index 0000000000..4f789d8b66 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Tools/VSWhere/VSWhereToolFixture.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tests.Fixtures.Tools.VSWhere +{ + using System.Linq; + using Cake.Core; + + internal class VSWhereToolFixture : VSWhereFixture + { + internal VSWhereToolFixture(bool is64BitOperativeSystem) + : base(is64BitOperativeSystem) + { + } + + protected override void RunTool() + { + var tool = new VSWhereTool(FileSystem, Environment, ProcessRunner, Tools); + tool.Run(Settings); + } + + private sealed class VSWhereTool : VSWhereTool + { + public VSWhereTool(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator toolLocator) + : base(fileSystem, environment, processRunner, toolLocator) + { + } + public DirectoryPath Run(ToolSettings settings) + { + return RunVSWhere(settings, new ProcessArgumentBuilder()).FirstOrDefault(); + } + } + } +} diff --git a/src/Cake.Common.Tests/Fixtures/Tools/WiX/HeatFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/WiX/HeatFixture.cs index 4efbf1063e..fba96a3fd0 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/WiX/HeatFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/WiX/HeatFixture.cs @@ -12,40 +12,45 @@ internal sealed class HeatFixture : ToolFixture { public DirectoryPath DirectoryPath { get; set; } - public List ObjectFiles { get; set; } + public FilePath ObjectFile { get; set; } public FilePath OutputFile { get; set; } - public string HarvestType { get; set; } + public string HarvestTarget { get; set; } + + public WiXHarvestType HarvestType { get; set; } public HeatFixture() : base("heat.exe") { DirectoryPath = new DirectoryPath("./src/Cake"); - ObjectFiles = new List(); - ObjectFiles.Add(new FilePath("Cake.dll")); + ObjectFile = new FilePath("Cake.dll"); OutputFile = new FilePath("cake.wxs"); Settings = new HeatSettings(); - Settings.HarvestType = WiXHarvestType.Dir; - HarvestType = "Default Web Site"; + HarvestType = WiXHarvestType.Dir; + HarvestTarget = "Default Web Site"; } protected override void RunTool() { var tool = new HeatRunner(FileSystem, Environment, ProcessRunner, Tools); - switch(Settings.HarvestType) + + switch (HarvestType) { case WiXHarvestType.Dir: - tool.Run(DirectoryPath, OutputFile, Settings); + tool.Run(DirectoryPath, OutputFile, HarvestType, Settings); break; case WiXHarvestType.File: case WiXHarvestType.Project: case WiXHarvestType.Reg: - tool.Run(ObjectFiles, OutputFile, Settings); + tool.Run(ObjectFile, OutputFile, HarvestType, Settings); break; case WiXHarvestType.Website: case WiXHarvestType.Perf: - tool.Run(HarvestType, OutputFile, Settings); + tool.Run(HarvestTarget, OutputFile, HarvestType, Settings); + break; + default: + tool.Run(DirectoryPath, OutputFile, HarvestType, Settings); break; } } diff --git a/src/Cake.Common.Tests/Fixtures/Tools/WiXFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/WiXFixture.cs index 2fd7c6b229..8fb786066d 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/WiXFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/WiXFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.WiX; using Cake.Core.IO; @@ -25,4 +26,4 @@ protected override void RunTool() tool.Run(ObjectFiles, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/XUnit2RunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/XUnit2RunnerFixture.cs index fbf4d61cee..0b5c13673d 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/XUnit2RunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/XUnit2RunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tools.XUnit; using Cake.Core.IO; @@ -18,10 +19,15 @@ public XUnit2RunnerFixture() AssemblyPaths = new FilePath[] { "./Test1.dll" }; } + public XUnit2RunnerFixture(string toolFileName) : base(toolFileName) + { + AssemblyPaths = new FilePath[] { "./Test1.dll" }; + } + protected override void RunTool() { var runner = new XUnit2Runner(FileSystem, Environment, ProcessRunner, Tools); runner.Run(AssemblyPaths, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/Tools/XUnitRunnerFixture.cs b/src/Cake.Common.Tests/Fixtures/Tools/XUnitRunnerFixture.cs index 1d6870c4ac..850adc7a36 100644 --- a/src/Cake.Common.Tests/Fixtures/Tools/XUnitRunnerFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/Tools/XUnitRunnerFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.XUnit; using Cake.Core.IO; using Cake.Testing.Fixtures; @@ -23,4 +24,4 @@ protected override void RunTool() runner.Run(AssemblyPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/XmlDocExampleCodeParserFixture.cs b/src/Cake.Common.Tests/Fixtures/XmlDocExampleCodeParserFixture.cs index 5e7922afe1..b01bdf6ef9 100644 --- a/src/Cake.Common.Tests/Fixtures/XmlDocExampleCodeParserFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/XmlDocExampleCodeParserFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Solution.Project.XmlDoc; using Cake.Common.Tests.Properties; @@ -31,7 +32,8 @@ public XmlDocExampleCodeParserFixture() FileSystem = fileSystem; Globber = Substitute.For(); - Globber.GetFiles(Pattern).Returns(new FilePath[] { "/Working/Cake.Common.xml", "/Working/Cake.UnCommon.xml"}); + Globber.Match(Pattern, Arg.Any()).Returns( + new FilePath[] { "/Working/Cake.Common.xml", "/Working/Cake.UnCommon.xml" }); Log = Substitute.For(); } @@ -48,4 +50,4 @@ public IEnumerable ParseFiles() return parser.ParseFiles(Pattern); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/XmlPeekAliasesFixture.cs b/src/Cake.Common.Tests/Fixtures/XmlPeekAliasesFixture.cs index 1a0783c912..6de8993769 100644 --- a/src/Cake.Common.Tests/Fixtures/XmlPeekAliasesFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/XmlPeekAliasesFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Xml; using Cake.Core; @@ -14,12 +15,13 @@ internal sealed class XmlPeekAliasesFixture { public IFileSystem FileSystem { get; set; } public ICakeContext Context { get; set; } + public FakeLog FakeLog { get; set; } public FilePath XmlPath { get; set; } public XmlPeekSettings Settings { get; set; } - public XmlPeekAliasesFixture(bool xmlExists = true, bool xmlWithDtd = false) + public XmlPeekAliasesFixture(bool xmlExists = true, bool xmlWithDtd = false, bool suppressWarning = false) { - Settings = new XmlPeekSettings(); + Settings = new XmlPeekSettings { SuppressWarning = suppressWarning }; var environment = FakeEnvironment.CreateUnixEnvironment(); var fileSystem = new FakeFileSystem(environment); @@ -33,10 +35,17 @@ public XmlPeekAliasesFixture(bool xmlExists = true, bool xmlWithDtd = false) } FileSystem = fileSystem; + FakeLog = new FakeLog(); Context = Substitute.For(); Context.FileSystem.Returns(FileSystem); Context.Environment.Returns(environment); + Context.Log.Returns(FakeLog); + } + + public void SetContent(string xml) + { + var file = ((FakeFileSystem)FileSystem).GetFile(XmlPath).SetContent(xml); } public string Peek(string xpath) @@ -44,4 +53,4 @@ public string Peek(string xpath) return XmlPeekAliases.XmlPeek(Context, XmlPath, xpath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/XmlPokeFixture.cs b/src/Cake.Common.Tests/Fixtures/XmlPokeFixture.cs index 48c8351989..b263d3dd60 100644 --- a/src/Cake.Common.Tests/Fixtures/XmlPokeFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/XmlPokeFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; using System.Linq; using System.Xml; @@ -15,55 +16,55 @@ namespace Cake.Common.Tests.Fixtures { internal sealed class XmlPokeFixture { - public IFileSystem FileSystem { get; set; } - public ICakeContext Context { get; set; } - public FilePath XmlPath { get; set; } - public XmlPokeSettings Settings { get; set; } - - public XmlPokeFixture(bool xmlExists = true, bool xmlWithDtd = false) - { - Settings = new XmlPokeSettings(); - - var environment = FakeEnvironment.CreateUnixEnvironment(); - var fileSystem = new FakeFileSystem(environment); - fileSystem.CreateDirectory("/Working"); - - if (xmlExists) - { - string content = xmlWithDtd ? Resources.XmlPoke_Xml_Dtd : Resources.XmlPoke_Xml; - var xmlFile = fileSystem.CreateFile("/Working/web.config").SetContent(content); - XmlPath = xmlFile.Path; - } - - FileSystem = fileSystem; - - Context = Substitute.For(); - Context.FileSystem.Returns(FileSystem); - Context.Environment.Returns(environment); - } - - public void Poke(string xpath, string value) - { - XmlPokeAliases.XmlPoke(Context, XmlPath, xpath, value, Settings); - } - - public string PokeString(string xml, string xpath, string value) - { - return XmlPokeAliases.XmlPokeString(Context, xml, xpath, value, Settings); - } + public FakeFileSystem FileSystem { get; set; } + public ICakeContext Context { get; set; } + public FilePath XmlPath { get; set; } + public XmlPokeSettings Settings { get; set; } + + public XmlPokeFixture(bool xmlExists = true, bool xmlWithDtd = false) + { + Settings = new XmlPokeSettings(); + + var environment = FakeEnvironment.CreateUnixEnvironment(); + var fileSystem = new FakeFileSystem(environment); + fileSystem.CreateDirectory("/Working"); + + if (xmlExists) + { + string content = xmlWithDtd ? Resources.XmlPoke_Xml_Dtd : Resources.XmlPoke_Xml; + var xmlFile = fileSystem.CreateFile("/Working/web.config").SetContent(content); + XmlPath = xmlFile.Path; + } + + FileSystem = fileSystem; + + Context = Substitute.For(); + Context.FileSystem.Returns(FileSystem); + Context.Environment.Returns(environment); + } + + public void Poke(string xpath, string value) + { + XmlPokeAliases.XmlPoke(Context, XmlPath, xpath, value, Settings); + } + + public string PokeString(string xml, string xpath, string value) + { + return XmlPokeAliases.XmlPokeString(Context, xml, xpath, value, Settings); + } public bool TestIsValue(string xpath, string value) { - var xmlString = new StreamReader(FileSystem.GetFile(XmlPath).OpenRead()).ReadToEnd(); + var xmlString = GetFullXml(); return TestIsValue(xmlString, xpath, value); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public bool TestIsValue(string xml, string xpath, string value) { using (var reader = new StringReader(xml)) using (var xmlReader = XmlReader.Create(reader, GetXmlReaderSettings(Settings))) { - var document = new XmlDocument(); document.Load(xmlReader); @@ -80,16 +81,16 @@ public bool TestIsValue(string xml, string xpath, string value) public bool TestIsRemoved(string xpath) { - var xmlString = new StreamReader(FileSystem.GetFile(XmlPath).OpenRead()).ReadToEnd(); + var xmlString = GetFullXml(); return TestIsRemoved(xmlString, xpath); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public bool TestIsRemoved(string xml, string xpath) { using (var reader = new StringReader(xml)) using (var xmlReader = XmlReader.Create(reader, GetXmlReaderSettings(Settings))) { - var document = new XmlDocument(); document.Load(xmlReader); @@ -104,8 +105,19 @@ public bool TestIsRemoved(string xml, string xpath) } } + // ReSharper disable once InconsistentNaming + public bool TestIsUTF8WithBOM() + { + return FileSystem.GetFile(XmlPath).HasUTF8BOM(); + } + + public string GetFullXml() + { + return FileSystem.GetFile(XmlPath).GetTextContent(); + } + /// - /// Gets a XmlReaderSettings from a XmlPokeSettings + /// Gets a XmlReaderSettings from a XmlPokeSettings. /// /// The xml reader settings. /// Additional settings to tweak Xml Poke behavior. @@ -117,4 +129,4 @@ private static XmlReaderSettings GetXmlReaderSettings(XmlPokeSettings settings) return xmlReaderSettings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Fixtures/XmlTransformationFixture.cs b/src/Cake.Common.Tests/Fixtures/XmlTransformationFixture.cs index 136be5dd98..ed45f13173 100644 --- a/src/Cake.Common.Tests/Fixtures/XmlTransformationFixture.cs +++ b/src/Cake.Common.Tests/Fixtures/XmlTransformationFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Properties; using Cake.Common.Xml; using Cake.Core.IO; @@ -54,4 +55,4 @@ public void Transform() XmlTransformation.Transform(FileSystem, XslPath, XmlPath, ResultPath, Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Properties/AssemblyInfo.cs b/src/Cake.Common.Tests/Properties/AssemblyInfo.cs index a8cd146bf5..62d2b3fe8d 100644 --- a/src/Cake.Common.Tests/Properties/AssemblyInfo.cs +++ b/src/Cake.Common.Tests/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Reflection; using System.Runtime.InteropServices; @@ -8,9 +9,8 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cake.Common.Tests")] -[assembly: AssemblyDescription("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/src/Cake.Common.Tests/Properties/PropertyList-1.0.dtd b/src/Cake.Common.Tests/Properties/PropertyList-1.0.dtd new file mode 100644 index 0000000000..6a117a8077 --- /dev/null +++ b/src/Cake.Common.Tests/Properties/PropertyList-1.0.dtd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Cake.Common.Tests/Properties/Resources.Designer.cs b/src/Cake.Common.Tests/Properties/Resources.Designer.cs index 6e6a4f02e3..6a15f5f8ca 100644 --- a/src/Cake.Common.Tests/Properties/Resources.Designer.cs +++ b/src/Cake.Common.Tests/Properties/Resources.Designer.cs @@ -19,10 +19,10 @@ namespace Cake.Common.Tests.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Cake.Common.Tests.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -72,7 +72,7 @@ internal Resources() { /// <owners>Owner #1,Owner #2</owners> /// <summary>The summa [rest of string was truncated]";. /// - internal static string ChocolateyNuspec_Metadata { + public static string ChocolateyNuspec_Metadata { get { return ResourceManager.GetString("ChocolateyNuspec_Metadata", resourceCulture); } @@ -92,7 +92,7 @@ internal static string ChocolateyNuspec_Metadata { /// <description>The description</description> /// [rest of string was truncated]";. /// - internal static string ChocolateyNuspec_Metadata_WithoutNamespaces { + public static string ChocolateyNuspec_Metadata_WithoutNamespaces { get { return ResourceManager.GetString("ChocolateyNuspec_Metadata_WithoutNamespaces", resourceCulture); } @@ -107,7 +107,7 @@ internal static string ChocolateyNuspec_Metadata_WithoutNamespaces { /// </files> ///</package>. /// - internal static string ChocolateyNuspec_NoMetadataElement { + public static string ChocolateyNuspec_NoMetadataElement { get { return ResourceManager.GetString("ChocolateyNuspec_NoMetadataElement", resourceCulture); } @@ -123,7 +123,7 @@ internal static string ChocolateyNuspec_NoMetadataElement { /// </files> ///</package>. /// - internal static string ChocolateyNuspec_NoMetadataValues { + public static string ChocolateyNuspec_NoMetadataValues { get { return ResourceManager.GetString("ChocolateyNuspec_NoMetadataValues", resourceCulture); } @@ -139,7 +139,7 @@ internal static string ChocolateyNuspec_NoMetadataValues { /// </files> ///</package>. /// - internal static string ChocolateyNuspec_NoMetadataValues_WithoutNamespaces { + public static string ChocolateyNuspec_NoMetadataValues_WithoutNamespaces { get { return ResourceManager.GetString("ChocolateyNuspec_NoMetadataValues_WithoutNamespaces", resourceCulture); } @@ -153,7 +153,7 @@ internal static string ChocolateyNuspec_NoMetadataValues_WithoutNamespaces { /// <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> /// <Platform Condition=" '$(Platform)' == [rest of string was truncated]";. /// - internal static string Csproj_IncompleteFile { + public static string Csproj_IncompleteFile { get { return ResourceManager.GetString("Csproj_IncompleteFile", resourceCulture); } @@ -167,7 +167,7 @@ internal static string Csproj_IncompleteFile { /// <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> /// <Platform Condition=" '$(Platform)' == [rest of string was truncated]";. /// - internal static string Csproj_ProjectFile { + public static string Csproj_ProjectFile { get { return ResourceManager.GetString("Csproj_ProjectFile", resourceCulture); } @@ -184,7 +184,7 @@ internal static string Csproj_ProjectFile { /// <Duplicates></Duplicates> ///</DuplicatesReport>. /// - internal static string DupFinderReportNoDuplicates { + public static string DupFinderReportNoDuplicates { get { return ResourceManager.GetString("DupFinderReportNoDuplicates", resourceCulture); } @@ -208,12 +208,34 @@ internal static string DupFinderReportNoDuplicates { /// <Fragment> /// <FileName>LangFe [rest of string was truncated]";. /// - internal static string DupFinderReportWithDuplicates { + public static string DupFinderReportWithDuplicates { get { return ResourceManager.GetString("DupFinderReportWithDuplicates", resourceCulture); } } + /// + /// Looks up a localized string similar to //------------------------------------------------------------------------------ + ///// <auto-generated> + ///// This code was generated by a tool. + ///// Runtime Version:4.0.30319.42000 + ///// + ///// Changes to this file may cause incorrect behavior and will be lost if + ///// the code is regenerated. + ///// </auto-generated> + /////------------------------------------------------------------------------------ + /// + ///using System; + ///using System.Reflection; + /// + ///[assembly: System.Reflection.AssemblyCompanyAttribute("FullyQu [rest of string was truncated]";. + /// + public static string FullyQualifiedAssemblyInfo { + get { + return ResourceManager.GetString("FullyQualifiedAssemblyInfo", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> ///<!-- Generated by InspectCode 8.2.1000.4527 --> @@ -228,7 +250,7 @@ internal static string DupFinderReportWithDuplicates { /// <Issues></Issues> ///</Report>. /// - internal static string InspectCodeReportNoViolations { + public static string InspectCodeReportNoViolations { get { return ResourceManager.GetString("InspectCodeReportNoViolations", resourceCulture); } @@ -251,7 +273,7 @@ internal static string InspectCodeReportNoViolations { /// <Project Name="Cake.Common"> /// <Issue TypeId="CSharpErrors" File="Cake.Common.Tests\F [rest of string was truncated]";. /// - internal static string InspectCodeReportWithViolations { + public static string InspectCodeReportWithViolations { get { return ResourceManager.GetString("InspectCodeReportWithViolations", resourceCulture); } @@ -271,12 +293,30 @@ internal static string InspectCodeReportWithViolations { ///[assembly: AssemblyProduct ("MonoDevelopProduct")] ///[assembly: Assembl [rest of string was truncated]";. /// - internal static string MonoDevelopAssemblyInfo { + public static string MonoDevelopAssemblyInfo { get { return ResourceManager.GetString("MonoDevelopAssemblyInfo", resourceCulture); } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata minClientVersion="3.3" xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</p [rest of string was truncated]";. + /// + public static string Nuspec_ContentFiles { + get { + return ResourceManager.GetString("Nuspec_ContentFiles", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -286,13 +326,13 @@ internal static string MonoDevelopAssemblyInfo { /// <title>The title</title> /// <authors>Author #1,Author #2</authors> /// <owners>Owner #1,Owner #2</owners> - /// <description>The description</description> - /// <summary>The summary</summary> - /// <licenseUrl>https://lic [rest of string was truncated]";. + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. /// - internal static string Nuspec_Metadata { + public static string Nuspec_FrameworkAssemblies { get { - return ResourceManager.GetString("Nuspec_Metadata", resourceCulture); + return ResourceManager.GetString("Nuspec_FrameworkAssemblies", resourceCulture); } } @@ -305,11 +345,69 @@ internal static string Nuspec_Metadata { /// <title>The title</title> /// <authors>Author #1,Author #2</authors> /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_License { + get { + return ResourceManager.GetString("Nuspec_License", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_Metadata { + get { + return ResourceManager.GetString("Nuspec_Metadata", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>nonexisting</id> + /// <version>1.0.0</version> + /// <authors>Author #1,Author #2</authors> /// <description>The description</description> - /// <summary>The summary</summary> - /// <licenseUrl>https://lic [rest of string was truncated]";. + /// <dependencies> + /// <group targetFramework="net452"> + /// <dependency id="Test1" version="1.0.0" /> + /// </group> + /// < [rest of string was truncated]";. + /// + public static string Nuspec_Metadata_PackWithTargetFrameworkDependencies { + get { + return ResourceManager.GetString("Nuspec_Metadata_PackWithTargetFrameworkDependencies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. /// - internal static string Nuspec_Metadata_WithDependencies { + public static string Nuspec_Metadata_WithDependencies { get { return ResourceManager.GetString("Nuspec_Metadata_WithDependencies", resourceCulture); } @@ -324,14 +422,14 @@ internal static string Nuspec_Metadata_WithDependencies { /// <title>The title</title> /// <authors>Author #1,Author #2</authors> /// <owners>Owner #1,Owner #2</owners> - /// <description>The description</description> - /// <summary>The summary</summary> /// <licenseUrl>https://license.com</licenseUrl> /// <projectUrl>https://project.com</projectUrl> + /// <icon>images\icon.png</icon> /// <iconUrl>https://icon.com</iconUrl> - /// <developmentDependency>true</developmentDepende [rest of string was truncated]";. + /// <requireLicenseAcceptance>true</requireLicenseAcceptance> + /// <developmentDependency>true</devel [rest of string was truncated]";. /// - internal static string Nuspec_Metadata_WithoutNamespaces { + public static string Nuspec_Metadata_WithoutNamespaces { get { return ResourceManager.GetString("Nuspec_Metadata_WithoutNamespaces", resourceCulture); } @@ -346,19 +444,60 @@ internal static string Nuspec_Metadata_WithoutNamespaces { /// <title>The title</title> /// <authors>Author #1,Author #2</authors> /// <owners>Owner #1,Owner #2</owners> - /// <description>The description</description> - /// <summary>The summary</summary> /// <licenseUrl>https://license.com</licenseUrl> /// <projectUrl>https://project.com</projectUrl> + /// <icon>images\icon.png</icon> /// <iconUrl>https://icon.com</iconUrl> - /// <developmentDependency>true</developmentDepende [rest of string was truncated]";. + /// <requireLicenseAcceptance>true</requireLicenseAcceptance> + /// <developmentDependency>true</devel [rest of string was truncated]";. /// - internal static string Nuspec_Metadata_WithoutNamespaces_WithDependencies { + public static string Nuspec_Metadata_WithoutNamespaces_WithDependencies { get { return ResourceManager.GetString("Nuspec_Metadata_WithoutNamespaces_WithDependencies", resourceCulture); } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package> + /// <metadata> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>images\icon.png</icon> + /// <iconUrl>https://icon.com</iconUrl> + /// <requireLicenseAcceptance>true</requireLicenseAcceptance> + /// <developmentDependency>true</devel [rest of string was truncated]";. + /// + public static string Nuspec_Metadata_WithoutNamespaces_WithTargetFramworkDependencies { + get { + return ResourceManager.GetString("Nuspec_Metadata_WithoutNamespaces_WithTargetFramworkDependencies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_Metadata_WithTargetFrameworkDependencies { + get { + return ResourceManager.GetString("Nuspec_Metadata_WithTargetFrameworkDependencies", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -370,7 +509,7 @@ internal static string Nuspec_Metadata_WithoutNamespaces_WithDependencies { /// </files> ///</package>. /// - internal static string Nuspec_NoMetadataElement { + public static string Nuspec_NoMetadataElement { get { return ResourceManager.GetString("Nuspec_NoMetadataElement", resourceCulture); } @@ -388,7 +527,7 @@ internal static string Nuspec_NoMetadataElement { /// </files> ///</package>. /// - internal static string Nuspec_NoMetadataValues { + public static string Nuspec_NoMetadataValues { get { return ResourceManager.GetString("Nuspec_NoMetadataValues", resourceCulture); } @@ -406,12 +545,31 @@ internal static string Nuspec_NoMetadataValues { /// </files> ///</package>. /// - internal static string Nuspec_NoMetadataValues_WithoutNamespaces { + public static string Nuspec_NoMetadataValues_WithoutNamespaces { get { return ResourceManager.GetString("Nuspec_NoMetadataValues_WithoutNamespaces", resourceCulture); } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_PackageTypes { + get { + return ResourceManager.GetString("Nuspec_PackageTypes", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> ///<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> @@ -423,12 +581,278 @@ internal static string Nuspec_NoMetadataValues_WithoutNamespaces { /// <ProjectGuid>{AE8E29D0-6FF2-42D0-94EC-E725276A864D}</ProjectGuid> /// <OutputType>Library</OutputTy [rest of string was truncated]";. /// - internal static string Nuspec_ProjectFile { + public static string Nuspec_ProjectFile { get { return ResourceManager.GetString("Nuspec_ProjectFile", resourceCulture); } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_ReadMe { + get { + return ResourceManager.GetString("Nuspec_ReadMe", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_References { + get { + return ResourceManager.GetString("Nuspec_References", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_References_WithTargetFramework { + get { + return ResourceManager.GetString("Nuspec_References_WithTargetFramework", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + /// <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + /// <id>The ID</id> + /// <version>The version</version> + /// <title>The title</title> + /// <authors>Author #1,Author #2</authors> + /// <owners>Owner #1,Owner #2</owners> + /// <licenseUrl>https://license.com</licenseUrl> + /// <projectUrl>https://project.com</projectUrl> + /// <icon>i [rest of string was truncated]";. + /// + public static string Nuspec_Repository { + get { + return ResourceManager.GetString("Nuspec_Repository", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Microsoft Visual Studio Solution File, Format Version 12.00 + ///# Visual Studio 14 + ///VisualStudioVersion = 14.0.25123.0 + ///MinimumVisualStudioVersion = 10.0.40219.1 + ///Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2400A22B-695E-4BDF-93CB-8757F5FB3FB7}" + ///EndProject + ///Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69930DD1-1688-4407-B4AB-B9E2C0BFB284}" + ///EndProject + ///Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "src\dummy\dummy.csproj", "{ADCB37DA-2469-462F-99 [rest of string was truncated]";. + /// + public static string Solution_WithProjectsAndFolders { + get { + return ResourceManager.GetString("Solution_WithProjectsAndFolders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Microsoft Visual Studio Solution File, Format Version 12.00 + ///# Visual Studio 14 + ///VisualStudioVersion = 14.0.25123.0 + ///MinimumVisualStudioVersion = 10.0.40219.1 + ///Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2400A22B-695E-4BDF-93CB-8757F5FB3FB7}" + ///EndProject + ///Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69930DD1-1688-4407-B4AB-B9E2C0BFB284}" + ///EndProject + ///Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "src\dummy\dummy.csproj", "{ADCB37DA-2469-462F-99 [rest of string was truncated]";. + /// + public static string Solution_WithProjectsAndFoldersAndMissingLine { + get { + return ResourceManager.GetString("Solution_WithProjectsAndFoldersAndMissingLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Microsoft Visual Studio Solution File, Format Version 12.00 + ///# Visual Studio Version 16 + ///VisualStudioVersion = 16.0.31702.278 + ///MinimumVisualStudioVersion = 10.0.40219.1 + ///Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "C:\project\dummy\src\dummy\dummy.csproj", "{A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}" + ///EndProject + ///Global + /// GlobalSection(SolutionConfigurationPlatforms) = preSolution + /// Debug|Any CPU = Debug|Any CPU + /// Release|Any CPU = Release|Any CPU + /// EndGlobalSection + /// GlobalSection(ProjectCon [rest of string was truncated]";. + /// + public static string Solution_WithProjectUsingAbsolutePath { + get { + return ResourceManager.GetString("Solution_WithProjectUsingAbsolutePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <Solution> + /// <Folder Name="/SolutionFolder1/"> + /// </Folder> + /// <Folder Name="/SolutionFolder1/NestedSolutionFolder/"> + /// <Project Path="ClassLibraryNestedSolutionFolder/ClassLibraryNestedSolutionFolder.csproj" /> + /// </Folder> + ///</Solution> + ///. + /// + public static string SolutionXml_WithNestedSolutionFolders { + get { + return ResourceManager.GetString("SolutionXml_WithNestedSolutionFolders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <Solution> + /// <Folder Name="/src/"> + /// <Project Path="src/dummy/dummy.csproj" /> + /// </Folder> + /// <Folder Name="/test/"> + /// <Project Path="test/dummy.Tests/dummy.Tests.csproj" /> + /// </Folder> + /// <Project Path="executable/executable.csproj" /> + ///</Solution> + ///. + /// + public static string SolutionXml_WithProjectsAndFolders { + get { + return ResourceManager.GetString("SolutionXml_WithProjectsAndFolders", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <Solution> + /// <Folder Name="/src/"> + /// <Project Path="src/dummy/dummy.csproj" /> + /// </Folder> + /// + /// + /// + /// <Folder Name="/test/"> + /// <Project Path="test/dummy.Tests/dummy.Tests.csproj" /> + /// </Folder> + /// + /// + /// + /// <Project Path="executable/executable.csproj" /> + /// + /// + /// + /// + ///</Solution> + ///. + /// + public static string SolutionXml_WithProjectsAndFoldersAndAdditionalLines { + get { + return ResourceManager.GetString("SolutionXml_WithProjectsAndFoldersAndAdditionalLines", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <Solution> + /// <Project Path="C:/project/dummy/src/dummy/dummy.csproj" /> + ///</Solution> + ///. + /// + public static string SolutionXml_WithProjectUsingAbsolutePath { + get { + return ResourceManager.GetString("SolutionXml_WithProjectUsingAbsolutePath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <Solution> + /// <Project Path="WebApplication/WebApplication.csproj" Type="e6fdf86b-f3d1-11d4-8576-0002a516ece8"> + /// <Platform Project="AnyCPU" /> + /// </Project> + ///</Solution> + ///. + /// + public static string SolutionXml_WithProjectWithDifferentTypeId { + get { + return ResourceManager.GetString("SolutionXml_WithProjectWithDifferentTypeId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" standalone="no"?> + ///<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> + ///<properties> + ///<comment>TeamCity build properties without 'system.' prefix</comment> + ///<entry key="teamcity.build.properties.file">/Working/teamcity.build.configuration</entry> + ///<entry key="teamcity.configuration.properties.file">/Working/teamcity.config.configuration</entry> + ///<entry key="teamcity.runner.properties.file">/Working/teamcity.runner.configuration</entry> + ///</properties>. + /// + public static string TeamCity_Build_Properties_Xml { + get { + return ResourceManager.GetString("TeamCity_Build_Properties_Xml", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" standalone="no"?> + ///<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> + ///<properties> + ///<comment>TeamCity configuration parameters for build with id 812869</comment> + ///<entry key="build.counter">414</entry> + ///<entry key="build.number">3246</entry> + ///<entry key="teamcity.build.branch">pull/5</entry> + ///<entry key="teamcity.build.branch.is_default">true</entry> + ///<entry key="teamcity.build.vcs.branch.MyVcsRootName">refs/pull/5/merge</entry> + ///</properties>. + /// + public static string TeamCity_Config_Properties_Xml { + get { + return ResourceManager.GetString("TeamCity_Config_Properties_Xml", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" standalone="no"?> + ///<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> + ///<properties> + ///<comment>TeamCity configuration parameters for build with id 812869</comment> + ///<entry key="command.executable">run.cmd</entry> + ///</properties>. + /// + public static string TeamCity_Runner_Properties_Xml { + get { + return ResourceManager.GetString("TeamCity_Runner_Properties_Xml", resourceCulture); + } + } + /// /// Looks up a localized string similar to using System.Reflection; ///using System.Runtime.CompilerServices; @@ -443,12 +867,32 @@ internal static string Nuspec_ProjectFile { ///[assembly: AssemblyProduct("VisualStudioProduct")] ///[assembly: Assembl [rest of string was truncated]";. /// - internal static string VisualStudioAssemblyInfo { + public static string VisualStudioAssemblyInfo { get { return ResourceManager.GetString("VisualStudioAssemblyInfo", resourceCulture); } } + /// + /// Looks up a localized string similar to Imports System.Reflection + ///Imports System.Runtime.CompilerServices; + /// + ///' Information about this assembly is defined by the following attributes. + ///' Change them to the values specific to your project. + /// + ///<Assembly: AssemblyTitle("VisualStudioAssemblyTitle")> + ///<Assembly: AssemblyDescription("VisualStudioAssemblyDescription")> + ///<Assembly: AssemblyConfiguration("VisualStudioConfiguration")> + ///<Assembly: AssemblyCompany("VisualStudioCompany")> + ///<Assembly: AssemblyProduct("VisualStudioProduct")> + ///<Assembly: Assemb [rest of string was truncated]";. + /// + public static string VisualStudioAssemblyInfo_VB { + get { + return ResourceManager.GetString("VisualStudioAssemblyInfo_VB", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0"?> ///<doc> @@ -464,7 +908,7 @@ internal static string VisualStudioAssemblyInfo { /// <param name="projectPath">The project file path.</param> /// <returns>A parsed project.< [rest of string was truncated]";. /// - internal static string XmlDoc_ExampeCode_Cake_Common_Xml { + public static string XmlDoc_ExampeCode_Cake_Common_Xml { get { return ResourceManager.GetString("XmlDoc_ExampeCode_Cake_Common_Xml", resourceCulture); } @@ -481,7 +925,7 @@ internal static string XmlDoc_ExampeCode_Cake_Common_Xml { ///</configuration> ///. /// - internal static string XmlPeek_Xml { + public static string XmlPeek_Xml { get { return ResourceManager.GetString("XmlPeek_Xml", resourceCulture); } @@ -489,21 +933,39 @@ internal static string XmlPeek_Xml { /// /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8"?> - ///<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + ///<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "./Properties/PropertyList-1.0.dtd"> ///<plist version="1.0"> ///<dict> - /// <key>CFBundleDisplayName</key> - /// <string>Cake</string> + /// <key>CFBundleDisplayName</key> + /// <string>Cake</string> ///</dict> ///</plist> - ///. + /// . /// - internal static string XmlPeek_Xml_Dtd { + public static string XmlPeek_Xml_Dtd { get { return ResourceManager.GetString("XmlPeek_Xml_Dtd", resourceCulture); } } + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + /// <PropertyGroup> + /// <WebPublishMethod>FileSystem</WebPublishMethod> + /// <LastUsedBuildConfiguration>DeploymentTemplate</LastUsedBuildConfiguration> + /// <LastUsedPlatform>Any CPU</LastUsedPlatform> + /// <SiteUrlToLaunchAfterPublish /> + /// <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> + /// <ExcludeApp_Data>False</ExcludeApp_Data> + /// <publishUrl>C:\Deployment\Deploym [rest of string was truncated]";. + /// + public static string XmlPeek_Xml_With_Namespace { + get { + return ResourceManager.GetString("XmlPeek_Xml_With_Namespace", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8" ?> ///<configuration> @@ -513,7 +975,7 @@ internal static string XmlPeek_Xml_Dtd { /// </appSettings> ///</configuration>. /// - internal static string XmlPoke_Xml { + public static string XmlPoke_Xml { get { return ResourceManager.GetString("XmlPoke_Xml", resourceCulture); } @@ -521,20 +983,36 @@ internal static string XmlPoke_Xml { /// /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8"?> - ///<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> + ///<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "./Properties/PropertyList-1.0.dtd"> ///<plist version="1.0"> ///<dict> - /// <key>CFBundleDisplayName</key> - /// <string>Cake</string> + /// <key>CFBundleDisplayName</key> + /// <string>Cake</string> ///</dict> - ///</plist>. + ///</plist> + /// . /// - internal static string XmlPoke_Xml_Dtd { + public static string XmlPoke_Xml_Dtd { get { return ResourceManager.GetString("XmlPoke_Xml_Dtd", resourceCulture); } } + /// + /// Looks up a localized string similar to <configuration> + /// <appSettings> + /// <add key="server" value="testhost.somecompany.com" /> + /// <add key="test" value="true" /> + /// </appSettings> + /// <test>test value</test> + ///</configuration>. + /// + public static string XmlPoke_Xml_Without_Declaration { + get { + return ResourceManager.GetString("XmlPoke_Xml_Without_Declaration", resourceCulture); + } + } + /// /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?><html><body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE"><div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">Belgian Waffles /// - @@ -543,7 +1021,7 @@ internal static string XmlPoke_Xml_Dtd { /// 650 /// [rest of string was truncated]";. /// - internal static string XmlTransformation_Htm { + public static string XmlTransformation_Htm { get { return ResourceManager.GetString("XmlTransformation_Htm", resourceCulture); } @@ -558,7 +1036,7 @@ internal static string XmlTransformation_Htm { /// calories per serving) /// [rest of string was truncated]";. /// - internal static string XmlTransformation_Htm_NoXmlDeclaration { + public static string XmlTransformation_Htm_NoXmlDeclaration { get { return ResourceManager.GetString("XmlTransformation_Htm_NoXmlDeclaration", resourceCulture); } @@ -579,7 +1057,7 @@ internal static string XmlTransformation_Htm_NoXmlDeclaration { /// <description>Light Belgian waffles covered with strawberries and whipped cream</description> /// <calories>900</calories> [rest of string was truncated]";. /// - internal static string XmlTransformation_Xml { + public static string XmlTransformation_Xml { get { return ResourceManager.GetString("XmlTransformation_Xml", resourceCulture); } @@ -597,10 +1075,49 @@ internal static string XmlTransformation_Xml { /// </span> /// <xsl:value-of select="price" [rest of string was truncated]";. /// - internal static string XmlTransformation_Xsl { + public static string XmlTransformation_Xsl { get { return ResourceManager.GetString("XmlTransformation_Xsl", resourceCulture); } } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8"?> + ///<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + ///<xsl:output method="html" encoding="utf-8"/> + ///<xsl:param name="BackgroundColor"></xsl:param> + ///<xsl:param name="Color"></xsl:param> + /// + ///<xsl:template match="/"> + ///<html> + /// <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE"> + /// <xsl:for-each select="breakfast_menu/food"> + /// <div style="background-color:{$BackgroundColor};color:{$Color};padding:4px"> + /// [rest of string was truncated]";. + /// + public static string XmlTransformationWithArguments_Xsl { + get { + return ResourceManager.GetString("XmlTransformationWithArguments_Xsl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="UTF-8"?> + ///<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:input="http://example.com" exclude-result-prefixes="input" version="1.0"> + ///<xsl:output method="html" encoding="utf-8"/> + ///<xsl:param name="input:BackgroundColor"></xsl:param> + ///<xsl:param name="input:Color"></xsl:param> + /// + ///<xsl:template match="/"> + ///<html> + /// <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE"> + /// <xsl:for-each select="breakfast_menu/food"> + /// <di [rest of string was truncated]";. + /// + public static string XmlTransformationWithArgumentsAndNamespace_Xsl { + get { + return ResourceManager.GetString("XmlTransformationWithArgumentsAndNamespace_Xsl", resourceCulture); + } + } } } diff --git a/src/Cake.Common.Tests/Properties/Resources.resx b/src/Cake.Common.Tests/Properties/Resources.resx index 4ca8c85492..3a99a4b076 100644 --- a/src/Cake.Common.Tests/Properties/Resources.resx +++ b/src/Cake.Common.Tests/Properties/Resources.resx @@ -143,6 +143,10 @@ <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> + <dependencies> + <dependency id="Dependency1" version="1.0.0" /> + <dependency id="Dependency2" version="[2.0.0]" /> + </dependencies> </metadata> <files> <file src="tools\**" target="tools" /> @@ -175,6 +179,10 @@ Line #3]]></releaseNotes> <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> + <dependencies> + <dependency id="Dependency1" version="1.0.0" /> + <dependency id="Dependency2" version="[2.0.0]" /> + </dependencies> </metadata> <files> <file src="tools\**" target="tools" /> @@ -289,18 +297,21 @@ Line #3]]></releaseNotes> <title>The title</title> <authors>Author #1,Author #2</authors> <owners>Owner #1,Owner #2</owners> - <description>The description</description> - <summary>The summary</summary> <licenseUrl>https://license.com</licenseUrl> <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> <iconUrl>https://icon.com</iconUrl> - <developmentDependency>true</developmentDependency> <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <serviceable>true</serviceable> <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> - <tags>Tag1 Tag2 Tag3</tags> </metadata> <files> <file src="Cake.Core.dll" target="lib/net45" /> @@ -319,18 +330,21 @@ Line #3]]></releaseNotes> <title>The title</title> <authors>Author #1,Author #2</authors> <owners>Owner #1,Owner #2</owners> - <description>The description</description> - <summary>The summary</summary> <licenseUrl>https://license.com</licenseUrl> <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> <iconUrl>https://icon.com</iconUrl> - <developmentDependency>true</developmentDependency> <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <serviceable>true</serviceable> <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> - <tags>Tag1 Tag2 Tag3</tags> <dependencies> <dependency id="Test1" version="1.0.0" /> <dependency id="Test2" version="[1.0.0]" /> @@ -353,18 +367,21 @@ Line #3]]></releaseNotes> <title>The title</title> <authors>Author #1,Author #2</authors> <owners>Owner #1,Owner #2</owners> - <description>The description</description> - <summary>The summary</summary> <licenseUrl>https://license.com</licenseUrl> <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> <iconUrl>https://icon.com</iconUrl> - <developmentDependency>true</developmentDependency> <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <serviceable>true</serviceable> <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> - <tags>Tag1 Tag2 Tag3</tags> </metadata> <files> <file src="Cake.Core.dll" target="lib/net45" /> @@ -383,18 +400,20 @@ Line #3]]></releaseNotes> <title>The title</title> <authors>Author #1,Author #2</authors> <owners>Owner #1,Owner #2</owners> - <description>The description</description> - <summary>The summary</summary> <licenseUrl>https://license.com</licenseUrl> <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> <iconUrl>https://icon.com</iconUrl> - <developmentDependency>true</developmentDependency> <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> <releaseNotes><![CDATA[Line #1 Line #2 Line #3]]></releaseNotes> - <tags>Tag1 Tag2 Tag3</tags> <dependencies> <dependency id="Test1" version="1.0.0" /> <dependency id="Test2" version="[1.0.0]" /> @@ -525,9 +544,9 @@ Line #3]]></releaseNotes> </configuration> - <?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> + <?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "./Properties/PropertyList-1.0.dtd"> +<plist version="1.0"> <dict> <key>CFBundleDisplayName</key> <string>Cake</string> @@ -547,9 +566,9 @@ Line #3]]></releaseNotes> - <?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> + <?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "./Properties/PropertyList-1.0.dtd"> +<plist version="1.0"> <dict> <key>CFBundleDisplayName</key> <string>Cake</string> @@ -676,7 +695,7 @@ Line #3]]></releaseNotes> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> </Target> @@ -765,6 +784,26 @@ Line #3]]></releaseNotes> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Class1.cs" /> </ItemGroup> + <ItemGroup> + <Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>/opt/microsoft/powershell/6/System.Management.Automation.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Cake.Common\Cake.Common.csproj"> + <Project>{ABC3F1CB-F84E-43ED-A120-0CCFE344D250}</Project> + <Name>Cake.Common</Name> + </ProjectReference> + <ProjectReference Include="..\Cake.Common\Cake.Core.csproj"> + <Project>{6C54B9D8-A47D-45D8-82EF-3ADAF7FDD530}</Project> + <Name>Cake.Core</Name> + <Private>True</Private> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> </Project> @@ -824,4 +863,734 @@ using System.Runtime.CompilerServices; //[assembly: AssemblyDelaySign(false)] //[assembly: AssemblyKeyFile("")] + + Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2400A22B-695E-4BDF-93CB-8757F5FB3FB7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69930DD1-1688-4407-B4AB-B9E2C0BFB284}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "src\dummy\dummy.csproj", "{ADCB37DA-2469-462F-99F6-9D4FB7691A3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy.Tests", "test\dummy.Tests\dummy.Tests.csproj", "{5D553DC6-36AB-4823-85BD-A33F57C81381}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "executable", "executable\executable.csproj", "{CF305C72-F3E0-44AA-9474-6F12C5276F9F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Release|Any CPU.Build.0 = Release|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Release|Any CPU.Build.0 = Release|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B} = {2400A22B-695E-4BDF-93CB-8757F5FB3FB7} + {5D553DC6-36AB-4823-85BD-A33F57C81381} = {69930DD1-1688-4407-B4AB-B9E2C0BFB284} + EndGlobalSection +EndGlobal + + + Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2400A22B-695E-4BDF-93CB-8757F5FB3FB7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69930DD1-1688-4407-B4AB-B9E2C0BFB284}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "src\dummy\dummy.csproj", "{ADCB37DA-2469-462F-99F6-9D4FB7691A3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy.Tests", "test\dummy.Tests\dummy.Tests.csproj", "{5D553DC6-36AB-4823-85BD-A33F57C81381}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "executable", "executable\executable.csproj", "{CF305C72-F3E0-44AA-9474-6F12C5276F9F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B}.Release|Any CPU.Build.0 = Release|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D553DC6-36AB-4823-85BD-A33F57C81381}.Release|Any CPU.Build.0 = Release|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF305C72-F3E0-44AA-9474-6F12C5276F9F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + + {ADCB37DA-2469-462F-99F6-9D4FB7691A3B} = {2400A22B-695E-4BDF-93CB-8757F5FB3FB7} + {5D553DC6-36AB-4823-85BD-A33F57C81381} = {69930DD1-1688-4407-B4AB-B9E2C0BFB284} + EndGlobalSection +EndGlobal + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>nonexisting</id> + <version>1.0.0</version> + <authors>Author #1,Author #2</authors> + <description>The description</description> + <dependencies> + <group targetFramework="net452"> + <dependency id="Test1" version="1.0.0" /> + </group> + <group targetFramework="net46"> + <dependency id="Test1" version="1.0.0" /> + </group> + </dependencies> + </metadata> + <files> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <dependencies> + <group targetFramework="net452"> + <dependency id="Test1" version="1.0.0" /> + </group> + <group targetFramework="net46"> + <dependency id="Test2" version="[1.0.0]" /> + </group> + </dependencies> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package> + <metadata> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <dependencies> + <group targetFramework="net452"> + <dependency id="Test1" version="1.0.0" /> + </group> + <group targetFramework="net46"> + <dependency id="Test2" version="[1.0.0]" /> + </group> + </dependencies> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + Imports System.Reflection +Imports System.Runtime.CompilerServices; + +' Information about this assembly is defined by the following attributes. +' Change them to the values specific to your project. + +<Assembly: AssemblyTitle("VisualStudioAssemblyTitle")> +<Assembly: AssemblyDescription("VisualStudioAssemblyDescription")> +<Assembly: AssemblyConfiguration("VisualStudioConfiguration")> +<Assembly: AssemblyCompany("VisualStudioCompany")> +<Assembly: AssemblyProduct("VisualStudioProduct")> +<Assembly: AssemblyCopyright("VisualStudioCopyright")> +<Assembly: AssemblyTrademark("VisualStudioTrademark")> +<Assembly: AssemblyCulture("en-US")> + +' The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +' The form "{Major}.{Minor}.*" will automatically update the build and revision, +' and "{Major}.{Minor}.{Build}.*" will update just the revision. + +<Assembly: AssemblyVersion("1.0.13")> + +' The following attributes are used to specify the signing key for the assembly, +' if desired. See the Mono documentation for more information about signing. + +'<Assembly: AssemblyDelaySign(false)> +'<Assembly: AssemblyKeyFile("")> + + + <?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <WebPublishMethod>FileSystem</WebPublishMethod> + <LastUsedBuildConfiguration>DeploymentTemplate</LastUsedBuildConfiguration> + <LastUsedPlatform>Any CPU</LastUsedPlatform> + <SiteUrlToLaunchAfterPublish /> + <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> + <ExcludeApp_Data>False</ExcludeApp_Data> + <publishUrl>C:\Deployment\DeploymentTemplate\WebApi</publishUrl> + <DeleteExistingFiles>False</DeleteExistingFiles> + </PropertyGroup> +</Project> + + + <configuration> + <appSettings> + <add key="server" value="testhost.somecompany.com" /> + <add key="test" value="true" /> + </appSettings> + <test>test value</test> +</configuration> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <repository type="git" url="https://test" commit="0000000000000000000000000000000000000000" branch="master" /> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + //------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("FullyQualifiedCompanyAttribute")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("FullyQualifiedConfigurationAttribute")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.3.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.3.0")] +[assembly: System.Reflection.AssemblyProductAttribute("FullyQualifiedProductAttribute")] +[assembly: System.Reflection.AssemblyTitleAttribute("FullyQualifiedTitleAttribute")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.3.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <frameworkAssemblies> + <frameworkAssembly assemblyName="System.Net" targetFramework="net40" /> + <frameworkAssembly assemblyName="System.Net.Cookie" targetFramework="net47" /> + </frameworkAssemblies> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <license type="expression" version="V1">MIT</license> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <packageTypes> + <packageType name="package1" version="V1" /> + <packageType name="package2" version="V2" /> + </packageTypes> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <references> + <reference file="Cake.Core.dll" /> + <reference file="Cake.Core.xml" /> + </references> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata minClientVersion="3.3" xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <contentFiles> + <files include="**/images/*.*" buildAction="EmbeddedResource" copyToOutput="false" flatten="false" /> + <files include="cs/**/*.*" buildAction="Compile" copyToOutput="true" flatten="false" /> + </contentFiles> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + <references> + <group targetFramework="net452"> + <reference file="Cake.Core.dll" /> + </group> + <group targetFramework="net46"> + <reference file="Cake.Core.xml" /> + </group> + </references> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + + + <?xml version="1.0" encoding="utf-8" standalone="no"?> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> +<comment>TeamCity configuration parameters for build with id 812869</comment> +<entry key="build.counter">414</entry> +<entry key="build.number">3246</entry> +<entry key="teamcity.build.branch">pull/5</entry> +<entry key="teamcity.build.branch.is_default">true</entry> +<entry key="teamcity.build.vcs.branch.MyVcsRootName">refs/pull/5/merge</entry> +</properties> + + + <?xml version="1.0" encoding="utf-8" standalone="no"?> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> +<comment>TeamCity build properties without 'system.' prefix</comment> +<entry key="teamcity.build.properties.file">/Working/teamcity.build.configuration</entry> +<entry key="teamcity.configuration.properties.file">/Working/teamcity.config.configuration</entry> +<entry key="teamcity.runner.properties.file">/Working/teamcity.runner.configuration</entry> +</properties> + + + <?xml version="1.0" encoding="utf-8" standalone="no"?> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> +<comment>TeamCity configuration parameters for build with id 812869</comment> +<entry key="command.executable">run.cmd</entry> +</properties> + + + Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31702.278 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dummy", "C:\project\dummy\src\dummy\dummy.csproj", "{A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A651D04F-DF2B-44C4-A1A2-2ED31CC3F128}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E70E9E11-7FE4-43FA-8552-347EDAF69F86} + EndGlobalSection +EndGlobal + + + <?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<xsl:output method="html" encoding="utf-8"/> +<xsl:param name="BackgroundColor"></xsl:param> +<xsl:param name="Color"></xsl:param> + +<xsl:template match="/"> +<html> + <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE"> + <xsl:for-each select="breakfast_menu/food"> + <div style="background-color:{$BackgroundColor};color:{$Color};padding:4px"> + <span style="font-weight:bold"> + <xsl:value-of select="name" /> + - + </span> + <xsl:value-of select="price" /> + </div> + <div style="margin-left:20px;margin-bottom:1em;font-size:10pt"> + <p> + <xsl:value-of select="description" /> + <span style="font-style:italic"> + ( + <xsl:value-of select="calories" /> + calories per serving) + </span> + </p> + </div> + </xsl:for-each> + </body> +</html> +</xsl:template> +</xsl:stylesheet> + + + <?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:input="http://example.com" exclude-result-prefixes="input" version="1.0"> +<xsl:output method="html" encoding="utf-8"/> +<xsl:param name="input:BackgroundColor"></xsl:param> +<xsl:param name="input:Color"></xsl:param> + +<xsl:template match="/"> +<html> + <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE"> + <xsl:for-each select="breakfast_menu/food"> + <div style="background-color:{$input:BackgroundColor};color:{$input:Color};padding:4px"> + <span style="font-weight:bold"> + <xsl:value-of select="name" /> + - + </span> + <xsl:value-of select="price" /> + </div> + <div style="margin-left:20px;margin-bottom:1em;font-size:10pt"> + <p> + <xsl:value-of select="description" /> + <span style="font-style:italic"> + ( + <xsl:value-of select="calories" /> + calories per serving) + </span> + </p> + </div> + </xsl:for-each> + </body> +</html> +</xsl:template> +</xsl:stylesheet> + + + <Solution> + <Folder Name="/src/"> + <Project Path="src/dummy/dummy.csproj" /> + </Folder> + <Folder Name="/test/"> + <Project Path="test/dummy.Tests/dummy.Tests.csproj" /> + </Folder> + <Project Path="executable/executable.csproj" /> +</Solution> + + + + <Solution> + <Folder Name="/src/"> + <Project Path="src/dummy/dummy.csproj" /> + </Folder> + + + + <Folder Name="/test/"> + <Project Path="test/dummy.Tests/dummy.Tests.csproj" /> + </Folder> + + + + <Project Path="executable/executable.csproj" /> + + + + +</Solution> + + + + <Solution> + <Project Path="C:/project/dummy/src/dummy/dummy.csproj" /> +</Solution> + + + + <Solution> + <Project Path="WebApplication/WebApplication.csproj" Type="e6fdf86b-f3d1-11d4-8576-0002a516ece8"> + <Platform Project="AnyCPU" /> + </Project> +</Solution> + + + + <Solution> + <Folder Name="/SolutionFolder1/"> + </Folder> + <Folder Name="/SolutionFolder1/NestedSolutionFolder/"> + <Project Path="ClassLibraryNestedSolutionFolder/ClassLibraryNestedSolutionFolder.csproj" /> + </Folder> +</Solution> + + + + <?xml version="1.0" encoding="utf-8"?> +<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <id>The ID</id> + <version>The version</version> + <title>The title</title> + <authors>Author #1,Author #2</authors> + <owners>Owner #1,Owner #2</owners> + <licenseUrl>https://license.com</licenseUrl> + <projectUrl>https://project.com</projectUrl> + <icon>images\icon.png</icon> + <iconUrl>https://icon.com</iconUrl> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <developmentDependency>true</developmentDependency> + <description>The description</description> + <summary>The summary</summary> + <copyright>The copyright</copyright> + <language>en-us</language> + <tags>Tag1 Tag2 Tag3</tags> + <serviceable>true</serviceable> + <readme>NuGet.org.md</readme> + <releaseNotes><![CDATA[Line #1 +Line #2 +Line #3]]></releaseNotes> + </metadata> + <files> + <file src="Cake.Core.dll" target="lib/net45" /> + <file src="Cake.Core.xml" target="lib/net45" /> + <file src="Cake.Core.pdb" target="lib/net45" /> + <file src="LICENSE" /> + </files> +</package> + \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/ArgumentAliasesTests.cs b/src/Cake.Common.Tests/Unit/ArgumentAliasesTests.cs new file mode 100644 index 0000000000..0d58f75a09 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/ArgumentAliasesTests.cs @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit +{ + public sealed class ArgumentAliases + { + private const string WorkingDirectoryArgumentName = "workdir"; + private const string WorkingDirectoryArgumentValue = "c:/data/work"; + private const string OutputFileArgumentName = "outputFile"; + private const string OutputFileArgumentValue = "c:/data/work/output.txt"; + private const string VerboseArgumentName = "verbose"; + private const string VerboseArgumentValueOne = "enabled"; + private const string VerboseArgumentValueTwo = "full"; + + public sealed class TheArgumentOfDirectoryPathMethod + { + [Fact] + public void Should_Convert_A_String_Value_To_A_DirectoryPath_If_Argument_Exist() + { + var context = Substitute.For(); + context.Arguments.GetArguments(WorkingDirectoryArgumentName) + .Returns(new[] { WorkingDirectoryArgumentValue }); + + var result = context.Argument(WorkingDirectoryArgumentName); + + Assert.Equal(WorkingDirectoryArgumentValue, result.FullPath); + } + } + + public sealed class TheArgumentOfFilePathMethod + { + [Fact] + public void Should_Convert_A_String_Value_To_A_FilePath_If_Argument_Exist() + { + var context = Substitute.For(); + context.Arguments.GetArguments(OutputFileArgumentName) + .Returns(new[] { OutputFileArgumentValue }); + + var result = context.Argument(OutputFileArgumentName); + + Assert.Equal(OutputFileArgumentValue, result.FullPath); + } + } + + public sealed class TheArgumentCollectionMethod + { + [Fact] + public void Should_Return_An_Arguments_Dictionary() + { + var context = Substitute.For(); + context.Arguments.GetArguments() + .Returns(new Dictionary> + { + { WorkingDirectoryArgumentName, new[] { WorkingDirectoryArgumentValue } }, + { VerboseArgumentName, new[] { VerboseArgumentValueOne, VerboseArgumentValueTwo } }, + }); + + var result = context.Arguments(); + var wdValues = result[WorkingDirectoryArgumentName]; + var vValues = result[VerboseArgumentName]; + + Assert.Equal(WorkingDirectoryArgumentValue, wdValues.First()); + Assert.Equal(2, vValues.Count); + Assert.Equal(VerboseArgumentValueOne, vValues.ElementAt(0)); + Assert.Equal(VerboseArgumentValueTwo, vValues.ElementAt(1)); + } + + [Fact] + public void Should_Return_An_Arguments_With_Single_DefaultValue() + { + var context = Substitute.For(); + context.Arguments.GetArguments(Arg.Any()) + .Returns(Array.Empty()); + + // Given + var expect = new[] { "a" }; + + // When + var arg = context.Arguments("nonexistingmultipleargs", expect[0]); + + // Then + Assert.Equal(expect, arg); + } + + [Fact] + public void Should_Return_An_Arguments_With_Multiple_DefaultValue() + { + var context = Substitute.For(); + context.Arguments.GetArguments(Arg.Any()) + .Returns(Array.Empty()); + + // Given + var expect = new[] { "a", "b" }; + + // When + var arg = context.Arguments("nonexistingmultipleargs", expect); + + // Then + Assert.Equal(expect, arg); + } + + [Fact] + public void Should_Return_An_Arguments_With_Lazy_DefaultValue() + { + var context = Substitute.For(); + context.Arguments.GetArguments(Arg.Any()) + .Returns(Array.Empty()); + + // Given + var expect = new[] { "a", "b" }; + + // When + var arg = context.Arguments("nonexistingmultipleargs", _ => expect); + + // Then + Assert.Equal(expect, arg); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/AppVeyorProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/AppVeyorProviderTests.cs index e4d6c4ac09..84047d8e17 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/AppVeyorProviderTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/AppVeyorProviderTests.cs @@ -1,9 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Common.Build.AppVeyor; using Cake.Common.Tests.Fixtures.Build; using Cake.Core; +using Cake.Core.Diagnostics; using Cake.Core.IO; using NSubstitute; using Xunit; @@ -18,22 +21,36 @@ public sealed class TheConstructor public void Should_Throw_If_Environment_Is_Null() { // Given, When + var cakeLog = Substitute.For(); var processRunner = Substitute.For(); - var result = Record.Exception(() => new AppVeyorProvider(null, processRunner)); + var result = Record.Exception(() => new AppVeyorProvider(null, processRunner, cakeLog)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] public void Should_Throw_If_Process_Runner_Is_Null() { // Given, When + var cakeLog = Substitute.For(); var environment = Substitute.For(); - var result = Record.Exception(() => new AppVeyorProvider(environment, null)); + var result = Record.Exception(() => new AppVeyorProvider(environment, null, cakeLog)); // Then - Assert.IsArgumentNullException(result, "processRunner"); + AssertEx.IsArgumentNullException(result, "processRunner"); + } + + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given, When + var processRunner = Substitute.For(); + var environment = Substitute.For(); + var result = Record.Exception(() => new AppVeyorProvider(environment, processRunner, null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); } } @@ -99,7 +116,21 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => appVeyor.UploadArtifact(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public void Should_Throw_If_Path_Is_Null_WithSettings() + { + // Given + var fixture = new AppVeyorFixture(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + var result = Record.Exception(() => appVeyor.UploadArtifact(null, settings => settings.SetArtifactType(AppVeyorUploadArtifactType.Auto))); + + // Then + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -113,7 +144,7 @@ public void Should_Throw_If_Not_Running_On_AppVeyor() var result = Record.Exception(() => appVeyor.UploadArtifact("./file.zip")); // Then - Assert.IsExceptionWithMessage(result, + AssertEx.IsExceptionWithMessage(result, "The current build is not running on AppVeyor."); } @@ -132,7 +163,61 @@ public void Should_Upload_Artifact() fixture.ProcessRunner.Received(1).Start( Arg.Is(p => p.FullPath == "appveyor"), Arg.Is(p => p.Arguments.Render() - == "PushArtifact -Path \"/Working/file.zip\" -FileName \"file.zip\"")); + == "PushArtifact \"/Working/file.zip\" -Type Auto")); + } + + [Theory] + [InlineData(AppVeyorUploadArtifactType.Auto, "Auto")] + [InlineData(AppVeyorUploadArtifactType.WebDeployPackage, "WebDeployPackage")] + [InlineData(AppVeyorUploadArtifactType.NuGetPackage, "NuGetPackage")] + public void Should_Upload_Artifact_For_ArtifactType(AppVeyorUploadArtifactType type, string arg) + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + appVeyor.UploadArtifact("./file.zip", settings => settings.SetArtifactType(type)); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() + == $"PushArtifact \"/Working/file.zip\" -Type {arg}")); + } + + [Fact] + public void Should_Upload_Artifact_For_DeploymentName() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + appVeyor.UploadArtifact("./file.zip", settings => settings.SetDeploymentName("MyApp.Web")); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() + == "PushArtifact \"/Working/file.zip\" -Type Auto -DeploymentName \"MyApp.Web\"")); + } + + [Fact] + public void Should_Upload_Artifact_Throw_Exception_For_DeploymentName_Containing_Spaces() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + var result = Record.Exception(() => appVeyor.UploadArtifact("./file.zip", settings => settings.SetDeploymentName("MyApp Web"))); + + // Then + AssertEx.IsCakeException(result, "The deployment name can not contain spaces"); } } @@ -149,7 +234,7 @@ public void Should_Throw_If_Build_Version_Is_Null() var result = Record.Exception(() => appVeyor.UpdateBuildVersion(null)); // Then - Assert.IsArgumentNullException(result, "version"); + AssertEx.IsArgumentNullException(result, "version"); } [Theory] @@ -165,7 +250,7 @@ public void Should_Throw_If_Build_Version_Is_Empty(string version) var result = Record.Exception(() => appVeyor.UpdateBuildVersion(version)); // Then - Assert.IsExceptionWithMessage(result, + AssertEx.IsExceptionWithMessage(result, "The build version cannot be empty."); } @@ -180,7 +265,7 @@ public void Should_Throw_If_Not_Running_On_AppVeyor() var result = Record.Exception(() => appVeyor.UpdateBuildVersion("build-123")); // Then - Assert.IsExceptionWithMessage(result, + AssertEx.IsExceptionWithMessage(result, "The current build is not running on AppVeyor."); } @@ -215,7 +300,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => appVeyor.UploadTestResults(null, AppVeyorTestResultsType.XUnit)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -229,9 +314,152 @@ public void Should_Throw_If_Not_Running_On_AppVeyor() var result = Record.Exception(() => appVeyor.UploadTestResults("./file.xml", AppVeyorTestResultsType.XUnit)); // Then - Assert.IsExceptionWithMessage(result, + AssertEx.IsExceptionWithMessage(result, + "The current build is not running on AppVeyor."); + } + } + + public sealed class TheAddMessageMethod + { + [Fact] + public void Should_Throw_If_Message_Is_Null() + { + // Given + var fixture = new AppVeyorFixture(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + var result = Record.Exception(() => appVeyor.AddMessage(null)); + + // Then + AssertEx.IsArgumentNullException(result, "message"); + } + + [Fact] + public void Should_Throw_If_Message_Is_Empty() + { + // Given + var fixture = new AppVeyorFixture(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + var result = Record.Exception(() => appVeyor.AddMessage("")); + + // Then + AssertEx.IsCakeException(result, "The message cannot be empty."); + } + + [Fact] + public void Should_Throw_If_Not_Running_On_AppVeyor() + { + // Given + var fixture = new AppVeyorFixture(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + var result = Record.Exception(() => appVeyor.AddMessage("Hello world")); + + // Then + AssertEx.IsExceptionWithMessage(result, "The current build is not running on AppVeyor."); } + + [Theory] + [InlineData("Hello world", AppVeyorMessageCategoryType.Information, null, "\"Hello world\" -Category \"Information\"")] + [InlineData("Hello world", AppVeyorMessageCategoryType.Warning, null, "\"Hello world\" -Category \"Warning\"")] + [InlineData("Hello world", AppVeyorMessageCategoryType.Error, null, "\"Hello world\" -Category \"Error\"")] + [InlineData("Hello world", AppVeyorMessageCategoryType.Error, "Details of message", "\"Hello world\" -Category \"Error\" -Details \"Details of message\"")] + public void Should_Add_Message(string message, AppVeyorMessageCategoryType category, string details, string args) + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + + // When + appVeyor.AddMessage(message, category, details); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() == $"AddMessage {args}")); + } + + [Fact] + public void Should_Add_InformationalMessage() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + const string message = "Hello world"; + + // When + appVeyor.AddInformationalMessage(message); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() == + $"AddMessage \"{message}\" -Category \"Information\"")); + } + + [Fact] + public void Should_Add_WarningMessage() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + const string message = "Hello world"; + + // When + appVeyor.AddWarningMessage(message); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() == + $"AddMessage \"{message}\" -Category \"Warning\"")); + } + + [Fact] + public void Should_Add_ErrorMessage() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + const string message = "Hello world"; + + // When + appVeyor.AddErrorMessage(message); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() == $"AddMessage \"{message}\" -Category \"Error\"")); + } + + [Fact] + public void Should_Add_ErrorMessageWithException() + { + // Given + var fixture = new AppVeyorFixture(); + fixture.IsRunningOnAppVeyor(); + var appVeyor = fixture.CreateAppVeyorService(); + const string message = "Hello world"; + var exception = new CakeException("This is an exception", new ArgumentException()); + + // When + appVeyor.AddErrorMessage(message, exception); + + // Then + fixture.ProcessRunner.Received(1).Start( + Arg.Is(p => p.FullPath == "appveyor"), + Arg.Is(p => p.Arguments.Render() == + $"AddMessage \"{message}\" -Category \"Error\" -Details \"{exception.ToString()}\"")); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorBuildInfoTests.cs index 45a332d9e3..187a534f1b 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorBuildInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -72,4 +73,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorCommitInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorCommitInfoTests.cs index c794f427dd..251d9c7cf8 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorCommitInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorCommitInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -104,4 +105,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorEnvironmentInfoTests.cs index e3c947428c..a1488a9d10 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorEnvironmentInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorEnvironmentInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using NSubstitute; using Xunit; @@ -176,4 +177,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorProjectInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorProjectInfoTests.cs index 76e9c352ce..1e33215b6e 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorProjectInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorProjectInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -56,4 +57,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorPullRequestInfoTests.cs index 7ef5c71856..8c5089cf56 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorPullRequestInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorPullRequestInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using NSubstitute; using Xunit; @@ -61,4 +62,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorRepositoryInfoTests.cs index 4ba56752d7..3e14e8a27a 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorRepositoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorRepositoryInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -104,4 +105,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorTagInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorTagInfoTests.cs index 5e26aa4510..3581409cfd 100644 --- a/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorTagInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/AppVeyor/Data/AppVeyorTagInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using NSubstitute; using Xunit; @@ -48,4 +49,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesCommandTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesCommandTests.cs new file mode 100644 index 0000000000..b88292631a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesCommandTests.cs @@ -0,0 +1,719 @@ +using System; +using System.Linq; +using Cake.Common.Build.AzurePipelines; +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Common.Tests.Fakes; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines +{ + public sealed class AzurePipelinesCommandTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var writer = new FakeBuildSystemServiceMessageWriter(); + var result = Record.Exception(() => new AzurePipelinesCommands(null, writer)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var result = Record.Exception(() => new AzurePipelinesCommands(new FakeEnvironment(PlatformFamily.Unknown), null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + } + + public sealed class TheCommands + { + [Fact] + public void Should_Not_Be_Null() + { + // Given + var fixture = new AzurePipelinesFixture(); + + // When + var service = fixture.CreateAzurePipelinesService(); + + // Then + Assert.NotNull(service.Commands); + } + + [Theory] + [InlineData("warning")] + [InlineData("message")] + public void Should_Log_Warning_Message(string msg) + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.WriteWarning(msg); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logissue type=warning;]{msg}"); + } + + [Fact] + public void Should_Log_Warning_Message_With_Data() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.WriteWarning("build warning", new AzurePipelinesMessageData + { + SourcePath = "./code file.cs", + LineNumber = 5, + ColumnNumber = 12, + ErrorCode = 9 + }); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logissue sourcepath=./code file.cs;linenumber=5;columnnumber=12;code=9;type=warning;]build warning"); + } + + [Theory] + [InlineData("error")] + [InlineData("message")] + public void Should_Log_Error_Message(string msg) + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.WriteError(msg); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logissue type=error;]{msg}"); + } + + [Fact] + public void Should_Log_Error_Message_With_Data() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.WriteError("build error", new AzurePipelinesMessageData + { + SourcePath = "./code.cs", + LineNumber = 1, + ColumnNumber = 2, + ErrorCode = 3 + }); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logissue sourcepath=./code.cs;linenumber=1;columnnumber=2;code=3;type=error;]build error"); + } + + [Fact] + public void Should_Begin_Group_With_Name() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.BeginGroup("Example Group"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##[group]Example Group"); + } + + [Fact] + public void Should_End_Group() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.EndGroup(); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##[endgroup]"); + } + + [Fact] + public void Should_Section_With_Name() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.Section("Example Section"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##[section]Example Section"); + } + + [Fact] + public void Should_Set_Current_Progress() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.SetProgress(75, "Testing Provider"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.setprogress value=75;]Testing Provider"); + } + + [Fact] + public void Should_Complete_Current_Task() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.CompleteCurrentTask(); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.complete]DONE"); + } + + [Fact] + public void Should_Complete_Current_Task_With_Status() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.CompleteCurrentTask(AzurePipelinesTaskResult.Failed); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.complete result=Failed;]DONE"); + } + + [Fact] + public void Should_Create_New_Record() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + var guid = service.Commands.CreateNewRecord("New record", "build", 1); + + // Then + Assert.NotNull(guid); + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logdetail id={guid.ToString()};name=New record;type=build;order=1;]create new timeline record"); + } + + [Fact] + public void Should_Create_New_Record_With_Data() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var date = DateTime.UtcNow; + + // When + var guid = service.Commands.CreateNewRecord("New record", "build", 2, new AzurePipelinesRecordData + { + StartTime = date, + Progress = 75, + Status = AzurePipelinesTaskStatus.Initialized + }); + + // Then + Assert.NotNull(guid); + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logdetail starttime={date.ToString()};progress=75;state=Initialized;id={guid.ToString()};name=New record;type=build;order=2;]create new timeline record"); + } + + [Fact] + public void Should_Update_Existing_Record() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var guid = Guid.NewGuid(); + var parent = Guid.NewGuid(); + + // When + service.Commands.UpdateRecord(guid, new AzurePipelinesRecordData + { + Progress = 95, + Status = AzurePipelinesTaskStatus.InProgress, + ParentRecord = parent + }); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.logdetail parentid={parent.ToString()};progress=95;state=InProgress;id={guid.ToString()};]update"); + } + + [Fact] + public void Should_Set_Variable() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.SetVariable("varname", "VarValue"); + + // Then + Assert.Contains(fixture.Writer.Entries, + m => m == $"##vso[task.setvariable variable=varname;]VarValue"); + } + + [Fact] + public void Should_Set_Secret_Variable() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.SetSecretVariable("Secret Variable", "Secret Value"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == "##vso[task.setvariable variable=Secret Variable;issecret=true;]Secret Value"); + } + + [Fact] + public void Should_Set_Output_Variable() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.SetOutputVariable("Output Variable", "Output Value"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == "##vso[task.setvariable variable=Output Variable;isOutput=true;]Output Value"); + } + + [Fact] + public void Should_Upload_Task_Summary() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = FilePath.FromString("./summary.md").MakeAbsolute(fixture.Environment); + + // When + service.Commands.UploadTaskSummary("./summary.md"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.uploadsummary]{path}"); + } + + [Fact] + public void Should_Upload_Task_Log() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = FilePath.FromString("./logs/task.log").MakeAbsolute(fixture.Environment); + + // When + service.Commands.UploadTaskLogFile("./logs/task.log"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[task.uploadfile]{path}"); + } + + [Theory] + [InlineData("drop", AzurePipelinesArtifactType.Container, "./drop")] + [InlineData("artifact", AzurePipelinesArtifactType.FilePath, "./dist/build/artifact.file")] + [InlineData("ref", AzurePipelinesArtifactType.GitRef, "895a00ec66af875c1593a7563beb0edee400aba0")] + public void Should_Link_Build_Artifacts(string name, AzurePipelinesArtifactType type, string location) + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.LinkArtifact(name, type, location); + + // Then + Assert.Contains(fixture.Writer.Entries, + m => m == $"##vso[artifact.associate artifactname={name};type={type};]{location}"); + } + + [Fact] + public void Should_Upload_To_Container() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = FilePath.FromString("./dist/package.nupkg").MakeAbsolute(fixture.Environment).FullPath; + + // When + service.Commands.UploadArtifact("packages", "./dist/package.nupkg"); + + // Then + Assert.Contains(fixture.Writer.Entries, + m => m == $"##vso[artifact.upload containerfolder=packages;]{path}"); + } + + [Fact] + public void Should_Upload_To_Container_Artifact() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = FilePath.FromString("./artifacts/results.trx").MakeAbsolute(fixture.Environment).FullPath; + + // When + service.Commands.UploadArtifact("tests", "./artifacts/results.trx", "Test Results"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[artifact.upload containerfolder=tests;artifactname=Test Results;]{path}"); + } + + [Fact] + public void UploadArtifactDirectory_Should_Throw_If_Directory_Is_Null() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + var result = Record.Exception(() => service.Commands.UploadArtifactDirectory(null)); + + // Then + AssertEx.IsArgumentNullException(result, "directory"); + } + + [Fact] + public void Should_Upload_Directory_As_Container() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = DirectoryPath.FromString("./artifacts/Packages").MakeAbsolute(fixture.Environment).FullPath; + + // When + service.Commands.UploadArtifactDirectory("./artifacts/Packages"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[artifact.upload containerfolder=Packages;artifactname=Packages;]{path}"); + } + + [Fact] + public void UploadArtifactDirectory_With_ArtifactName_Should_Throw_If_Directory_Is_Null() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + var result = Record.Exception(() => service.Commands.UploadArtifactDirectory(null, "Packages")); + + // Then + AssertEx.IsArgumentNullException(result, "directory"); + } + + [Fact] + public void UploadArtifactDirectory_Should_Throw_If_ArtifactName_Is_Null() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + var result = Record.Exception(() => service.Commands.UploadArtifactDirectory("./artifacts/Packages", null)); + + // Then + AssertEx.IsArgumentNullException(result, "artifactName"); + } + + [Fact] + public void Should_Upload_Directory_As_Container_Artifact() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = DirectoryPath.FromString("./artifacts/Packages").MakeAbsolute(fixture.Environment).FullPath; + + // When + service.Commands.UploadArtifactDirectory("./artifacts/Packages", "NuGet"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[artifact.upload containerfolder=NuGet;artifactname=NuGet;]{path}"); + } + + [Fact] + public void Should_Upload_Build_Log() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var path = FilePath.FromString("./dist/buildlog.txt").MakeAbsolute(fixture.Environment).FullPath; + + // When + service.Commands.UploadBuildLogFile("./dist/buildlog.txt"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == $"##vso[build.uploadlog]{path}"); + } + + [Fact] + public void Should_Update_Build_Number() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.UpdateBuildNumber("CIBuild_1"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == "##vso[build.updatebuildnumber]CIBuild_1"); + } + + [Fact] + public void Should_Add_Build_Tag() + { + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.AddBuildTag("Stable"); + + // Then + Assert.Contains(fixture.Writer.Entries, m => m == "##vso[build.addbuildtag]Stable"); + } + + [Fact] + public void Should_Publish_Test_Results() + { + const string expected = @"##vso[results.publish type=XUnit;mergeResults=true;platform=x86;config=Debug;runTitle='Cake Test Run 1 [master]';publishRunAttachments=true;resultFiles=C:\build\CAKE-CAKE-JOB1\artifacts\resultsXUnit.trx,C:\build\CAKE-CAKE-JOB1\artifacts\resultsJs.trx;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishTestResultsData + { + Configuration = "Debug", + MergeTestResults = true, + Platform = "x86", + PublishRunAttachments = true, + TestRunner = AzurePipelinesTestRunnerType.XUnit, + TestRunTitle = "Cake Test Run 1 [master]", + TestResultsFiles = new FilePath[] + { + "./artifacts/resultsXUnit.trx", + "./artifacts/resultsJs.trx" + } + }; + + // When + service.Commands.PublishTestResults(data); + + // Then + Assert.Equal(expected.Replace('\\', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [Fact] + public void Should_Publish_Test_Results_If_File_Path_Is_Relative() + { + const string expected = @"##vso[results.publish type=XUnit;mergeResults=true;platform=x86;config=Debug;runTitle='Cake Test Run 1 [master]';publishRunAttachments=true;resultFiles=C:\build\CAKE-CAKE-JOB1\artifacts\resultsXUnit.trx,C:\build\CAKE-CAKE-JOB1\artifacts\resultsJs.trx;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishTestResultsData + { + Configuration = "Debug", + MergeTestResults = true, + Platform = "x86", + PublishRunAttachments = true, + TestRunner = AzurePipelinesTestRunnerType.XUnit, + TestRunTitle = "Cake Test Run 1 [master]", + TestResultsFiles = new FilePath[] + { + "./artifacts/resultsXUnit.trx", + "./artifacts/resultsJs.trx" + } + }; + + // When + service.Commands.PublishTestResults(data); + + // Then + Assert.Equal(expected.Replace('\\', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [Fact] + public void Should_Publish_Test_Results_If_File_Path_Is_Absolute() + { + const string expected = @"##vso[results.publish type=XUnit;mergeResults=true;platform=x86;config=Debug;runTitle='Cake Test Run 1 [master]';publishRunAttachments=true;resultFiles=/build/CAKE-CAKE-JOB1/artifacts/resultsXUnit.trx,/build/CAKE-CAKE-JOB1/artifacts/resultsJs.trx;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + fixture.Environment.WorkingDirectory.Returns("/build/CAKE-CAKE-JOB1"); + fixture.Environment.Platform.Family.Returns(PlatformFamily.OSX); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishTestResultsData + { + Configuration = "Debug", + MergeTestResults = true, + Platform = "x86", + PublishRunAttachments = true, + TestRunner = AzurePipelinesTestRunnerType.XUnit, + TestRunTitle = "Cake Test Run 1 [master]", + TestResultsFiles = new FilePath[] + { + "/build/CAKE-CAKE-JOB1/artifacts/resultsXUnit.trx", + "/build/CAKE-CAKE-JOB1/artifacts/resultsJs.trx" + } + }; + + // When + service.Commands.PublishTestResults(data); + + // Then + Assert.Equal(expected.Replace('/', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [WindowsFact] + public void Should_Publish_Test_Results_If_File_Path_Is_Absolute_Windows() + { + const string expected = @"##vso[results.publish type=XUnit;mergeResults=true;platform=x86;config=Debug;runTitle='Cake Test Run 1 [master]';publishRunAttachments=true;resultFiles=C:\build\CAKE-CAKE-JOB1\artifacts\resultsXUnit.trx,C:\build\CAKE-CAKE-JOB1\artifacts\resultsJs.trx;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishTestResultsData + { + Configuration = "Debug", + MergeTestResults = true, + Platform = "x86", + PublishRunAttachments = true, + TestRunner = AzurePipelinesTestRunnerType.XUnit, + TestRunTitle = "Cake Test Run 1 [master]", + TestResultsFiles = new FilePath[] + { + "C:\\build\\CAKE-CAKE-JOB1\\artifacts\\resultsXUnit.trx", + "C:\\build\\CAKE-CAKE-JOB1\\artifacts\\resultsJs.trx" + } + }; + + // When + service.Commands.PublishTestResults(data); + + // Then + Assert.Equal(expected.Replace('/', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + // TODO: Windows Fact, OSX Fact + // TODO: TestResultFilePaths + [Fact] + public void Should_Publish_Code_Coverage() + { + const string expected = @"##vso[codecoverage.publish codecoveragetool=Cobertura;summaryfile=/build/CAKE-CAKE-JOB1/coverage/cobertura-coverage.xml;reportdirectory=/build/CAKE-CAKE-JOB1/coverage/report;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(PlatformFamily.OSX, "/build/CAKE-CAKE-JOB1"); + var data = new AzurePipelinesPublishCodeCoverageData + { + CodeCoverageTool = AzurePipelinesCodeCoverageToolType.Cobertura, + SummaryFileLocation = "./coverage/cobertura-coverage.xml", + ReportDirectory = "./coverage/report" + }; + + // When + service.Commands.PublishCodeCoverage(data); + + // Then + Assert.Equal(expected.Replace('/', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [WindowsFact] + public void Should_Publish_Code_Coverage_Windows() + { + const string expected = @"##vso[codecoverage.publish codecoveragetool=Cobertura;summaryfile=C:\build\CAKE-CAKE-JOB1\coverage\cobertura-coverage.xml;reportdirectory=C:\build\CAKE-CAKE-JOB1\coverage\report;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishCodeCoverageData + { + CodeCoverageTool = AzurePipelinesCodeCoverageToolType.Cobertura, + SummaryFileLocation = "./coverage/cobertura-coverage.xml", + ReportDirectory = "./coverage/report" + }; + + // When + service.Commands.PublishCodeCoverage(data); + + // Then + Assert.Equal(expected.Replace('\\', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [Fact] + public void Should_Publish_Code_Coverage_If_File_Path_Provided() + { + const string expected = @"##vso[codecoverage.publish codecoveragetool=Cobertura;summaryfile=C:\build\CAKE-CAKE-JOB1\coverage\cobertura-coverage.xml;reportdirectory=C:\build\CAKE-CAKE-JOB1\coverage\report;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + var data = new AzurePipelinesPublishCodeCoverageData + { + CodeCoverageTool = AzurePipelinesCodeCoverageToolType.Cobertura, + ReportDirectory = "./coverage/report" + }; + + // When + service.Commands.PublishCodeCoverage("./coverage/cobertura-coverage.xml", data); + + // Then + Assert.Equal(expected.Replace('\\', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + + [Fact] + public void Should_Publish_Code_Coverage_If_File_Path_And_Action_Provided() + { + const string expected = @"##vso[codecoverage.publish codecoveragetool=Cobertura;summaryfile=C:\build\CAKE-CAKE-JOB1\coverage\cobertura-coverage.xml;reportdirectory=C:\build\CAKE-CAKE-JOB1\coverage\report;]"; + + // Given + var fixture = new AzurePipelinesFixture(); + var service = fixture.CreateAzurePipelinesService(); + + // When + service.Commands.PublishCodeCoverage("./coverage/cobertura-coverage.xml", + data => + { + data.CodeCoverageTool = AzurePipelinesCodeCoverageToolType.Cobertura; + data.ReportDirectory = "./coverage/report"; + }); + + // Then + Assert.Equal(expected.Replace('\\', System.IO.Path.DirectorySeparatorChar), fixture.Writer.Entries.FirstOrDefault()); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesProviderTests.cs new file mode 100644 index 0000000000..1ea0a99195 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/AzurePipelinesProviderTests.cs @@ -0,0 +1,85 @@ +using Cake.Common.Build.AzurePipelines; +using Cake.Common.Tests.Fakes; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines +{ + public sealed class AzurePipelinesProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var writer = new FakeBuildSystemServiceMessageWriter(); + var result = Record.Exception(() => new AzurePipelinesProvider(null, writer)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var result = Record.Exception(() => new AzurePipelinesProvider(new FakeEnvironment(PlatformFamily.Unknown), null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + } + + public sealed class TheIsRunningOnAzurePipelinesProperty + { + [Fact] + public void Should_Return_True_If_Running_On_AzurePipelines() + { + // Given + var fixture = new AzurePipelinesFixture(); + fixture.IsRunningOnAzurePipelines(); + var tfBuild = fixture.CreateAzurePipelinesService(); + + // When + var result = tfBuild.IsRunningOnAzurePipelines; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_AzurePipelines() + { + // Given + var fixture = new AzurePipelinesFixture(); + var tfBuild = fixture.CreateAzurePipelinesService(); + + // When + var result = tfBuild.IsRunningOnAzurePipelines; + + // Then + Assert.False(result); + } + } + + public sealed class TheEnvironmentProperty + { + [Fact] + public void Should_Return_Non_Null_Reference() + { + // Given + var fixture = new AzurePipelinesFixture(); + var tfBuild = fixture.CreateAzurePipelinesService(); + + // When + var result = tfBuild.Environment; + + // Then + Assert.NotNull(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesAgentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesAgentInfoTests.cs new file mode 100644 index 0000000000..858409b4df --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesAgentInfoTests.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesAgentInfoTests + { + public sealed class TheBuildDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.BuildDirectory; + + // Then + Assert.Equal("c:/agent/_work/1", result.FullPath); + } + } + + public sealed class TheHomeDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.HomeDirectory; + + // Then + Assert.Equal("c:/agent", result.FullPath); + } + } + + public sealed class TheWorkingDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.WorkingDirectory; + + // Then + Assert.Equal("c:/agent/_work", result.FullPath); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(71, result); + } + } + + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("Agent-1", result); + } + } + + public sealed class TheMachineNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.MachineName; + + // Then + Assert.Equal("BuildServer", result); + } + } + + public sealed class TheToolsDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.ToolsDirectory; + + // Then + Assert.Equal("C:/hostedtoolcache/windows", result.FullPath); + } + } + + public sealed class TheJobNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.JobName; + + // Then + Assert.Equal("Job", result); + } + } + + public sealed class TheJobStatusProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.JobStatus; + + // Then + Assert.Equal("SucceededWithIssues", result); + } + } + + public sealed class TheIsHostedProperty + { + [Fact] + public void Should_Return_True_On_Hosted_Agent() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateHostedAgentInfo(); + + // When + var result = info.IsHosted; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_On_Other_Agent() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateAgentInfo(); + + // When + var result = info.IsHosted; + + // Then + Assert.False(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfoTests.cs new file mode 100644 index 0000000000..9d505b273d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesDefinitionInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateDefinitionInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(1855, result); + } + } + + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateDefinitionInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("Cake-CI", result); + } + } + + public sealed class TheVersionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateDefinitionInfo(); + + // When + var result = info.Version; + + // Then + Assert.Equal(47, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesInfoTests.cs new file mode 100644 index 0000000000..0a3537a152 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesInfoTests.cs @@ -0,0 +1,318 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(100234, result); + } + } + + public sealed class TheNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal("Build-20160927.1", result); + } + } + + public sealed class TheUriProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.Uri; + + // Then + var uri = new Uri("vstfs:///Build/Build/1430"); + Assert.Equal(uri, result); + } + } + + public sealed class TheQueuedByProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.QueuedBy; + + // Then + Assert.Equal(@"[DefaultCollection]\Project Collection Service Accounts", result); + } + } + + public sealed class TheRequestedForProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.RequestedFor; + + // Then + Assert.Equal("Alistair Chapman", result); + } + } + + public sealed class TheRequestedForEmailProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.RequestedForEmail; + + // Then + Assert.Equal("author@mail.com", result); + } + } + + public sealed class TheAccessTokenProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.AccessToken; + + // Then + Assert.Equal("f662dbe218144c86bdecb1e9b2eb336c", result); + } + } + + public sealed class TheDebugProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.Debug; + + // Then + Assert.Equal(true, result); + } + } + + public sealed class TheArtifactStagingDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.ArtifactStagingDirectory; + + // Then + Assert.Equal(@"c:/agent/_work/1/a", result.FullPath); + } + } + + public sealed class TheBinariesDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.BinariesDirectory; + + // Then + Assert.Equal(@"c:/agent/_work/1/b", result.FullPath); + } + } + + public sealed class TheSourcesDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.SourcesDirectory; + + // Then + Assert.Equal(@"c:/agent/_work/1/s", result.FullPath); + } + } + + public sealed class TheStagingDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.StagingDirectory; + + // Then + Assert.Equal(@"c:/agent/_work/1/a", result.FullPath); + } + } + + public sealed class TheTestResultsDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TestResultsDirectory; + + // Then + Assert.Equal(@"c:/agent/_work/1/TestResults", result.FullPath); + } + } + + public sealed class TheReasonProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.Reason; + + // Then + Assert.Equal(@"PullRequest", result); + } + } + + public sealed class TheTriggeredByBuildIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TriggeredBy.BuildId; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class TheTriggeredByDefinitionIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TriggeredBy.DefinitionId; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class TheTriggeredByDefinitionNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TriggeredBy.DefinitionName; + + // Then + Assert.Equal(@"Build", result); + } + } + + public sealed class TheTriggeredByBuildNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TriggeredBy.BuildNumber; + + // Then + Assert.Equal(@"123", result); + } + } + + public sealed class TheTriggeredByProjectIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateBuildInfo(); + + // When + var result = info.TriggeredBy.ProjectId; + + // Then + Assert.Equal(@"456", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfoTests.cs new file mode 100644 index 0000000000..f0d8106886 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfoTests.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("1", true)] + [InlineData("2147483648", true)] + [InlineData("0", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new AzurePipelinesInfoFixture(); + fixture.Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTID").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + [Obsolete("The Id property is marked obsolete since the type will change to long in the next major version")] + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class TheLongIdProperty + { + [Theory] + [InlineData("1", 1)] + [InlineData("2147483648", 2147483648)] + public void Should_Return_Correct_Value(string value, long expected) + { + // Given + var fixture = new AzurePipelinesInfoFixture(); + fixture.Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTID").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.LongId; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class ThePullRequestIsForkProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.IsFork; + + // Then + Assert.Equal(false, result); + } + } + + public sealed class ThePullRequestSourceBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.SourceBranch; + + // Then + Assert.Equal(@"refs/heads/FeatureBranch", result); + } + } + + public sealed class ThePullRequestSourceRepositoryUriProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.SourceRepositoryUri; + + // Then + Assert.Equal(new Uri(@"https://fabrikamfiber.visualstudio.com/Project/_git/ProjectRepo"), result); + } + } + + public sealed class ThePullRequestTargetBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.TargetBranch; + + // Then + Assert.Equal(@"refs/heads/master", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfoTests.cs new file mode 100644 index 0000000000..da485b84f3 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfoTests.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesRepositoryInfoTests + { + public sealed class TheBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(); + + // When +#pragma warning disable 618 + var result = info.SourceBranchName; +#pragma warning restore 618 + + // Then + Assert.Equal("develop", result); + } + } + + public sealed class TheSourceBranchNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.SourceBranchName; + + // Then + Assert.Equal("develop", result); + } + } + + public sealed class TheSourceVersionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.SourceVersion; + + // Then + Assert.Equal("4efbc1ffb993dfbcf024e6a9202865cc0b6d9c50", result); + } + } + + public sealed class TheShelvesetProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Shelveset; + + // Then + Assert.Equal("Shelveset1", result); + } + } + + public sealed class TheRepoNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoName; + + // Then + Assert.Equal("cake", result); + } + } + + public sealed class TheProviderProperty + { + [Theory] + [InlineData("Git", AzurePipelinesRepositoryType.Git)] + [InlineData("GitHub", AzurePipelinesRepositoryType.GitHub)] + [InlineData("Svn", AzurePipelinesRepositoryType.Svn)] + [InlineData("TfsGit", AzurePipelinesRepositoryType.TfsGit)] + [InlineData("TfsVersionControl", AzurePipelinesRepositoryType.TfsVersionControl)] + public void Should_Return_Correct_Value(string type, AzurePipelinesRepositoryType provider) + { + // Given + var info = new AzurePipelinesInfoFixture().CreateRepositoryInfo(type); + + // When + var result = info.Provider; + + // Then + Assert.Equal(provider, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfoTests.cs new file mode 100644 index 0000000000..6e2a20eaf5 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfoTests.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.AzurePipelines.Data +{ + public sealed class AzurePipelinesTeamProjectInfoTests + { + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateTeamProjectInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("TeamProject", result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateTeamProjectInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal("D0A3B6B8-499B-4D4B-BD46-DB70C19E6D33", result); + } + } + + public sealed class TheCollectionUriProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new AzurePipelinesInfoFixture().CreateTeamProjectInfo(); + + // When + var result = info.CollectionUri; + + // Then + var uri = new Uri("https://fabrikamfiber.visualstudio.com/"); + Assert.Equal(uri, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/BambooProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/BambooProviderTests.cs index d91605039f..21aa5b4ced 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/BambooProviderTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/BambooProviderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bamboo; using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -18,7 +19,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => new BambooProvider(null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -71,4 +72,4 @@ public void Should_Return_Non_Null_Reference() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooBuildInfoTests.cs index 54f34a089f..72fd2a6159 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooBuildInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -104,4 +105,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCommitInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCommitInfoTests.cs index 23226cb409..b6c67be3fe 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCommitInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCommitInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -21,8 +22,7 @@ public void Should_Return_Correct_Value() // Then Assert.Equal("d4a3a4cb304548450e3cab2ff735f778ffe58d03", result); - } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCustomBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCustomBuildInfoTests.cs index 050f0619f6..ac231bb924 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCustomBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooCustomBuildInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -8,7 +9,6 @@ namespace Cake.Common.Tests.Unit.Build.Bamboo.Data { public sealed class BambooCustomBuildInfoTests { - public sealed class TheIsCustomBuildProperty { [Fact] @@ -41,4 +41,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooPlanInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooPlanInfoTests.cs index 18ae0e530b..d8c387cab7 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooPlanInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooPlanInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -103,7 +104,5 @@ public void Should_Return_Correct_Value() Assert.Equal("Cake", result); } } - } - -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooRepositoryInfoTests.cs index cefe71ce15..9b93b7db76 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooRepositoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bamboo/Data/BambooRepositoryInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -8,7 +9,6 @@ namespace Cake.Common.Tests.Unit.Build.Bamboo.Data { public sealed class BambooRepositoryInfoTests { - public sealed class TheScmProperty { [Fact] @@ -57,4 +57,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfoTests.cs new file mode 100644 index 0000000000..010dd9833e --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfoTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.BitbucketPipelines.Data +{ + public sealed class BitbucketPipelinesPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("1", true)] + [InlineData("0", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new BitbucketPipelinesInfoFixture(); + fixture.Environment.GetEnvironmentVariable("BITBUCKET_PR_ID").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(1, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfoTests.cs new file mode 100644 index 0000000000..5a450ac088 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfoTests.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.BitbucketPipelines.Data +{ + public sealed class BitbucketPipelinesRepositoryInfoTests + { + public sealed class TheCommitProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Commit; + + // Then + Assert.Equal("4efbc1ffb993dfbcf024e6a9202865cc0b6d9c50", result); + } + } + + public sealed class TheRepoSlugProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoSlug; + + // Then + Assert.Equal("cake", result); + } + } + + public sealed class TheRepoOwnerProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoOwner; + + // Then + Assert.Equal("cakebuild", result); + } + } + + public sealed class TheBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Branch; + + // Then + Assert.Equal("develop", result); + } + } + + public sealed class TheTagProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitbucketPipelinesInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Tag; + + // Then + Assert.Equal("BitbucketPipelines", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseApplicationInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseApplicationInfoTests.cs index f986cd7959..6dbf33728d 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseApplicationInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseApplicationInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,7 +20,7 @@ public void Should_Return_Correct_Value() // When var result = info.ApplicationTitle; - //Then + // Then Assert.Equal("CAKE-EXE", result); } } @@ -35,7 +36,7 @@ public void Should_Return_Correct_Value() // When var result = info.ApplicationUrl; - //Then + // Then Assert.Equal("https://www.bitrise.io/app/089v339k300ba3cd", result); } } @@ -51,9 +52,9 @@ public void Should_Return_Correct_Value() // When var result = info.AppSlug; - //Then + // Then Assert.Equal("089v339k300ba3cd", result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseBuildInfoTests.cs index 1d1d641ada..dc3f501d2d 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseBuildInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,7 +20,7 @@ public void Should_Return_Correct_Value() // When var result = info.BuildNumber; - //Then + // Then Assert.Equal("456", result); } } @@ -35,7 +36,7 @@ public void Should_Return_Correct_Value() // When var result = info.BuildUrl; - //Then + // Then Assert.Equal("https://www.bitrise.io/build/e794ed892f3a59dd", result); } } @@ -51,7 +52,7 @@ public void Should_Return_Correct_Value() // When var result = info.BuildSlug; - //Then + // Then Assert.Equal("e794ed892f3a59dd", result); } } @@ -67,7 +68,7 @@ public void Should_Return_Correct_Value() // When var result = info.BuildTriggerTimestamp; - //Then + // Then Assert.Equal("2016-03-12 23:49:26", result); } } @@ -81,8 +82,8 @@ public void Should_Return_Correct_Value() // When var result = info.BuildStatus; - //Then + // Then Assert.True(result); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseDirectoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseDirectoryInfoTests.cs index 06ebc1e955..a012640dfa 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseDirectoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseDirectoryInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,10 +20,9 @@ public void Should_Return_Correct_Value() // When var result = info.SourceDirectory; - //Then + // Then Assert.Equal("/Users/vagrant/git", result); } - } public sealed class TheDeployDirectoryProperty @@ -36,10 +36,9 @@ public void Should_Return_Correct_Value() // When var result = info.DeployDirectory; - //Then + // Then Assert.Equal("/Users/vagrant/deploy", result); } - } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseProvisioningInfo.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseProvisioningInfo.cs index f518487aef..fb7c388b3e 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseProvisioningInfo.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseProvisioningInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,7 +20,7 @@ public void Should_Return_Correct_Value() // When var result = info.ProvisionUrl; - //Then + // Then Assert.Equal("file://cake-build/cake/cake.provision", result); } } @@ -35,7 +36,7 @@ public void Should_Return_Correct_Value() // When var result = info.CertificateUrl; - //Then + // Then Assert.Equal("file://cake-build/cake/Cert.p12", result); } } @@ -51,9 +52,9 @@ public void Should_Return_Correct_Value() // When var result = info.CertificatePassphrase; - //Then + // Then Assert.Equal("CAKE", result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitrisePullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitrisePullRequestInfoTests.cs new file mode 100644 index 0000000000..734a639336 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitrisePullRequestInfoTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Bitrise.Data +{ + public sealed class BitrisePullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("1", true)] + [InlineData("0", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new BitriseInfoFixture(); + fixture.Environment.GetEnvironmentVariable("BITRISE_PULL_REQUEST").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new BitriseInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(1, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseRepositoryInfoTests.cs index 75fd91e31a..9b87168648 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseRepositoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseRepositoryInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,7 +20,7 @@ public void Should_Return_Correct_Value() // When var result = info.GitRepositoryUrl; - //Then + // Then Assert.Equal("git@github.com:/cake-build/cake.git", result); } } @@ -35,7 +36,7 @@ public void Should_Return_Correct_Value() // When var result = info.GitBranch; - //Then + // Then Assert.Equal("cake-branch", result); } } @@ -51,7 +52,7 @@ public void Should_Return_Correct_Value() // When var result = info.GitTag; - //Then + // Then Assert.Equal("v0.0.1", result); } } @@ -67,7 +68,7 @@ public void Should_Return_Correct_Value() // When var result = info.GitCommit; - //Then + // Then Assert.Equal("63dd7b", result); } } @@ -83,9 +84,9 @@ public void Should_Return_Correct_Value() // When var result = info.PullRequest; - //Then - Assert.Equal("[WIP] Bitrise cake support #000", result); + // Then + Assert.Equal("1", result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseWorkflowInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseWorkflowInfoTests.cs index 7498a842ba..cc4c650aec 100644 --- a/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseWorkflowInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Bitrise/Data/BitriseWorkflowInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -19,7 +20,7 @@ public void Should_Return_Correct_Value() // When var result = info.WorkflowId; - //Then + // Then Assert.Equal("Build & Test Cake on BitRise", result); } } @@ -35,9 +36,9 @@ public void Should_Return_Correct_Value() // When var result = info.WorkflowTitle; - //Then + // Then Assert.Equal("Build & Test Cake on BitRise", result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/BuildSystemAliasesTests.cs b/src/Cake.Common.Tests/Unit/Build/BuildSystemAliasesTests.cs index 4f36857cd2..b62bcb713a 100644 --- a/src/Cake.Common.Tests/Unit/Build/BuildSystemAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/BuildSystemAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build; using Xunit; @@ -17,7 +18,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.BuildSystem(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } public sealed class TheAppVeyorMethod @@ -29,7 +30,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.AppVeyor(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } public sealed class TheTeamCityMethod @@ -41,7 +42,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.TeamCity(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } public sealed class TheBambooMethod @@ -53,7 +54,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.Bamboo(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } @@ -66,7 +67,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.Jenkins(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } @@ -79,7 +80,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.Bitrise(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } } @@ -92,8 +93,73 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => BuildSystemAliases.TravisCI(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); + } + } + + public sealed class TheBitbucketPipelinesMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => BuildSystemAliases.BitbucketPipelines(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + } + + public sealed class TheGitLabCIMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => BuildSystemAliases.GitLabCI(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + } + + public sealed class TheTFBuildMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => BuildSystemAliases.AzurePipelines(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + } + + public sealed class TheGitHubActionsMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => BuildSystemAliases.GitHubActions(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + } + + public sealed class TheAzurePipelinesMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => BuildSystemAliases.AzurePipelines(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/BuildSystemTests.cs b/src/Cake.Common.Tests/Unit/Build/BuildSystemTests.cs index 9a24214e9e..497d8cd155 100644 --- a/src/Cake.Common.Tests/Unit/Build/BuildSystemTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/BuildSystemTests.cs @@ -1,17 +1,26 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build; using Cake.Common.Build.AppVeyor; -using Cake.Common.Build.MyGet; -using Cake.Common.Build.TeamCity; +using Cake.Common.Build.AzurePipelines; using Cake.Common.Build.Bamboo; +using Cake.Common.Build.BitbucketPipelines; using Cake.Common.Build.Bitrise; -using NSubstitute; -using Xunit; using Cake.Common.Build.ContinuaCI; +using Cake.Common.Build.GitHubActions; +using Cake.Common.Build.GitLabCI; +using Cake.Common.Build.GoCD; using Cake.Common.Build.Jenkins; +using Cake.Common.Build.MyGet; +using Cake.Common.Build.Rwx; +using Cake.Common.Build.TeamCity; using Cake.Common.Build.TravisCI; +using Cake.Common.Build.WoodpeckerCI; +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; namespace Cake.Common.Tests.Unit.Build { @@ -30,12 +39,19 @@ public void Should_Throw_If_AppVeyor_Is_Null() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(null, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(null, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "appVeyorProvider"); + AssertEx.IsArgumentNullException(result, "appVeyorProvider"); } [Fact] @@ -49,12 +65,19 @@ public void Should_Throw_If_TeamCity_Is_Null() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, null, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, null, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "teamCityProvider"); + AssertEx.IsArgumentNullException(result, "teamCityProvider"); } [Fact] @@ -68,12 +91,19 @@ public void Should_Throw_If_MyGet_Is_Null() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, null, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, null, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "myGetProvider"); + AssertEx.IsArgumentNullException(result, "myGetProvider"); } [Fact] @@ -87,13 +117,19 @@ public void Should_Throw_If_Bamboo_Is_Null() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, null, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, null, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "bambooProvider"); - + AssertEx.IsArgumentNullException(result, "bambooProvider"); } [Fact] @@ -107,13 +143,19 @@ public void Should_Throw_If_ContinuaCI_Is_Null() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, null, jenkinsProvider, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, null, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "continuaCIProvider"); - + AssertEx.IsArgumentNullException(result, "continuaCIProvider"); } [Fact] @@ -127,12 +169,19 @@ public void Should_Throw_If_Jenkins_Is_Null() var continuaCIProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, null, bitriseProvider, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, null, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "jenkinsProvider"); + AssertEx.IsArgumentNullException(result, "jenkinsProvider"); } [Fact] @@ -146,12 +195,19 @@ public void Should_Throw_If_Bitrise_Is_Null() var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, null, travisCIProvider)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, null, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); // Then - Assert.IsArgumentNullException(result, "bitriseProvider"); + AssertEx.IsArgumentNullException(result, "bitriseProvider"); } [Fact] @@ -165,12 +221,201 @@ public void Should_Throw_If_TravisCI_Is_Null() var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, null, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "travisCIProvider"); + } + + [Fact] + public void Should_Throw_If_BitbucketPipelines_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, null, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "bitbucketPipelinesProvider"); + } + + [Fact] + public void Should_Throw_If_GoCD_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, null, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "goCDProvider"); + } + + [Fact] + public void Should_Throw_If_GitLabCI_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, null, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "gitLabCIProvider"); + } + + [Fact] + public void Should_Throw_If_AzurePipelines_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, null, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "azurePipelinesProvider"); + } + + [Fact] + public void Should_Throw_If_GitHubActions_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, null, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "gitHubActionsProvider"); + } + + [Fact] + public void Should_Throw_If_WoodpeckerCI_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + // When + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, null, rwxProvider)); + + // Then + AssertEx.IsArgumentNullException(result, "woodpeckerCIProvider"); + } + + [Fact] + public void Should_Throw_If_Rwx_Is_Null() + { + // Given + var appVeyorProvider = Substitute.For(); + var teamCityProvider = Substitute.For(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var bitriseProvider = Substitute.For(); + var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); // When - var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, null)); + var result = Record.Exception(() => new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, null)); // Then - Assert.IsArgumentNullException(result, "travisCIProvider"); + AssertEx.IsArgumentNullException(result, "rwxProvider"); } } @@ -181,6 +426,7 @@ public void Should_Return_True_If_Running_On_AppVeyor() { // Given var appVeyorProvider = Substitute.For(); + var appVeyorEnvironment = new AppVeyorInfoFixture().CreateEnvironmentInfo(); var teamCityProvider = Substitute.For(); var myGetProvider = Substitute.For(); var bambooProvider = Substitute.For(); @@ -188,15 +434,22 @@ public void Should_Return_True_If_Running_On_AppVeyor() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); appVeyorProvider.IsRunningOnAppVeyor.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + appVeyorProvider.Environment.Returns(appVeyorEnvironment); // When - var result = buildSystem.IsRunningOnAppVeyor; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnAppVeyor); } } @@ -208,21 +461,29 @@ public void Should_Return_True_If_Running_On_TeamCity() // Given var appVeyorProvider = Substitute.For(); var teamCityProvider = Substitute.For(); + var teamCityEnvironment = new TeamCityInfoFixture().CreateEnvironmentInfo(); var myGetProvider = Substitute.For(); var bambooProvider = Substitute.For(); var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); teamCityProvider.IsRunningOnTeamCity.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + teamCityProvider.Environment.Returns(teamCityEnvironment); // When - var result = buildSystem.IsRunningOnTeamCity; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnTeamCity); } } @@ -240,15 +501,21 @@ public void Should_Return_True_If_Running_On_MyGet() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); myGetProvider.IsRunningOnMyGet.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); // When - var result = buildSystem.IsRunningOnMyGet; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnMyGet); } } @@ -266,15 +533,21 @@ public void Should_Return_True_If_Running_On_Bamboo() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); bambooProvider.IsRunningOnBamboo.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); // When - var result = buildSystem.IsRunningOnBamboo; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnBamboo); } } @@ -292,15 +565,21 @@ public void Should_Return_True_If_Running_On_ContinuaCI() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); continuaCIProvider.IsRunningOnContinuaCI.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); // When - var result = buildSystem.IsRunningOnContinuaCI; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnContinuaCI); } } @@ -316,17 +595,25 @@ public void Should_Return_True_If_Running_On_Jenkins() var bambooProvider = Substitute.For(); var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); + var jenkinsEnvironment = new JenkinsInfoFixture().CreateEnvironmentInfo(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); jenkinsProvider.IsRunningOnJenkins.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + jenkinsProvider.Environment.Returns(jenkinsEnvironment); // When - var result = buildSystem.IsRunningOnJenkins; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnJenkins); } } @@ -343,16 +630,24 @@ public void Should_Return_True_If_Running_On_Bitrise() var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); + var bitriseEnvironment = new BitriseInfoFixture().CreateEnvironmentInfo(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); bitriseProvider.IsRunningOnBitrise.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + bitriseProvider.Environment.Returns(bitriseEnvironment); // When - var result = buildSystem.IsRunningOnBitrise; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnBitrise); } } @@ -370,22 +665,30 @@ public void Should_Return_True_If_Running_On_TravisCI() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var travisCIEnvironment = new TravisCIInfoFixture().CreateEnvironmentInfo(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); travisCIProvider.IsRunningOnTravisCI.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + travisCIProvider.Environment.Returns(travisCIEnvironment); // When - var result = buildSystem.IsRunningOnTravisCI; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.True(buildSystem.IsRunningOnTravisCI); } } - public sealed class TheIsLocalBuildProperty + public sealed class TheIsRunningOnBitbucketPipelinesProperty { [Fact] - public void Should_Return_False_If_Running_On_AppVeyor() + public void Should_Return_True_If_Running_On_BitbucketPipelines() { // Given var appVeyorProvider = Substitute.For(); @@ -396,26 +699,30 @@ public void Should_Return_False_If_Running_On_AppVeyor() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(true); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var bitbucketPipelinesProvider = Substitute.For(); + var bitbucketPipelinesEnvironment = new BitbucketPipelinesInfoFixture().CreateEnvironmentInfo(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + bitbucketPipelinesProvider.IsRunningOnBitbucketPipelines.Returns(true); + bitbucketPipelinesProvider.Environment.Returns(bitbucketPipelinesEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnBitbucketPipelines); } + } + public sealed class TheIsRunningOnGoCDProperty + { [Fact] - public void Should_Return_False_If_Running_On_TeamCity() + public void Should_Return_True_If_Running_On_GoCD() { // Given var appVeyorProvider = Substitute.For(); @@ -426,26 +733,28 @@ public void Should_Return_False_If_Running_On_TeamCity() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(true); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + goCDProvider.IsRunningOnGoCD.Returns(true); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnGoCD); } + } + public sealed class TheIsRunningOnGitLabCIProperty + { [Fact] - public void Should_Return_False_If_Running_On_MyGet() + public void Should_Return_True_If_Running_On_GitLabCI() { // Given var appVeyorProvider = Substitute.For(); @@ -456,26 +765,30 @@ public void Should_Return_False_If_Running_On_MyGet() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(true); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitLabCIEnvironment = new GitLabCIInfoFixture().CreateEnvironmentInfo(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + gitLabCIProvider.IsRunningOnGitLabCI.Returns(true); + gitLabCIProvider.Environment.Returns(gitLabCIEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnGitLabCI); } + } + public sealed class TheIsRunningOnAzurePipelinesProperty + { [Fact] - public void Should_Return_False_If_Running_On_Bamboo() + public void Should_Return_True_If_Running_On_AzurePipelines() { // Given var appVeyorProvider = Substitute.For(); @@ -486,26 +799,30 @@ public void Should_Return_False_If_Running_On_Bamboo() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(true); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var azurePipelinesEnvironment = new AzurePipelinesInfoFixture().CreateEnvironmentInfo(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + azurePipelinesProvider.IsRunningOnAzurePipelines.Returns(true); + azurePipelinesProvider.Environment.Returns(azurePipelinesEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnAzurePipelines); } + } + public sealed class TheIsRunningOnGitHubActionsProperty + { [Fact] - public void Should_Return_False_If_Running_On_ContinuaCI() + public void Should_Return_True_If_Running_On_GitHubActions() { // Given var appVeyorProvider = Substitute.For(); @@ -516,86 +833,199 @@ public void Should_Return_False_If_Running_On_ContinuaCI() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(true); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var gitHubActionsEnvironment = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + + gitHubActionsProvider.IsRunningOnGitHubActions.Returns(true); + gitHubActionsProvider.Environment.Returns(gitHubActionsEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnGitHubActions); } + } - [Fact] - public void Should_Return_False_If_Running_On_Jenkins() + public sealed class TheProviderProperty + { + [Theory] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, BuildProvider.Local)] + [InlineData(true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, BuildProvider.AppVeyor)] + [InlineData(false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, BuildProvider.TeamCity)] + [InlineData(false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, BuildProvider.MyGet)] + [InlineData(false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, BuildProvider.Bamboo)] + [InlineData(false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, BuildProvider.ContinuaCI)] + [InlineData(false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, BuildProvider.Jenkins)] + [InlineData(false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, BuildProvider.Bitrise)] + [InlineData(false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, BuildProvider.TravisCI)] + [InlineData(false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, BuildProvider.BitbucketPipelines)] + [InlineData(false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, BuildProvider.GoCD)] + [InlineData(false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, BuildProvider.GitLabCI)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, BuildProvider.AzurePipelines)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, BuildProvider.GitHubActions)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, BuildProvider.WoodpeckerCI)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, BuildProvider.Rwx)] + public void Should_Return_Provider_If_Running_On_Provider(bool appVeyor, bool teamCity, bool myGet, bool bamboo, bool continuaCI, bool jenkins, bool bitrise, bool travisCI, bool bitbucketPipelines, bool goCD, bool gitLabCI, bool azurePipelines, bool gitHubActions, bool woodpeckerCI, bool rwx, BuildProvider provider) { // Given var appVeyorProvider = Substitute.For(); + var appVeyorEnvironment = new AppVeyorInfoFixture().CreateEnvironmentInfo(); var teamCityProvider = Substitute.For(); + var teamCityEnvironment = new TeamCityInfoFixture().CreateEnvironmentInfo(); var myGetProvider = Substitute.For(); var bambooProvider = Substitute.For(); var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); + var jenkinsEnvironment = new JenkinsInfoFixture().CreateEnvironmentInfo(); var bitriseProvider = Substitute.For(); + var bitriseEnvironment = new BitriseInfoFixture().CreateEnvironmentInfo(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(true); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var travisCIEnvironment = new TravisCIInfoFixture().CreateEnvironmentInfo(); + var bitbucketPipelinesProvider = Substitute.For(); + var bitbucketPipelinesEnvironment = new BitbucketPipelinesInfoFixture().CreateEnvironmentInfo(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitLabCIEnvironment = new GitLabCIInfoFixture().CreateEnvironmentInfo(); + var gitHubActionsProvider = Substitute.For(); + var gitHubActionsEnvironment = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + var azurePipelinesProvider = Substitute.For(); + var azurePipelinesEnvironment = new AzurePipelinesInfoFixture().CreateEnvironmentInfo(); + + appVeyorProvider.IsRunningOnAppVeyor.Returns(appVeyor); + appVeyorProvider.Environment.Returns(appVeyorEnvironment); + teamCityProvider.IsRunningOnTeamCity.Returns(teamCity); + teamCityProvider.Environment.Returns(teamCityEnvironment); + myGetProvider.IsRunningOnMyGet.Returns(myGet); + bambooProvider.IsRunningOnBamboo.Returns(bamboo); + continuaCIProvider.IsRunningOnContinuaCI.Returns(continuaCI); + jenkinsProvider.IsRunningOnJenkins.Returns(jenkins); + jenkinsProvider.Environment.Returns(jenkinsEnvironment); + bitriseProvider.IsRunningOnBitrise.Returns(bitrise); + bitriseProvider.Environment.Returns(bitriseEnvironment); + travisCIProvider.IsRunningOnTravisCI.Returns(travisCI); + travisCIProvider.Environment.Returns(travisCIEnvironment); + bitbucketPipelinesProvider.IsRunningOnBitbucketPipelines.Returns(bitbucketPipelines); + bitbucketPipelinesProvider.Environment.Returns(bitbucketPipelinesEnvironment); + goCDProvider.IsRunningOnGoCD.Returns(goCD); + gitLabCIProvider.IsRunningOnGitLabCI.Returns(gitLabCI); + gitLabCIProvider.Environment.Returns(gitLabCIEnvironment); + gitHubActionsProvider.IsRunningOnGitHubActions.Returns(gitHubActions); + gitHubActionsProvider.Environment.Returns(gitHubActionsEnvironment); + azurePipelinesProvider.IsRunningOnAzurePipelines.Returns(azurePipelines); + azurePipelinesProvider.Environment.Returns(azurePipelinesEnvironment); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + var woodpeckerCIEnvironment = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + woodpeckerCIProvider.IsRunningOnWoodpeckerCI.Returns(woodpeckerCI); + woodpeckerCIProvider.Environment.Returns(woodpeckerCIEnvironment); + var rwxEnvironment = new RwxInfoFixture().CreateEnvironmentInfo(); + rwxProvider.IsRunningOnRwx.Returns(rwx); + rwxProvider.Environment.Returns(rwxEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.Equal(provider, buildSystem.Provider); } + } - [Fact] - public void Should_Return_False_If_Running_On_Bitrise() + public sealed class TheIsLocalBuildProperty + { + [Theory] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true)] + [InlineData(true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false)] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false)] + public void Should_Return_Whether_Or_Not_Running_On_Provider(bool appVeyor, bool teamCity, bool myGet, bool bamboo, bool continuaCI, bool jenkins, bool bitrise, bool travisCI, bool bitbucketPipelines, bool goCD, bool gitLabCI, bool azurePipelines, bool gitHubActions, bool woodpeckerCI, bool rwx, bool isLocalBuild) { // Given var appVeyorProvider = Substitute.For(); + var appVeyorEnvironment = new AppVeyorInfoFixture().CreateEnvironmentInfo(); var teamCityProvider = Substitute.For(); + var teamCityEnvironment = new TeamCityInfoFixture().CreateEnvironmentInfo(); var myGetProvider = Substitute.For(); var bambooProvider = Substitute.For(); var continuaCIProvider = Substitute.For(); var jenkinsProvider = Substitute.For(); + var jenkinsEnvironment = new JenkinsInfoFixture().CreateEnvironmentInfo(); var bitriseProvider = Substitute.For(); + var bitriseEnvironment = new BitriseInfoFixture().CreateEnvironmentInfo(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(true); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var travisCIEnvironment = new TravisCIInfoFixture().CreateEnvironmentInfo(); + var bitbucketPipelinesProvider = Substitute.For(); + var bitbucketPipelinesEnvironment = new BitbucketPipelinesInfoFixture().CreateEnvironmentInfo(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitLabCIEnvironment = new GitLabCIInfoFixture().CreateEnvironmentInfo(); + var gitHubActionsProvider = Substitute.For(); + var gitHubActionsEnvironment = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + var azurePipelinesProvider = Substitute.For(); + var azurePipelinesEnvironment = new AzurePipelinesInfoFixture().CreateEnvironmentInfo(); + + appVeyorProvider.IsRunningOnAppVeyor.Returns(appVeyor); + appVeyorProvider.Environment.Returns(appVeyorEnvironment); + teamCityProvider.IsRunningOnTeamCity.Returns(teamCity); + teamCityProvider.Environment.Returns(teamCityEnvironment); + myGetProvider.IsRunningOnMyGet.Returns(myGet); + bambooProvider.IsRunningOnBamboo.Returns(bamboo); + continuaCIProvider.IsRunningOnContinuaCI.Returns(continuaCI); + jenkinsProvider.IsRunningOnJenkins.Returns(jenkins); + jenkinsProvider.Environment.Returns(jenkinsEnvironment); + bitriseProvider.IsRunningOnBitrise.Returns(bitrise); + bitriseProvider.Environment.Returns(bitriseEnvironment); + travisCIProvider.IsRunningOnTravisCI.Returns(travisCI); + travisCIProvider.Environment.Returns(travisCIEnvironment); + bitbucketPipelinesProvider.IsRunningOnBitbucketPipelines.Returns(bitbucketPipelines); + bitbucketPipelinesProvider.Environment.Returns(bitbucketPipelinesEnvironment); + goCDProvider.IsRunningOnGoCD.Returns(goCD); + gitLabCIProvider.IsRunningOnGitLabCI.Returns(gitLabCI); + gitLabCIProvider.Environment.Returns(gitLabCIEnvironment); + gitHubActionsProvider.IsRunningOnGitHubActions.Returns(gitHubActions); + gitHubActionsProvider.Environment.Returns(gitHubActionsEnvironment); + azurePipelinesProvider.IsRunningOnAzurePipelines.Returns(azurePipelines); + azurePipelinesProvider.Environment.Returns(azurePipelinesEnvironment); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + var woodpeckerCIEnvironment = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + woodpeckerCIProvider.IsRunningOnWoodpeckerCI.Returns(woodpeckerCI); + woodpeckerCIProvider.Environment.Returns(woodpeckerCIEnvironment); + var rwxEnvironment = new RwxInfoFixture().CreateEnvironmentInfo(); + rwxProvider.IsRunningOnRwx.Returns(rwx); + rwxProvider.Environment.Returns(rwxEnvironment); // When - var result = buildSystem.IsLocalBuild; + System.Console.WriteLine(jenkinsProvider); + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.Equal(isLocalBuild, buildSystem.IsLocalBuild); } + } + public sealed class TheIsRunningOnWoodpeckerCIProperty + { [Fact] - public void Should_Return_False_If_Running_On_TravisCI() + public void Should_Return_True_If_Running_On_WoodpeckerCI() { // Given var appVeyorProvider = Substitute.For(); @@ -606,26 +1036,30 @@ public void Should_Return_False_If_Running_On_TravisCI() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); - - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - jenkinsProvider.IsRunningOnJenkins.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(true); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + var woodpeckerCIEnvironment = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + woodpeckerCIProvider.IsRunningOnWoodpeckerCI.Returns(true); + woodpeckerCIProvider.Environment.Returns(woodpeckerCIEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.False(result); + Assert.True(buildSystem.IsRunningOnWoodpeckerCI); } + } + public sealed class TheIsRunningOnRwxProperty + { [Fact] - public void Should_Return_True_If_Not_Running_On_Any() + public void Should_Return_True_If_Running_On_Rwx() { // Given var appVeyorProvider = Substitute.For(); @@ -636,22 +1070,108 @@ public void Should_Return_True_If_Not_Running_On_Any() var jenkinsProvider = Substitute.For(); var bitriseProvider = Substitute.For(); var travisCIProvider = Substitute.For(); + var bitbucketPipelinesProvider = Substitute.For(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitHubActionsProvider = Substitute.For(); + var azurePipelinesProvider = Substitute.For(); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + var rwxEnvironment = new RwxInfoFixture().CreateEnvironmentInfo(); + + rwxProvider.IsRunningOnRwx.Returns(true); + rwxProvider.Environment.Returns(rwxEnvironment); + + // When + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); + + // Then + Assert.True(buildSystem.IsRunningOnRwx); + } + } - appVeyorProvider.IsRunningOnAppVeyor.Returns(false); - teamCityProvider.IsRunningOnTeamCity.Returns(false); - myGetProvider.IsRunningOnMyGet.Returns(false); - bambooProvider.IsRunningOnBamboo.Returns(false); - continuaCIProvider.IsRunningOnContinuaCI.Returns(false); - bitriseProvider.IsRunningOnBitrise.Returns(false); - travisCIProvider.IsRunningOnTravisCI.Returns(false); - var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false)] // none + [InlineData(true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true)] // appveyor + [InlineData(false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, true)] // teamcity + [InlineData(false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false)] // myget + [InlineData(false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false)] // bamboo + [InlineData(false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false)] // continua + [InlineData(false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, true)] // jenkins + [InlineData(false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, true)] // bitrise + [InlineData(false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true)] // travis + [InlineData(false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, true)] // bitbucket + [InlineData(false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false)] // gocd + [InlineData(false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, true)] // gitlab + [InlineData(false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, true)] // az pipelines + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, true)] // gh actions + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true)] // woodpecker + [InlineData(false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false)] // rwx (no PR identifier exposed) + public void Should_Return_True_If_Running_On_Supported_Provider(bool appVeyor, bool teamCity, bool myGet, bool bamboo, bool continuaCI, bool jenkins, bool bitrise, bool travisCI, bool bitbucketPipelines, bool goCD, bool gitLabCI, bool azurePipelines, bool gitHubActions, bool woodpeckerCI, bool rwx, bool isPullRequest) + { + // Given + var appVeyorProvider = Substitute.For(); + var appVeyorEnvironment = new AppVeyorInfoFixture().CreateEnvironmentInfo(); + var teamCityProvider = Substitute.For(); + var teamCityEnvironment = new TeamCityInfoFixture().CreateEnvironmentInfo(); + var myGetProvider = Substitute.For(); + var bambooProvider = Substitute.For(); + var continuaCIProvider = Substitute.For(); + var jenkinsProvider = Substitute.For(); + var jenkinsEnvironment = new JenkinsInfoFixture().CreateEnvironmentInfo(); + var bitriseProvider = Substitute.For(); + var bitriseEnvironment = new BitriseInfoFixture().CreateEnvironmentInfo(); + var travisCIProvider = Substitute.For(); + var travisCIEnvironment = new TravisCIInfoFixture().CreateEnvironmentInfo(); + var bitbucketPipelinesProvider = Substitute.For(); + var bitbucketPipelinesEnvironment = new BitbucketPipelinesInfoFixture().CreateEnvironmentInfo(); + var goCDProvider = Substitute.For(); + var gitLabCIProvider = Substitute.For(); + var gitLabCIEnvironment = new GitLabCIInfoFixture().CreateEnvironmentInfo(); + var gitHubActionsProvider = Substitute.For(); + var gitHubActionsEnvironment = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + var azurePipelinesProvider = Substitute.For(); + var azurePipelinesEnvironment = new AzurePipelinesInfoFixture().CreateEnvironmentInfo(); + + appVeyorProvider.IsRunningOnAppVeyor.Returns(appVeyor); + appVeyorProvider.Environment.Returns(appVeyorEnvironment); + teamCityProvider.IsRunningOnTeamCity.Returns(teamCity); + teamCityProvider.Environment.Returns(teamCityEnvironment); + myGetProvider.IsRunningOnMyGet.Returns(myGet); + bambooProvider.IsRunningOnBamboo.Returns(bamboo); + continuaCIProvider.IsRunningOnContinuaCI.Returns(continuaCI); + jenkinsProvider.IsRunningOnJenkins.Returns(jenkins); + jenkinsProvider.Environment.Returns(jenkinsEnvironment); + bitriseProvider.IsRunningOnBitrise.Returns(bitrise); + bitriseProvider.Environment.Returns(bitriseEnvironment); + travisCIProvider.IsRunningOnTravisCI.Returns(travisCI); + travisCIProvider.Environment.Returns(travisCIEnvironment); + bitbucketPipelinesProvider.IsRunningOnBitbucketPipelines.Returns(bitbucketPipelines); + bitbucketPipelinesProvider.Environment.Returns(bitbucketPipelinesEnvironment); + goCDProvider.IsRunningOnGoCD.Returns(goCD); + gitLabCIProvider.IsRunningOnGitLabCI.Returns(gitLabCI); + gitLabCIProvider.Environment.Returns(gitLabCIEnvironment); + gitHubActionsProvider.IsRunningOnGitHubActions.Returns(gitHubActions); + gitHubActionsProvider.Environment.Returns(gitHubActionsEnvironment); + azurePipelinesProvider.IsRunningOnAzurePipelines.Returns(azurePipelines); + azurePipelinesProvider.Environment.Returns(azurePipelinesEnvironment); + var woodpeckerCIProvider = Substitute.For(); + var rwxProvider = Substitute.For(); + var woodpeckerCIEnvironment = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + woodpeckerCIProvider.IsRunningOnWoodpeckerCI.Returns(woodpeckerCI); + woodpeckerCIProvider.Environment.Returns(woodpeckerCIEnvironment); + var rwxEnvironment = new RwxInfoFixture().CreateEnvironmentInfo(); + rwxProvider.IsRunningOnRwx.Returns(rwx); + rwxProvider.Environment.Returns(rwxEnvironment); // When - var result = buildSystem.IsLocalBuild; + var buildSystem = new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); // Then - Assert.True(result); + Assert.Equal(isPullRequest, buildSystem.IsPullRequest); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/ContinuaCIProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/ContinuaCIProviderTests.cs index 4db294fdca..62e9ddd6fd 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/ContinuaCIProviderTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/ContinuaCIProviderTests.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.ContinuaCI; +using Cake.Common.Tests.Fakes; using Cake.Common.Tests.Fixtures.Build; +using Cake.Testing; using Xunit; namespace Cake.Common.Tests.Unit.Build.ContinuaCI @@ -15,10 +18,22 @@ public sealed class TheConstructor public void Should_Throw_If_Environment_Is_Null() { // Given, When - var result = Record.Exception(() => new ContinuaCIProvider(null)); + var writer = new FakeBuildSystemServiceMessageWriter(); + var result = Record.Exception(() => new ContinuaCIProvider(null, writer)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var environment = FakeEnvironment.CreateUnixEnvironment(); + var result = Record.Exception(() => new ContinuaCIProvider(environment, null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "writer"); } } @@ -71,4 +86,4 @@ public void Should_Return_Non_Null_Reference() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIBuildInfoTests.cs index a32ee77ff5..b05423535c 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIBuildInfoTests.cs @@ -1,9 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Build; + using System; using System.Linq; +using Cake.Common.Tests.Fixtures.Build; using Xunit; namespace Cake.Common.Tests.Unit.Build.ContinuaCI.Data @@ -308,4 +309,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIChangesetInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIChangesetInfoTests.cs index fd92136d98..06ceeabbb8 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIChangesetInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIChangesetInfoTests.cs @@ -1,9 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Build; + using System; using System.Linq; +using Cake.Common.Tests.Fixtures.Build; using Xunit; namespace Cake.Common.Tests.Unit.Build.ContinuaCI.Data @@ -158,4 +159,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIConfigurationInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIConfigurationInfoTests.cs index 7d2e8ebc01..14d1efc149 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIConfigurationInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIConfigurationInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -24,4 +25,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfoTests.cs index 3da279771d..13677dd7de 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -62,4 +63,4 @@ public void Should_Return_Correct_Values() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIProjectInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIProjectInfoTests.cs index 67bedbf603..8f1e86bcbb 100644 --- a/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIProjectInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/ContinuaCI/Data/ContinuaCIProjectInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -24,4 +25,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Commands/GitHubActionsCommandsTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Commands/GitHubActionsCommandsTests.cs new file mode 100644 index 0000000000..1f25cf0e1f --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Commands/GitHubActionsCommandsTests.cs @@ -0,0 +1,762 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Cake.Common.Build; +using Cake.Common.Build.GitHubActions.Commands; +using Cake.Common.Build.GitHubActions.Data; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Commands +{ + public sealed class GitHubActionsCommandsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var result = Record.Exception(() => new GitHubActionsCommands(null, null, null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given + var environment = Substitute.For(); + + // When + var result = Record.Exception(() => new GitHubActionsCommands(environment, null, null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given + var environment = Substitute.For(); + var filesystem = Substitute.For(); + + // When + var result = Record.Exception(() => new GitHubActionsCommands(environment, filesystem, null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + + [Fact] + public void Should_Throw_If_ActionsEnvironment_Is_Null() + { + // Given + var environment = Substitute.For(); + var filesystem = Substitute.For(); + var writer = Substitute.For(); + + // When + var result = Record.Exception(() => new GitHubActionsCommands(environment, filesystem, writer, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "actionsEnvironment"); + } + + [Fact] + public void Should_Throw_If_CreateHttpClient_Is_Null() + { + // Given + var environment = Substitute.For(); + var filesystem = Substitute.For(); + var writer = Substitute.For(); + var actionsEnvironment = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = Record.Exception(() => new GitHubActionsCommands(environment, filesystem, writer, actionsEnvironment, null)); + + // Then + AssertEx.IsArgumentNullException(result, "createHttpClient"); + } + } + + public sealed class TheDebugMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Debug("message"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::debug::message", entry); + } + } + + public sealed class TheNoticeMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Notice("message"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::notice::message", entry); + } + + [Fact] + public void Should_WriteOutputWithAnnotation() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Notice("message", new GitHubActionsAnnotation { Title = "title", File = "file", StartLine = 1, EndLine = 2, StartColumn = 10, EndColumn = 20 }); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::notice title=title,file=file,line=1,endLine=2,col=10,endColumn=20::message", entry); + } + } + + public sealed class TheWarningMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Warning("message"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::warning::message", entry); + } + + [Fact] + public void Should_WriteOutputWithAnnotation() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Warning("message", new GitHubActionsAnnotation { Title = "title", File = "file", StartLine = 1, EndLine = 2, StartColumn = 10, EndColumn = 20 }); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::warning title=title,file=file,line=1,endLine=2,col=10,endColumn=20::message", entry); + } + } + + public sealed class TheErrorMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Error("message"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::error::message", entry); + } + + [Fact] + public void Should_WriteOutputWithAnnotation() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.Error("message", new GitHubActionsAnnotation { Title = "title", File = "file", StartLine = 1, EndLine = 2, StartColumn = 10, EndColumn = 20 }); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::error title=title,file=file,line=1,endLine=2,col=10,endColumn=20::message", entry); + } + } + + public sealed class TheStartGroupMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.StartGroup("title"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::group::title", entry); + } + } + + public sealed class TheEndGroupMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.EndGroup(); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::endgroup::", entry); + } + } + + public sealed class TheSetSecretMethod + { + [Fact] + public void Should_WriteOutput() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.SetSecret("secret"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::add-mask::secret", entry); + } + } + + public sealed class TheWriteCommandMethod + { + [Fact] + public void Should_EscapeCommandMessage() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.WriteCommand("test", "%\r\n"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::test::%25%0D%0A", entry); + } + + [Fact] + public void Should_EscapeCommandParameter() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + commands.WriteCommand("test", new Dictionary { ["parameter"] = "%\r\n:," }, "message"); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal("::test parameter=%25%0D%0A%3A%2C::message", entry); + } + } + + public sealed class TheAddPathMethod + { + [Fact] + public void Should_Throw_If_Path_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + + // When + var result = Record.Exception(() => commands.AddPath(null)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public void Should_Throw_If_SystemPath_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture() + .WithNoGitHubPath() + .CreateGitHubActionsCommands(); + + var path = "/temp/dev/bin"; + + // When + var result = Record.Exception(() => commands.AddPath(path)); + + // Then + AssertEx.IsCakeException(result, "GitHub Actions Runtime SystemPath missing."); + } + + [Fact] + public void Should_AddPath() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + var path = "/temp/dev/bin"; + + // When + commands.AddPath(path); + + // Then + Assert.Equal( + (path + System.Environment.NewLine).NormalizeLineEndings(), + fixture.FileSystem.GetFile("/opt/github.path").GetTextContent().NormalizeLineEndings()); + } + } + + public sealed class TheSetEnvironmentVariableMethod + { + [Fact] + public void Should_Throw_If_Key_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "key"); + } + + [Fact] + public void Should_Throw_If_Value_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var key = "Key"; + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(key, null)); + + // Then + AssertEx.IsArgumentNullException(result, "value"); + } + + [Fact] + public void Should_Throw_If_EnvPath_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture() + .WithNoGitHubEnv() + .CreateGitHubActionsCommands(); + + var key = "Key"; + var value = "Value"; + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(key, value)); + + // Then + AssertEx.IsCakeException(result, "GitHub Actions Runtime EnvPath missing."); + } + + [Fact] + public void Should_SetEnvironmentVariable() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + var key = "Key"; + var value = "Value"; + + // When + commands.SetEnvironmentVariable(key, value); + + // Then + Assert.Equal( + @"Key< commands.SetOutputParameter(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "key"); + } + + [Fact] + public void Should_Throw_If_Value_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var key = "Key"; + + // When + var result = Record.Exception(() => commands.SetOutputParameter(key, null)); + + // Then + AssertEx.IsArgumentNullException(result, "value"); + } + + [Fact] + public void Should_Throw_If_OutputPath_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture() + .WithNoGitHubOutput() + .CreateGitHubActionsCommands(); + + var key = "Key"; + var value = "Value"; + + // When + var result = Record.Exception(() => commands.SetOutputParameter(key, value)); + + // Then + AssertEx.IsCakeException(result, "GitHub Actions Runtime OutputPath missing."); + } + + [Fact] + public void Should_SetOutputParameter() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + var key = "Key"; + var value = "Value"; + + // When + commands.SetOutputParameter(key, value); + + // Then + Assert.Equal( + @"Key=Value +".NormalizeLineEndings(), + fixture.FileSystem.GetFile("/opt/github.output").GetTextContent().NormalizeLineEndings()); + } + } + + public sealed class TheSetStepSummaryMethod + { + [Fact] + public void Should_Throw_If_Summary_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + + // When + var result = Record.Exception(() => commands.SetStepSummary(null)); + + // Then + AssertEx.IsArgumentNullException(result, "summary"); + } + + [Fact] + public void Should_Throw_If_StepSummary_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture() + .WithNoGitHubStepSummary() + .CreateGitHubActionsCommands(); + + var summary = "summary"; + + // When + var result = Record.Exception(() => commands.SetStepSummary(summary)); + + // Then + AssertEx.IsCakeException(result, "GitHub Actions Runtime StepSummary missing."); + } + + [Fact] + public void Should_SetStepSummary() + { + // Given + var fixture = new GitHubActionsCommandsFixture(); + var commands = fixture.CreateGitHubActionsCommands(); + var summary = "## This is some markdown content :rocket:"; + + // When + commands.SetStepSummary(summary); + + // Then + Assert.Equal( + string.Concat(summary, System.Environment.NewLine).NormalizeLineEndings(), + fixture.FileSystem.GetFile("/opt/github.stepsummary").GetTextContent().NormalizeLineEndings()); + } + } + + public sealed class TheUploadArtifactMethod + { + public sealed class File + { + [Fact] + public async Task Should_Throw_If_Path_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + FilePath path = null; + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, null)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public async Task Should_Throw_If_ArtifactName_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = FilePath.FromString("/artifacts/artifact.zip"); + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, null)); + + // Then + AssertEx.IsArgumentNullException(result, "artifactName"); + } + + [Fact] + public async Task Should_Throw_If_File_Missing() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = FilePath.FromString("/artifacts/artifact.zip"); + var artifactName = "artifact"; + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, artifactName)); + + // Then + AssertEx.IsExceptionWithMessage(result, "Artifact file not found."); + } + + [Theory] + [InlineData("/", "/artifacts/artifact.txt")] + [InlineData("/artifacts", "artifact.txt")] + public async Task Should_Upload(string workingDirectory, string testPath) + { + // Given + var fixture = new GitHubActionsCommandsFixture() + .WithWorkingDirectory(workingDirectory); + var testFilePath = FilePath.FromString(testPath); + var artifactName = "artifact"; + fixture + .FileSystem + .CreateFile("/artifacts/artifact.txt") + .SetContent(artifactName); + var commands = fixture.CreateGitHubActionsCommands(); + + // When + await commands.UploadArtifact(testFilePath, artifactName); + } + } + + public sealed class Directory + { + [Fact] + public async Task Should_Throw_If_Path_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + DirectoryPath path = null; + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, null)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public async Task Should_Throw_If_ArtifactName_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = DirectoryPath.FromString("/artifacts"); + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, null)); + + // Then + AssertEx.IsArgumentNullException(result, "artifactName"); + } + + [Fact] + public async Task Should_Throw_If_Directory_Missing() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = DirectoryPath.FromString("/artifacts"); + var artifactName = "artifact"; + + // When + var result = await Record.ExceptionAsync(() => commands.UploadArtifact(path, artifactName)); + + // Then + AssertEx.IsExceptionWithMessage(result, "Artifact directory /artifacts not found."); + } + + [Theory] + [InlineData("/", "/src/artifacts")] + [InlineData("/src", "artifacts")] + public async Task Should_Upload(string workingDirectory, string testPath) + { + // Given + var fixture = new GitHubActionsCommandsFixture() + .WithWorkingDirectory(workingDirectory); + var testDirectoryPath = DirectoryPath.FromString(testPath); + var artifactName = "artifacts"; + var directory = DirectoryPath.FromString("/src/artifacts"); + + fixture + .FileSystem + .CreateFile(directory.CombineWithFilePath("artifact.txt")) + .SetContent(artifactName); + + fixture + .FileSystem + .CreateFile(directory.Combine("folder_a").CombineWithFilePath("artifact.txt")) + .SetContent(artifactName); + + fixture + .FileSystem + .CreateFile(directory.Combine("folder_b").CombineWithFilePath("artifact.txt")) + .SetContent(artifactName); + + fixture + .FileSystem + .CreateFile(directory.Combine("folder_b").Combine("folder_c").CombineWithFilePath("artifact.txt")) + .SetContent(artifactName); + + var commands = fixture.CreateGitHubActionsCommands(); + + // When + await commands.UploadArtifact(testDirectoryPath, artifactName); + } + } + } + + public sealed class TheDownloadArtifactMethod + { + [Fact] + public async Task Should_Throw_If_ArtifactName_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = DirectoryPath.FromString("/artifacts"); + + // When + var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(null, path)); + + // Then + AssertEx.IsArgumentNullException(result, "artifactName"); + } + + [Fact] + public async Task Should_Throw_If_Path_Is_Null() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var artifactName = "artifactName"; + + // When + var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(artifactName, null)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public async Task Should_Throw_If_Directory_Missing() + { + // Given + var commands = new GitHubActionsCommandsFixture().CreateGitHubActionsCommands(); + var path = DirectoryPath.FromString("/artifacts"); + var artifactName = "artifact"; + + // When + var result = await Record.ExceptionAsync(() => commands.DownloadArtifact(artifactName, path)); + + // Then + AssertEx.IsExceptionWithMessage(result, "Local directory /artifacts not found."); + } + + [Theory] + [InlineData("/", "/src/artifacts")] + [InlineData("/src", "artifacts")] + public async Task Should_Download(string workingDirectory, string testPath) + { + // Given + var fixture = new GitHubActionsCommandsFixture() + .WithWorkingDirectory(workingDirectory); + var testDirectoryPath = DirectoryPath.FromString(testPath); + var artifactName = "artifact"; + var directory = DirectoryPath.FromString("/src/artifacts"); + var filePath = directory.CombineWithFilePath("test.txt"); + + fixture + .FileSystem + .CreateDirectory(directory); + + var commands = fixture.CreateGitHubActionsCommands(); + + // When + await commands.DownloadArtifact(artifactName, testDirectoryPath); + var file = fixture + .FileSystem + .GetFile(filePath); + + // Then + Assert.True(file.Exists, $"{filePath.FullPath} doesn't exist."); + Assert.Equal("Cake", file.GetTextContent()); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsEnvironmentInfoTests.cs new file mode 100644 index 0000000000..27c6815b0a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsEnvironmentInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Data +{ + public sealed class GitHubActionsEnvironmentInfoTests + { + public sealed class TheHomeProperty + { + [Fact] + public void Should_Return_Correct_Values() + { + // Given + var info = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Home.FullPath; + + // Then + Assert.Equal("/home/runner", result); + } + } + + public sealed class TheWorkflowProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Workflow; + + // Then + Assert.NotNull(result); + } + } + + public sealed class ThePullRequestProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.PullRequest; + + // Then + Assert.NotNull(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsPullRequestInfoTests.cs new file mode 100644 index 0000000000..1eb2dedaa6 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsPullRequestInfoTests.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Data +{ + public sealed class GitHubActionsPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("pull_request", true)] + [InlineData("push", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new GitHubActionsInfoFixture(); + fixture.Environment.GetEnvironmentVariable("GITHUB_EVENT_NAME").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRunnerInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRunnerInfoTests.cs new file mode 100644 index 0000000000..bca1252fa3 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRunnerInfoTests.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitHubActions.Data; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Data +{ + public sealed class GitHubActionsRunnerInfoTests + { + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("RunnerName", result); + } + } + + // ReSharper disable once InconsistentNaming + public sealed class TheOSProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.OS; + + // Then + Assert.Equal("Linux", result); + } + } + + public sealed class TheTempProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Temp.FullPath; + + // Then + Assert.Equal("/home/runner/work/_temp", result); + } + } + + public sealed class TheToolCacheProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.ToolCache.FullPath; + + // Then + Assert.Equal("/opt/hostedtoolcache", result); + } + } + + public sealed class TheWorkspaceProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Workspace.FullPath; + + // Then + Assert.Equal("/home/runner/work/cake", result); + } + } + + public sealed class TheImageOSProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.ImageOS; + + // Then + Assert.Equal("ubuntu20", result); + } + } + + public sealed class TheImageVersionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.ImageVersion; + + // Then + Assert.Equal("20211209.3", result); + } + } + + public sealed class TheUserProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(); + + // When + var result = info.User; + + // Then + Assert.Equal("runner", result); + } + } + + public sealed class TheArchitectureProperty + { + [Theory] + [InlineData("X86", GitHubActionsArchitecture.X86)] + [InlineData("X64", GitHubActionsArchitecture.X64)] + [InlineData("ARM", GitHubActionsArchitecture.ARM)] + [InlineData("ARM64", GitHubActionsArchitecture.ARM64)] + [InlineData("", GitHubActionsArchitecture.Unknown)] + public void Should_Return_Correct_Value(string value, GitHubActionsArchitecture expected) + { + // Given + var info = new GitHubActionsInfoFixture().CreateRunnerInfo(architecture: value); + + // When + var result = info.Architecture; + + // Then + Assert.Equal(expected, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRuntimeInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRuntimeInfoTests.cs new file mode 100644 index 0000000000..758618f565 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsRuntimeInfoTests.cs @@ -0,0 +1,90 @@ +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Data +{ + public sealed class GitHubActionsRuntimeInfoTests + { + public sealed class TheIsRuntimeAvailableProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.IsRuntimeAvailable; + + // Then + Assert.Equal(true, result); + } + } + + public sealed class TheTokenProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.Token; + + // Then + Assert.Equal( + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ikh5cTROQVRBanNucUM3bWRydEFoaHJDUjJfUSJ9.eyJuYW1laWQiOiJkZGRkZGRkZC1kZGRkLWRkZGQtZGRkZC1kZGRkZGRkZGRkZGQiLCJzY3AiOiJBY3Rpb25zLkdlbmVyaWNSZWFkOjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCBBY3Rpb25zLlJlc3VsdHM6YjllMjgxNTMtY2EyMC00Yjg2LTkxZGQtMDllOGY2NDRlZmRmOjFkODQ5YTQ1LTJmMzAtNWZiYi0zMjI2LWI3MzBhMTdhOTNhZiBBY3Rpb25zLlVwbG9hZEFydGlmYWN0czowMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAvMTpCdWlsZC9CdWlsZC8xNiBMb2NhdGlvblNlcnZpY2UuQ29ubmVjdCBSZWFkQW5kVXBkYXRlQnVpbGRCeVVyaTowMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAvMTpCdWlsZC9CdWlsZC8xNiIsIklkZW50aXR5VHlwZUNsYWltIjoiU3lzdGVtOlNlcnZpY2VJZGVudGl0eSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3NpZCI6IkRERERERERELUREREQtRERERC1ERERELURERERERERERERERCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcHJpbWFyeXNpZCI6ImRkZGRkZGRkLWRkZGQtZGRkZC1kZGRkLWRkZGRkZGRkZGRkZCIsImF1aSI6ImUyMTI4OTY1LThlY2EtNDgxYy1hODhkLWJmOTFlZDg3Y2RiNSIsInNpZCI6ImMwNmVjY2E0LWY3ZjUtNGY4Mi1iM2IxLTJhYjM0M2Y4Mjg3NCIsImFjIjoiW3tcIlNjb3BlXCI6XCJyZWZzL2hlYWRzL21haW5cIixcIlBlcm1pc3Npb25cIjozfV0iLCJhY3NsIjoiMTAiLCJvcmNoaWQiOiJiOWUyODE1My1jYTIwLTRiODYtOTFkZC0wOWU4ZjY0NGVmZGYuYnVpbGQudWJ1bnR1LWxhdGVzdCIsImlzcyI6InZzdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20iLCJhdWQiOiJ2c3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tfHZzbzo0M2YwNTdkMC0wODAzLTRkOTEtOTRhMS1mOGViMTAzZGYxMWYiLCJuYmYiOjE3Mjc1NDQzOTIsImV4cCI6MTcyNzU2NzE5Mn0.sUTvwxD-NlbAhQJB7cIInovd9qDkFHWcwOiiQAlHCsjpRBCEUWb3tWfOmCEpn8It4FWkaSszjMd8oecBEMlyEUtk6Cm6l1AqCUnIT13B48c_2sjhjWz-UDNMt94nzYH2ulC8mBcV_kSEIHJUvOnFKrFMKEdg6axAjLCx4la9MOklVq2ehx6DC12qbUNpTELJGeWz_JvKHWexyfN1qJgUw3y4ritZDJF3HLTpb5IJS7sQmFZVB7F2P6DF-1iaCBX5hgA9KfiwWXw6oTkKd6aOEyJpcBe0b87V_-fVTivOUS-ABE5XN6TCLZSmt7X6qwTPeSoLKgQGx1h_tHwubGDjtQ", + result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.Url; + + // Then + Assert.Equal("https://pipelines.actions.githubusercontent.com/ip0FyYnZXxdEOcOwPHkRsZJd2x6G5XoT486UsAb0/", result); + } + } + + public sealed class TheEnvPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.EnvPath.FullPath; + + // Then + Assert.Equal("/opt/github.env", result); + } + } + + public sealed class TheSystemPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.SystemPath.FullPath; + + // Then + Assert.Equal("/opt/github.path", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsWorkflowInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsWorkflowInfoTests.cs new file mode 100644 index 0000000000..fa523ba95a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/Data/GitHubActionsWorkflowInfoTests.cs @@ -0,0 +1,384 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitHubActions.Data; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions.Data +{ + public sealed class GitHubActionsWorkflowInfoTests + { + public sealed class TheActionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Action; + + // Then + Assert.Equal("run1", result); + } + } + + public sealed class TheActionPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.ActionPath.FullPath; + + // Then + Assert.Equal("/path/to/action", result); + } + } + + public sealed class TheActorProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Actor; + + // Then + Assert.Equal("dependabot", result); + } + } + + public sealed class TheApiUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.ApiUrl; + + // Then + Assert.Equal("https://api.github.com", result); + } + } + + public sealed class TheBaseRefProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.BaseRef; + + // Then + Assert.Equal("master", result); + } + } + + public sealed class TheEventNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.EventName; + + // Then + Assert.Equal("pull_request", result); + } + } + + public sealed class TheEventPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.EventPath.FullPath; + + // Then + Assert.Equal("/home/runner/work/_temp/_github_workflow/event.json", result); + } + } + + public sealed class TheGraphQLUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.GraphQLUrl; + + // Then + Assert.Equal("https://api.github.com/graphql", result); + } + } + + public sealed class TheHeadRefProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.HeadRef; + + // Then + Assert.Equal("dependabot/nuget/Microsoft.SourceLink.GitHub-1.0.0", result); + } + } + + public sealed class TheJobProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Job; + + // Then + Assert.Equal("job", result); + } + } + + public sealed class TheRefProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Ref; + + // Then + Assert.Equal("refs/pull/1/merge", result); + } + } + + public sealed class TheRepositoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Repository; + + // Then + Assert.Equal("cake-build/cake", result); + } + } + + public sealed class TheRepositoryOwnerProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.RepositoryOwner; + + // Then + Assert.Equal("cake-build", result); + } + } + + public sealed class TheRunIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.RunId; + + // Then + Assert.Equal("34058136", result); + } + } + + public sealed class TheRunNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.RunNumber; + + // Then + Assert.Equal(60, result); + } + } + + public sealed class TheServerUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.ServerUrl; + + // Then + Assert.Equal("https://github.com", result); + } + } + + public sealed class TheShaProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Sha; + + // Then + Assert.Equal("d1e4f990f57349334368c8253382abc63be02d73", result); + } + } + + public sealed class TheWorkflowProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Workflow; + + // Then + Assert.Equal("Build", result); + } + } + + public sealed class TheWorkspaceProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Workspace.FullPath; + + // Then + Assert.Equal("/home/runner/work/cake/cake", result); + } + } + + public sealed class TheAttemptProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.Attempt; + + // Then + Assert.Equal(2, result); + } + } + + public sealed class TheRefProtectedProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.RefProtected; + + // Then + Assert.True(result); + } + } + + public sealed class TheRefNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(); + + // When + var result = info.RefName; + + // Then + Assert.Equal("main", result); + } + } + + public sealed class TheRefTypeProperty + { + [Theory] + [InlineData("branch", GitHubActionsRefType.Branch)] + [InlineData("tag", GitHubActionsRefType.Tag)] + [InlineData("", GitHubActionsRefType.Unknown)] + public void Should_Return_Correct_Value(string value, GitHubActionsRefType expected) + { + // Given + var info = new GitHubActionsInfoFixture().CreateWorkflowInfo(refType: value); + + // When + var result = info.RefType; + + // Then + Assert.Equal(expected, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitHubActions/GitHubActionsProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/GitHubActions/GitHubActionsProviderTests.cs new file mode 100644 index 0000000000..c1bca1f5d7 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitHubActions/GitHubActionsProviderTests.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitHubActions; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitHubActions +{ + public sealed class GitHubActionsProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var result = Record.Exception(() => new GitHubActionsProvider(null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given + var environment = Substitute.For(); + + // When + var result = Record.Exception(() => new GitHubActionsProvider(environment, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given + var environment = Substitute.For(); + var filesystem = Substitute.For(); + + // When + var result = Record.Exception(() => new GitHubActionsProvider(environment, filesystem, null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + } + + public sealed class TheIsRunningOnGitHubActionsProperty + { + [Fact] + public void Should_Return_True_If_Running_On_GitHubActions() + { + // Given + var fixture = new GitHubActionsFixture(); + fixture.IsRunningOnGitHubActions(); + var gitHubActions = fixture.CreateGitHubActionsService(); + + // When + var result = gitHubActions.IsRunningOnGitHubActions; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_GitHubActions() + { + // Given + var fixture = new GitHubActionsFixture(); + var gitHubActions = fixture.CreateGitHubActionsService(); + + // When + var result = gitHubActions.IsRunningOnGitHubActions; + + // Then + Assert.False(result); + } + } + + public sealed class TheEnvironmentProperty + { + [Fact] + public void Should_Return_Non_Null_Reference() + { + // Given + var fixture = new GitHubActionsFixture(); + var gitHubActions = fixture.CreateGitHubActionsService(); + + // When + var result = gitHubActions.Environment; + + // Then + Assert.NotNull(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs new file mode 100644 index 0000000000..14b9ace302 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIBuildInfoTests.cs @@ -0,0 +1,404 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI.Data; +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data +{ + public sealed class GitLabCIBuildInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(50, result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(50, result); + } + } + + public sealed class TheManualProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Manual; + + // Then + Assert.Equal(true, result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Manual; + + // Then + Assert.Equal(true, result); + } + } + + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("spec:other", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("spec:other", result); + } + } + + public sealed class ThePipelineIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.PipelineId; + + // Then + Assert.Equal(1000, result); + } + } + + public sealed class ThePipelineIIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.PipelineIId; + + // Then + Assert.Equal(100, result); + } + } + + public sealed class TheReferenceProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Reference; + + // Then + Assert.Equal("1ecfd275763eff1d6b4844ea3168962458c9f27a", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Reference; + + // Then + Assert.Equal("1ecfd275763eff1d6b4844ea3168962458c9f27a", result); + } + } + + public sealed class TheRefNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.RefName; + + // Then + Assert.Equal("master", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.RefName; + + // Then + Assert.Equal("master", result); + } + } + + public sealed class TheRepoUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.RepoUrl; + + // Then + Assert.Equal("https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.RepoUrl; + + // Then + Assert.Equal("https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git", result); + } + } + + public sealed class TheStageProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Stage; + + // Then + Assert.Equal("test", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Stage; + + // Then + Assert.Equal("test", result); + } + } + + public sealed class TheTagProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Tag; + + // Then + Assert.Equal("1.0.0", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Tag; + + // Then + Assert.Equal("1.0.0", result); + } + } + + public sealed class TheTokenProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Token; + + // Then + Assert.Equal("abcde-1234ABCD5678ef", result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Token; + + // Then + Assert.Equal("abcde-1234ABCD5678ef", result); + } + } + + public sealed class TheTriggeredProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.Triggered; + + // Then + Assert.Equal(true, result); + } + + [Fact] + public void Should_Return_Correct_Value_Version_Nine_Or_Newer() + { + // Given + var info = new GitLabCIInfoFixture(true).CreateBuildInfo(); + + // When + var result = info.Triggered; + + // Then + Assert.Equal(true, result); + } + } + + public sealed class TheUserEmailProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.UserEmail; + + // Then + Assert.Equal("anthony@warwickcontrol.com", result); + } + } + + public sealed class TheUserIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateBuildInfo(); + + // When + var result = info.UserId; + + // Then + Assert.Equal(42, result); + } + } + + public sealed class TheSourceProperty + { + // Values taken from https://docs.gitlab.com/ee/ci/jobs/job_rules.html#ci_pipeline_source-predefined-variable + [Theory] + [InlineData("", null)] + [InlineData("unknown_source", null)] + [InlineData("api", GitLabCIPipelineSource.Api)] + [InlineData("chat", GitLabCIPipelineSource.Chat)] + [InlineData("external", GitLabCIPipelineSource.External)] + [InlineData("external_pull_request_event", GitLabCIPipelineSource.ExternalPullRequestEvent)] + [InlineData("merge_request_event", GitLabCIPipelineSource.MergeRequestEvent)] + [InlineData("ondemand_dast_scan", GitLabCIPipelineSource.OnDemandDastScan)] + [InlineData("ondemand_dast_validation", GitLabCIPipelineSource.OnDemandDastValidation)] + [InlineData("parent_pipeline", GitLabCIPipelineSource.ParentPipeline)] + [InlineData("pipeline", GitLabCIPipelineSource.Pipeline)] + [InlineData("push", GitLabCIPipelineSource.Push)] + [InlineData("schedule", GitLabCIPipelineSource.Schedule)] + [InlineData("security_orchestration_policy", GitLabCIPipelineSource.SecurityOrchestrationPolicy)] + [InlineData("trigger", GitLabCIPipelineSource.Trigger)] + [InlineData("web", GitLabCIPipelineSource.Web)] + [InlineData("webide", GitLabCIPipelineSource.WebIde)] + public void Should_Return_Correct_Value(string pipelineSourceEnvironmentVariable, GitLabCIPipelineSource? expectedSource) + { + // Given + var fixture = new GitLabCIInfoFixture(); + fixture.Environment.GetEnvironmentVariable("CI_PIPELINE_SOURCE").Returns(pipelineSourceEnvironmentVariable); + var info = fixture.CreateBuildInfo(); + + // When + var result = info.Source; + + // Then + Assert.Equal(expectedSource, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIProjectInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIProjectInfoTests.cs new file mode 100644 index 0000000000..3f29513e0d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIProjectInfoTests.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data +{ + public sealed class GitLabCIProjectInfoTests + { + public sealed class TheDirectoryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Directory; + + // Then + Assert.Equal("/builds/gitlab-org/gitlab-ce", result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(34, result); + } + } + + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("gitlab-ce", result); + } + } + + public sealed class TheNamespaceProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Namespace; + + // Then + Assert.Equal("gitlab-org", result); + } + } + + public sealed class ThePathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Path; + + // Then + Assert.Equal("gitlab-org/gitlab-ce", result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateProjectInfo(); + + // When + var result = info.Url; + + // Then + Assert.Equal("https://gitlab.com/gitlab-org/gitlab-ce", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIPullRequestInfoTests.cs new file mode 100644 index 0000000000..97d588dff2 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIPullRequestInfoTests.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data +{ + public sealed class GitLabCIPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("1", true)] + [InlineData("0", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new GitLabCIInfoFixture(); + fixture.Environment.GetEnvironmentVariable("CI_MERGE_REQUEST_ID").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(10, result); + } + } + + public sealed class TheIIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.IId; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class TheSourceBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.SourceBranch; + + // Then + Assert.Equal("source-branch", result); + } + } + + public sealed class TheTargetBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.TargetBranch; + + // Then + Assert.Equal("main", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIRunnerInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIRunnerInfoTests.cs new file mode 100644 index 0000000000..4225d2c6fe --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIRunnerInfoTests.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data +{ + public sealed class GitLabCIRunnerInfoTests + { + public sealed class TheDescriptionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Description; + + // Then + Assert.Equal("my runner", result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(10, result); + } + } + + public sealed class TheTagsProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateRunnerInfo(); + + // When + var result = info.Tags; + + // Then + Assert.Equal(new[] { "docker", "linux" }, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIServerInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIServerInfoTests.cs new file mode 100644 index 0000000000..ef01b5922e --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/Data/GitLabCIServerInfoTests.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI.Data +{ + public sealed class GitLabCIServerInfoTests + { + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateServerInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("GitLab", result); + } + } + } + + public sealed class TheRevisionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateServerInfo(); + + // When + var result = info.Revision; + + // Then + Assert.Equal("70606bf", result); + } + } + + public sealed class TheVersionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateServerInfo(); + + // When + var result = info.Version; + + // Then + Assert.Equal("8.9.0", result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GitLabCIInfoFixture().CreateServerInfo(); + + // When + var result = info.Url; + + // Then + Assert.Equal("https://gitlab.example.com:8080", result); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCICommandsTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCICommandsTests.cs new file mode 100644 index 0000000000..79ffe7811b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCICommandsTests.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI +{ + public sealed class GitLabCICommandsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // When + var result = Record.Exception(() => new GitLabCICommands(null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + } + + public sealed class TheSetEnvironmentVariableMethod + { + [Fact] + public void Should_Throw_If_EnvPath_Is_Null() + { + // Given + var commands = new GitLabCIFixture().CreateGitLabCIService().Commands; + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "envPath"); + } + + [Fact] + public void Should_Throw_If_Key_Is_Null() + { + // Given + var commands = new GitLabCIFixture().CreateGitLabCIService().Commands; + var envPath = "cake.env"; + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(envPath, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "key"); + } + + [Fact] + public void Should_Throw_If_Value_Is_Null() + { + // Given + var commands = new GitLabCIFixture().CreateGitLabCIService().Commands; + var envPath = "cake.env"; + var key = "Key"; + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(envPath, key, null)); + + // Then + AssertEx.IsArgumentNullException(result, "value"); + } + + [Fact] + public void Should_SetEnvironmentVariable() + { + // Given + var gitLabCIFixture = new GitLabCIFixture(); + var commands = gitLabCIFixture.CreateGitLabCIService().Commands; + var envPath = "cake.env"; + var key = "Key"; + var value = "Value"; + + // When + commands.SetEnvironmentVariable(envPath, key, value); + + // Then + Assert.Equal( + @"Key=Value +".NormalizeLineEndings(), + gitLabCIFixture.FileSystem.GetFile("cake.env").GetTextContent().NormalizeLineEndings()); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCIProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCIProviderTests.cs new file mode 100644 index 0000000000..04931cbd2d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GitLabCI/GitLabCIProviderTests.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GitLabCI +{ + public sealed class GitLabCIProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var result = Record.Exception(() => new GitLabCIProvider(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given + var environment = Substitute.For(); + + // When + var result = Record.Exception(() => new GitLabCIProvider(environment, null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + } + + public sealed class TheIsRunningOnGitLabCIProperty + { + [Fact] + public void Should_Return_True_If_Running_On_GitLabCI() + { + // Given + var fixture = new GitLabCIFixture(); + fixture.IsRunningOnGitLabCI(); + var gitLabCI = fixture.CreateGitLabCIService(); + + // When + var result = gitLabCI.IsRunningOnGitLabCI; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_GitLabCI() + { + // Given + var fixture = new GitLabCIFixture(); + var gitLabCI = fixture.CreateGitLabCIService(); + + // When + var result = gitLabCI.IsRunningOnGitLabCI; + + // Then + Assert.False(result); + } + } + + public sealed class TheEnvironmentProperty + { + [Fact] + public void Should_Return_Non_Null_Reference() + { + // Given + var fixture = new GitLabCIFixture(); + var gitLabCI = fixture.CreateGitLabCIService(); + + // When + var result = gitLabCI.Environment; + + // Then + Assert.NotNull(result); + } + } + + public sealed class TheCommandsProperty + { + [Fact] + public void Should_Return_Non_Null_Reference() + { + // Given + var fixture = new GitLabCIFixture(); + var gitLabCI = fixture.CreateGitLabCIService(); + + // When + var result = gitLabCI.Commands; + + // Then + Assert.NotNull(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDEnvironmentInfoTests.cs new file mode 100644 index 0000000000..b45ab71d37 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDEnvironmentInfoTests.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GoCD.Data +{ + public sealed class GoCDEnvironmentInfoTests + { + public sealed class TheGoCDUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.GoCDUrl; + + // Then + Assert.Equal("https://127.0.0.1:8154/go", result); + } + } + + public sealed class TheEnvironmentNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.EnvironmentName; + + // Then + Assert.Equal("Development", result); + } + } + + public sealed class TheJobNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.JobName; + + // Then + Assert.Equal("linux-firefox", result); + } + } + + public sealed class TheUserProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.User; + + // Then + Assert.Equal("changes", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDPipelineInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDPipelineInfoTests.cs new file mode 100644 index 0000000000..32450ed105 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDPipelineInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GoCD.Data +{ + public sealed class GoCDPipelineInfoTests + { + public sealed class ThePipelineNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("main", result); + } + } + + public sealed class ThePipelineCounterProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Counter; + + // Then + Assert.Equal(2345, result); + } + } + + public sealed class ThePipelineLabelProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Label; + + // Then + Assert.Equal("1.1.2345", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDRepositoryInfoTests.cs new file mode 100644 index 0000000000..f1d222573f --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDRepositoryInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GoCD.Data +{ + public sealed class GoCDRepositoryInfoTests + { + public sealed class TheRevisionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Revision; + + // Then + Assert.Equal("123", result); + } + } + + public sealed class TheFromRevisionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.FromRevision; + + // Then + Assert.Equal("122", result); + } + } + + public sealed class TheToRevisionProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.ToRevision; + + // Then + Assert.Equal("124", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDStageInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDStageInfoTests.cs new file mode 100644 index 0000000000..0ee367662b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GoCD/Data/GoCDStageInfoTests.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GoCD.Data +{ + public sealed class GoCDStageInfoTests + { + public sealed class TheCounterProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateStageInfo(); + + // When + var result = info.Counter; + + // Then + Assert.Equal(1, result); + } + } + + public sealed class TheStageNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new GoCDInfoFixture().CreateStageInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("dev", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/GoCD/GoCDProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/GoCD/GoCDProviderTests.cs new file mode 100644 index 0000000000..eb58701b53 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/GoCD/GoCDProviderTests.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GoCD; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.Diagnostics; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.GoCD +{ + public sealed class GoCDProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var cakeLog = Substitute.For(); + var result = Record.Exception(() => new GoCDProvider(null, cakeLog)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given, When + var environment = Substitute.For(); + var result = Record.Exception(() => new GoCDProvider(environment, null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + } + + public sealed class TheIsRunningOnGoCDProperty + { + [Fact] + public void Should_Return_True_If_Running_On_GoCD() + { + // Given + var fixture = new GoCDFixture(); + fixture.IsRunningOnGoCD(); + var gocd = fixture.CreateGoCDService(); + + // When + var result = gocd.IsRunningOnGoCD; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_GoCD() + { + // Given + var fixture = new GoCDFixture(); + var gocd = fixture.CreateGoCDService(); + + // When + var result = gocd.IsRunningOnGoCD; + + // Then + Assert.False(result); + } + } + + public sealed class TheEnvironmentProperty + { + [Fact] + public void Should_Return_Non_Null_Reference() + { + // Given + var fixture = new GoCDFixture(); + var gocd = fixture.CreateGoCDService(); + + // When + var result = gocd.Environment; + + // Then + Assert.NotNull(result); + } + } + + public sealed class TheGetHistoryMethod + { + [Fact] + public void Should_Throw_If_Username_Is_Null() + { + // Given + var fixture = new GoCDFixture(); + var appVeyor = fixture.CreateGoCDService(); + + // When + var result = Record.Exception(() => appVeyor.GetHistory(null, "password")); + + // Then + AssertEx.IsArgumentNullException(result, "username"); + } + + [Fact] + public void Should_Throw_If_Password_Is_Null() + { + // Given + var fixture = new GoCDFixture(); + var appVeyor = fixture.CreateGoCDService(); + + // When + var result = Record.Exception(() => appVeyor.GetHistory("username", null)); + + // Then + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Server_Url_Is_Null() + { + // Given + var fixture = new GoCDFixture(); + var appVeyor = fixture.CreateGoCDService(); + + // When + var result = Record.Exception(() => appVeyor.GetHistory("username", "password", null)); + + // Then + AssertEx.IsArgumentNullException(result, "serverUrl"); + } + + [Fact] + public void Should_Throw_If_Not_Running_On_GoCD() + { + // Given + var fixture = new GoCDFixture(); + var appVeyor = fixture.CreateGoCDService(); + + // When + var result = Record.Exception(() => appVeyor.GetHistory("username", "password")); + + // Then + AssertEx.IsExceptionWithMessage(result, + "The current build is not running on Go.CD."); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsBuildInfoTests.cs index 55ef85c098..171b38e68f 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsBuildInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -24,7 +25,7 @@ public void Should_Return_Correct_Value() } } - public sealed class TheBuildDisplayNameProperty + public sealed class TheBuildIdProperty { [Fact] public void Should_Return_Correct_Value() @@ -33,14 +34,14 @@ public void Should_Return_Correct_Value() var info = new JenkinsInfoFixture().CreateBuildInfo(); // When - var result = info.BuildDisplayName; + var result = info.BuildId; // Then - Assert.Equal("#456", result); + Assert.Equal("456", result); } } - public sealed class TheBuildIdProperty + public sealed class TheBuildDisplayNameProperty { [Fact] public void Should_Return_Correct_Value() @@ -49,10 +50,10 @@ public void Should_Return_Correct_Value() var info = new JenkinsInfoFixture().CreateBuildInfo(); // When - var result = info.BuildId; + var result = info.BuildDisplayName; // Then - Assert.Equal("456", result); + Assert.Equal("#456", result); } } @@ -87,6 +88,36 @@ public void Should_Return_Correct_Value() Assert.Equal("http://localhost:8080/jenkins/job/cake/456/", result); } } + public sealed class TheExecutorNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + // When + var result = info.Build.ExecutorNumber; + + // Then + Assert.Equal(2112, result); + } + } + + public sealed class TheWorkspaceProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Build.Workspace; + + // Then + Assert.Equal("C:\\Jenkins\\build\\456", result); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsChangeInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsChangeInfoTests.cs new file mode 100644 index 0000000000..f1256220c0 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsChangeInfoTests.cs @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Jenkins.Data +{ + public sealed class JenkinsChangeInfoTests + { + public sealed class TheChangeIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Id; + + // Then + Assert.Equal("42178", result); + } + } + + public sealed class TheChangeUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Url; + + // Then + Assert.Equal("http://changeurl", result); + } + } + + public sealed class TheChangeTitleProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Title; + + // Then + Assert.Equal("Modified x", result); + } + } + + public sealed class TheChangeAuthorProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Author; + + // Then + Assert.Equal("cu", result); + } + } + + public sealed class TheChangeAuthorDisplayNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.AuthorDisplayName; + + // Then + Assert.Equal("Cake User", result); + } + } + + public sealed class TheChangeAuthorEmailroperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.AuthorEmail; + + // Then + Assert.Equal("cake@cakebuild.net", result); + } + } + + public sealed class TheChangeTargetProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Target; + + // Then + Assert.Equal("develop", result); + } + } + + public sealed class TheChangeBranchProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Branch; + + // Then + Assert.Equal("feature/feature1", result); + } + } + + public sealed class TheChangeForkProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.Fork; + + // Then + Assert.Equal("fork1", result); + } + } + + public sealed class TheIsPullRequestProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Change.IsPullRequest; + + // Then + Assert.Equal(true, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsJobInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsJobInfoTests.cs index 144eb27377..7e0a7c88eb 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsJobInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsJobInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -24,6 +25,22 @@ public void Should_Return_Correct_Value() } } + public sealed class TheJobBaseNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateJobInfo(); + + // When + var result = info.JobBaseName; + + // Then + Assert.Equal("JOB1BASE", result); + } + } + public sealed class TheJobUrlProperty { [Fact] @@ -40,4 +57,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsNodeInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsNodeInfoTests.cs index 983bce3174..1b4398edf9 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsNodeInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsNodeInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -36,8 +37,8 @@ public void Should_Return_Correct_Value() var result = info.NodeLabels; // Then - Assert.Equal("cake development build", result); + Assert.Equal(new[] { "cake", "development", "build" }, result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsRepositoryInfoTests.cs index e56c2d2fb2..e08c4a6706 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsRepositoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/Data/JenkinsRepositoryInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -8,6 +9,22 @@ namespace Cake.Common.Tests.Unit.Build.Jenkins.Data { public sealed class JenkinsRepositoryInfoTests { + public sealed class TheBranchNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new JenkinsInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.BranchName; + + // Then + Assert.Equal("CAKE-BRANCH", result); + } + } + public sealed class TheGitBranchProperty { [Fact] @@ -88,4 +105,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsEnvironmentInfoTests.cs index cc95a3ed1d..0dce22ba38 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsEnvironmentInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsEnvironmentInfoTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; @@ -8,38 +9,6 @@ namespace Cake.Common.Tests.Unit.Build.Jenkins { public sealed class JenkinsEnvironmentInfoTests { - public sealed class TheWorkspaceProperty - { - [Fact] - public void Should_Return_Correct_Value() - { - // Given - var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); - - // When - var result = info.Workspace; - - // Then - Assert.Equal("C:\\Jenkins\\build\\456", result); - } - } - - public sealed class TheExecutorNumberProperty - { - [Fact] - public void Should_Return_Correct_Value() - { - // Given - var info = new JenkinsInfoFixture().CreateEnvironmentInfo(); - - // When - var result = info.ExecutorNumber; - - // Then - Assert.Equal(2112, result); - } - } - public sealed class TheJenkinsHomeProperty { [Fact] @@ -72,4 +41,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsProviderTests.cs index f34d5a832b..8334a5a050 100644 --- a/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsProviderTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/Jenkins/JenkinsProviderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Jenkins; using NSubstitute; using Xunit; @@ -18,7 +19,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => new JenkinsProvider(null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -52,4 +53,4 @@ public void Should_Return_False_If_Not_Running_On_Jenkins() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs new file mode 100644 index 0000000000..c6e7c3cb16 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs @@ -0,0 +1,179 @@ +using Cake.Common.Build.MyGet; +using Cake.Common.Tests.Fakes; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.MyGet +{ + public sealed class MyGetProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var writer = new FakeBuildSystemServiceMessageWriter(); + var result = Record.Exception(() => new MyGetProvider(null, writer)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var result = Record.Exception(() => new MyGetProvider(new FakeEnvironment(PlatformFamily.Unknown), null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + } + + public sealed class IsRunningOnMyGet + { + [Theory] + [InlineData(true)] + [InlineData(false)] + [InlineData(null)] + public void Should_Return_True_If_Running_On_MyGet(bool? capitalCase) + { + // Given + var fixture = new MyGetFixture(); + fixture.IsRunningOnMyGet(capitalCase); + var provider = fixture.CreateMyGetProvider(); + + // When + var result = provider.IsRunningOnMyGet; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_MyGet() + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + var result = provider.IsRunningOnMyGet; + + // Then + Assert.False(result); + } + } + + public sealed class BuildProblem + { + [Theory] + [InlineData("Test build problem", "Test build problem")] + [InlineData("", "")] + [InlineData(null, "")] + [InlineData("[Special characters:\r\n\"\'test|split\'\"]", "|[Special characters:|r|n\"|\'test||split|\'\"|]")] + public void Should_Log_Description(string description, string expectedOutput) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.BuildProblem(description); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[buildProblem description='{expectedOutput}']", entry); + } + } + + public sealed class SetParameter + { + [Theory] + [InlineData("Parameter", "Value", "Parameter", "Value")] + [InlineData("", "", "", "")] + [InlineData(null, null, "", "")] + [InlineData("Special [param] \'name\'", "test\n|\r||value||", "Special |[param|] |\'name|\'", "test|n|||r||||value||||")] + public void Should_Log_Parameter_Value(string name, string value, string expectedName, string expectedValue) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.SetParameter(name, value); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[setParameter name='{expectedName}' value='{expectedValue}']", entry); + } + } + + public sealed class WriteStatus + { + [Theory] + [InlineData("M", MyGetBuildStatus.Normal, "M", "NORMAL")] + [InlineData(null, MyGetBuildStatus.Warning, "", "WARNING")] + [InlineData("Message \n text", MyGetBuildStatus.Error, "Message |n text", "ERROR")] + [InlineData("[Failure]|status", MyGetBuildStatus.Failure, "|[Failure|]||status", "FAILURE")] + public void Should_Log_Status(string message, MyGetBuildStatus status, string expectedMessage, string expectedStatus) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.WriteStatus(message, status); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[message text='{expectedMessage}' status='{expectedStatus}']", entry); + } + + [Theory] + [InlineData("Hello, World!", MyGetBuildStatus.Normal, "", "Hello, World!", "NORMAL", "")] + [InlineData("My custom message", MyGetBuildStatus.Warning, "Hello, World!", "My custom message", "WARNING", "Hello, World!")] + [InlineData("Test", MyGetBuildStatus.Error, "r = (a - b) * c + (s1 & s2)", "Test", "ERROR", "r = (a - b) * c + (s1 & s2)")] + [InlineData("T", MyGetBuildStatus.Failure, "i = (b << 4) | c;\r\nr = a[i] / c;", "T", "FAILURE", "i = (b << 4) || c;|r|nr = a|[i|] / c;")] + public void Should_Log_Status_With_Error_Details(string message, MyGetBuildStatus status, string errorDetails, string expectedMessage, string expectedStatus, string expectedDetails) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.WriteStatus(message, status, errorDetails); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[message text='{expectedMessage}' status='{expectedStatus}' errorDetails='{expectedDetails}']", entry); + } + } + + public sealed class SetBuildNumber + { + [Theory] + [InlineData("2.3.1", "2.3.1")] + [InlineData("", "")] + [InlineData(null, "")] + [InlineData("99.4-beta", "99.4-beta")] + [InlineData("[net5.0\r\n\"\'beta|preview\'\"]", "|[net5.0|r|n\"|\'beta||preview|\'\"|]")] + public void Should_Log_Build_Number(string buildNumber, string expectedOutput) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.SetBuildNumber(buildNumber); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[buildNumber '{expectedOutput}']", entry); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Commands/RwxCommandsTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Commands/RwxCommandsTests.cs new file mode 100644 index 0000000000..93de89dcfc --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Commands/RwxCommandsTests.cs @@ -0,0 +1,396 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Commands +{ + public sealed class RwxCommandsTests + { + public sealed class TheSetValueMethod + { + [Fact] + public void Should_Write_Value_File() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + + // When + commands.SetValue("hello", "world"); + + // Then + var file = fixture.FileSystem.GetFile("/rwx/values/hello"); + Assert.True(file.Exists); + Assert.Equal("world", file.GetTextContent()); + } + + [Fact] + public void Should_Overwrite_Existing_Value() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + commands.SetValue("hello", "first"); + + // When + commands.SetValue("hello", "second"); + + // Then + Assert.Equal("second", fixture.FileSystem.GetFile("/rwx/values/hello").GetTextContent()); + } + + [Fact] + public void Should_Preserve_Multiline_Content_Verbatim() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + const string value = "line one\nline two\nline three"; + + // When + commands.SetValue("multi", value); + + // Then + Assert.Equal(value, fixture.FileSystem.GetFile("/rwx/values/multi").GetTextContent()); + } + + [Fact] + public void Should_Create_Values_Directory_When_Missing() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.GetDirectory("/rwx/values").Delete(true); + var commands = fixture.CreateRwxCommands(); + + // When + commands.SetValue("hello", "world"); + + // Then + Assert.True(fixture.FileSystem.GetFile("/rwx/values/hello").Exists); + } + + [Fact] + public void Should_Throw_On_Null_Key() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetValue(null, "value")); + + // Then + AssertEx.IsArgumentNullException(result, "key"); + } + + [Fact] + public void Should_Throw_On_Empty_Key() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetValue(string.Empty, "value")); + + // Then + AssertEx.IsArgumentNullException(result, "key"); + } + + [Fact] + public void Should_Throw_On_Null_Value() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetValue("key", null)); + + // Then + AssertEx.IsArgumentNullException(result, "value"); + } + + [Theory] + [InlineData("foo/bar")] + [InlineData("foo\\bar")] + [InlineData("..")] + [InlineData("../escape")] + public void Should_Throw_On_Key_With_Path_Components(string key) + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetValue(key, "value")); + + // Then + Assert.IsType(result); + } + + [Fact] + public void Should_Throw_When_Values_Path_Missing() + { + // Given + var fixture = new RwxCommandsFixture().WithNoRwxValues(); + var commands = fixture.CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetValue("key", "value")); + + // Then + Assert.IsType(result); + Assert.Contains("ValuesPath", result.Message); + } + } + + public sealed class TheSetEnvironmentVariableMethod + { + [Fact] + public void Should_Write_Env_File() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + + // When + commands.SetEnvironmentVariable("FOO", "bar"); + + // Then + var file = fixture.FileSystem.GetFile("/rwx/env/FOO"); + Assert.True(file.Exists); + Assert.Equal("bar", file.GetTextContent()); + } + + [Fact] + public void Should_Overwrite_Existing_Env_Var() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + commands.SetEnvironmentVariable("FOO", "first"); + + // When + commands.SetEnvironmentVariable("FOO", "second"); + + // Then + Assert.Equal("second", fixture.FileSystem.GetFile("/rwx/env/FOO").GetTextContent()); + } + + [Fact] + public void Should_Write_Value_Verbatim_Without_Trailing_Newline() + { + // Given + var fixture = new RwxCommandsFixture(); + var commands = fixture.CreateRwxCommands(); + + // When + commands.SetEnvironmentVariable("FOO", "bar"); + + // Then + // RWX trims a single trailing \n when materializing the env var; writing verbatim + // lets callers decide whether they want one, and `bar` stays `bar`. + Assert.Equal("bar", fixture.FileSystem.GetFile("/rwx/env/FOO").GetTextContent()); + } + + [Fact] + public void Should_Create_Env_Directory_When_Missing() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.GetDirectory("/rwx/env").Delete(true); + var commands = fixture.CreateRwxCommands(); + + // When + commands.SetEnvironmentVariable("FOO", "bar"); + + // Then + Assert.True(fixture.FileSystem.GetFile("/rwx/env/FOO").Exists); + } + + [Fact] + public void Should_Throw_On_Null_Name() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(null, "value")); + + // Then + AssertEx.IsArgumentNullException(result, "name"); + } + + [Fact] + public void Should_Throw_On_Empty_Name() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(string.Empty, "value")); + + // Then + AssertEx.IsArgumentNullException(result, "name"); + } + + [Fact] + public void Should_Throw_On_Null_Value() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable("FOO", null)); + + // Then + AssertEx.IsArgumentNullException(result, "value"); + } + + [Theory] + [InlineData("foo/bar")] + [InlineData("foo\\bar")] + [InlineData("..")] + [InlineData("../escape")] + public void Should_Throw_On_Name_With_Path_Components(string name) + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(name, "value")); + + // Then + Assert.IsType(result); + } + + [Fact] + public void Should_Throw_When_Env_Path_Missing() + { + // Given + var fixture = new RwxCommandsFixture().WithNoRwxEnv(); + var commands = fixture.CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable("FOO", "bar")); + + // Then + Assert.IsType(result); + Assert.Contains("EnvPath", result.Message); + } + } + + public sealed class TheUploadArtifactFileMethod + { + [Fact] + public void Should_Copy_File_To_Artifacts_Directory() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.CreateFile("/work/output/report.txt").SetContent("payload"); + var commands = fixture.CreateRwxCommands(); + + // When + commands.UploadArtifact("/work/output/report.txt"); + + // Then + var artifact = fixture.FileSystem.GetFile("/rwx/artifacts/report.txt"); + Assert.True(artifact.Exists); + Assert.Equal("payload", artifact.GetTextContent()); + } + + [Fact] + public void Should_Resolve_Relative_Paths_Against_Working_Directory() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.CreateFile("/work/report.txt").SetContent("payload"); + var commands = fixture.CreateRwxCommands(); + + // When + commands.UploadArtifact("report.txt"); + + // Then + Assert.True(fixture.FileSystem.GetFile("/rwx/artifacts/report.txt").Exists); + } + + [Fact] + public void Should_Overwrite_Existing_Artifact() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.CreateFile("/work/report.txt").SetContent("new"); + fixture.FileSystem.CreateFile("/rwx/artifacts/report.txt").SetContent("old"); + var commands = fixture.CreateRwxCommands(); + + // When + commands.UploadArtifact("/work/report.txt"); + + // Then + Assert.Equal("new", fixture.FileSystem.GetFile("/rwx/artifacts/report.txt").GetTextContent()); + } + + [Fact] + public void Should_Create_Artifacts_Directory_When_Missing() + { + // Given + var fixture = new RwxCommandsFixture(); + fixture.FileSystem.GetDirectory("/rwx/artifacts").Delete(true); + fixture.FileSystem.CreateFile("/work/report.txt").SetContent("payload"); + var commands = fixture.CreateRwxCommands(); + + // When + commands.UploadArtifact("/work/report.txt"); + + // Then + Assert.True(fixture.FileSystem.GetFile("/rwx/artifacts/report.txt").Exists); + } + + [Fact] + public void Should_Throw_On_Null_Path() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.UploadArtifact((FilePath)null)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public void Should_Throw_When_Source_File_Missing() + { + // Given + var commands = new RwxCommandsFixture().CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.UploadArtifact("/work/does-not-exist.txt")); + + // Then + Assert.IsType(result); + } + + [Fact] + public void Should_Throw_When_Artifacts_Path_Missing() + { + // Given + var fixture = new RwxCommandsFixture().WithNoRwxArtifacts(); + fixture.FileSystem.CreateFile("/work/report.txt").SetContent("payload"); + var commands = fixture.CreateRwxCommands(); + + // When + var result = Record.Exception(() => commands.UploadArtifact("/work/report.txt")); + + // Then + Assert.IsType(result); + Assert.Contains("ArtifactsPath", result.Message); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxActorInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxActorInfoTests.cs new file mode 100644 index 0000000000..cd4f0cac4e --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxActorInfoTests.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Data +{ + public sealed class RwxActorInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateActorInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal("usr_12345", result); + } + } + + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateActorInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("octocat", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxGitInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxGitInfoTests.cs new file mode 100644 index 0000000000..3eb93a0386 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxGitInfoTests.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Data +{ + public sealed class RwxGitInfoTests + { + public sealed class TheRepositoryUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.RepositoryUrl; + + // Then + Assert.Equal("https://github.com/cake-build/cake.git", result); + } + } + + public sealed class TheRepositoryNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.RepositoryName; + + // Then + Assert.Equal("cake-build/cake", result); + } + } + + public sealed class TheCommitShaProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.CommitSha; + + // Then + Assert.Equal("0123456789abcdef0123456789abcdef01234567", result); + } + } + + public sealed class TheCommitMessageProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.CommitMessage; + + // Then + Assert.Equal("Add RWX build provider\n\nMore details here.", result); + } + } + + public sealed class TheCommitSummaryProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.CommitSummary; + + // Then + Assert.Equal("Add RWX build provider", result); + } + } + + public sealed class TheCommitterNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.CommitterName; + + // Then + Assert.Equal("Octo Cat", result); + } + } + + public sealed class TheCommitterEmailProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.CommitterEmail; + + // Then + Assert.Equal("octocat@example.com", result); + } + } + + public sealed class TheRefProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.Ref; + + // Then + Assert.Equal("refs/heads/main", result); + } + } + + public sealed class TheRefNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateGitInfo(); + + // When + var result = info.RefName; + + // Then + Assert.Equal("main", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRunInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRunInfoTests.cs new file mode 100644 index 0000000000..1f4bb64ca9 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRunInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Data +{ + public sealed class RwxRunInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRunInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal("01J9ABCDEFG", result); + } + } + + public sealed class TheTitleProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRunInfo(); + + // When + var result = info.Title; + + // Then + Assert.Equal("Build and test", result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRunInfo(); + + // When + var result = info.Url; + + // Then + Assert.Equal("https://cloud.rwx.com/runs/01J9ABCDEFG", result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRuntimeInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRuntimeInfoTests.cs new file mode 100644 index 0000000000..0d2ae019bb --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxRuntimeInfoTests.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Data +{ + public sealed class RwxRuntimeInfoTests + { + public sealed class TheValuesPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.ValuesPath; + + // Then + Assert.Equal("/rwx/values", result.FullPath); + } + + [Fact] + public void Should_Return_Null_When_Env_Var_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_VALUES").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.ValuesPath; + + // Then + Assert.Null(result); + } + } + + public sealed class TheArtifactsPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.ArtifactsPath; + + // Then + Assert.Equal("/rwx/artifacts", result.FullPath); + } + + [Fact] + public void Should_Return_Null_When_Env_Var_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_ARTIFACTS").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.ArtifactsPath; + + // Then + Assert.Null(result); + } + } + + public sealed class TheEnvPathProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.EnvPath; + + // Then + Assert.Equal("/rwx/env", result.FullPath); + } + + [Fact] + public void Should_Return_Null_When_Env_Var_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_ENV").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.EnvPath; + + // Then + Assert.Null(result); + } + } + + public sealed class TheIsRuntimeAvailableProperty + { + [Fact] + public void Should_Return_True_When_All_Env_Vars_Set() + { + // Given + var info = new RwxInfoFixture().CreateRuntimeInfo(); + + // When + var result = info.IsRuntimeAvailable; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_Values_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_VALUES").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.IsRuntimeAvailable; + + // Then + Assert.False(result); + } + + [Fact] + public void Should_Return_False_When_Artifacts_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_ARTIFACTS").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.IsRuntimeAvailable; + + // Then + Assert.False(result); + } + + [Fact] + public void Should_Return_False_When_Env_Missing() + { + // Given + var fixture = new RwxInfoFixture(); + fixture.Environment.GetEnvironmentVariable("RWX_ENV").Returns(null as string); + var info = fixture.CreateRuntimeInfo(); + + // When + var result = info.IsRuntimeAvailable; + + // Then + Assert.False(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxTaskInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxTaskInfoTests.cs new file mode 100644 index 0000000000..933e946959 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/Data/RwxTaskInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx.Data +{ + public sealed class RwxTaskInfoTests + { + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateTaskInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal("01J9TASKABC", result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateTaskInfo(); + + // When + var result = info.Url; + + // Then + Assert.Equal("https://cloud.rwx.com/tasks/01J9TASKABC", result); + } + } + + public sealed class TheAttemptNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateTaskInfo(); + + // When + var result = info.AttemptNumber; + + // Then + Assert.Equal(2, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/RwxEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/RwxEnvironmentInfoTests.cs new file mode 100644 index 0000000000..68a0569976 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/RwxEnvironmentInfoTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx +{ + public sealed class RwxEnvironmentInfoTests + { + public sealed class TheCIProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.CI; + + // Then + Assert.True(result); + } + } + + public sealed class TheRwxProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new RwxInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Rwx; + + // Then + Assert.True(result); + } + } + + public sealed class TheRuntimeProperty + { + [Fact] + public void Should_Be_Populated() + { + // Given + var info = new RwxInfoFixture().CreateEnvironmentInfo(); + + // When, Then + Assert.NotNull(info.Runtime); + Assert.Equal("/rwx/values", info.Runtime.ValuesPath.FullPath); + Assert.Equal("/rwx/artifacts", info.Runtime.ArtifactsPath.FullPath); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/Rwx/RwxProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/Rwx/RwxProviderTests.cs new file mode 100644 index 0000000000..81b7d7726b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/Rwx/RwxProviderTests.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.Rwx; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core.IO; +using Cake.Testing; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.Rwx +{ + public sealed class RwxProviderTests + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var result = Record.Exception(() => new RwxProvider(null, Substitute.For())); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given, When + var result = Record.Exception(() => new RwxProvider(Substitute.For(), null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + public sealed class TheIsRunningOnRwxProperty + { + [Fact] + public void Should_Return_True_When_RWX_Env_Var_Set() + { + // Given + var fixture = new RwxFixture(); + fixture.IsRunningOnRwx(); + var provider = fixture.CreateRwxProvider(); + + // When + var result = provider.IsRunningOnRwx; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_RWX_Env_Var_Not_Set() + { + // Given + var fixture = new RwxFixture(); + var provider = fixture.CreateRwxProvider(); + + // When + var result = provider.IsRunningOnRwx; + + // Then + Assert.False(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityBuildInfoTests.cs new file mode 100644 index 0000000000..a35afcc383 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityBuildInfoTests.cs @@ -0,0 +1,267 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TeamCity.Data +{ + public sealed class TeamCityBuildInfoTests + { + public sealed class TheBuildConfNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new TeamCityInfoFixture().CreateBuildInfo(); + + // When + var result = info.BuildConfName; + + // Then + Assert.Equal("Cake Build", result); + } + } + + public sealed class TheNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new TeamCityInfoFixture().CreateBuildInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal("10-Foo", result); + } + } + + public sealed class TheStartDateTimeProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new TeamCityInfoFixture().CreateBuildInfo(); + + // When + var result = info.StartDateTime; + + // Then + var expected = new DateTimeOffset?(new DateTime(2020, 08, 22, 12, 34, 56, DateTimeKind.Local)); + Assert.Equal(expected, result); + } + + [Fact] + public void Should_Use_Build_Server_Local_Time() + { + // Given + var now = DateTime.Now; + var startDate = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); + var startTime = now.ToString("HHmmss", CultureInfo.InvariantCulture); + + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildStartDate(startDate); + fixture.SetBuildStartTime(startTime); + + var info = fixture.CreateBuildInfo(); + + // When + var result = info.StartDateTime; + + // Then + var expected = new DateTimeOffset?(new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, DateTimeKind.Local)); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(null, null)] + [InlineData("Cake", null)] + [InlineData(null, "Build")] + [InlineData("Cake", "Build")] + [InlineData("Cake", "123456")] + [InlineData("20200822", "Build")] + public void Should_Return_Null_If_Cannot_Parse_Values(string startDate, string startTime) + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildStartDate(startDate); + fixture.SetBuildStartTime(startTime); + + var info = fixture.CreateBuildInfo(); + + // When + var result = info.StartDateTime; + + // Then + Assert.Equal(null, result); + } + } + + public sealed class TheBranchProperty + { + [Fact] + public void Should_Return_Empty_When_No_Properties() + { + // Given + var info = new TeamCityInfoFixture().CreateBuildInfo(); + + // When + var result = info.BranchName; + + // Then + Assert.Equal(string.Empty, result); + } + + [Fact] + public void Should_Return_Value_From_Properties() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetConfigPropertiesContent(Properties.Resources.TeamCity_Config_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var result = info.BranchName; + + // Then + Assert.Equal("pull/5", result); + } + } + + public sealed class TheVcsBranchProperty + { + [Fact] + public void Should_Return_Empty_When_No_Properties() + { + // Given + var info = new TeamCityInfoFixture().CreateBuildInfo(); + + // When + var result = info.VcsBranchName; + + // Then + Assert.Equal(string.Empty, result); + } + + [Fact] + public void Should_Return_Value_From_Properties() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetConfigPropertiesContent(Properties.Resources.TeamCity_Config_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var result = info.VcsBranchName; + + // Then + Assert.Equal("refs/pull/5/merge", result); + } + } + + public sealed class ThePropertiesProperties + { + [Fact] + public void Should_Return_Empty_ForAll_When_File_Not_Created() + { + // Given + var fixture = new TeamCityInfoFixture(); + var info = fixture.CreateBuildInfo(); + + // When + var buildProperties = info.BuildProperties; + var configProperties = info.ConfigProperties; + var runnerProperties = info.RunnerProperties; + + // Then + Assert.Empty(buildProperties); + Assert.Empty(configProperties); + Assert.Empty(runnerProperties); + } + + [Fact] + public void Should_Return_Empty_When_Config_Properties_File_Not_Created() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var buildProperties = info.BuildProperties; + var configProperties = info.ConfigProperties; + + // Then + Assert.NotEmpty(buildProperties); + Assert.Empty(configProperties); + } + + [Fact] + public void Should_Return_Config_Values_When_Files_Exist() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetConfigPropertiesContent(Properties.Resources.TeamCity_Config_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var buildProperties = info.BuildProperties; + var configProperties = info.ConfigProperties; + + // Then + Assert.NotEmpty(buildProperties); + Assert.NotEmpty(configProperties); + Assert.Equal(5, configProperties.Count); + Assert.Equal("3246", configProperties["build.number"]); + } + + [Fact] + public void Should_Return_Empty_When_Runner_Properties_File_Not_Created() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var buildProperties = info.BuildProperties; + var runnerProperties = info.RunnerProperties; + + // Then + Assert.NotEmpty(buildProperties); + Assert.Empty(runnerProperties); + } + + [Fact] + public void Should_Return_Runner_Values_When_Files_Exist() + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetRunnerPropertiesContent(Properties.Resources.TeamCity_Runner_Properties_Xml); + var info = fixture.CreateBuildInfo(); + + // When + var buildProperties = info.BuildProperties; + var runnerProperties = info.RunnerProperties; + + // Then + Assert.NotEmpty(buildProperties); + Assert.NotEmpty(runnerProperties); + Assert.Single(runnerProperties); + Assert.Equal("run.cmd", runnerProperties["command.executable"]); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityEnvironmentInfoTests.cs new file mode 100644 index 0000000000..82a4736ceb --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityEnvironmentInfoTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TeamCity.Data +{ + public sealed class TeamCityEnvironmentInfoTests + { + public sealed class TheProjectProperty + { + [Fact] + public void Should_Not_Be_Null() + { + // Given + var info = new TeamCityInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Project; + + // Then + Assert.NotNull(result); + } + } + + public sealed class TheBuildProperty + { + [Fact] + public void Should_Not_Be_Null() + { + // Given + var info = new TeamCityInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Build; + + // Then + Assert.NotNull(result); + } + } + + public sealed class ThePullRequestProperty + { + [Fact] + public void Should_Not_Be_Null() + { + // Given + var info = new TeamCityInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.PullRequest; + + // Then + Assert.NotNull(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityProjectInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityProjectInfoTests.cs new file mode 100644 index 0000000000..1d6445f6a2 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityProjectInfoTests.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TeamCity.Data +{ + public sealed class TeamCityProjectInfoTests + { + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new TeamCityInfoFixture().CreateProjectInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("Cake", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityPullRequestInfoTests.cs new file mode 100644 index 0000000000..508d108952 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TeamCity/Data/TeamCityPullRequestInfoTests.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TeamCity.Data +{ + public sealed class TeamCityPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("refs/pull-requests/1/merge", true)] + [InlineData("refs/merge-requests/1/head", true)] + [InlineData("refs/pull/1/head", true)] + [InlineData("refs/pull/1/merge", true)] + [InlineData("refs/changes/1/head", true)] + [InlineData("refs/heads/master", false)] + [InlineData("", true)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetGitBranch(value); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetConfigPropertiesContent(Properties.Resources.TeamCity_Config_Properties_Xml); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheNumberProperty + { + [Theory] + [InlineData("refs/pull-requests/1/merge", 1)] + [InlineData("refs/pull/2/merge", 2)] + [InlineData("refs/changes/3/merge", 3)] + [InlineData("refs/merge-requests/4/merge", 4)] + [InlineData("refs/heads/master", null)] + [InlineData("", 5)] + public void Should_Return_Correct_Value(string value, int? expected) + { + // Given + var fixture = new TeamCityInfoFixture(); + fixture.SetGitBranch(value); + fixture.SetBuildPropertiesContent(Properties.Resources.TeamCity_Build_Properties_Xml); + fixture.SetConfigPropertiesContent(Properties.Resources.TeamCity_Config_Properties_Xml); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal(expected, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TeamCity/TeamCityProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/TeamCity/TeamCityProviderTests.cs index 6cd03a6982..c025ec0794 100644 --- a/src/Cake.Common.Tests/Unit/Build/TeamCity/TeamCityProviderTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/TeamCity/TeamCityProviderTests.cs @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Build.TeamCity; using Cake.Common.Tests.Fixtures.Build; using Cake.Core.IO; -using Cake.Testing.Extensions; using Xunit; namespace Cake.Common.Tests.Unit.Build.TeamCity @@ -18,23 +18,36 @@ public sealed class TheConstructor public void Should_Throw_If_Environment_Is_Null() { // Given, When - var result = Record.Exception(() => new TeamCityProvider(null, null)); + var result = Record.Exception(() => new TeamCityProvider(null, null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given + var fixture = new TeamCityFixture(); + + // When + var result = Record.Exception(() => new TeamCityProvider(fixture.Environment, null, null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] - public void Should_Throw_If_Log_Is_Null() + public void Should_Throw_If_Writer_Is_Null() { // Given var fixture = new TeamCityFixture(); // When - var result = Record.Exception(() => new TeamCityProvider(fixture.Environment, null)); + var result = Record.Exception(() => new TeamCityProvider(fixture.Environment, fixture.FileSystem, null)); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "writer"); } } @@ -87,7 +100,7 @@ public void Should_Use_Bundled_DotCover_If_ToolPath_Is_Null() // Then Assert.Equal("##teamcity[dotNetCoverage ]" + Environment.NewLine + "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='/path/to/result.dcvr']" + Environment.NewLine, - fixture.Log.AggregateLogMessages()); + fixture.Writer.GetOutput()); } [Fact] @@ -106,8 +119,47 @@ public void Should_Use_Provided_DotCover_If_ToolPath_Is_Not_Null() // Then Assert.Equal("##teamcity[dotNetCoverage dotcover_home='/path/to/dotcover_home']" + Environment.NewLine + "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='/path/to/result.dcvr']" + Environment.NewLine, - fixture.Log.AggregateLogMessages()); + fixture.Writer.GetOutput()); + } + } + + public sealed class TheSetParameterMethod + { + [Fact] + public void SetParameter_Should_Write_To_The_Log_Correctly() + { + // Given + var fixture = new TeamCityFixture(); + var teamCity = fixture.CreateTeamCityService(); + + // When + teamCity.SetParameter("internal.artifactVersion", "1.2.3.4"); + + // Then + Assert.Equal("##teamcity[setParameter name='internal.artifactVersion' value='1.2.3.4']" + Environment.NewLine, + fixture.Writer.GetOutput()); + } + } + + public sealed class TheBuildProblemMethod + { + [Theory] + [InlineData("A build problem", "identity_id", "description='A build problem' identity='identity_id'")] + [InlineData("A build problem", "", "description='A build problem'")] + [InlineData("A build problem", null, "description='A build problem'")] + public void BuildProblem_Should_Write_To_The_Log_Correctly(string description, string identity, string expected) + { + // Given + var fixture = new TeamCityFixture(); + var teamCity = fixture.CreateTeamCityService(); + + // When + teamCity.BuildProblem(description, identity); + + // Then + Assert.Equal($"##teamcity[buildProblem {expected}]" + Environment.NewLine, + fixture.Writer.GetOutput()); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIProviderTests.cs deleted file mode 100644 index b879d503c3..0000000000 --- a/src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIProviderTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Build.TravisCI; -using Cake.Common.Tests.Fixtures.Build; -using Cake.Testing.Extensions; -using Xunit; - -namespace Cake.Common.Tests.Unit.Build.TravicCI -{ - public sealed class TravisCIProviderTests - { - public sealed class TravisCIWriteStartFold - { - [Fact] - public void Should_Be_In_Correct_Format() - { - // Given - var fixture = new TravisCIFixture(); - var travisCI = fixture.CreateTravisCIProvider(); - - // When - travisCI.WriteStartFold("cake"); - - // Then - Assert.Contains("travis_fold:start:cake\r", fixture.Log.AggregateLogMessages()); - } - } - - public sealed class TravisCIWriteEndFold - { - [Fact] - public void Should_Be_In_Correct_Format() - { - // Given - var fixture = new TravisCIFixture(); - var travisCI = fixture.CreateTravisCIProvider(); - - // When - travisCI.WriteEndFold("cake"); - - // Then - Assert.Contains("travis_fold:end:cake\r", fixture.Log.AggregateLogMessages()); - } - } - - public sealed class TravisFold - { - [Fact] - public void Should_Write_On_Dispose() - { - // Given - var fixture = new TravisCIFixture(); - var travisCI = fixture.CreateTravisCIProvider(); - - // When - using (var folded = travisCI.Fold("cake")) - { - } - - // Then - Assert.Contains("travis_fold:start:cake\r", fixture.Log.AggregateLogMessages()); - Assert.Contains("travis_fold:end:cake\r", fixture.Log.AggregateLogMessages()); - } - - [Fact] - public void Should_Write_Start_Fold() - { - // Given - var fixture = new TravisCIFixture(); - var travisCI = fixture.CreateTravisCIProvider(); - - // When - using (var folded = travisCI.Fold("cake")) - { - } - - // Then - Assert.Contains("travis_fold:start:cake\r", fixture.Log.AggregateLogMessages()); - } - - [Fact] - public void Should_Write_End_Fold() - { - // Given - var fixture = new TravisCIFixture(); - var travisCI = fixture.CreateTravisCIProvider(); - - // When - using (var folded = travisCI.Fold("cake")) - { - } - - // Then - Assert.Contains("travis_fold:end:cake\r", fixture.Log.AggregateLogMessages()); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisBuildInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisBuildInfoTests.cs similarity index 98% rename from src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisBuildInfoTests.cs rename to src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisBuildInfoTests.cs index cffb9cea63..e107b30342 100644 --- a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisBuildInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisBuildInfoTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; -namespace Cake.Common.Tests.Unit.Build.TravicCI.Data +namespace Cake.Common.Tests.Unit.Build.TravisCI.Data { public sealed class TravisBuildInfoTests { @@ -104,4 +105,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisJobInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisJobInfoTests.cs similarity index 94% rename from src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisJobInfoTests.cs rename to src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisJobInfoTests.cs index 47ba92b596..96b7cf8233 100644 --- a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisJobInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisJobInfoTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; -namespace Cake.Common.Tests.Unit.Build.TravicCI.Data +namespace Cake.Common.Tests.Unit.Build.TravisCI.Data { public sealed class TravisJobInfoTests { @@ -40,6 +41,7 @@ public void Should_Return_Correct_Value() } } + // ReSharper disable once InconsistentNaming public sealed class TheOSNameProperty { [Fact] @@ -72,4 +74,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisPullRequestInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisPullRequestInfoTests.cs new file mode 100644 index 0000000000..f98d367162 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisPullRequestInfoTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TravisCI.Data +{ + public sealed class TravisPullRequestInfoTests + { + public sealed class TheIsPullRequestProperty + { + [Theory] + [InlineData("1", true)] + [InlineData("0", false)] + public void Should_Return_Correct_Value(string value, bool expected) + { + // Given + var fixture = new TravisCIInfoFixture(); + fixture.Environment.GetEnvironmentVariable("TRAVIS_PULL_REQUEST").Returns(value); + var info = fixture.CreatePullRequestInfo(); + + // When + var result = info.IsPullRequest; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIdProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new TravisCIInfoFixture().CreatePullRequestInfo(); + + // When + var result = info.Id; + + // Then + Assert.Equal(1, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisRepositoryInfoTests.cs similarity index 93% rename from src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisRepositoryInfoTests.cs rename to src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisRepositoryInfoTests.cs index a9c5b391c2..320736d504 100644 --- a/src/Cake.Common.Tests/Unit/Build/TravicCI/Data/TravisRepositoryInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/Data/TravisRepositoryInfoTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; -namespace Cake.Common.Tests.Unit.Build.TravicCI.Data +namespace Cake.Common.Tests.Unit.Build.TravisCI.Data { public sealed class TravisRepositoryInfoTests { @@ -52,7 +53,7 @@ public void Should_Return_Correct_Value() var result = info.PullRequest; // Then - Assert.Equal("#786 (GH742) Added TravisCI build system support", result); + Assert.Equal("1", result); } } @@ -72,4 +73,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIEnvironmentInfoTests.cs similarity index 96% rename from src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIEnvironmentInfoTests.cs rename to src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIEnvironmentInfoTests.cs index 302099b9ce..c17e44eaaa 100644 --- a/src/Cake.Common.Tests/Unit/Build/TravicCI/TravisCIEnvironmentInfoTests.cs +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIEnvironmentInfoTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Build; using Xunit; -namespace Cake.Common.Tests.Unit.Build.TravicCI +namespace Cake.Common.Tests.Unit.Build.TravisCI { public sealed class TravisCIEnvironmentInfoTests { @@ -56,4 +57,4 @@ public void Should_Return_Correct_Value() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIProviderTests.cs new file mode 100644 index 0000000000..e9c19484a7 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/TravisCI/TravisCIProviderTests.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.TravisCI; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.TravisCI +{ + public sealed class TravisCIProviderTests + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var result = Record.Exception(() => new TravisCIProvider(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var environment = FakeEnvironment.CreateUnixEnvironment(); + var result = Record.Exception(() => new TravisCIProvider(environment, null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + + public sealed class TravisCIWriteStartFold + { + [Fact] + public void Should_Be_In_Correct_Format() + { + // Given + var fixture = new TravisCIFixture(); + var travisCI = fixture.CreateTravisCIProvider(); + + // When + travisCI.WriteStartFold("cake"); + + // Then + Assert.Contains("travis_fold:start:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + } + } + + public sealed class TravisCIWriteEndFold + { + [Fact] + public void Should_Be_In_Correct_Format() + { + // Given + var fixture = new TravisCIFixture(); + var travisCI = fixture.CreateTravisCIProvider(); + + // When + travisCI.WriteEndFold("cake"); + + // Then + Assert.Contains("travis_fold:end:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + } + } + + public sealed class TravisFold + { + [Fact] + public void Should_Write_On_Dispose() + { + // Given + var fixture = new TravisCIFixture(); + var travisCI = fixture.CreateTravisCIProvider(); + + // When + using (var folded = travisCI.Fold("cake")) + { + } + + // Then + Assert.Contains("travis_fold:start:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + Assert.Contains("travis_fold:end:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + } + + [Fact] + public void Should_Write_Start_Fold() + { + // Given + var fixture = new TravisCIFixture(); + var travisCI = fixture.CreateTravisCIProvider(); + + // When + using (var folded = travisCI.Fold("cake")) + { + } + + // Then + Assert.Contains("travis_fold:start:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + } + + [Fact] + public void Should_Write_End_Fold() + { + // Given + var fixture = new TravisCIFixture(); + var travisCI = fixture.CreateTravisCIProvider(); + + // When + using (var folded = travisCI.Fold("cake")) + { + } + + // Then + Assert.Contains("travis_fold:end:cake\r", fixture.Writer.GetOutput(), StringComparison.Ordinal); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Commands/WoodpeckerCICommandsTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Commands/WoodpeckerCICommandsTests.cs new file mode 100644 index 0000000000..1ce5658c34 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Commands/WoodpeckerCICommandsTests.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Commands +{ + public sealed class WoodpeckerCICommandsTests + { + public sealed class TheSetEnvironmentVariableMethod + { + [Fact] + public void Should_Throw_If_Name_Is_Null() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(null, "value")); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_Throw_If_Name_Is_Empty() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(string.Empty, "value")); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_Throw_If_Name_Is_WhiteSpace() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.SetEnvironmentVariable(" ", "value")); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_SetEnvironmentVariable() + { + // Given + var fixture = new WoodpeckerCICommandsFixture(); + var commands = fixture.CreateWoodpeckerCICommands(); + var name = "MY_VAR"; + var value = "my_value"; + + // When + commands.SetEnvironmentVariable(name, value); + + // Then + Assert.Equal( + "MY_VAR=my_value\n", + ((FakeFile)fixture.FileSystem.GetFile("/woodpecker/src/git.example.com/john-doe/my-repo/.woodpecker/env")).GetTextContent()); + } + + [Fact] + public void Should_Append_To_Existing_Environment_File() + { + // Given + var fixture = new WoodpeckerCICommandsFixture(); + var commands = fixture.CreateWoodpeckerCICommands(); + var envFile = (FakeFile)fixture.FileSystem.GetFile("/woodpecker/src/git.example.com/john-doe/my-repo/.woodpecker/env"); + envFile.SetContent("EXISTING_VAR=existing_value\n"); + + // When + commands.SetEnvironmentVariable("NEW_VAR", "new_value"); + + // Then + Assert.Equal( + "EXISTING_VAR=existing_value\nNEW_VAR=new_value\n", + envFile.GetTextContent()); + } + } + + public sealed class TheGetEnvironmentVariableMethod + { + [Fact] + public void Should_Throw_If_Name_Is_Null() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.GetEnvironmentVariable(null)); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_Throw_If_Name_Is_Empty() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.GetEnvironmentVariable(string.Empty)); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_Throw_If_Name_Is_WhiteSpace() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = Record.Exception(() => commands.GetEnvironmentVariable(" ")); + + // Then + AssertEx.IsArgumentException(result, "name", "Environment variable name cannot be null or empty."); + } + + [Fact] + public void Should_Return_Environment_Variable_Value() + { + // Given + var fixture = new WoodpeckerCICommandsFixture(); + ((FakeEnvironment)fixture.Environment).SetEnvironmentVariable("TEST_VAR", "test_value"); + var commands = fixture.CreateWoodpeckerCICommands(); + + // When + var result = commands.GetEnvironmentVariable("TEST_VAR"); + + // Then + Assert.Equal("test_value", result); + } + + [Fact] + public void Should_Return_Null_If_Environment_Variable_Not_Found() + { + // Given + var commands = new WoodpeckerCICommandsFixture().CreateWoodpeckerCICommands(); + + // When + var result = commands.GetEnvironmentVariable("NONEXISTENT_VAR"); + + // Then + Assert.Null(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfoTests.cs new file mode 100644 index 0000000000..d8334ba5b0 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfoTests.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIEnvironmentInfoTests + { + public sealed class TheCIProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.CI; + + // Then + Assert.Equal("woodpecker", result); + } + } + + public sealed class TheWorkspaceProperty + { + [Fact] + public void Should_Return_Correct_DirectoryPath_For_Valid_Path() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Workspace; + + // Then + Assert.NotNull(result); + Assert.Equal("/woodpecker/src/git.example.com/john-doe/my-repo", result.FullPath); + } + + [Fact] + public void Should_Return_Null_For_Empty_Path() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_WORKSPACE", ""); + var info = fixture.CreateEnvironmentInfo(); + + // When + var result = info.Workspace; + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Return_Null_For_Missing_Path() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_WORKSPACE", null); + var info = fixture.CreateEnvironmentInfo(); + + // When + var result = info.Workspace; + + // Then + Assert.Null(result); + } + } + + public sealed class TheRepositoryProperty + { + [Fact] + public void Should_Return_Repository_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Repository; + + // Then + Assert.NotNull(result); + Assert.Equal("john-doe/my-repo", result.Repo); + } + } + + public sealed class TheCommitProperty + { + [Fact] + public void Should_Return_Commit_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Commit; + + // Then + Assert.NotNull(result); + Assert.Equal("eba09b46064473a1d345da7abf28b477468e8dbd", result.Sha); + } + } + + public sealed class ThePipelineProperty + { + [Fact] + public void Should_Return_Pipeline_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Pipeline; + + // Then + Assert.NotNull(result); + Assert.Equal(8, result.Number); + } + } + + public sealed class TheWorkflowProperty + { + [Fact] + public void Should_Return_Workflow_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Workflow; + + // Then + Assert.NotNull(result); + Assert.Equal("release", result.Name); + } + } + + public sealed class TheStepProperty + { + [Fact] + public void Should_Return_Step_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Step; + + // Then + Assert.NotNull(result); + Assert.Equal("build package", result.Name); + } + } + + public sealed class TheSystemProperty + { + [Fact] + public void Should_Return_System_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.System; + + // Then + Assert.NotNull(result); + Assert.Equal("woodpecker", result.Name); + } + } + + public sealed class TheForgeProperty + { + [Fact] + public void Should_Return_Forge_Info() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateEnvironmentInfo(); + + // When + var result = info.Forge; + + // Then + Assert.NotNull(result); + Assert.Equal(WoodpeckerCIForgeType.GitHub, result.Type); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfoTests.cs new file mode 100644 index 0000000000..0e2d04074f --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfoTests.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIForgeInfoTests + { + public sealed class TheTypeProperty + { + [Fact] + public void Should_Return_Correct_Forge_Type() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_TYPE", "github"); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Type; + + // Then + Assert.Equal(WoodpeckerCIForgeType.GitHub, result); + } + + [Fact] + public void Should_Return_Unknown_For_Invalid_Forge_Type() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_TYPE", "invalid"); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Type; + + // Then + Assert.Equal(WoodpeckerCIForgeType.Unknown, result); + } + + [Fact] + public void Should_Return_Unknown_For_Empty_Forge_Type() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_TYPE", ""); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Type; + + // Then + Assert.Equal(WoodpeckerCIForgeType.Unknown, result); + } + + [Theory] + [InlineData("bitbucket", WoodpeckerCIForgeType.Bitbucket)] + [InlineData("bitbucket_dc", WoodpeckerCIForgeType.BitbucketDC)] + [InlineData("forgejo", WoodpeckerCIForgeType.Forgejo)] + [InlineData("gitea", WoodpeckerCIForgeType.Gitea)] + [InlineData("github", WoodpeckerCIForgeType.GitHub)] + [InlineData("gitlab", WoodpeckerCIForgeType.GitLab)] + public void Should_Parse_All_Valid_Forge_Types(string forgeType, WoodpeckerCIForgeType expected) + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_TYPE", forgeType); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Type; + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_URL", "https://github.com"); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Url; + + // Then + Assert.NotNull(result); + Assert.Equal("https://github.com/", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_URL", "not-a-valid-url"); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Url; + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Return_Null_For_Empty_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_URL", ""); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Url; + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Return_Null_For_Missing_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_FORGE_URL", null); + var forgeInfo = fixture.CreateForgeInfo(); + + // When + var result = forgeInfo.Url; + + // Then + Assert.Null(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeTypeTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeTypeTests.cs new file mode 100644 index 0000000000..378392949b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIForgeTypeTests.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Data; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIForgeTypeTests + { + public sealed class TheParseForgeTypeMethod + { + [Theory] + [InlineData(null, WoodpeckerCIForgeType.Unknown)] + [InlineData("", WoodpeckerCIForgeType.Unknown)] + [InlineData(" ", WoodpeckerCIForgeType.Unknown)] + [InlineData("unknown", WoodpeckerCIForgeType.Unknown)] + public void Should_Return_Unknown_For_Invalid_Values(string value, WoodpeckerCIForgeType expected) + { + // When + var result = WoodpeckerCIForgeTypeExtensions.ParseForgeType(value); + + // Then + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("bitbucket", WoodpeckerCIForgeType.Bitbucket)] + [InlineData("BITBUCKET", WoodpeckerCIForgeType.Bitbucket)] + [InlineData("Bitbucket", WoodpeckerCIForgeType.Bitbucket)] + [InlineData("bitbucket_dc", WoodpeckerCIForgeType.BitbucketDC)] + [InlineData("BITBUCKET_DC", WoodpeckerCIForgeType.BitbucketDC)] + [InlineData("Bitbucket_DC", WoodpeckerCIForgeType.BitbucketDC)] + [InlineData("forgejo", WoodpeckerCIForgeType.Forgejo)] + [InlineData("FORGEJO", WoodpeckerCIForgeType.Forgejo)] + [InlineData("Forgejo", WoodpeckerCIForgeType.Forgejo)] + [InlineData("gitea", WoodpeckerCIForgeType.Gitea)] + [InlineData("GITEA", WoodpeckerCIForgeType.Gitea)] + [InlineData("Gitea", WoodpeckerCIForgeType.Gitea)] + [InlineData("github", WoodpeckerCIForgeType.GitHub)] + [InlineData("GITHUB", WoodpeckerCIForgeType.GitHub)] + [InlineData("GitHub", WoodpeckerCIForgeType.GitHub)] + [InlineData("gitlab", WoodpeckerCIForgeType.GitLab)] + [InlineData("GITLAB", WoodpeckerCIForgeType.GitLab)] + [InlineData("GitLab", WoodpeckerCIForgeType.GitLab)] + public void Should_Parse_Valid_Forge_Types(string value, WoodpeckerCIForgeType expected) + { + // When + var result = WoodpeckerCIForgeTypeExtensions.ParseForgeType(value); + + // Then + Assert.Equal(expected, result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfoTests.cs new file mode 100644 index 0000000000..972422adf5 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfoTests.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIPipelineInfoTests + { + public sealed class TheNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal(8, result); + } + } + + public sealed class TheCreatedProperty + { + [Fact] + public void Should_Return_Correct_DateTimeOffset_For_Valid_Timestamp() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Created; + + // Then + Assert.NotEqual(DateTimeOffset.MinValue, result); + Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1722617519), result); + } + + [Fact] + public void Should_Return_MinValue_For_Invalid_Timestamp() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_PIPELINE_CREATED", "0"); + var info = fixture.CreatePipelineInfo(); + + // When + var result = info.Created; + + // Then + Assert.Equal(DateTimeOffset.MinValue, result); + } + } + + public sealed class TheStartedProperty + { + [Fact] + public void Should_Return_Correct_DateTimeOffset_For_Valid_Timestamp() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Started; + + // Then + Assert.NotEqual(DateTimeOffset.MinValue, result); + Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1722617519), result); + } + + [Fact] + public void Should_Return_MinValue_For_Invalid_Timestamp() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_PIPELINE_STARTED", "0"); + var info = fixture.CreatePipelineInfo(); + + // When + var result = info.Started; + + // Then + Assert.Equal(DateTimeOffset.MinValue, result); + } + } + + public sealed class TheEventProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Event; + + // Then + Assert.Equal("push", result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Url; + + // Then + Assert.NotNull(result); + Assert.Equal("https://ci.example.com/repos/john-doe/my-repo/pipeline/123", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_PIPELINE_URL", "not-a-valid-url"); + var info = fixture.CreatePipelineInfo(); + + // When + var result = info.Url; + + // Then + Assert.Null(result); + } + } + + public sealed class TheForgeUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.ForgeUrl; + + // Then + Assert.NotNull(result); + Assert.Equal("https://git.example.com/john-doe/my-repo/commit/abc123", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_PIPELINE_FORGE_URL", "not-a-valid-url"); + var info = fixture.CreatePipelineInfo(); + + // When + var result = info.ForgeUrl; + + // Then + Assert.Null(result); + } + } + + public sealed class TheAvatarProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreatePipelineInfo(); + + // When + var result = info.Avatar; + + // Then + Assert.NotNull(result); + Assert.Equal("https://git.example.com/avatars/john-doe", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_PIPELINE_AVATAR", "not-a-valid-url"); + var info = fixture.CreatePipelineInfo(); + + // When + var result = info.Avatar; + + // Then + Assert.Null(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfoTests.cs new file mode 100644 index 0000000000..edf8bd15cb --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfoTests.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIRepositoryInfoTests + { + public sealed class TheRepoProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.Repo; + + // Then + Assert.Equal("john-doe/my-repo", result); + } + } + + public sealed class TheRepoOwnerProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoOwner; + + // Then + Assert.Equal("john-doe", result); + } + } + + public sealed class TheRepoNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoName; + + // Then + Assert.Equal("my-repo", result); + } + } + + public sealed class TheRepoUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoUrl; + + // Then + Assert.NotNull(result); + Assert.Equal("https://git.example.com/john-doe/my-repo", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_REPO_URL", "not-a-valid-url"); + var info = fixture.CreateRepositoryInfo(); + + // When + var result = info.RepoUrl; + + // Then + Assert.Null(result); + } + } + + public sealed class TheRepoCloneUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoCloneUrl; + + // Then + Assert.NotNull(result); + Assert.Equal("https://git.example.com/john-doe/my-repo.git", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_REPO_CLONE_URL", "not-a-valid-url"); + var info = fixture.CreateRepositoryInfo(); + + // When + var result = info.RepoCloneUrl; + + // Then + Assert.Null(result); + } + } + + public sealed class TheRepoCloneSshUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoCloneSshUrl; + + // Then + Assert.NotNull(result); + Assert.Equal("git@git.example.com:john-doe/my-repo.git", result.ToString()); + } + } + + public sealed class TheRepoPrivateProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateRepositoryInfo(); + + // When + var result = info.RepoPrivate; + + // Then + Assert.True(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfoTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfoTests.cs new file mode 100644 index 0000000000..ceef52b69d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfoTests.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Build; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI.Data +{ + public sealed class WoodpeckerCIStepInfoTests + { + public sealed class TheNameProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateStepInfo(); + + // When + var result = info.Name; + + // Then + Assert.Equal("build package", result); + } + } + + public sealed class TheNumberProperty + { + [Fact] + public void Should_Return_Correct_Value() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateStepInfo(); + + // When + var result = info.Number; + + // Then + Assert.Equal(0, result); + } + } + + public sealed class TheStartedProperty + { + [Fact] + public void Should_Return_Correct_DateTimeOffset_For_Valid_Timestamp() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateStepInfo(); + + // When + var result = info.Started; + + // Then + Assert.NotEqual(DateTimeOffset.MinValue, result); + Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1722617519), result); + } + + [Fact] + public void Should_Return_MinValue_For_Invalid_Timestamp() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_STEP_STARTED", "0"); + var info = fixture.CreateStepInfo(); + + // When + var result = info.Started; + + // Then + Assert.Equal(DateTimeOffset.MinValue, result); + } + } + + public sealed class TheUrlProperty + { + [Fact] + public void Should_Return_Correct_Uri_For_Valid_Url() + { + // Given + var info = new WoodpeckerCIInfoFixture().CreateStepInfo(); + + // When + var result = info.Url; + + // Then + Assert.NotNull(result); + Assert.Equal("https://ci.example.com/repos/7/pipeline/8", result.ToString()); + } + + [Fact] + public void Should_Return_Null_For_Invalid_Url() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + fixture.SetEnvironmentVariable("CI_STEP_URL", "not-a-valid-url"); + var info = fixture.CreateStepInfo(); + + // When + var result = info.Url; + + // Then + Assert.Null(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/WoodpeckerCIProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/WoodpeckerCIProviderTests.cs new file mode 100644 index 0000000000..490aedee05 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/WoodpeckerCI/WoodpeckerCIProviderTests.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.WoodpeckerCI +{ + public sealed class WoodpeckerCIProviderTests + { + public sealed class TheIsRunningOnWoodpeckerCIProperty + { + [Fact] + public void Should_Return_True_When_CI_Environment_Variable_Is_Set_To_Woodpecker() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + var provider = new WoodpeckerCIProvider(fixture.Environment, Substitute.For()); + + // When + var result = provider.IsRunningOnWoodpeckerCI; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_CI_Environment_Variable_Is_Not_Set() + { + // Given + var environment = Substitute.For(); + environment.GetEnvironmentVariable("CI").Returns((string)null); + var provider = new WoodpeckerCIProvider(environment, Substitute.For()); + + // When + var result = provider.IsRunningOnWoodpeckerCI; + + // Then + Assert.False(result); + } + + [Fact] + public void Should_Return_False_When_CI_Environment_Variable_Is_Set_To_Something_Else() + { + // Given + var environment = Substitute.For(); + environment.GetEnvironmentVariable("CI").Returns("github"); + var provider = new WoodpeckerCIProvider(environment, Substitute.For()); + + // When + var result = provider.IsRunningOnWoodpeckerCI; + + // Then + Assert.False(result); + } + } + + public sealed class TheEnvironmentProperty + { + [Fact] + public void Should_Return_Non_Null_Environment() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + var provider = new WoodpeckerCIProvider(fixture.Environment, Substitute.For()); + + // When + var result = provider.Environment; + + // Then + Assert.NotNull(result); + } + } + + public sealed class TheCommandsProperty + { + [Fact] + public void Should_Return_Non_Null_Commands() + { + // Given + var fixture = new WoodpeckerCIInfoFixture(); + var provider = new WoodpeckerCIProvider(fixture.Environment, Substitute.For()); + + // When + var result = provider.Commands; + + // Then + Assert.NotNull(result); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Diagnostics/LoggingAliasesTests.cs b/src/Cake.Common.Tests/Unit/Diagnostics/LoggingAliasesTests.cs index c6594c1a42..b91135e698 100644 --- a/src/Cake.Common.Tests/Unit/Diagnostics/LoggingAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/Diagnostics/LoggingAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Diagnostics; using Cake.Common.Tests.Fixtures.Diagnostics; using Cake.Core; @@ -43,6 +44,36 @@ public void Should_Evaluate_And_Write_Error_Message_To_Log() fixture.Context.Log.Received(1).Write(Verbosity.Quiet, LogLevel.Error, fixture.Format, fixture.Args); Assert.True(fixture.Evaluated); } + + [Fact] + public void Should_Write_Error_String_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + const string value = "Hello {0}"; + + // When + context.Error(value); + + // Then + context.Log.Received(1).Write(Verbosity.Quiet, LogLevel.Error, "{0}", value); + } + + [Fact] + public void Should_Write_Error_Object_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + var value = new { FirstName = "John", LastName="Doe" }; + + // When + context.Error(value); + + // Then + context.Log.Received(1).Write(Verbosity.Quiet, LogLevel.Error, "{0}", value); + } } public sealed class TheWarningMethod @@ -67,7 +98,7 @@ public void Should_Write_Warning_Message_To_Log() public void Should_Evaluate_And_Write_Warning_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Minimal); + var fixture = new LogActionFixture(verbosity: Verbosity.Minimal); // When fixture.Context.Warning(fixture.Log); @@ -90,6 +121,36 @@ public void Should_Not_Evaluate_And_Write_Warning_Message_To_Log() fixture.Context.Log.DidNotReceive().Write(Verbosity.Minimal, LogLevel.Warning, fixture.Format, fixture.Args); Assert.False(fixture.Evaluated); } + + [Fact] + public void Should_Write_Warning_String_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + const string value = "Hello {0}"; + + // When + context.Warning(value); + + // Then + context.Log.Received(1).Write(Verbosity.Minimal, LogLevel.Warning, "{0}", value); + } + + [Fact] + public void Should_Write_Warning_Object_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + var value = new { FirstName = "John", LastName="Doe" }; + + // When + context.Warning(value); + + // Then + context.Log.Received(1).Write(Verbosity.Minimal, LogLevel.Warning, "{0}", value); + } } public sealed class TheInformationMethod @@ -114,7 +175,7 @@ public void Should_Write_Informational_Message_To_Log() public void Should_Evaluate_And_Write_Information_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Normal); + var fixture = new LogActionFixture(verbosity: Verbosity.Normal); // When fixture.Context.Information(fixture.Log); @@ -128,7 +189,7 @@ public void Should_Evaluate_And_Write_Information_Message_To_Log() public void Should_Not_Evaluate_And_Write_Information_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Minimal); + var fixture = new LogActionFixture(verbosity: Verbosity.Minimal); // When fixture.Context.Information(fixture.Log); @@ -137,6 +198,36 @@ public void Should_Not_Evaluate_And_Write_Information_Message_To_Log() fixture.Context.Log.DidNotReceive().Write(Verbosity.Normal, LogLevel.Information, fixture.Format, fixture.Args); Assert.False(fixture.Evaluated); } + + [Fact] + public void Should_Write_Information_String_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + const string value = "Hello {0}"; + + // When + context.Information(value); + + // Then + context.Log.Received(1).Write(Verbosity.Normal, LogLevel.Information, "{0}", value); + } + + [Fact] + public void Should_Write_Information_Object_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + var value = new { FirstName = "John", LastName="Doe" }; + + // When + context.Information(value); + + // Then + context.Log.Received(1).Write(Verbosity.Normal, LogLevel.Information, "{0}", value); + } } public sealed class TheVerboseMethod @@ -161,7 +252,7 @@ public void Should_Write_Verbose_Message_Log() public void Should_Evaluate_And_Write_Verbose_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Verbose); + var fixture = new LogActionFixture(verbosity: Verbosity.Verbose); // When fixture.Context.Verbose(fixture.Log); @@ -175,7 +266,7 @@ public void Should_Evaluate_And_Write_Verbose_Message_To_Log() public void Should_Not_Evaluate_And_Write_Verbose_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Normal); + var fixture = new LogActionFixture(verbosity: Verbosity.Normal); // When fixture.Context.Verbose(fixture.Log); @@ -184,6 +275,36 @@ public void Should_Not_Evaluate_And_Write_Verbose_Message_To_Log() fixture.Context.Log.DidNotReceive().Write(Verbosity.Verbose, LogLevel.Verbose, fixture.Format, fixture.Args); Assert.False(fixture.Evaluated); } + + [Fact] + public void Should_Write_Verbose_String_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + const string value = "Hello {0}"; + + // When + context.Verbose(value); + + // Then + context.Log.Received(1).Write(Verbosity.Verbose, LogLevel.Verbose, "{0}", value); + } + + [Fact] + public void Should_Write_Verbose_Object_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + var value = new { FirstName = "John", LastName="Doe" }; + + // When + context.Verbose(value); + + // Then + context.Log.Received(1).Write(Verbosity.Verbose, LogLevel.Verbose, "{0}", value); + } } public sealed class TheDebugMethod @@ -208,7 +329,7 @@ public void Should_Write_Debug_Message_To_Log() public void Should_Evaluate_And_Write_Debug_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Diagnostic); + var fixture = new LogActionFixture(verbosity: Verbosity.Diagnostic); // When fixture.Context.Debug(fixture.Log); @@ -222,7 +343,7 @@ public void Should_Evaluate_And_Write_Debug_Message_To_Log() public void Should_Not_Evaluate_And_Write_Debug_Message_To_Log() { // Given - var fixture = new LogActionFixture(verbosity:Verbosity.Normal); + var fixture = new LogActionFixture(verbosity: Verbosity.Normal); // When fixture.Context.Debug(fixture.Log); @@ -231,6 +352,36 @@ public void Should_Not_Evaluate_And_Write_Debug_Message_To_Log() fixture.Context.Log.DidNotReceive().Write(Verbosity.Diagnostic, LogLevel.Debug, fixture.Format, fixture.Args); Assert.False(fixture.Evaluated); } + + [Fact] + public void Should_Write_Debug_String_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + const string value = "Hello {0}"; + + // When + context.Debug(value); + + // Then + context.Log.Received(1).Write(Verbosity.Diagnostic, LogLevel.Debug, "{0}", value); + } + + [Fact] + public void Should_Write_Debug_Object_Value_To_Log() + { + // Given + var context = Substitute.For(); + context.Log.Returns(Substitute.For()); + var value = new { FirstName = "John", LastName="Doe" }; + + // When + context.Debug(value); + + // Then + context.Log.Received(1).Write(Verbosity.Diagnostic, LogLevel.Debug, "{0}", value); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/EnvironmentAliasesTests.cs b/src/Cake.Common.Tests/Unit/EnvironmentAliasesTests.cs index b639d7e7be..cf149fa2c5 100644 --- a/src/Cake.Common.Tests/Unit/EnvironmentAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/EnvironmentAliasesTests.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Core; +using Cake.Testing; using NSubstitute; using Xunit; @@ -69,7 +72,7 @@ public void Should_Return_False_If_Variable_Was_Null() } } - public sealed class TheGetEnvironmentVariableMethod + public sealed class TheEnvironmentVariableMethod { [Fact] public void Should_Return_Value() @@ -86,11 +89,11 @@ public void Should_Return_Value() var result = EnvironmentAliases.EnvironmentVariable(context, TestVariableName); // Then - Assert.Equal(result, TestVariableValue); + Assert.Equal(TestVariableValue, result); } [Fact] - public void Should_Return_Null_If_Value_Do_Not_Exist() + public void Should_Return_Null_If_Value_Does_Not_Exist() { // Given var environment = Substitute.For(); @@ -106,6 +109,62 @@ public void Should_Return_Null_If_Value_Do_Not_Exist() // Then Assert.Null(result); } + + [Fact] + public void Should_Return_Typed_Value() + { + // Given + var environment = Substitute.For(); + environment.GetEnvironmentVariable(TestVariableName) + .Returns("123"); + + var context = Substitute.For(); + context.Environment.Returns(environment); + + // When + var result = EnvironmentAliases.EnvironmentVariable(context, TestVariableName, 456); + + // Then + Assert.Equal(123, result); + } + + [Fact] + public void Should_Return_Typed_DefaultValue_If_Value_Does_Not_Exist() + { + // Given + var environment = Substitute.For(); + environment.GetEnvironmentVariable(TestVariableName) + .Returns((string)null); + + var context = Substitute.For(); + context.Environment.Returns(environment); + + // When + var result = EnvironmentAliases.EnvironmentVariable(context, TestVariableName, 456); + + // Then + Assert.Equal(456, result); + } + + [Fact] + public void Should_Throw_If_Value_Does_Not_Convert() + { + // Given + var environment = Substitute.For(); + environment.GetEnvironmentVariable(TestVariableName) + .Returns("abc"); + + var context = Substitute.For(); + context.Environment.Returns(environment); + + // When + var ex = Record.Exception(() => EnvironmentAliases.EnvironmentVariable(context, TestVariableName, 456)); + + // Then + Assert.NotNull(ex); + Assert.NotNull(ex.InnerException); + Assert.IsType(ex.InnerException); + } } public sealed class TheIsRunningOnWindowsMethod @@ -117,25 +176,25 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => EnvironmentAliases.IsRunningOnWindows(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Theory] - [InlineData(true, false)] - [InlineData(false, true)] - public void Should_Return_Correct_Value(bool isRunningOnUnix, bool expected) + [InlineData(PlatformFamily.Linux, false)] + [InlineData(PlatformFamily.OSX, false)] + [InlineData(PlatformFamily.FreeBSD, false)] + [InlineData(PlatformFamily.Windows, true)] + public void Should_Return_Correct_Value(PlatformFamily family, bool expected) { // Given - var environment = Substitute.For(); - environment.IsUnix().Returns(x => isRunningOnUnix); var context = Substitute.For(); - context.Environment.Returns(c => environment); + context.Environment.Returns(new FakeEnvironment(family)); // When var result = EnvironmentAliases.IsRunningOnWindows(context); // Then - Assert.Equal(result, expected); + Assert.Equal(expected, result); } } @@ -148,25 +207,118 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => EnvironmentAliases.IsRunningOnUnix(null)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Theory] - [InlineData(true, true)] - [InlineData(false, false)] - public void Should_Return_Correct_Value(bool isRunningOnUnix, bool expected) + [InlineData(PlatformFamily.Linux, true)] + [InlineData(PlatformFamily.OSX, true)] + [InlineData(PlatformFamily.FreeBSD, true)] + [InlineData(PlatformFamily.Windows, false)] + public void Should_Return_Correct_Value(PlatformFamily family, bool expected) { // Given - var environment = Substitute.For(); - environment.IsUnix().Returns(x => isRunningOnUnix); var context = Substitute.For(); - context.Environment.Returns(c => environment); + context.Environment.Returns(new FakeEnvironment(family)); // When var result = EnvironmentAliases.IsRunningOnUnix(context); // Then - Assert.Equal(result, expected); + Assert.Equal(expected, result); + } + } + + public sealed class TheIsRunningOnLinuxMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => EnvironmentAliases.IsRunningOnLinux(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Theory] + [InlineData(PlatformFamily.Linux, true)] + [InlineData(PlatformFamily.OSX, false)] + [InlineData(PlatformFamily.FreeBSD, false)] + [InlineData(PlatformFamily.Windows, false)] + public void Should_Return_Correct_Value(PlatformFamily family, bool expected) + { + // Given + var context = Substitute.For(); + context.Environment.Returns(new FakeEnvironment(family)); + + // When + var result = EnvironmentAliases.IsRunningOnLinux(context); + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIsRunningOnMacMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => EnvironmentAliases.IsRunningOnMacOs(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Theory] + [InlineData(PlatformFamily.Linux, false)] + [InlineData(PlatformFamily.OSX, true)] + [InlineData(PlatformFamily.FreeBSD, false)] + [InlineData(PlatformFamily.Windows, false)] + public void Should_Return_Correct_Value(PlatformFamily family, bool expected) + { + // Given + var context = Substitute.For(); + context.Environment.Returns(new FakeEnvironment(family)); + + // When + var result = EnvironmentAliases.IsRunningOnMacOs(context); + + // Then + Assert.Equal(expected, result); + } + } + + public sealed class TheIsRunningOnFreeBSDMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => EnvironmentAliases.IsRunningOnFreeBSD(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Theory] + [InlineData(PlatformFamily.Linux, false)] + [InlineData(PlatformFamily.OSX, false)] + [InlineData(PlatformFamily.FreeBSD, true)] + [InlineData(PlatformFamily.Windows, false)] + public void Should_Return_Correct_Value(PlatformFamily family, bool expected) + { + // Given + var context = Substitute.For(); + context.Environment.Returns(new FakeEnvironment(family)); + + // When + var result = EnvironmentAliases.IsRunningOnFreeBSD(context); + + // Then + Assert.Equal(expected, result); } } } diff --git a/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs b/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs index a89908e128..ad12d8b6a3 100644 --- a/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/DirectoryAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.IO; @@ -11,6 +12,7 @@ using Cake.Core; using Cake.Core.IO; using Cake.Testing; +using Cake.Testing.Xunit; using NSubstitute; using Xunit; @@ -30,7 +32,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => DirectoryAliases.Directory(null, path)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -43,7 +45,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => DirectoryAliases.Directory(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -70,7 +72,7 @@ public void Should_Throw_If_Context_Is_Null() DirectoryAliases.CleanDirectory(null, "/Temp/Hello")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -85,7 +87,7 @@ public void Should_Throw_If_Directory_Are_Null() DirectoryAliases.CleanDirectory(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -124,7 +126,7 @@ public void Should_Delete_Files_Respecting_Predicate_In_Provided_Directory() DirectoryAliases.CleanDirectory(context, directory, wherePredicate); // Then - Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive).Where(wherePredicate)); + Assert.DoesNotContain(fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive), file => wherePredicate(file)); Assert.Equal(filesNotMatchingPredicate, fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive)); } @@ -144,6 +146,65 @@ public void Should_Delete_Directories_In_Provided_Directory() Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetDirectories("*", SearchScope.Recursive)); } + [Fact] + public void Should_Throw_When_Deleting_Readonly_Files_In_Provided_Directory_If_Not_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectory(context, directory, new CleanDirectorySettings { Force = false })); + + // Then + AssertEx.IsExceptionWithMessage(result, "Cannot delete readonly file '/HasReadonly/Readonly.txt'."); + } + + [Fact] + public void Should_Delete_Readonly_Files_In_Provided_Directory_If_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + // When + DirectoryAliases.CleanDirectory(context, directory, new CleanDirectorySettings { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory(directory).GetDirectories("*", SearchScope.Recursive)); + } + + [Fact] + public void Should_Delete_Readonly_Files_Respecting_Predicate_In_Provided_Directory_If_Forced() + { + // Given + var directory = new DirectoryPath("/HasReadonly"); + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + Func wherePredicate = entry => !entry.Hidden; + context.FileSystem.Returns(fixture.FileSystem); + var filesNotMatchingPredicate = fixture + .FileSystem + .GetDirectory(directory) + .GetFiles("*", SearchScope.Recursive) + .Where(entry => !wherePredicate(entry)) + .ToArray(); + + // When + DirectoryAliases.CleanDirectory(context, directory, wherePredicate, new CleanDirectorySettings { Force = true }); + + // Then + Assert.DoesNotContain(fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive), file => wherePredicate(file)); + Assert.Equal(filesNotMatchingPredicate, fixture.FileSystem.GetDirectory(directory).GetFiles("*", SearchScope.Recursive)); + + Assert.Single(filesNotMatchingPredicate); + Assert.Equal("/HasReadonly/Hidden.txt", filesNotMatchingPredicate[0].Path.FullPath); + } + [Fact] public void Should_Delete_Directories_Respecting_Predicate_In_Provided_Directory() { @@ -157,7 +218,7 @@ public void Should_Delete_Directories_Respecting_Predicate_In_Provided_Directory DirectoryAliases.CleanDirectory(context, directory.Path, info => !info.Hidden); // Then - Assert.Equal(1, directory.GetDirectories("*", SearchScope.Recursive).Count()); + Assert.Single(directory.GetDirectories("*", SearchScope.Recursive)); Assert.True(fixture.FileSystem.GetDirectory("/Temp/Hello/Hidden").Exists); } @@ -225,7 +286,7 @@ public void Should_Skip_Predicate_Folder() // Then Assert.True(context.FileSystem.GetDirectory("/Temp").Exists); - Assert.Equal(1, directory.GetDirectories("*", SearchScope.Recursive).Count()); + Assert.Single(directory.GetDirectories("*", SearchScope.Recursive)); Assert.True(context.FileSystem.GetDirectory("/Temp/Goodbye").Exists); } } @@ -245,7 +306,7 @@ public void Should_Throw_If_Context_Is_Null() DirectoryAliases.CleanDirectories(null, paths)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -259,7 +320,7 @@ public void Should_Throw_If_Directories_Are_Null() DirectoryAliases.CleanDirectories(context, (IEnumerable)null)); // Then - Assert.IsArgumentNullException(result, "directories"); + AssertEx.IsArgumentNullException(result, "directories"); } [Fact] @@ -270,12 +331,13 @@ public void Should_Delete_Files_In_Provided_Directories() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { + var paths = new DirectoryPath[] + { "/Temp/Hello", "/Temp/Goodbye" }; // When - Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths)); + DirectoryAliases.CleanDirectories(context, paths); // Then Assert.Empty(fixture.FileSystem.GetDirectory(paths[0]).GetFiles("*", SearchScope.Recursive)); @@ -290,7 +352,8 @@ public void Should_Leave_Provided_Directories_Intact() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { + var paths = new DirectoryPath[] + { "/Temp/Hello", "/Temp/Goodbye" }; @@ -310,7 +373,8 @@ public void Should_Create_Directories_If_Missing() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { + var paths = new DirectoryPath[] + { "/Temp/Hello", "/NonExisting" }; @@ -320,6 +384,41 @@ public void Should_Create_Directories_If_Missing() // Then Assert.True(fixture.FileSystem.Exist((DirectoryPath)"/NonExisting")); } + + [Fact] + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new DirectoryPath[] { "/HasReadonly" }; + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = false })); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); + } + + [Fact] + public void Should_Delete_Directories_With_Readonly_Files_If_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new DirectoryPath[] { "/HasReadonly" }; + + // When + DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory("/HasReadonly").GetDirectories("*", SearchScope.Recursive)); + } } public sealed class WithStrings @@ -335,7 +434,7 @@ public void Should_Throw_If_Context_Is_Null() DirectoryAliases.CleanDirectories(null, paths)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -349,7 +448,7 @@ public void Should_Throw_If_Directories_Are_Null() DirectoryAliases.CleanDirectories(context, (IEnumerable)null)); // Then - Assert.IsArgumentNullException(result, "directories"); + AssertEx.IsArgumentNullException(result, "directories"); } [Fact] @@ -360,12 +459,13 @@ public void Should_Delete_Files_In_Provided_Directories() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new[] { + var paths = new[] + { "/Temp/Hello", "/Temp/Goodbye" }; // When - Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths)); + DirectoryAliases.CleanDirectories(context, paths); // Then Assert.Empty(fixture.FileSystem.GetDirectory(paths[0]).GetFiles("*", SearchScope.Recursive)); @@ -380,7 +480,8 @@ public void Should_Leave_Provided_Directories_Intact() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new[] { + var paths = new[] + { "/Temp/Hello", "/Temp/Goodbye" }; @@ -400,7 +501,8 @@ public void Should_Create_Directories_If_Missing() var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new[] { + var paths = new[] + { "/Temp/Hello", "/NonExisting" }; @@ -410,6 +512,41 @@ public void Should_Create_Directories_If_Missing() // Then Assert.True(fixture.FileSystem.Exist((DirectoryPath)"/NonExisting")); } + + [Fact] + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new[] { "/HasReadonly" }; + + // When + var result = Record.Exception(() => DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = false })); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); + } + + [Fact] + public void Should_Delete_Directories_With_Readonly_Files_If_Force() + { + // Given + var fixture = new FileSystemFixture(); + var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new[] { "/HasReadonly" }; + + // When + DirectoryAliases.CleanDirectories(context, paths, new CleanDirectorySettings() { Force = true }); + + // Then + Assert.Empty(fixture.FileSystem.GetDirectory("/HasReadonly").GetDirectories("*", SearchScope.Recursive)); + } } } @@ -426,7 +563,7 @@ public void Should_Throw_If_Context_Is_Null() DirectoryAliases.CreateDirectory(null, path)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -440,7 +577,7 @@ public void Should_Throw_If_Directory_Is_Null() DirectoryAliases.CreateDirectory(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -505,155 +642,163 @@ public void Should_Make_Relative_Path_Absolute() public sealed class TheEnsureExistsMethod { [Fact] - public void Should_Throw_If_Context_Is_Null () + public void Should_Throw_If_Context_Is_Null() { // Given - var path = new DirectoryPath ("/Temp"); + var path = new DirectoryPath("/Temp"); // When - var result = Record.Exception (() => - DirectoryAliases.EnsureDirectoryExists (null, path)); + var result = Record.Exception(() => + DirectoryAliases.EnsureDirectoryExists(null, path)); // Then - Assert.IsArgumentNullException (result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] - public void Should_Throw_If_Directory_Is_Null () + public void Should_Throw_If_Directory_Is_Null() { // Given - var context = Substitute.For (); + var context = Substitute.For(); // When - var result = Record.Exception (() => - DirectoryAliases.EnsureDirectoryExists (context, null)); + var result = Record.Exception(() => + DirectoryAliases.EnsureDirectoryExists(context, null)); // Then - Assert.IsArgumentNullException (result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] - public void Should_Create_Non_Existing_Directory () + public void Should_Create_Non_Existing_Directory() { // Given - var fileSystem = Substitute.For (); - var directory = Substitute.For (); - directory.Exists.Returns (false); - fileSystem.GetDirectory (Arg.Is (p => p.FullPath == "/Temp")).Returns (directory); + var fileSystem = Substitute.For(); + var directory = Substitute.For(); + directory.Exists.Returns(false); + fileSystem.GetDirectory(Arg.Is(p => p.FullPath == "/Temp")).Returns(directory); - var context = Substitute.For (); - context.FileSystem.Returns (fileSystem); + var context = Substitute.For(); + context.FileSystem.Returns(fileSystem); // When - DirectoryAliases.EnsureDirectoryExists (context, "/Temp"); + DirectoryAliases.EnsureDirectoryExists(context, "/Temp"); // Then - directory.Received (1).Create (); + directory.Received(1).Create(); } [Fact] - public void Should_Not_Create_Directory_Or_Fail_If_It_Already_Exist () + public void Should_Not_Create_Directory_Or_Fail_If_It_Already_Exist() { // Given - var fileSystem = Substitute.For (); - var directory = Substitute.For (); - directory.Exists.Returns (true); - fileSystem.GetDirectory (Arg.Is (p => p.FullPath == "/Temp")).Returns (directory); + var fileSystem = Substitute.For(); + var directory = Substitute.For(); + directory.Exists.Returns(true); + fileSystem.GetDirectory(Arg.Is(p => p.FullPath == "/Temp")).Returns(directory); - var context = Substitute.For (); - context.FileSystem.Returns (fileSystem); + var context = Substitute.For(); + context.FileSystem.Returns(fileSystem); // When - DirectoryAliases.EnsureDirectoryExists (context, "/Temp"); + DirectoryAliases.EnsureDirectoryExists(context, "/Temp"); // Then - directory.Received (0).Create (); + directory.Received(0).Create(); } [Fact] - public void Should_Make_Relative_Path_Absolute () + public void Should_Make_Relative_Path_Absolute() { // Given - var fileSystem = Substitute.For (); - var context = Substitute.For (); - var environment = Substitute.For (); - environment.WorkingDirectory.Returns ("/Temp"); + var fileSystem = Substitute.For(); + var context = Substitute.For(); + var environment = Substitute.For(); + environment.WorkingDirectory.Returns("/Temp"); - context.FileSystem.Returns (fileSystem); - context.Environment.Returns (environment); + context.FileSystem.Returns(fileSystem); + context.Environment.Returns(environment); // When - DirectoryAliases.EnsureDirectoryExists (context, "Hello"); + DirectoryAliases.EnsureDirectoryExists(context, "Hello"); // Then - fileSystem.Received (1).GetDirectory (Arg.Is ( + fileSystem.Received(1).GetDirectory(Arg.Is( p => p.FullPath == "/Temp/Hello")); } } - public sealed class TheDeleteDirectoryMethod + public sealed class TheEnsureDoNotExistsMethod { [Fact] public void Should_Throw_If_Context_Is_Null() { - // Given, When - var result = Record.Exception(() => DirectoryAliases.DeleteDirectory(null, "/Temp/DoNotExist")); + // Given + var path = new DirectoryPath("/Temp"); + + // When + var result = Record.Exception(() => + DirectoryAliases.EnsureDirectoryDoesNotExist(null, path)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] public void Should_Throw_If_Directory_Is_Null() { // Given - var fixture = new FileSystemFixture(); var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); // When - var result = Record.Exception(() => context.DeleteDirectory(null)); + var result = Record.Exception(() => + DirectoryAliases.EnsureDirectoryDoesNotExist(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] - public void Should_Throw_If_Directory_Do_Not_Exist() + public void Should_Not_Throw_Exception_Or_Fail_For_Non_Existing_Directory() { // Given - var fixture = new FileSystemFixture(); + var fileSystem = Substitute.For(); + var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); + context.FileSystem.Returns(fileSystem); // When var result = Record.Exception(() => - context.DeleteDirectory("/Temp/DoNotExist")); + DirectoryAliases.EnsureDirectoryDoesNotExist(context, "/Temp")); // Then - Assert.IsType(result); - Assert.Equal("The directory '/Temp/DoNotExist' do not exist.", result.Message); + Assert.Null(result); } [Fact] - public void Should_Throw_When_Deleting_Directory_With_Sub_Directories_If_Non_Recursive() + public void Should_Delete_Directory_If_It_Exists() { // Given - var fixture = new FileSystemFixture(); + var fileSystem = Substitute.For(); + var directory = Substitute.For(); + directory.Exists.Returns(true); + fileSystem.GetDirectory(Arg.Is(p => p.FullPath == "/Temp")).Returns(directory); + var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); + context.FileSystem.Returns(fileSystem); // When - var result = Record.Exception(() => - context.DeleteDirectory("/Temp/HasDirectories")); + DirectoryAliases.EnsureDirectoryDoesNotExist(context, "/Temp"); // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasDirectories' without recursion since it's not empty.", result.Message); + directory.Received(1).Delete(true); } + } + public sealed class TheDeleteDirectoryMethod + { [Fact] - public void Should_Throw_When_Deleting_Directory_With_Files_If_Non_Recursive() + public void Should_Delete_Directory_With_Content_If_Recursive() { // Given var fixture = new FileSystemFixture(); @@ -661,16 +806,14 @@ public void Should_Throw_When_Deleting_Directory_With_Files_If_Non_Recursive() context.FileSystem.Returns(fixture.FileSystem); // When - var result = Record.Exception(() => - context.DeleteDirectory("/Temp/HasFiles")); + context.DeleteDirectory("/Temp/Hello", new DeleteDirectorySettings { Recursive = true }); // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasFiles' without recursion since it's not empty.", result.Message); + Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); } [Fact] - public void Should_Delete_Empty_Directory_If_Non_Recursive() + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() { // Given var fixture = new FileSystemFixture(); @@ -678,14 +821,15 @@ public void Should_Delete_Empty_Directory_If_Non_Recursive() context.FileSystem.Returns(fixture.FileSystem); // When - context.DeleteDirectory("/Temp/Hello/Empty"); + var result = Record.Exception(() => context.DeleteDirectory("/HasReadonly", new DeleteDirectorySettings { Recursive = true })); // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello/Empty").Exists); + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); } [Fact] - public void Should_Delete_Directory_With_Content_If_Recursive() + public void Should_Delete_Directory_With_Readonly_Files_If_Force() { // Given var fixture = new FileSystemFixture(); @@ -693,10 +837,10 @@ public void Should_Delete_Directory_With_Content_If_Recursive() context.FileSystem.Returns(fixture.FileSystem); // When - context.DeleteDirectory("/Temp/Hello", true); + context.DeleteDirectory("/HasReadonly", new DeleteDirectorySettings { Recursive = true, Force = true }); // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); + Assert.False(fixture.FileSystem.GetDirectory("/HasReadonly").Exists); } } @@ -705,284 +849,148 @@ public sealed class TheDeleteDirectoriesMethod public sealed class WithPaths { [Fact] - public void Should_Throw_If_Context_Is_Null() - { - // Given - var paths = new DirectoryPath[] { "/Temp/DoNotExist" }; - - // When - var result = Record.Exception(() => - DirectoryAliases.DeleteDirectories(null, paths)); - - // Then - Assert.IsArgumentNullException(result, "context"); - } - - [Fact] - public void Should_Throw_If_Directories_Are_Null() + public void Should_Delete_Directory_With_Content_If_Recursive() { // Given + var fixture = new FileSystemFixture(); var context = Substitute.For(); + context.FileSystem.Returns(fixture.FileSystem); + + var paths = new DirectoryPath[] { "/Temp/Hello", "/Temp/Goodbye" }; // When - var result = Record.Exception(() => - context.DeleteDirectories((IEnumerable)null)); + context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true }); // Then - Assert.IsArgumentNullException(result, "directories"); + Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); + Assert.False(fixture.FileSystem.GetDirectory("/Temp/Goodbye").Exists); } [Fact] - public void Should_Throw_If_Any_Directory_Do_Not_Exist() + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() { // Given var fixture = new FileSystemFixture(); var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { "/Temp/DoNotExist" }; + var paths = new DirectoryPath[] { "/HasReadonly" }; // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); + var result = Record.Exception(() => context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true })); // Then Assert.IsType(result); - Assert.Equal("The directory '/Temp/DoNotExist' do not exist.", result.Message); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); } [Fact] - public void Should_Throw_When_Deleting_Directory_With_Sub_Directories_If_Non_Recursive() + public void Should_Delete_Directories_With_Readonly_Files_If_Force() { // Given var fixture = new FileSystemFixture(); var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { "/Temp/HasDirectories" }; + var paths = new DirectoryPath[] { "/HasReadonly" }; // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); + context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true, Force = true }); // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasDirectories' without recursion since it's not empty.", result.Message); + Assert.False(fixture.FileSystem.GetDirectory("/HasReadonly").Exists); } + } + public sealed class WithStrings + { [Fact] - public void Should_Throw_When_Deleting_Directory_With_Files_If_Non_Recursive() + public void Should_Delete_Directory_With_Content_If_Recursive() { // Given var fixture = new FileSystemFixture(); var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { "/Temp/HasFiles" }; + var paths = new[] { "/Temp/Hello", "/Temp/Goodbye" }; // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); + context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true }); // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasFiles' without recursion since it's not empty.", result.Message); + Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); + Assert.False(fixture.FileSystem.GetDirectory("/Temp/Goodbye").Exists); } [Fact] - public void Should_Delete_Empty_Directory_If_Non_Recursive() + public void Should_Throw_When_Deleting_With_Readonly_Files_If_Not_Force() { // Given var fixture = new FileSystemFixture(); var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { "/Temp/Hello/Empty", "/Temp/Hello/More/Empty" }; + var paths = new[] { "/HasReadonly" }; // When - context.DeleteDirectories(paths); + var result = Record.Exception(() => context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true })); // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello/Empty").Exists); - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello/More/Empty").Exists); + Assert.IsType(result); + Assert.Equal("Cannot delete readonly file '/HasReadonly/Readonly.txt'.", result?.Message); } [Fact] - public void Should_Delete_Directory_With_Content_If_Recursive() + public void Should_Delete_Directories_With_Readonly_Files_If_Force() { // Given var fixture = new FileSystemFixture(); var context = Substitute.For(); context.FileSystem.Returns(fixture.FileSystem); - var paths = new DirectoryPath[] { "/Temp/Hello", "/Temp/Goodbye" }; + var paths = new[] { "/HasReadonly" }; // When - context.DeleteDirectories(paths, true); + context.DeleteDirectories(paths, new DeleteDirectorySettings { Recursive = true, Force = true }); // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Goodbye").Exists); + Assert.False(fixture.FileSystem.GetDirectory("/HasReadonly").Exists); } } + } - public sealed class WithStrings + public sealed class TheCopyDirectoryMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() { - [Fact] - public void Should_Throw_If_Context_Is_Null() - { - // Given - var paths = new[] { "/Temp/DoNotExist" }; + // Given + var sourcePath = new DirectoryPath("C:/Temp"); + var destinationPath = new DirectoryPath("C:/Temp2"); - // When - var result = Record.Exception(() => - DirectoryAliases.DeleteDirectories(null, paths)); + // When + var result = Record.Exception(() => + DirectoryAliases.CopyDirectory(null, sourcePath, destinationPath)); - // Then - Assert.IsArgumentNullException(result, "context"); - } + // Then + Assert.IsType(result); + Assert.Equal("context", ((ArgumentNullException)result)?.ParamName); + } - [Fact] - public void Should_Throw_If_Directories_Are_Null() - { - // Given - var context = Substitute.For(); + [Fact] + public void Should_Throw_If_Source_Directory_Is_Null() + { + // Given + var context = Substitute.For(); - // When - var result = Record.Exception(() => - context.DeleteDirectories((IEnumerable)null)); + // When + var result = Record.Exception(() => + DirectoryAliases.CopyDirectory(context, null, null)); - // Then - Assert.IsArgumentNullException(result, "directories"); - } - - [Fact] - public void Should_Throw_If_Any_Directory_Do_Not_Exist() - { - // Given - var fixture = new FileSystemFixture(); - var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); - - var paths = new[] { "/Temp/DoNotExist" }; - - // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); - - // Then - Assert.IsType(result); - Assert.Equal("The directory '/Temp/DoNotExist' do not exist.", result.Message); - } - - [Fact] - public void Should_Throw_When_Deleting_Directory_With_Sub_Directories_If_Non_Recursive() - { - // Given - var fixture = new FileSystemFixture(); - var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); - - var paths = new[] { "/Temp/HasDirectories" }; - - // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); - - // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasDirectories' without recursion since it's not empty.", result.Message); - } - - [Fact] - public void Should_Throw_When_Deleting_Directory_With_Files_If_Non_Recursive() - { - // Given - var fixture = new FileSystemFixture(); - var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); - - var paths = new[] { "/Temp/HasFiles" }; - - // When - var result = Record.Exception(() => - context.DeleteDirectories(paths)); - - // Then - Assert.IsType(result); - Assert.Equal("Cannot delete directory '/Temp/HasFiles' without recursion since it's not empty.", result.Message); - } - - [Fact] - public void Should_Delete_Empty_Directory_If_Non_Recursive() - { - // Given - var fixture = new FileSystemFixture(); - var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); - - var paths = new[] { "/Temp/Hello/Empty", "/Temp/Hello/More/Empty" }; - - // When - context.DeleteDirectories(paths); - - // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello/Empty").Exists); - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello/More/Empty").Exists); - } - - [Fact] - public void Should_Delete_Directory_With_Content_If_Recursive() - { - // Given - var fixture = new FileSystemFixture(); - var context = Substitute.For(); - context.FileSystem.Returns(fixture.FileSystem); - - var paths = new[] { "/Temp/Hello", "/Temp/Goodbye" }; - - // When - context.DeleteDirectories(paths, true); - - // Then - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Hello").Exists); - Assert.False(fixture.FileSystem.GetDirectory("/Temp/Goodbye").Exists); - } - } - } - - public sealed class TheCopyDirectoryMethod - { - [Fact] - public void Should_Throw_If_Context_Is_Null() - { - // Given - var sourcePath = new DirectoryPath("C:/Temp"); - var destinationPath = new DirectoryPath("C:/Temp2"); - - // When - var result = Record.Exception(() => - DirectoryAliases.CopyDirectory(null, sourcePath, destinationPath)); - - // Then - Assert.IsType(result); - Assert.Equal("context", ((ArgumentNullException)result).ParamName); - } - - [Fact] - public void Should_Throw_If_Source_Directory_Is_Null() - { - // Given - var context = Substitute.For(); - - // When - var result = Record.Exception(() => - DirectoryAliases.CopyDirectory(context, null, null)); - - // Then - Assert.IsType(result); - Assert.Equal("source", ((ArgumentNullException)result).ParamName); - } + // Then + Assert.IsType(result); + Assert.Equal("source", ((ArgumentNullException)result)?.ParamName); + } [Fact] public void Should_Throw_If_Destination_Directory_Is_Null() @@ -997,7 +1005,7 @@ public void Should_Throw_If_Destination_Directory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("destination", ((ArgumentNullException)result).ParamName); + Assert.Equal("destination", ((ArgumentNullException)result)?.ParamName); } [Fact] @@ -1016,7 +1024,7 @@ public void Should_Throw_If_Source_Directory_Does_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("Source directory does not exist or could not be found: /Temp", result.Message); + Assert.Equal("Source directory does not exist or could not be found: /Temp", result?.Message); } [Fact] @@ -1115,7 +1123,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => DirectoryAliases.DirectoryExists(null, "something")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -1128,7 +1136,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => DirectoryAliases.DirectoryExists(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -1174,7 +1182,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => DirectoryAliases.MakeAbsolute(null, "./build")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -1187,7 +1195,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => DirectoryAliases.MakeAbsolute(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -1204,5 +1212,328 @@ public void Should_Return_Absolute_Directory_Path() Assert.Equal("/Working/build", result.FullPath); } } + + public sealed class TheMakeRelativeMethod + { + [Fact] + public void Should_Throw_For_DirectoryPath_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => DirectoryAliases.MakeRelative(null, new DirectoryPath("./build"))); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_For_FilePath_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => DirectoryAliases.MakeRelative(null, new FilePath("./build"))); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_DirectoryPath_Is_Null() + { + // Given + var context = Substitute.For(); + DirectoryPath path = null; + + // When + var result = Record.Exception(() => DirectoryAliases.MakeRelative(context, path)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [Fact] + public void Should_Throw_If_FilePath_Is_Null() + { + // Given + var context = Substitute.For(); + FilePath path = null; + + // When + var result = Record.Exception(() => DirectoryAliases.MakeRelative(context, path)); + + // Then + AssertEx.IsArgumentNullException(result, "path"); + } + + [WindowsTheory] + [InlineData(@"\Working", @"\Working\build", "build")] + [InlineData(@"\Working", @"\Working", ".")] + [InlineData("C:/Working/build/core", "C:/Working/stage/core", "../../stage/core")] + [InlineData("C:/Working/build/core", "C:/Working", "../..")] + public void Should_Return_Relative_Directory_Path_For_Working_Directory(string rootPath, string path, string expected) + { + // Given + var context = Substitute.For(); + context.Environment.WorkingDirectory.Returns(d => rootPath); + + // When + var result = DirectoryAliases.MakeRelative(context, new DirectoryPath(path)); + + // Then + Assert.Equal(expected, result.FullPath); + } + + [WindowsTheory] + [InlineData(@"\Working", @"\Working\build", "build")] + [InlineData(@"\Working", @"\Working", ".")] + [InlineData("C:/Working/build/core", "C:/Working/stage/core", "../../stage/core")] + [InlineData("C:/Working/build/core", "C:/Working", "../..")] + public void Should_Return_Relative_Directory_Path_For_Defined_Root_Directory(string rootPath, string path, string expected) + { + // Given + var context = Substitute.For(); + + // When + var result = DirectoryAliases.MakeRelative(context, new DirectoryPath(path), new DirectoryPath(rootPath)); + + // Then + Assert.Equal(expected, result.FullPath); + } + + [WindowsTheory] + [InlineData(@"\Working", @"\Working\build\file.cake", "build/file.cake")] + [InlineData(@"\Working", @"\Working\file.cake", "file.cake")] + [InlineData("C:/Working/build/core", "C:/Working/stage/core/file.cake", "../../stage/core/file.cake")] + [InlineData("C:/Working/build/core", "C:/Working/file.cake", "../../file.cake")] + public void Should_Return_Relative_File_Path_For_Working_Directory(string rootPath, string path, string expected) + { + // Given + var context = Substitute.For(); + context.Environment.WorkingDirectory.Returns(d => rootPath); + + // When + var result = DirectoryAliases.MakeRelative(context, new FilePath(path)); + + // Then + Assert.Equal(expected, result.FullPath); + } + + [WindowsTheory] + [InlineData(@"\Working", @"\Working\build\file.cake", "build/file.cake")] + [InlineData(@"\Working", @"\Working\file.cake", "file.cake")] + [InlineData("C:/Working/build/core", "C:/Working/stage/core/file.cake", "../../stage/core/file.cake")] + [InlineData("C:/Working/build/core", "C:/Working/file.cake", "../../file.cake")] + public void Should_Return_Relative_File_Path_For_Defined_Root_Directory(string rootPath, string path, string expected) + { + // Given + var context = Substitute.For(); + + // When + var result = DirectoryAliases.MakeRelative(context, new FilePath(path), new DirectoryPath(rootPath)); + + // Then + Assert.Equal(expected, result.FullPath); + } + } + + public sealed class TheMoveDirectoryMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var source = new DirectoryPath("./source"); + var target = new DirectoryPath("./target"); + + var result = Record.Exception(() => + DirectoryAliases.MoveDirectory(null, source, target)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Source_Directory_Path_Is_Null() + { + // Given + var context = Substitute.For(); + var target = new DirectoryPath("./target"); + + // When + var result = Record.Exception(() => + DirectoryAliases.MoveDirectory(context, null, target)); + + // Then + AssertEx.IsArgumentNullException(result, "directoryPath"); + } + + [Fact] + public void Should_Throw_If_Target_Directory_Path_Is_Null() + { + // Given + var context = Substitute.For(); + var source = new DirectoryPath("./source"); + + // When + var result = Record.Exception(() => + DirectoryAliases.MoveDirectory(context, source, null)); + + // Then + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); + } + + [Fact] + public void Should_Recursively_Move_Files_And_Directory() + { + // Given + var context = Substitute.For(); + var environment = FakeEnvironment.CreateUnixEnvironment(); + var fileSystem = new FakeFileSystem(environment); + CreateFileStructure(fileSystem); + context.FileSystem.Returns(fileSystem); + var sourcePath = new DirectoryPath("/Temp"); + var destinationPath = new DirectoryPath("/Temp2"); + + // When + DirectoryAliases.MoveDirectory(context, sourcePath, destinationPath); + + // Then + Assert.False(fileSystem.GetDirectory("/Temp/Stuff").Exists); + Assert.False(fileSystem.GetDirectory("/Temp/Things").Exists); + Assert.True(fileSystem.GetDirectory("/Temp2/Stuff").Exists); + Assert.True(fileSystem.GetDirectory("/Temp2/Things").Exists); + + Assert.False(fileSystem.GetFile("/Temp/Stuff/file1.txt").Exists); + Assert.False(fileSystem.GetFile("/Temp/Stuff/file2.txt").Exists); + Assert.False(fileSystem.GetFile("/Temp/Things/file1.txt").Exists); + Assert.True(fileSystem.GetFile("/Temp2/Stuff/file1.txt").Exists); + Assert.True(fileSystem.GetFile("/Temp2/Stuff/file2.txt").Exists); + Assert.True(fileSystem.GetFile("/Temp2/Things/file1.txt").Exists); + } + + private static void CreateFileStructure(FakeFileSystem ffs) + { + Action dir = path => ffs.CreateDirectory(path); + Action file = path => ffs.CreateFile(path); + + dir("/Temp"); + { + file("/Temp/file1.txt"); + file("/Temp/file2.txt"); + dir("/Temp/Stuff"); + { + file("/Temp/Stuff/file1.txt"); + file("/Temp/Stuff/file2.txt"); + } + dir("/Temp/Things"); + { + file("/Temp/Things/file1.txt"); + } + } + } + } + + public sealed class TheGetSubDirectoriesMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var directoryPath = new DirectoryPath("./some/path"); + + var result = Record.Exception(() => + DirectoryAliases.GetSubDirectories(null, directoryPath)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Directory_Path_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => + DirectoryAliases.GetSubDirectories(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "directoryPath"); + } + + [Fact] + public void Should_List_All_Directories_In_Directory() + { + // Given + var context = Substitute.For(); + var environment = FakeEnvironment.CreateUnixEnvironment(); + var fileSystem = new FakeFileSystem(environment); + CreateFileStructure(fileSystem); + context.FileSystem.Returns(fileSystem); + var directoryPath = new DirectoryPath("/Temp"); + + // When + var directories = DirectoryAliases.GetSubDirectories(context, directoryPath); + + // Then + Assert.Contains(directories, d => d.GetDirectoryName() == "Stuff"); + Assert.Contains(directories, d => d.GetDirectoryName() == "Things"); + Assert.DoesNotContain(directories, d => d.GetDirectoryName() == "file1.txt"); + } + + private static void CreateFileStructure(FakeFileSystem ffs) + { + Action dir = path => ffs.CreateDirectory(path); + Action file = path => ffs.CreateFile(path); + + dir("/Temp"); + { + file("/Temp/file1.txt"); + dir("/Temp/Stuff"); + dir("/Temp/Things"); + } + } + } + + public sealed class TheExpandEnvironmentVariablesMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => DirectoryAliases.ExpandEnvironmentVariables(null, "some file")); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Path_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => DirectoryAliases.ExpandEnvironmentVariables(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "directoryPath"); + } + + [Fact] + public void Should_Expand_Existing_Environment_Variables() + { + // Given + var context = Substitute.For(); + var environment = FakeEnvironment.CreateWindowsEnvironment(); + environment.SetEnvironmentVariable("FOO", "bar"); + context.Environment.Returns(environment); + + // When + var result = DirectoryAliases.ExpandEnvironmentVariables(context, "/%FOO%/baz"); + + // Then + Assert.Equal("/bar/baz", result.FullPath); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/IO/FileAliasesTests.cs b/src/Cake.Common.Tests/Unit/IO/FileAliasesTests.cs index fbeb02dbc2..6e836dc451 100644 --- a/src/Cake.Common.Tests/Unit/IO/FileAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/FileAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.IO; using System.Linq; @@ -31,7 +32,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => FileAliases.File(null, path)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -44,7 +45,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => FileAliases.File(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -71,7 +72,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.CopyFileToDirectory(null, "./file.txt", "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -85,7 +86,7 @@ public void Should_Throw_If_File_Path_Is_Null() FileAliases.CopyFileToDirectory(context, null, "./target")); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -99,7 +100,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.CopyFileToDirectory(context, "./file.txt", null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -127,7 +128,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.CopyFile(null, "./file.txt", "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -141,7 +142,7 @@ public void Should_Throw_If_File_Path_Is_Null() FileAliases.CopyFile(context, null, "./target")); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -155,7 +156,7 @@ public void Should_Throw_If_Target_File_Path_Is_Null() FileAliases.CopyFile(context, "./file.txt", null)); // Then - Assert.IsArgumentNullException(result, "targetFilePath"); + AssertEx.IsArgumentNullException(result, "targetFilePath"); } [Fact] @@ -172,7 +173,7 @@ public void Should_Throw_If_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -189,7 +190,7 @@ public void Should_Throw_If_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file1.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file1.txt' does not exist.", result?.Message); } [Fact] @@ -238,7 +239,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.CopyFiles(null, fixture.SourceFilePaths, "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -252,7 +253,7 @@ public void Should_Throw_If_File_Paths_Are_Null() FileAliases.CopyFiles(context, (IEnumerable)null, "./target")); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] @@ -266,7 +267,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.CopyFiles(fixture.Context, fixture.SourceFilePaths, null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -283,7 +284,7 @@ public void Should_Throw_If_Any_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -300,7 +301,21 @@ public void Should_Throw_If_Any_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file2.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file2.txt' does not exist.", result?.Message); + } + + [Fact] + public void Should_Keep_Folder_Structure() + { + // Given + var fixture = new FileCopyFixture(); + + // When + FileAliases.CopyFiles(fixture.Context, fixture.SourceFilePaths, "./target"); + + // Then + fixture.TargetFiles[0].Received(1).Copy(Arg.Any(), true); + fixture.TargetFiles[1].Received(1).Copy(Arg.Any(), true); } [Fact] @@ -332,7 +347,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.CopyFiles(null, filePaths, "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -346,7 +361,7 @@ public void Should_Throw_If_File_Paths_Are_Null() FileAliases.CopyFiles(context, (IEnumerable)null, "./target")); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] @@ -361,7 +376,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.CopyFiles(fixture.Context, filePaths, null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -379,7 +394,7 @@ public void Should_Throw_If_Any_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -397,7 +412,22 @@ public void Should_Throw_If_Any_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file2.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file2.txt' does not exist.", result?.Message); + } + + [Fact] + public void Should_Keep_Folder_Structure() + { + // Given + var fixture = new FileCopyFixture(); + var filePaths = fixture.SourceFilePaths.Select(x => x.FullPath); + + // When + FileAliases.CopyFiles(fixture.Context, filePaths, "./target"); + + // Then + fixture.TargetFiles[0].Received(1).Copy(Arg.Any(), true); + fixture.TargetFiles[1].Received(1).Copy(Arg.Any(), true); } [Fact] @@ -426,7 +456,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.CopyFiles(null, "", "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -440,7 +470,7 @@ public void Should_Throw_If_Glob_Expression_Is_Null() FileAliases.CopyFiles(fixture.Context, (string)null, "./target")); // Then - Assert.IsArgumentNullException(result, "pattern"); + AssertEx.IsArgumentNullException(result, "pattern"); } [Fact] @@ -454,7 +484,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.CopyFiles(fixture.Context, "*", null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -471,7 +501,7 @@ public void Should_Throw_If_Any_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -488,7 +518,21 @@ public void Should_Throw_If_Any_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file2.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file2.txt' does not exist.", result?.Message); + } + + [Fact] + public void Should_Keep_Folder_Structure() + { + // Given + var fixture = new FileCopyFixture(); + + // When + FileAliases.CopyFiles(fixture.Context, "*", "./target", true); + + // Then + fixture.TargetFiles[0].Received(1).Copy(Arg.Any(), true); + fixture.TargetFiles[1].Received(1).Copy(Arg.Any(), true); } [Fact] @@ -520,7 +564,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.DeleteFile(null, filePath)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -534,7 +578,7 @@ public void Should_Throw_If_File_Path_Is_Null() FileAliases.DeleteFile(context, null)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -549,7 +593,7 @@ public void Should_Throw_If_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/file.txt' do not exist.", result.Message); + Assert.Equal("The file '/file.txt' does not exist.", result?.Message); } [Fact] @@ -595,7 +639,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.DeleteFiles(null, filePaths)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -609,7 +653,7 @@ public void Should_Throw_If_File_Paths_Are_Null() FileAliases.DeleteFiles(context, (IEnumerable)null)); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] @@ -637,7 +681,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.DeleteFiles(null, "*")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -651,7 +695,7 @@ public void Should_Throw_If_Glob_Expression_Is_Null() FileAliases.DeleteFiles(context, (string)null)); // Then - Assert.IsArgumentNullException(result, "pattern"); + AssertEx.IsArgumentNullException(result, "pattern"); } [Fact] @@ -683,7 +727,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.MoveFileToDirectory(null, source, target)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -698,7 +742,7 @@ public void Should_Throw_If_Source_File_Path_Is_Null() FileAliases.MoveFileToDirectory(context, null, target)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -713,7 +757,7 @@ public void Should_Throw_If_Target_File_Path_Is_Null() FileAliases.MoveFileToDirectory(context, source, null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -746,7 +790,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.MoveFiles(null, fixture.SourceFilePaths, "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -760,7 +804,7 @@ public void Should_Throw_If_File_Paths_Are_Null() FileAliases.MoveFiles(context, (IEnumerable)null, "./target")); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] @@ -774,7 +818,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.MoveFiles(fixture.Context, fixture.SourceFilePaths, null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -791,7 +835,7 @@ public void Should_Throw_If_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -808,7 +852,7 @@ public void Should_Throw_If_Any_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file2.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file2.txt' does not exist.", result?.Message); } [Fact] @@ -836,7 +880,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.MoveFiles(null, "", "./target")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -850,7 +894,7 @@ public void Should_Throw_If_Glob_Expression_Is_Null() FileAliases.MoveFiles(fixture.Context, (string)null, "./target")); // Then - Assert.IsArgumentNullException(result, "pattern"); + AssertEx.IsArgumentNullException(result, "pattern"); } [Fact] @@ -864,7 +908,7 @@ public void Should_Throw_If_Target_Directory_Path_Is_Null() FileAliases.MoveFiles(fixture.Context, "*", null)); // Then - Assert.IsArgumentNullException(result, "targetDirectoryPath"); + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); } [Fact] @@ -881,7 +925,7 @@ public void Should_Throw_If_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -898,7 +942,7 @@ public void Should_Throw_If_Any_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file2.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file2.txt' does not exist.", result?.Message); } [Fact] @@ -930,7 +974,7 @@ public void Should_Throw_If_Context_Is_Null() FileAliases.MoveFile(null, source, target)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -945,7 +989,7 @@ public void Should_Throw_If_Source_File_Path_Is_Null() FileAliases.MoveFile(context, null, target)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -960,7 +1004,7 @@ public void Should_Throw_If_Target_File_Path_Is_Null() FileAliases.MoveFile(context, source, null)); // Then - Assert.IsArgumentNullException(result, "targetFilePath"); + AssertEx.IsArgumentNullException(result, "targetFilePath"); } [Fact] @@ -977,7 +1021,7 @@ public void Should_Throw_If_Target_Directory_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The directory '/Working/target' do not exist.", result.Message); + Assert.Equal("The directory '/Working/target' does not exist.", result?.Message); } [Fact] @@ -994,7 +1038,7 @@ public void Should_Throw_If_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The file '/Working/file1.txt' do not exist.", result.Message); + Assert.Equal("The file '/Working/file1.txt' does not exist.", result?.Message); } [Fact] @@ -1021,7 +1065,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => FileAliases.FileExists(null, "some file")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -1034,7 +1078,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => FileAliases.FileExists(context, null)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -1102,7 +1146,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => FileAliases.MakeAbsolute(null, "./build.txt")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -1115,7 +1159,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => FileAliases.MakeAbsolute(context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -1142,7 +1186,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => FileAliases.FileSize(null, "some file")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -1155,7 +1199,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => FileAliases.FileSize(context, null)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -1182,7 +1226,7 @@ public void Should_Return_Size_If_Path_Exist(string workingDirectory, string fil var environment = FakeEnvironment.CreateUnixEnvironment(); environment.WorkingDirectory = workingDirectory; var fileSystem = new FakeFileSystem(environment); - fileSystem.CreateFile(filePath, new byte[] {1, 2, 3, 4}); + fileSystem.CreateFile(filePath, new byte[] { 1, 2, 3, 4 }); context.FileSystem.Returns(fileSystem); context.Environment.Returns(environment); @@ -1193,5 +1237,47 @@ public void Should_Return_Size_If_Path_Exist(string workingDirectory, string fil Assert.Equal(result, 4); } } + + public sealed class TheExpandEnvironmentVariablesMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => FileAliases.ExpandEnvironmentVariables(null, "some file")); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Path_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => FileAliases.ExpandEnvironmentVariables(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "filePath"); + } + + [Fact] + public void Should_Expand_Existing_Environment_Variables() + { + // Given + var context = Substitute.For(); + var environment = FakeEnvironment.CreateWindowsEnvironment(); + environment.SetEnvironmentVariable("FOO", "bar"); + context.Environment.Returns(environment); + + // When + var result = FileAliases.ExpandEnvironmentVariables(context, "/%FOO%/baz.qux"); + + // Then + Assert.Equal("/bar/baz.qux", result.FullPath); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/IO/FileCopierTests.cs b/src/Cake.Common.Tests/Unit/IO/FileCopierTests.cs new file mode 100644 index 0000000000..5ab9a39eea --- /dev/null +++ b/src/Cake.Common.Tests/Unit/IO/FileCopierTests.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Cake.Common.IO; +using Cake.Common.Tests.Fixtures.IO; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Common.Tests.Unit.IO +{ + public sealed class FileCopierTests + { + public sealed class FileCopierTestsMethod + { + [Fact] + public void Should_Copy_Single_File_Relative_Path() + { + const string filePath = "./src/a/a.txt"; + const string dstPath = "./dst"; + + // Given + var fixture = new FileCopierFixture(); + fixture.EnsureFileExists(filePath); + fixture.EnsureDirectoryExists(dstPath); + + // When + FileCopier.CopyFiles( + fixture.Context, + new FilePath[] + { + filePath + }, + new DirectoryPath(dstPath), + true); + + // Then + Assert.True(fixture.ExistsFile($"{dstPath}/a/a.txt")); + } + + [Fact] + public void Should_Copy_Single_File_Absolute_Path() + { + const string filePath = "./src/a/a.txt"; + const string dstPath = "./dst"; + + // Given + var fixture = new FileCopierFixture(); + fixture.EnsureFileExists(filePath); + fixture.EnsureDirectoryExists(dstPath); + + // When + FileCopier.CopyFiles( + fixture.Context, + new[] { fixture.MakeAbsolute(filePath) }, + new DirectoryPath(dstPath), + true); + + // Then + Assert.True(fixture.ExistsFile($"{dstPath}/a/a.txt")); + } + + [Fact] + public void Should_Copy_Multiple_Files_Relative_Path() + { + const string filePath1 = "./src/a/a.txt"; + const string filePath2 = "./src/b/b.txt"; + const string dstPath = "./dst"; + + // Given + var fixture = new FileCopierFixture(); + fixture.EnsureFileExists(filePath1); + fixture.EnsureFileExists(filePath2); + fixture.EnsureDirectoryExists(dstPath); + + // When + FileCopier.CopyFiles( + fixture.Context, + new FilePath[] + { + filePath1, + filePath2 + }, + new DirectoryPath(dstPath), + true); + + // Then + Assert.True(fixture.ExistsFile($"{dstPath}/a/a.txt")); + Assert.True(fixture.ExistsFile($"{dstPath}/b/b.txt")); + } + + [Fact] + public void Should_Copy_Multiple_Files_Absolute_Path() + { + const string filePath1 = "./src/a/a.txt"; + const string filePath2 = "./src/b/b.txt"; + const string dstPath = "./dst"; + + // Given + var fixture = new FileCopierFixture(); + fixture.EnsureFileExists(filePath1); + fixture.EnsureFileExists(filePath2); + fixture.EnsureDirectoryExists(dstPath); + + // When + FileCopier.CopyFiles( + fixture.Context, + new[] + { + fixture.MakeAbsolute(filePath1), + fixture.MakeAbsolute(filePath2) + }, + new DirectoryPath(dstPath), + true); + + // Then + Assert.True(fixture.ExistsFile($"{dstPath}/a/a.txt")); + Assert.True(fixture.ExistsFile($"{dstPath}/b/b.txt")); + } + + [Fact] + public void Should_Copy_Multiple_Files_Mixed_Path() + { + const string filePath1 = "./src/a/a.txt"; + const string filePath2 = "./src/b/b.txt"; + const string dstPath = "./dst"; + + // Given + var fixture = new FileCopierFixture(); + fixture.EnsureFileExists(filePath1); + fixture.EnsureFileExists(filePath2); + fixture.EnsureDirectoryExists(dstPath); + + // When + FileCopier.CopyFiles( + fixture.Context, + new[] + { + filePath1, + fixture.MakeAbsolute(filePath2) + }, + new DirectoryPath(dstPath), + true); + + // Then + Assert.True(fixture.ExistsFile($"{dstPath}/a/a.txt")); + Assert.True(fixture.ExistsFile($"{dstPath}/b/b.txt")); + } + + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // When + var result = Record.Exception(() => FileCopier.CopyFiles(null, Enumerable.Empty(), new DirectoryPath(""), true)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_File_Paths_Is_Null() + { + // Given + var fixture = new FileCopierFixture(); + + // When + var result = Record.Exception(() => FileCopier.CopyFiles(fixture.Context, (List)null, new DirectoryPath(""), true)); + + // Then + AssertEx.IsArgumentNullException(result, "filePaths"); + } + + [Fact] + public void Should_Throw_If_Target_Directory_Path_Is_Null() + { + // Given + var fixture = new FileCopierFixture(); + + // When + var result = Record.Exception(() => FileCopier.CopyFiles(fixture.Context, Enumerable.Empty(), null, true)); + + // Then + AssertEx.IsArgumentNullException(result, "targetDirectoryPath"); + } + + [Fact] + public void Should_Throw_If_Target_Directory_Does_Not_Exist() + { + const string dstPath = "/dst"; + + // Given + var fixture = new FileCopierFixture(); + + // When + var result = Record.Exception(() => FileCopier.CopyFiles(fixture.Context, Enumerable.Empty(), new DirectoryPath(dstPath), true)); + + // Then + AssertEx.IsExceptionWithMessage(result, $"The directory '{dstPath}' does not exist."); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableDirectoryPathTests.cs b/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableDirectoryPathTests.cs index b22d6bf86b..76ea669f86 100644 --- a/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableDirectoryPathTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableDirectoryPathTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Diagnostics.CodeAnalysis; using Cake.Common.IO.Paths; using Cake.Core.IO; @@ -19,7 +20,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => new ConvertableDirectoryPath(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } } @@ -77,7 +78,7 @@ public void Should_Throw_If_Left_Operand_Is_Null() (ConvertableDirectoryPath)null + new ConvertableDirectoryPath("other")); // Then - Assert.IsArgumentNullException(result, "left"); + AssertEx.IsArgumentNullException(result, "left"); } [Fact] @@ -88,7 +89,7 @@ public void Should_Throw_If_Right_Operand_Is_Null() new ConvertableDirectoryPath("./root") + (ConvertableDirectoryPath)null); // Then - Assert.IsArgumentNullException(result, "right"); + AssertEx.IsArgumentNullException(result, "right"); } } @@ -128,7 +129,7 @@ public void Should_Throw_If_Left_Operand_Is_Null() (ConvertableDirectoryPath)null + new DirectoryPath("other")); // Then - Assert.IsArgumentNullException(result, "left"); + AssertEx.IsArgumentNullException(result, "left"); } [Fact] @@ -139,7 +140,58 @@ public void Should_Throw_If_Right_Operand_Is_Null() new ConvertableDirectoryPath("./root") + (DirectoryPath)null); // Then - Assert.IsArgumentNullException(result, "right"); + AssertEx.IsArgumentNullException(result, "right"); + } + } + + public sealed class AddingDirectoryPathAndConvertableDirectoryPath + { + [Fact] + public void Should_Combine_The_Two_Paths() + { + // Given + var path = new DirectoryPath("./root"); + + // When + var result = path + new ConvertableDirectoryPath("other"); + + // Then + Assert.Equal("root/other", result.Path.FullPath); + } + + [Fact] + public void Should_Return_A_New_Convertable_Directory_Path() + { + // Given + var path = new DirectoryPath("./root"); + + // When + var result = path + new ConvertableDirectoryPath("other"); + + // Then + Assert.IsType(result); + } + + [Fact] + public void Should_Throw_If_Left_Operand_Is_Null() + { + // Given, When + var result = Record.Exception(() => + (DirectoryPath)null + new ConvertableDirectoryPath("other")); + + // Then + AssertEx.IsArgumentNullException(result, "left"); + } + + [Fact] + public void Should_Throw_If_Right_Operand_Is_Null() + { + // Given, When + var result = Record.Exception(() => + new DirectoryPath("./root") + (ConvertableDirectoryPath)null); + + // Then + AssertEx.IsArgumentNullException(result, "right"); } } @@ -179,7 +231,7 @@ public void Should_Throw_If_Left_Operand_Is_Null() (ConvertableDirectoryPath)null + new ConvertableFilePath("other.txt")); // Then - Assert.IsArgumentNullException(result, "directory"); + AssertEx.IsArgumentNullException(result, "directory"); } [Fact] @@ -190,7 +242,7 @@ public void Should_Throw_If_Right_Operand_Is_Null() new ConvertableDirectoryPath("./root") + (ConvertableFilePath)null); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } } @@ -230,7 +282,7 @@ public void Should_Throw_If_Left_Operand_Is_Null() (ConvertableDirectoryPath)null + new FilePath("other.txt")); // Then - Assert.IsArgumentNullException(result, "directory"); + AssertEx.IsArgumentNullException(result, "directory"); } [Fact] @@ -241,7 +293,7 @@ public void Should_Throw_If_Right_Operand_Is_Null() new ConvertableDirectoryPath("./root") + (FilePath)null); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } } } @@ -268,10 +320,10 @@ public void Should_Return_A_Representation_Of_The_Current_Instance() public void Should_Return_Null_If_Convertable_Directory_Path_Is_Null() { // Given - var path = (ConvertableDirectoryPath) null; + var path = (ConvertableDirectoryPath)null; // When - var result = (DirectoryPath) path; + var result = (DirectoryPath)path; // Then Assert.Null(result); @@ -325,4 +377,4 @@ public void Should_Return_String_Representation_Of_Path() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableFilePathTests.cs b/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableFilePathTests.cs index e12af81841..39af4e4e68 100644 --- a/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableFilePathTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/Paths/ConvertableFilePathTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Diagnostics.CodeAnalysis; using Cake.Common.IO.Paths; using Cake.Core.IO; -using Xunit; +using NSubstitute; namespace Cake.Common.Tests.Unit.IO.Paths { @@ -19,7 +20,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => new ConvertableFilePath(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } } @@ -69,6 +70,65 @@ public void Should_Return_Null_If_Convertable_Directory_Path_Is_Null() // Then Assert.Null(result); } + + [Fact] + [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] + public void Should_Return_Null_Exception_If_Convertable_Directory_Path_Is_Null() + { + // Given + DirectoryPath dirPath = null; + + // When + var filePath = new ConvertableFilePath("file.txt"); + // Then + var ex = Assert.Throws(() => (dirPath + filePath).ToString()); + } + + [Fact] + [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] + public void Should_Return_Null_Exception_If_Convertable_FilePath_Is_Null() + { + // Given + DirectoryPath dirPath = new DirectoryPath("X"); + + // When + ConvertableFilePath filePath = null; + // Then + var ex = Assert.Throws(() => (dirPath + filePath).ToString()); + } + + [Fact] + [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] + public void Should_Return_Combined_Directorypath_FilePath_Including_Separator() + { + // Given + DirectoryPath dirPath = new DirectoryPath("X"); + var filePath = new ConvertableFilePath("file.txt"); + + // When + string result = dirPath + filePath; + + // Then + Assert.Equal("X/file.txt", result); + } + + [Fact] + [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] + public void Should_Return_Combined_RootPath_Directorypath_FilePath_Including_Separator() + { + // Given + var contextcake = Substitute.For(); + + DirectoryPath dirPath = new ConvertableDirectoryPath(new DirectoryPath("X")); + var filePath = new ConvertableFilePath(new FilePath("file.txt")); + var rootdir = new ConvertableDirectoryPath(new DirectoryPath("..")); + + // When + var result = (rootdir + dirPath + filePath).ToString(); + + // Then + Assert.Equal("../X/file.txt", result); + } } public sealed class ConvertToString @@ -118,4 +178,4 @@ public void Should_Return_String_Representation_Of_Path() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/IO/ZipperTests.cs b/src/Cake.Common.Tests/Unit/IO/ZipperTests.cs index 8a80241e3a..1f6a4578fe 100644 --- a/src/Cake.Common.Tests/Unit/IO/ZipperTests.cs +++ b/src/Cake.Common.Tests/Unit/IO/ZipperTests.cs @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; using System.IO.Compression; using Cake.Common.IO; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; using Cake.Testing; using Cake.Testing.Xunit; using NSubstitute; @@ -29,7 +31,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => new Zipper(null, environment, log)); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -43,7 +45,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => new Zipper(fileSystem, null, log)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -57,7 +59,7 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => new Zipper(fileSystem, environment, null)); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "log"); } } @@ -73,10 +75,10 @@ public void Should_Throw_If_Root_Path_Is_Null() var zipper = new Zipper(fileSystem, environment, log); // When - var result = Record.Exception(() => zipper.Zip(null, "/file.zip", new FilePath[] {"/Root/file.txt"})); + var result = Record.Exception(() => zipper.Zip(null, "/file.zip", new FilePath[] { "/Root/file.txt" })); // Then - Assert.IsArgumentNullException(result, "rootPath"); + AssertEx.IsArgumentNullException(result, "rootPath"); } [Fact] @@ -92,7 +94,7 @@ public void Should_Throw_If_Output_Path_Is_Null() var result = Record.Exception(() => zipper.Zip("/Root", null, new FilePath[] { "/Root/file.txt" })); // Then - Assert.IsArgumentNullException(result, "outputPath"); + AssertEx.IsArgumentNullException(result, "outputPath"); } [Fact] @@ -108,11 +110,11 @@ public void Should_Throw_If_File_Paths_Are_Null() var result = Record.Exception(() => zipper.Zip("/Root", "/file.txt", null)); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] - public void Should_Throw_If_File_Is_Not_Relative_To_Root() + public void Should_Throw_If_Path_Is_Not_Relative_To_Root() { // Given var environment = FakeEnvironment.CreateUnixEnvironment(); @@ -126,7 +128,41 @@ public void Should_Throw_If_File_Is_Not_Relative_To_Root() // Then Assert.IsType(result); - Assert.Equal("File '/NotRoot/file.txt' is not relative to root path '/Root'.", result.Message); + Assert.Equal("Path '/NotRoot/file.txt' is not relative to root path '/Root'.", result?.Message); + } + + [Fact] + public void Should_Zip_Provided_Directory() + { + // Given + var environment = FakeEnvironment.CreateUnixEnvironment(); + var fileSystem = new FakeFileSystem(environment); + var globber = new Globber(fileSystem, environment); + var context = new CakeContextFixture { Environment = environment, FileSystem = fileSystem, Globber = globber }.CreateContext(); + fileSystem.CreateDirectory("/Dir0"); // empty directory + fileSystem.CreateFile("/File1.txt").SetContent("1"); + fileSystem.CreateFile("/Dir1/File2.txt").SetContent("22"); + fileSystem.CreateFile("/Dir2/File3.txt").SetContent("333"); + fileSystem.CreateFile("/Dir2/Dir3/File4.txt").SetContent("4444"); + fileSystem.CreateFile("/Dir2/Dir3/File5.txt").SetContent("55555"); + var log = Substitute.For(); + var zipper = new Zipper(fileSystem, environment, log); + + // When + zipper.Zip("/", "/Root.zip", context.GetPaths("/**/*")); + + // Then + var archive = new ZipArchive(fileSystem.GetFile("/Root.zip").Open(FileMode.Open, FileAccess.Read, FileShare.Read)); + Assert.True(archive.Entries.Count == 9); + Assert.True(archive.GetEntry("Dir0/")?.Length == 0); // directory entries; includes empty directories + Assert.True(archive.GetEntry("Dir1/")?.Length == 0); + Assert.True(archive.GetEntry("Dir2/")?.Length == 0); + Assert.True(archive.GetEntry("Dir2/Dir3/")?.Length == 0); + Assert.True(archive.GetEntry("File1.txt")?.Length == 1); // file entries + Assert.True(archive.GetEntry("Dir1/File2.txt")?.Length == 2); + Assert.True(archive.GetEntry("Dir2/File3.txt")?.Length == 3); + Assert.True(archive.GetEntry("Dir2/Dir3/File4.txt")?.Length == 4); + Assert.True(archive.GetEntry("Dir2/Dir3/File5.txt")?.Length == 5); } [Fact] @@ -135,15 +171,30 @@ public void Should_Zip_Provided_Files() // Given var environment = FakeEnvironment.CreateUnixEnvironment(); var fileSystem = new FakeFileSystem(environment); - fileSystem.CreateFile("/Root/file.txt").SetContent("HelloWorld"); + var globber = new Globber(fileSystem, environment); + var context = new CakeContextFixture { Environment = environment, FileSystem = fileSystem, Globber = globber }.CreateContext(); + fileSystem.CreateFile("/File1.txt").SetContent("1"); + fileSystem.CreateFile("/Dir1/File2.txt").SetContent("22"); + fileSystem.CreateFile("/Dir2/File3.txt").SetContent("333"); + fileSystem.CreateFile("/Dir2/Dir3/File4.txt").SetContent("4444"); + fileSystem.CreateFile("/Dir2/Dir3/File5.txt").SetContent("55555"); var log = Substitute.For(); var zipper = new Zipper(fileSystem, environment, log); // When - zipper.Zip("/Root", "/file.zip", new FilePath[] {"/Root/file.txt"}); + zipper.Zip("/", "/Root.zip", context.GetFiles("/**/*.txt")); // Then - Assert.True(fileSystem.GetFile("/file.zip").Exists); + var archive = new ZipArchive(fileSystem.GetFile("/Root.zip").Open(FileMode.Open, FileAccess.Read, FileShare.Read)); + Assert.True(archive.Entries.Count == 8); + Assert.True(archive.GetEntry("Dir1/")?.Length == 0); // directory entries + Assert.True(archive.GetEntry("Dir2/")?.Length == 0); + Assert.True(archive.GetEntry("Dir2/Dir3/")?.Length == 0); + Assert.True(archive.GetEntry("File1.txt")?.Length == 1); // file entries + Assert.True(archive.GetEntry("Dir1/File2.txt")?.Length == 2); + Assert.True(archive.GetEntry("Dir2/File3.txt")?.Length == 3); + Assert.True(archive.GetEntry("Dir2/Dir3/File4.txt")?.Length == 4); + Assert.True(archive.GetEntry("Dir2/Dir3/File5.txt")?.Length == 5); } [WindowsFact("Investigate why this fail on Mono 4.2.1.")] @@ -167,4 +218,4 @@ public void Zipped_File_Should_Contain_Correct_Content() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/ProcessAliasesTests.cs b/src/Cake.Common.Tests/Unit/ProcessAliasesTests.cs index 6a52a1fb18..c705769200 100644 --- a/src/Cake.Common.Tests/Unit/ProcessAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/ProcessAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures; using Cake.Core; using Cake.Core.IO; @@ -26,7 +27,7 @@ public void Should_Throw_If_Context_Is_Null() ProcessAliases.StartProcess(null, fileName)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -40,7 +41,7 @@ public void Should_Throw_If_Filename_Is_Null() context.StartProcess(null)); // Then - Assert.IsArgumentNullException(result, "fileName"); + AssertEx.IsArgumentNullException(result, "fileName"); } [Fact] @@ -71,7 +72,7 @@ public void Should_Throw_If_No_Process_Was_Returned_From_Process_Runner() // Then Assert.IsType(result); - Assert.Equal("Could not start process.", result.Message); + Assert.Equal("Could not start process.", result?.Message); } [Fact] @@ -103,7 +104,7 @@ public void Should_Throw_If_Context_Is_Null() ProcessAliases.StartProcess(null, fileName, settings)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -118,7 +119,7 @@ public void Should_Throw_If_Filename_Is_Null() context.StartProcess(null, settings)); // Then - Assert.IsArgumentNullException(result, "fileName"); + AssertEx.IsArgumentNullException(result, "fileName"); } [Fact] @@ -133,7 +134,7 @@ public void Should_Throw_If_Settings_Are_Null() context.StartProcess(fileName, (ProcessSettings)null)); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -209,7 +210,7 @@ public void Should_Throw_If_No_Process_Was_Returned_From_Process_Runner() // Then Assert.IsType(result); - Assert.Equal("Could not start process.", result.Message); + Assert.Equal("Could not start process.", result?.Message); } [Fact] @@ -283,7 +284,7 @@ public void Should_Throw_If_Context_Is_Null() ProcessAliases.StartAndReturnProcess(null, fileName)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -297,7 +298,7 @@ public void Should_Throw_If_Filename_Is_Null() context.StartAndReturnProcess(null)); // Then - Assert.IsArgumentNullException(result, "fileName"); + AssertEx.IsArgumentNullException(result, "fileName"); } [Fact] @@ -311,8 +312,8 @@ public void Should_Use_Environments_Working_Directory() // Then fixture.ProcessRunner.Received(1).Start( - Arg.Any(), Arg.Is(info => - info.WorkingDirectory.FullPath == "/Working")); + Arg.Any(), Arg.Is( + info => info.WorkingDirectory.FullPath == "/Working")); } [Fact] @@ -328,7 +329,7 @@ public void Should_Throw_If_No_Process_Was_Returned_From_Process_Runner() // Then Assert.IsType(result); - Assert.Equal("Could not start process.", result.Message); + Assert.Equal("Could not start process.", result?.Message); } [Fact] @@ -363,7 +364,7 @@ public void Should_Throw_If_Context_Is_Null() ProcessAliases.StartAndReturnProcess(null, fileName, settings)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -378,7 +379,7 @@ public void Should_Throw_If_Filename_Is_Null() context.StartAndReturnProcess(null, settings)); // Then - Assert.IsArgumentNullException(result, "fileName"); + AssertEx.IsArgumentNullException(result, "fileName"); } [Fact] @@ -393,7 +394,7 @@ public void Should_Throw_If_Settings_Are_Null() context.StartAndReturnProcess(fileName, null)); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -469,7 +470,7 @@ public void Should_Throw_If_No_Process_Was_Returned_From_Process_Runner() // Then Assert.IsType(result); - Assert.Equal("Could not start process.", result.Message); + Assert.Equal("Could not start process.", result?.Message); } [Fact] @@ -492,4 +493,4 @@ public void Should_Return_Process_Started_By_ProcessRunner() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/ReleaseNotesAliasesTests.cs b/src/Cake.Common.Tests/Unit/ReleaseNotesAliasesTests.cs index 6cf6c7332d..fca05ac075 100644 --- a/src/Cake.Common.Tests/Unit/ReleaseNotesAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/ReleaseNotesAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.IO; using Cake.Testing; @@ -23,7 +24,7 @@ public void Should_Throw_If_File_Path_Is_Null() var result = Record.Exception(() => context.ParseAllReleaseNotes(null)); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -41,7 +42,7 @@ public void Should_Throw_If_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("Release notes file '/Working/ReleaseNotes.md' do not exist.", result.Message); + Assert.Equal("Release notes file '/Working/ReleaseNotes.md' does not exist.", result?.Message); } [Fact] @@ -60,6 +61,7 @@ public void Should_Read_Content_Of_File_And_Parse_It() // Then Assert.Equal("1.2.3", result[0].Version.ToString()); + Assert.Equal("1.2.3", result[0].SemVersion.ToString()); } } @@ -81,7 +83,8 @@ public void Should_Return_The_Latest_Release_Notes() // Then Assert.Equal("1.2.5", result.Version.ToString()); + Assert.Equal("1.2.5", result.SemVersion.ToString()); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs b/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs index 2b05d6982d..418c142e97 100644 --- a/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs +++ b/src/Cake.Common.Tests/Unit/ReleaseNotesParserTests.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Cake.Core; using Xunit; - namespace Cake.Common.Tests.Unit { public sealed class ReleaseNotesParserTests @@ -20,7 +19,7 @@ public void Should_Throw_If_Content_Is_Null() var result = Record.Exception(() => parser.Parse(null)); // Then - Assert.IsArgumentNullException(result, "content"); + AssertEx.IsArgumentNullException(result, "content"); } [Fact] @@ -34,7 +33,7 @@ public void Should_Throw_If_Content_Is_Empty() // Then Assert.IsType(result); - Assert.Equal("Unknown release notes format.", result.Message); + Assert.Equal("Unknown release notes format.", result?.Message); } public sealed class ComplexFormat @@ -51,7 +50,7 @@ public void Should_Throw_If_Header_Is_Missing_Version() // Then Assert.IsType(result); - Assert.Equal("Could not parse version from release notes header.", result.Message); + Assert.Equal("Could not parse version from release notes header.", result?.Message); } [Fact] @@ -66,6 +65,7 @@ public void Should_Parse_Release_Note_Version() // Then Assert.Equal("0.1.9", result[0].Version.ToString()); + Assert.Equal("0.1.9", result[0].SemVersion.ToString()); } [Fact] @@ -79,7 +79,7 @@ public void Should_Parse_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -94,7 +94,7 @@ public void Should_Remove_Bullets_From_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -128,6 +128,8 @@ public void Should_Return_Release_Notes_In_Descending_Order() Assert.Equal(2, result.Count); Assert.Equal("0.1.10", result[0].Version.ToString()); Assert.Equal("0.1.9", result[1].Version.ToString()); + Assert.Equal("0.1.10", result[0].SemVersion.ToString()); + Assert.Equal("0.1.9", result[1].SemVersion.ToString()); } [Fact] @@ -141,9 +143,57 @@ public void Should_Remove_Empty_Lines_From_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } + + [Fact] + public void Should_Set_RawVersionLine_Property_To_Line_Containing_Version_Number() + { + // Given + var parser = new ReleaseNotesParser(); + const string content = "### New in 0.1.9-beta1 (Releases 2014/06/28)\nLine 1\n \n\t\n"; + + // When + var result = parser.Parse(content); + + // Then + Assert.Equal("### New in 0.1.9-beta1 (Releases 2014/06/28)", result[0].RawVersionLine); + } + + [Fact] + public void Should_Parse_Release_Note_Version_With_Prerelease() + { + // Given + var parser = new ReleaseNotesParser(); + const string content = "### New in 0.1.9-beta1 (Releases 2014/06/28)\nLine 1\n \n\t\n"; + + // When + var result = parser.Parse(content); + + // Then + Assert.Equal("0.1.9", result[0].Version.ToString()); + Assert.Equal("0.1.9-beta1", result[0].SemVersion.ToString()); + } + + [Fact] + public void Should_Return_Multiple_Release_Notes_With_Prerelease() + { + // Given + var parser = new ReleaseNotesParser(); + const string content = "### New in 0.1.9-alpha1 (Releases 2014/06/28)\n* Line 1\n" + + "###New in 0.1.10-gamma3\n* Line 2\n Line 3"; + + // When + var result = parser.Parse(content); + + // Then + Assert.Equal(2, result.Count); + Assert.Equal("0.1.10", result[0].Version.ToString()); + Assert.Equal("0.1.9", result[1].Version.ToString()); + Assert.Equal("0.1.10-gamma3", result[0].SemVersion.ToString()); + Assert.Equal("0.1.9-alpha1", result[1].SemVersion.ToString()); + } } public sealed class SimpleFormat @@ -160,7 +210,7 @@ public void Should_Throw_If_Header_Is_Missing_Version() // Then Assert.IsType(result); - Assert.Equal("Could not parse version from release notes header.", result.Message); + Assert.Equal("Could not parse version from release notes header.", result?.Message); } [Fact] @@ -175,6 +225,7 @@ public void Should_Parse_Release_Note_Version() // Then Assert.Equal("0.1.9", result[0].Version.ToString()); + Assert.Equal("0.1.9", result[0].SemVersion.ToString()); } [Fact] @@ -188,7 +239,7 @@ public void Should_Parse_Release_Note_Text() var result = parser.Parse(content); // Then - Assert.Equal(1, result[0].Notes.Count); + Assert.Single(result[0].Notes); Assert.Equal("Line 1", result[0].Notes[0]); } @@ -234,8 +285,10 @@ public void Should_Return_Release_Notes_In_Descending_Order() Assert.Equal(2, result.Count); Assert.Equal("0.1.10", result[0].Version.ToString()); Assert.Equal("0.1.9", result[1].Version.ToString()); + Assert.Equal("0.1.10", result[0].SemVersion.ToString()); + Assert.Equal("0.1.9", result[1].SemVersion.ToString()); } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/ReleaseNotesTests.cs b/src/Cake.Common.Tests/Unit/ReleaseNotesTests.cs index ad8689962d..013870fe41 100644 --- a/src/Cake.Common.Tests/Unit/ReleaseNotesTests.cs +++ b/src/Cake.Common.Tests/Unit/ReleaseNotesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Linq; using Xunit; @@ -14,11 +15,23 @@ public sealed class TheConstructor public void Should_Throw_If_Version_Is_Null() { // Given, When - var result = Record.Exception(() => new ReleaseNotes(null, Enumerable.Empty())); + Version version = null; + var result = Record.Exception(() => new ReleaseNotes(version, Enumerable.Empty(), null)); // Then - Assert.IsArgumentNullException(result, "version"); + AssertEx.IsArgumentNullException(result, "version"); + } + + [Fact] + public void Should_Throw_If_SemVersion_Is_Null() + { + // Given, When + SemVersion semVersion = null; + var result = Record.Exception(() => new ReleaseNotes(semVersion, Enumerable.Empty(), null)); + + // Then + AssertEx.IsArgumentNullException(result, "semVersion"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Security/DirectoryHashCalculatorTests.cs b/src/Cake.Common.Tests/Unit/Security/DirectoryHashCalculatorTests.cs new file mode 100644 index 0000000000..c60f1d6077 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Security/DirectoryHashCalculatorTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Security; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Security +{ + public sealed class DirectoryHashCalculatorTests + { + public sealed class TheCalculateMethod + { + [Fact] + public void Should_Throw_If_Directory_Path_Is_Null() + { + // Given + var hashAlgorithmBuilder = Substitute.For(); + var cakeContext = Substitute.For(); + var calculator = new DirectoryHashCalculator(cakeContext, hashAlgorithmBuilder); + + // When + var result = Record.Exception(() => calculator.Calculate(null, null as string[], HashAlgorithm.MD5)); + + // Then + AssertEx.IsArgumentNullException(result, "directoryPath"); + } + + [Fact] + public void Should_Throw_If_Directory_Does_Not_Exist() + { + // Given + var hashAlgorithmBuilder = Substitute.For(); + var fileSystem = Substitute.For(); + var file = Substitute.For(); + file.Exists.Returns(false); + fileSystem.GetFile(Arg.Any()).Returns(file); + var cakeContext = Substitute.For(); + cakeContext.FileSystem.Returns(fileSystem); + + var calculator = new DirectoryHashCalculator(cakeContext, hashAlgorithmBuilder); + + // When + var result = Record.Exception(() => calculator.Calculate("./non-existent-path", new List { "./**/*.cs" }, HashAlgorithm.MD5)); + + // Then + AssertEx.IsExceptionWithMessage(result, "Directory 'non-existent-path' does not exist."); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Security/FileHashCalculatorTests.cs b/src/Cake.Common.Tests/Unit/Security/FileHashCalculatorTests.cs index f20f60ab64..586a1ca6b0 100644 --- a/src/Cake.Common.Tests/Unit/Security/FileHashCalculatorTests.cs +++ b/src/Cake.Common.Tests/Unit/Security/FileHashCalculatorTests.cs @@ -1,49 +1,52 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Security; -using Cake.Core; -using Cake.Core.IO; -using NSubstitute; -using Xunit; - -namespace Cake.Common.Tests.Unit.Security -{ - public sealed class FileHashCalculatorTests - { - public sealed class TheCalculateMethod - { - [Fact] - public void Should_Throw_If_File_Path_Is_Null() - { - // Given - var fileSystem = Substitute.For(); - var calculator = new FileHashCalculator(fileSystem); - - // When - var result = Record.Exception(() => calculator.Calculate(null, HashAlgorithm.MD5)); - - // Then - Assert.IsArgumentNullException(result, "filePath"); - } - - [Fact] - public void Should_Throw_If_File_Does_Not_Exist() - { - // Given - var fileSystem = Substitute.For(); - var file = Substitute.For(); - file.Exists.Returns(false); - fileSystem.GetFile(Arg.Any()).Returns(file); - - var calculator = new FileHashCalculator(fileSystem); - - // When - var result = Record.Exception(() => calculator.Calculate("./non-existent-path", HashAlgorithm.MD5)); - - // Then - Assert.IsExceptionWithMessage(result, "File 'non-existent-path' does not exist."); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Security; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Security +{ + public sealed class FileHashCalculatorTests + { + public sealed class TheCalculateMethod + { + [Fact] + public void Should_Throw_If_File_Path_Is_Null() + { + // Given + var hashAlgorithmBuilder = Substitute.For(); + var fileSystem = Substitute.For(); + var calculator = new FileHashCalculator(fileSystem, hashAlgorithmBuilder); + + // When + var result = Record.Exception(() => calculator.Calculate(null, HashAlgorithm.MD5)); + + // Then + AssertEx.IsArgumentNullException(result, "filePath"); + } + + [Fact] + public void Should_Throw_If_File_Does_Not_Exist() + { + // Given + var hashAlgorithmBuilder = Substitute.For(); + var fileSystem = Substitute.For(); + var file = Substitute.For(); + file.Exists.Returns(false); + fileSystem.GetFile(Arg.Any()).Returns(file); + + var calculator = new FileHashCalculator(fileSystem, hashAlgorithmBuilder); + + // When + var result = Record.Exception(() => calculator.Calculate("./non-existent-path", HashAlgorithm.MD5)); + + // Then + AssertEx.IsExceptionWithMessage(result, "File 'non-existent-path' does not exist."); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Security/HashAlgorithmBuilderTests.cs b/src/Cake.Common.Tests/Unit/Security/HashAlgorithmBuilderTests.cs new file mode 100644 index 0000000000..c00c4188e8 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Security/HashAlgorithmBuilderTests.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Security; +using Xunit; + +namespace Cake.Common.Tests.Unit.Security +{ + public sealed class HashAlgorithmBuilderTests + { + public sealed class TheCreateHashAlgorithmMethod + { + [Fact] + public void Should_Return_MD5_Algorithm_Instance_If_Input_Is_Cake_HashAlgorithm_Enum_MD5() + { + // Given + var hashAlgorithmbuilder = new HashAlgorithmBuilder(); + + // When + var result = hashAlgorithmbuilder.CreateHashAlgorithm(HashAlgorithm.MD5); + + // Then + Assert.IsAssignableFrom(result); + } + + [Fact] + public void Should_Return_SHA256_Algorithm_Instance_If_Input_Is_Cake_HashAlgorithm_Enum_SHA256() + { + // Given + var hashAlgorithmbuilder = new HashAlgorithmBuilder(); + + // When + var result = hashAlgorithmbuilder.CreateHashAlgorithm(HashAlgorithm.SHA256); + + // Then + Assert.IsAssignableFrom(result); + } + + [Fact] + public void Should_Return_SHA512_Algorithm_Instance_If_Input_Is_Cake_HashAlgorithm_Enum_SHA512() + { + // Given + var hashAlgorithmbuilder = new HashAlgorithmBuilder(); + + // When + var result = hashAlgorithmbuilder.CreateHashAlgorithm(HashAlgorithm.SHA512); + + // Then + Assert.IsAssignableFrom(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/SemVerTests.cs b/src/Cake.Common.Tests/Unit/SemVerTests.cs new file mode 100644 index 0000000000..62a1527f2f --- /dev/null +++ b/src/Cake.Common.Tests/Unit/SemVerTests.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Cake.Common.Tests.Unit +{ + public sealed class SemVerTests + { + public sealed class TheTryParseMethod + { + [Fact] + public void Should_Return_False_If_Version_Is_Null() + { + // Given + string version = null; + + // When + var result = SemVersion.TryParse(version, out _); + + // Then + Assert.False(result); + } + + [Theory] + [InlineData(1, 2, 3, null, null, "1.2.3")] + [InlineData(1, 2, 3, "rc001", null, "1.2.3-rc001")] + [InlineData(1, 2, 3, "rc001", "meta1", "1.2.3-rc001+meta1")] + public void Should_Return_True_If_Version_Is_Valid(int major, int minor, int patch, string preRelease, string meta, string versionString) + { + // Given + var expect = new SemVersion(major, minor, patch, preRelease, meta); + + // When + var result = SemVersion.TryParse(versionString, out var parsedSemVersion); + + // Then + Assert.True(result, nameof(SemVersion.TryParse)); + Assert.Equal(expect, parsedSemVersion); + Assert.True(expect == parsedSemVersion, "expect == parsedSemVersion"); + Assert.Equal(versionString, parsedSemVersion.VersionString); + } + } + + public sealed class Operators + { + private const bool ExpectGreaterThanTrue = true; + private const bool ExpectGreaterThanFalse = false; + private const bool ExpectLessThanTrue = true; + private const bool ExpectLessThanFalse = false; + private const bool ExpectGreaterThanOrEqualTrue = true; + private const bool ExpectGreaterThanOrEqualFalse = false; + private const bool ExpectLesserThanOrEqualTrue = true; + private const bool ExpectLesserThanOrEqualFalse = false; + private const bool ExpectEqualToTrue = true; + private const bool ExpectEqualToFalse = false; + private const bool ExpectNotEqualToTrue = true; + private const bool ExpectNotEqualToFalse = false; + + [Theory] + [InlineData("1.2.3", "1.2.3", ExpectGreaterThanFalse, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualTrue, ExpectEqualToTrue, ExpectNotEqualToFalse)] + [InlineData("1.2.4", "1.2.3", ExpectGreaterThanTrue, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualFalse, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3", "1.2.4", ExpectGreaterThanFalse, ExpectLessThanTrue, ExpectGreaterThanOrEqualFalse, ExpectLesserThanOrEqualTrue, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001", "1.2.3-rc001", ExpectGreaterThanFalse, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualTrue, ExpectEqualToTrue, ExpectNotEqualToFalse)] + [InlineData("1.2.4-rc001", "1.2.3-rc001", ExpectGreaterThanTrue, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualFalse, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001", "1.2.4-rc001", ExpectGreaterThanFalse, ExpectLessThanTrue, ExpectGreaterThanOrEqualFalse, ExpectLesserThanOrEqualTrue, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001+meta001", "1.2.3-rc001+meta001", ExpectGreaterThanFalse, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualTrue, ExpectEqualToTrue, ExpectNotEqualToFalse)] + [InlineData("1.2.4-rc001+meta001", "1.2.3-rc001+meta001", ExpectGreaterThanTrue, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualFalse, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001+meta001", "1.2.4-rc001+meta001", ExpectGreaterThanFalse, ExpectLessThanTrue, ExpectGreaterThanOrEqualFalse, ExpectLesserThanOrEqualTrue, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3", "1.2.3-rc001", ExpectGreaterThanTrue, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualFalse, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001", "1.2.3", ExpectGreaterThanFalse, ExpectLessThanTrue, ExpectGreaterThanOrEqualFalse, ExpectLesserThanOrEqualTrue, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001", "1.2.3-rc001+meta001", ExpectGreaterThanTrue, ExpectLessThanFalse, ExpectGreaterThanOrEqualTrue, ExpectLesserThanOrEqualFalse, ExpectEqualToFalse, ExpectNotEqualToTrue)] + [InlineData("1.2.3-rc001+meta001", "1.2.3-rc001", ExpectGreaterThanFalse, ExpectLessThanTrue, ExpectGreaterThanOrEqualFalse, ExpectLesserThanOrEqualTrue, ExpectEqualToFalse, ExpectNotEqualToTrue)] + public void Should_Return_Expected(string operand1string, string operand2string, bool expectGreaterThan, bool expectLessThan, bool expectGreaterThanOrEqual, bool expectLesserThanOrEqual, bool expectEqualTo, bool expectNotEqualTo) + { + // Given + var expect = new + { + ParsedOperand1 = true, + ParsedOperand2 = true, + GreaterThan = expectGreaterThan, + LessThan = expectLessThan, + GreaterThanOrEqual = expectGreaterThanOrEqual, + LesserThanOrEqual = expectLesserThanOrEqual, + EqualTo = expectEqualTo, + NotEqualTo = expectNotEqualTo + }; + + // When + var result = new + { + ParsedOperand1 = SemVersion.TryParse(operand1string, out var operand1), + ParsedOperand2 = SemVersion.TryParse(operand2string, out var operand2), + GreaterThan = operand1 > operand2, + LessThan = operand1 < operand2, + GreaterThanOrEqual = operand1 >= operand2, + LesserThanOrEqual = operand1 <= operand2, + EqualTo = operand1 == operand2, + NotEqualTo = operand1 != operand2, + }; + + // Then + Assert.Equal(expect, result); + } + + [Fact] + public void Should_Be_Able_To_Compare_Equals_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.True(semVersion == null); + } + + [Fact] + public void Should_Be_Able_To_Compare_Not_Equals_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.False(semVersion != null); + } + + [Fact] + public void Should_Be_Able_To_Compare_GreaterThan_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.False(semVersion > null); + } + + [Fact] + public void Should_Be_Able_To_Compare_LessThan_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.False(semVersion < null); + } + + [Fact] + public void Should_Be_Able_To_Compare_GreaterEqualThan_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.True(semVersion >= null); + } + + [Fact] + public void Should_Be_Able_To_Compare_LessEqualThan_Null() + { + // Given + SemVersion semVersion = null; + + // When / Then + Assert.True(semVersion <= null); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/ProjectParserTests.cs b/src/Cake.Common.Tests/Unit/Solution/Project/ProjectParserTests.cs index 81043d35e0..8e74972ac0 100644 --- a/src/Cake.Common.Tests/Unit/Solution/Project/ProjectParserTests.cs +++ b/src/Cake.Common.Tests/Unit/Solution/Project/ProjectParserTests.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Cake.Common.Solution.Project; using Cake.Common.Tests.Fixtures.Solution.Project; using Cake.Core; @@ -29,7 +27,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => new ProjectParser(null, environment)); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -42,13 +40,12 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => new ProjectParser(fileSystem, null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } public sealed class TheParseMethod { - [Fact] public void Should_Return_Parser_Result() { @@ -167,6 +164,19 @@ public void Should_Parse_Platform() Assert.Equal(result.Platform, "AnyCPU"); } + [Fact] + public void Should_Parse_OutputAssembly() + { + // Given + var fixture = new ProjectParserFixture(); + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal(@"bin/Debug", result.OutputPath.FullPath); + } + [Fact] public void Should_Return_Correct_File_Count() { @@ -194,6 +204,45 @@ public void Should_Return_Valid_Guid() var parseResult = Guid.TryParseExact(result.ProjectGuid, "B", out projectGuid); Assert.True(parseResult); } + + [Fact] + public void Should_Return_References() + { + // Given + var fixture = new ProjectParserFixture(); + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal(2, result.References.Count); + Assert.Equal("System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL", + result.References.First().Include); + Assert.Equal("/Working/../packages/System.Collections.Immutable.1.1.37/lib/dotnet/System.Collections.Immutable.dll", + result.References.First().HintPath.FullPath); + Assert.Equal("/opt/microsoft/powershell/6/System.Management.Automation.dll", + result.References.Last().HintPath.FullPath); + Assert.Equal(true, result.References.First().Private); + } + + [Fact] + public void Should_Return_Project_References() + { + // Given + var fixture = new ProjectParserFixture(); + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal(2, result.ProjectReferences.Count); + Assert.Equal("/Working/../Cake.Common/Cake.Common.csproj", result.ProjectReferences.First().FilePath.FullPath); + Assert.Equal("..\\Cake.Common\\Cake.Common.csproj", result.ProjectReferences.First().RelativePath); + Assert.Equal("{ABC3F1CB-F84E-43ED-A120-0CCFE344D250}", result.ProjectReferences.First().Project); + Assert.Equal("Cake.Common", result.ProjectReferences.First().Name); + Assert.Equal(null, result.ProjectReferences.First().Private); + Assert.Equal(true, result.ProjectReferences.ElementAt(1).Private); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests.cs b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests.cs index 4235859627..fb2ba317a9 100644 --- a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests.cs +++ b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests.cs @@ -1,335 +1,460 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Cake.Common.Solution.Project.Properties; -using Cake.Common.Tests.Fixtures; -using Cake.Core; -using Cake.Core.Diagnostics; -using Cake.Core.IO; -using NSubstitute; -using Xunit; - -namespace Cake.Common.Tests.Unit.Solution.Project.Properties -{ - public sealed class AssemblyInfoCreatorTests - { - public sealed class TheConstructor - { - [Fact] - public void Should_Throw_If_File_System_Is_Null() - { - // Given - var environment = Substitute.For(); - var log = Substitute.For(); - - // When - var result = Record.Exception(() => new AssemblyInfoCreator(null, environment, log)); - - // Then - Assert.IsArgumentNullException(result, "fileSystem"); - } - - [Fact] - public void Should_Throw_If_Environment_Is_Null() - { - // Given - var fileSystem = Substitute.For(); - var log = Substitute.For(); - - // When - var result = Record.Exception(() => new AssemblyInfoCreator(fileSystem, null, log)); - - // Then - Assert.IsArgumentNullException(result, "environment"); - } - - [Fact] - public void Should_Throw_If_Log_Is_Null() - { - // Given - var fileSystem = Substitute.For(); - var environment = Substitute.For(); - - // When - var result = Record.Exception(() => new AssemblyInfoCreator(fileSystem, environment, null)); - - // Then - Assert.IsArgumentNullException(result, "log"); - } - } - - public sealed class TheCreateMethod - { - [Fact] - public void Should_Throw_If_Output_Path_Is_Null() - { - // Given - var fixture = new AssemblyInfoFixture(); - var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); - - // When - var result = Record.Exception(() => creator.Create(null, new AssemblyInfoSettings())); - - // Then - Assert.IsArgumentNullException(result, "outputPath"); - } - - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new AssemblyInfoFixture(); - var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); - - // When - var result = Record.Exception(() => creator.Create("A.cs", null)); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Make_Relative_Output_Path_Absolute() - { - // Given - var fixture = new AssemblyInfoFixture(); - var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); - - // When - creator.Create("AssemblyInfo.cs", new AssemblyInfoSettings()); - - // Then - Assert.True(fixture.FileSystem.Exist((FilePath)"/Working/AssemblyInfo.cs")); - } - - [Fact] - public void Should_Add_Title_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Title = "TheTitle"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyTitle(\"TheTitle\")]")); - } - - [Fact] - public void Should_Add_Description_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Description = "TheDescription"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyDescription(\"TheDescription\")]")); - } - - [Fact] - public void Should_Add_Guid_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Guid = "TheGuid"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Runtime.InteropServices;")); - Assert.True(result.Contains("[assembly: Guid(\"TheGuid\")]")); - } - - [Fact] - public void Should_Add_Company_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Company = "TheCompany"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyCompany(\"TheCompany\")]")); - } - - [Fact] - public void Should_Add_Product_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Product = "TheProduct"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyProduct(\"TheProduct\")]")); - } - - [Fact] - public void Should_Add_Copyright_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Copyright = "TheCopyright"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyCopyright(\"TheCopyright\")]")); - } - - [Fact] - public void Should_Add_Trademark_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Trademark = "TheTrademark"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyTrademark(\"TheTrademark\")]")); - } - - [Fact] - public void Should_Add_Version_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Version = "TheVersion"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyVersion(\"TheVersion\")]")); - } - - [Fact] - public void Should_Add_FileVersion_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.FileVersion = "TheFileVersion"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyFileVersion(\"TheFileVersion\")]")); - } - - [Fact] - public void Should_Add_InformationalVersion_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.InformationalVersion = "TheInformationalVersion"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyInformationalVersion(\"TheInformationalVersion\")]")); - } - - [Fact] - public void Should_Add_ComVisible_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.ComVisible = true; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Runtime.InteropServices;")); - Assert.True(result.Contains("[assembly: ComVisible(true)]")); - } - - [Fact] - public void Should_Add_CLSCompliant_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.CLSCompliant = true; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System;")); - Assert.True(result.Contains("[assembly: CLSCompliant(true)]")); - } - - [Fact] - public void Should_Add_InternalsVisibleTo_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.InternalsVisibleTo = new List { "Assembly1.Tests" }; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Runtime.CompilerServices;")); - Assert.True(result.Contains("[assembly: InternalsVisibleTo(\"Assembly1.Tests\")]")); - } - - [Fact] - public void Should_Add_Multiple_InternalsVisibleTo_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.InternalsVisibleTo = new Collection { "Assembly1.Tests", "Assembly2.Tests", "Assembly3.Tests" }; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Runtime.CompilerServices;")); - Assert.True(result.Contains("[assembly: InternalsVisibleTo(\"Assembly1.Tests\")]")); - Assert.True(result.Contains("[assembly: InternalsVisibleTo(\"Assembly2.Tests\")]")); - Assert.True(result.Contains("[assembly: InternalsVisibleTo(\"Assembly3.Tests\")]")); - } - - [Fact] - public void Should_Add_Configuration_Attribute_If_Set() - { - // Given - var fixture = new AssemblyInfoFixture(); - fixture.Settings.Configuration = "TheConfiguration"; - - // When - var result = fixture.CreateAndReturnContent(); - - // Then - Assert.True(result.Contains("using System.Reflection;")); - Assert.True(result.Contains("[assembly: AssemblyConfiguration(\"TheConfiguration\")]")); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Cake.Common.Solution.Project.Properties; +using Cake.Common.Tests.Fixtures; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Solution.Project.Properties +{ + public sealed class AssemblyInfoCreatorTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_File_System_Is_Null() + { + // Given + var environment = Substitute.For(); + var log = Substitute.For(); + + // When + var result = Record.Exception(() => new AssemblyInfoCreator(null, environment, log)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var fileSystem = Substitute.For(); + var log = Substitute.For(); + + // When + var result = Record.Exception(() => new AssemblyInfoCreator(fileSystem, null, log)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given + var fileSystem = Substitute.For(); + var environment = Substitute.For(); + + // When + var result = Record.Exception(() => new AssemblyInfoCreator(fileSystem, environment, null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + } + + public sealed class TheCreateMethod + { + [Fact] + public void Should_Throw_If_Output_Path_Is_Null() + { + // Given + var fixture = new AssemblyInfoFixture(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + var result = Record.Exception(() => creator.Create(null, new AssemblyInfoSettings())); + + // Then + AssertEx.IsArgumentNullException(result, "outputPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new AssemblyInfoFixture(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + var result = Record.Exception(() => creator.Create("A.cs", null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Make_Relative_Output_Path_Absolute() + { + // Given + var fixture = new AssemblyInfoFixture(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + creator.Create("AssemblyInfo.cs", new AssemblyInfoSettings()); + + // Then + Assert.True(fixture.FileSystem.Exist((FilePath)"/Working/AssemblyInfo.cs")); + } + + [Fact] + public void Should_Add_Title_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Title = "TheTitle"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyTitle(\"TheTitle\")]", result); + } + + [Fact] + public void Should_Add_Description_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Description = "TheDescription"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyDescription(\"TheDescription\")]", result); + } + + [Fact] + public void Should_Add_Guid_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Guid = "TheGuid"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.InteropServices;", result); + Assert.Contains("[assembly: Guid(\"TheGuid\")]", result); + } + + [Fact] + public void Should_Add_Company_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Company = "TheCompany"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyCompany(\"TheCompany\")]", result); + } + + [Fact] + public void Should_Add_Product_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Product = "TheProduct"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyProduct(\"TheProduct\")]", result); + } + + [Fact] + public void Should_Add_Copyright_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Copyright = "TheCopyright"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyCopyright(\"TheCopyright\")]", result); + } + + [Fact] + public void Should_Add_Trademark_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Trademark = "TheTrademark"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyTrademark(\"TheTrademark\")]", result); + } + + [Fact] + public void Should_Add_Version_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Version = "TheVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyVersion(\"TheVersion\")]", result); + } + + [Fact] + public void Should_Add_FileVersion_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.FileVersion = "TheFileVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyFileVersion(\"TheFileVersion\")]", result); + } + + [Fact] + public void Should_Add_InformationalVersion_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.InformationalVersion = "TheInformationalVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyInformationalVersion(\"TheInformationalVersion\")]", result); + } + + [Fact] + public void Should_Add_ComVisible_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.ComVisible = true; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.InteropServices;", result); + Assert.Contains("[assembly: ComVisible(true)]", result); + } + + [Fact] + public void Should_Add_CLSCompliant_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CLSCompliant = true; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System;", result); + Assert.Contains("[assembly: CLSCompliant(true)]", result); + } + + [Fact] + public void Should_Add_InternalsVisibleTo_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.InternalsVisibleTo = new List { "Assembly1.Tests" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.CompilerServices;", result); + Assert.Contains("[assembly: InternalsVisibleTo(\"Assembly1.Tests\")]", result); + } + + [Fact] + public void Should_Add_Multiple_InternalsVisibleTo_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.InternalsVisibleTo = new Collection { "Assembly1.Tests", "Assembly2.Tests", "Assembly3.Tests" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.CompilerServices;", result); + Assert.Contains("[assembly: InternalsVisibleTo(\"Assembly1.Tests\")]", result); + Assert.Contains("[assembly: InternalsVisibleTo(\"Assembly2.Tests\")]", result); + Assert.Contains("[assembly: InternalsVisibleTo(\"Assembly3.Tests\")]", result); + } + + [Fact] + public void Should_Add_SupportedOSPlatform_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.SupportedOSPlatform = new List { "windows" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.Versioning;", result); + Assert.Contains("[assembly: SupportedOSPlatform(\"windows\")]", result); + } + + [Fact] + public void Should_Add_Multiple_SupportedOSPlatform_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.SupportedOSPlatform = new Collection { "windows", "linux", "macos" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Runtime.Versioning;", result); + Assert.Contains("[assembly: SupportedOSPlatform(\"windows\")]", result); + Assert.Contains("[assembly: SupportedOSPlatform(\"linux\")]", result); + Assert.Contains("[assembly: SupportedOSPlatform(\"macos\")]", result); + } + + [Fact] + public void Should_Add_Configuration_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.Configuration = "TheConfiguration"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyConfiguration(\"TheConfiguration\")]", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Raw_Value() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = "RawTestValue", UseRawValue = true } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute(RawTestValue)]", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = "TestValue" } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute(\"TestValue\")]", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Boolean_Value() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = true } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute(true)]", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Null_Value() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = null } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute()]", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Empty_Value() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = string.Empty } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute()]", result); + } + + [Fact] + public void Should_Add_MetadataAttributes_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.MetaDataAttributes = new Collection { new AssemblyInfoMetadataAttribute { Key = "Key1", Value = "TestValue1" }, new AssemblyInfoMetadataAttribute { Key = "Key2", Value = "TestValue2" }, new AssemblyInfoMetadataAttribute { Key = "Key1", Value = "TestValue3" } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyMetadata(\"Key1\", \"TestValue3\")]", result); + Assert.Contains("[assembly: AssemblyMetadata(\"Key2\", \"TestValue2\")]", result); + Assert.DoesNotContain("[assembly: AssemblyMetadata(\"Key1\", \"TestValue1\")]", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests_VB.cs b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests_VB.cs new file mode 100644 index 0000000000..1ecf09ae98 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoCreatorTests_VB.cs @@ -0,0 +1,351 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Cake.Common.Solution.Project.Properties; +using Cake.Common.Tests.Fixtures; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Solution.Project.Properties +{ + public sealed class AssemblyInfoCreatorTests_VB + { + public sealed class TheCreateMethod + { + [Fact] + public void Should_Throw_If_Output_Path_Is_Null() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + var result = Record.Exception(() => creator.Create(null, new AssemblyInfoSettings())); + + // Then + AssertEx.IsArgumentNullException(result, "outputPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + var result = Record.Exception(() => creator.Create("A.cs", null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Make_Relative_Output_Path_Absolute() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + var creator = new AssemblyInfoCreator(fixture.FileSystem, fixture.Environment, fixture.Log); + + // When + creator.Create("AssemblyInfo.vb", new AssemblyInfoSettings()); + + // Then + Assert.True(fixture.FileSystem.Exist((FilePath)"/Working/AssemblyInfo.vb")); + } + + [Fact] + public void Should_Add_Title_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Title = "TheTitle"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Description_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Description = "TheDescription"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Guid_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Guid = "TheGuid"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Runtime.InteropServices", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Company_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Company = "TheCompany"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Product_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Product = "TheProduct"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Copyright_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Copyright = "TheCopyright"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Trademark_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Trademark = "TheTrademark"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Version_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Version = "TheVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_FileVersion_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.FileVersion = "TheFileVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_InformationalVersion_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.InformationalVersion = "TheInformationalVersion"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_ComVisible_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.ComVisible = true; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Runtime.InteropServices", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_CLSCompliant_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.CLSCompliant = true; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_InternalsVisibleTo_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.InternalsVisibleTo = new List { "Assembly1.Tests" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Runtime.CompilerServices", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Multiple_InternalsVisibleTo_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.InternalsVisibleTo = new Collection { "Assembly1.Tests", "Assembly2.Tests", "Assembly3.Tests" }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Runtime.CompilerServices", result); + Assert.Contains("", result); + Assert.Contains("", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_Configuration_Attribute_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.Configuration = "TheConfiguration"; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports System.Reflection", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = "TestValue" } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports Test.NameSpace", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Boolean_Value() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = true } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports Test.NameSpace", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Null_Value() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = null } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports Test.NameSpace", result); + Assert.Contains("", result); + } + + [Fact] + public void Should_Add_CustomAttributes_If_Set_With_Empty_Value() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.CustomAttributes = new Collection { new AssemblyInfoCustomAttribute { Name = "TestAttribute", NameSpace = "Test.NameSpace", Value = string.Empty } }; + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports Test.NameSpace", result); + Assert.Contains("", result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests.cs b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests.cs new file mode 100644 index 0000000000..37eec078b4 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Solution.Project.Properties; +using Cake.Common.Tests.Fixtures; +using Xunit; + +namespace Cake.Common.Tests.Unit.Solution.Project.Properties +{ + public sealed class AssemblyInfoExtensionTests + { + [Fact] + public void Should_Add_CustomAttributes_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.AddCustomAttribute("TestAttribute", "Test.NameSpace", "TestValue"); + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using Test.NameSpace;", result); + Assert.Contains("[assembly: TestAttribute(\"TestValue\")]", result); + } + + [Fact] + public void Should_Add_MetadataAttributes_If_Set() + { + // Given + var fixture = new AssemblyInfoFixture(); + fixture.Settings.AddMetadataAttribute("Key1", "TestValue1"); + fixture.Settings.AddMetadataAttribute("Key2", "TestValue2"); + fixture.Settings.AddMetadataAttribute("Key1", "TestValue3"); + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("using System.Reflection;", result); + Assert.Contains("[assembly: AssemblyMetadata(\"Key1\", \"TestValue3\")]", result); + Assert.Contains("[assembly: AssemblyMetadata(\"Key2\", \"TestValue2\")]", result); + Assert.DoesNotContain("[assembly: AssemblyMetadata(\"Key1\", \"TestValue1\")]", result); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests_VB.cs b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests_VB.cs new file mode 100644 index 0000000000..901f6e33da --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoExtensionTests_VB.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Solution.Project.Properties; +using Cake.Common.Tests.Fixtures; +using Xunit; + +namespace Cake.Common.Tests.Unit.Solution.Project.Properties +{ + public sealed class AssemblyInfoExtensionTests_VB + { + [Fact] + public void Should_Add_CustomAttributes_If_Set_VB() + { + // Given + var fixture = new AssemblyInfoFixture_VB(); + fixture.Settings.AddCustomAttribute("TestAttribute", "Test.NameSpace", "TestValue"); + + // When + var result = fixture.CreateAndReturnContent(); + + // Then + Assert.Contains("Imports Test.NameSpace", result); + Assert.Contains("", result); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoParserTests.cs b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoParserTests.cs index e0c5064909..e69e8e29f4 100644 --- a/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoParserTests.cs +++ b/src/Cake.Common.Tests/Unit/Solution/Project/Properties/AssemblyInfoParserTests.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Common.Solution.Project.Properties; @@ -27,7 +28,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => new AssemblyInfoParser(null, environment)); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -40,7 +41,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => new AssemblyInfoParser(fileSystem, null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -57,7 +58,7 @@ public void Should_Throw_If_AssemblyInfo_Path_Is_Null() var result = Record.Exception(() => fixture.Parse()); // Then - Assert.IsArgumentNullException(result, "assemblyInfoPath"); + AssertEx.IsArgumentNullException(result, "assemblyInfoPath"); } [Fact] @@ -72,17 +73,21 @@ public void Should_Throw_If_AssemblyInfo_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("Assembly info file '/Working/output.cs' does not exist.", result.Message); + Assert.Equal("Assembly info file '/Working/output.cs' does not exist.", result?.Message); } [Theory] - [InlineData(true, true)] - [InlineData(false, false)] - [InlineData(null, false)] - public void Should_Read_ClsCompliance(bool value, bool expected) + [InlineData(true, true, true)] + [InlineData(false, false, true)] + [InlineData(null, false, true)] + [InlineData(true, true, false)] + [InlineData(false, false, false)] + [InlineData(null, false, false)] + public void Should_Read_ClsCompliance(bool value, bool expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.ClsCompliant = value; // When @@ -93,12 +98,15 @@ public void Should_Read_ClsCompliance(bool value, bool expected) } [Theory] - [InlineData("CompanyA", "CompanyA")] - [InlineData(null, "")] - public void Should_Read_Company(string value, string expected) + [InlineData("CompanyA", "CompanyA", true)] + [InlineData("CompanyA", "CompanyA", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Company(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Company = value; // When @@ -109,13 +117,36 @@ public void Should_Read_Company(string value, string expected) } [Theory] - [InlineData(true, true)] - [InlineData(false, false)] - [InlineData(null, false)] - public void Should_Read_ComVisible(bool value, bool expected) + [InlineData("CompanyA", "CompanyA", true)] + [InlineData("CompanyA", "CompanyA", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Company_VB_AssemblyInfo_File(string value, string expected, bool extraWhiteSpaces) + { + // Given + var fixture = new AssemblyInfoParserFixture_VB(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; + fixture.Company = value; + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal(expected, result.Company); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(false, false, true)] + [InlineData(null, false, true)] + [InlineData(true, true, false)] + [InlineData(false, false, false)] + [InlineData(null, false, false)] + public void Should_Read_ComVisible(bool value, bool expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.ComVisible = value; // When @@ -126,13 +157,17 @@ public void Should_Read_ComVisible(bool value, bool expected) } [Theory] - [InlineData("Debug", "Debug")] - [InlineData("Release", "Release")] - [InlineData(null, "")] - public void Should_Read_Configuration(string value, string expected) + [InlineData("Debug", "Debug", true)] + [InlineData("Debug", "Debug", false)] + [InlineData("Release", "Release", true)] + [InlineData("Release", "Release", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Configuration(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Configuration = value; // When @@ -143,12 +178,15 @@ public void Should_Read_Configuration(string value, string expected) } [Theory] - [InlineData("Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park and contributors", "Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park and contributors")] - [InlineData(null, "")] - public void Should_Read_Copyright(string value, string expected) + [InlineData("Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors", "Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors", true)] + [InlineData("Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors", "Copyright (c) Patrik Svensson, Mattias Karlsson, Gary Ewan Park, Alistair Chapman, Martin Björkström, Dave Glick, Pascal Berger, Jérémie Desautels, Enrico Campidoglio, C. Augusto Proiete, Nils Andresen, and contributors", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Copyright(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Copyright = value; // When @@ -159,12 +197,15 @@ public void Should_Read_Copyright(string value, string expected) } [Theory] - [InlineData("Assembly Description", "Assembly Description")] - [InlineData(null, "")] - public void Should_Read_Description(string value, string expected) + [InlineData("Assembly Description", "Assembly Description", true)] + [InlineData("Assembly Description", "Assembly Description", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Description(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Description = value; // When @@ -175,13 +216,17 @@ public void Should_Read_Description(string value, string expected) } [Theory] - [InlineData("1.2.3.4", "1.2.3.4")] - [InlineData("1.2.*.*", "1.2.*.*")] - [InlineData(null, "1.0.0.0")] - public void Should_Read_AssemblyFileVersion(string value, string expected) + [InlineData("1.2.3.4", "1.2.3.4", true)] + [InlineData("1.2.3.4", "1.2.3.4", false)] + [InlineData("1.2.*.*", "1.2.*.*", true)] + [InlineData("1.2.*.*", "1.2.*.*", false)] + [InlineData(null, "1.0.0.0", true)] + [InlineData(null, "1.0.0.0", false)] + public void Should_Read_AssemblyFileVersion(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.FileVersion = value; // When @@ -192,12 +237,15 @@ public void Should_Read_AssemblyFileVersion(string value, string expected) } [Theory] - [InlineData("D394B7DB-0DDC-4D11-AD69-C408212E1E80", "D394B7DB-0DDC-4D11-AD69-C408212E1E80")] - [InlineData(null, "")] - public void Should_Read_Guid(string value, string expected) + [InlineData("D394B7DB-0DDC-4D11-AD69-C408212E1E80", "D394B7DB-0DDC-4D11-AD69-C408212E1E80", true)] + [InlineData("D394B7DB-0DDC-4D11-AD69-C408212E1E80", "D394B7DB-0DDC-4D11-AD69-C408212E1E80", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Guid(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Guid = value; // When @@ -208,14 +256,19 @@ public void Should_Read_Guid(string value, string expected) } [Theory] - [InlineData("1.2.3.4", "1.2.3.4")] - [InlineData("1.2.*.*", "1.2.*.*")] - [InlineData("1.2.3-rc1", "1.2.3-rc1")] - [InlineData(null, "1.0.0.0")] - public void Should_Read_AssemblyInformationalVersion(string value, string expected) + [InlineData("1.2.3.4", "1.2.3.4", true)] + [InlineData("1.2.3.4", "1.2.3.4", false)] + [InlineData("1.2.*.*", "1.2.*.*", true)] + [InlineData("1.2.*.*", "1.2.*.*", false)] + [InlineData("1.2.3-rc1", "1.2.3-rc1", true)] + [InlineData("1.2.3-rc1", "1.2.3-rc1", false)] + [InlineData(null, "1.0.0.0", true)] + [InlineData(null, "1.0.0.0", false)] + public void Should_Read_AssemblyInformationalVersion(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.InformationalVersion = value; // When @@ -225,11 +278,14 @@ public void Should_Read_AssemblyInformationalVersion(string value, string expect Assert.Equal(expected, result.AssemblyInformationalVersion); } - [Fact] - public void Should_Read_InternalsVisibleTo() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Read_InternalsVisibleTo(bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.InternalsVisibleTo = new List { "Cake.Core.Tests", @@ -246,12 +302,53 @@ public void Should_Read_InternalsVisibleTo() } [Theory] - [InlineData("Cake", "Cake")] - [InlineData(null, "")] - public void Should_Read_Product(string value, string expected) + [InlineData(true)] + [InlineData(false)] + public void Should_Read_SupportedOSPlatform(bool extraWhiteSpaces) + { + // Given + var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; + fixture.SupportedOSPlatform = new List { "windows" }; + + // When + var result = fixture.Parse(); + + // Then + Assert.Single(result.SupportedOSPlatform, x => x == "windows"); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Read_Multiple_SupportedOSPlatform(bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; + fixture.SupportedOSPlatform = new List { "windows", "linux", "macos" }; + + // When + var result = fixture.Parse(); + + // Then + Assert.Collection( + result.SupportedOSPlatform, + item => Assert.Equal("windows", item), + item => Assert.Equal("linux", item), + item => Assert.Equal("macos", item)); + } + + [Theory] + [InlineData("Cake", "Cake", true)] + [InlineData("Cake", "Cake", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Product(string value, string expected, bool extraWhiteSpaces) + { + // Given + var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Product = value; // When @@ -262,12 +359,15 @@ public void Should_Read_Product(string value, string expected) } [Theory] - [InlineData("Cake.Common", "Cake.Common")] - [InlineData(null, "")] - public void Should_Read_Title(string value, string expected) + [InlineData("Cake.Common", "Cake.Common", true)] + [InlineData("Cake.Common", "Cake.Common", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Title(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Title = value; // When @@ -278,12 +378,15 @@ public void Should_Read_Title(string value, string expected) } [Theory] - [InlineData("Trademark Cake", "Trademark Cake")] - [InlineData(null, "")] - public void Should_Read_Trademark(string value, string expected) + [InlineData("Trademark Cake", "Trademark Cake", true)] + [InlineData("Trademark Cake", "Trademark Cake", false)] + [InlineData(null, "", true)] + [InlineData(null, "", false)] + public void Should_Read_Trademark(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Trademark = value; // When @@ -294,13 +397,17 @@ public void Should_Read_Trademark(string value, string expected) } [Theory] - [InlineData("1.2.3.4", "1.2.3.4")] - [InlineData("1.2.*.*", "1.2.*.*")] - [InlineData(null, "1.0.0.0")] - public void Should_Read_AssemblyVersion(string value, string expected) + [InlineData("1.2.3.4", "1.2.3.4", true)] + [InlineData("1.2.3.4", "1.2.3.4", false)] + [InlineData("1.2.*.*", "1.2.*.*", true)] + [InlineData("1.2.*.*", "1.2.*.*", false)] + [InlineData(null, "1.0.0.0", true)] + [InlineData(null, "1.0.0.0", false)] + public void Should_Read_AssemblyVersion(string value, string expected, bool extraWhiteSpaces) { // Given var fixture = new AssemblyInfoParserFixture(); + fixture.ExtraWhiteSpaces = extraWhiteSpaces; fixture.Version = value; // When @@ -331,6 +438,27 @@ public void Should_Correctly_Parse_VisualStudio_AssemblyInfo_File() Assert.Equal(result.Trademark, "VisualStudioTrademark"); } + [Fact] + public void Should_Correctly_Parse_VisualStudio_VB_AssemblyInfo_File() + { + // Given + var fixture = new AssemblyInfoParserFixture_VB(); + fixture.CreateAssemblyInfo = false; + fixture.WithAssemblyInfoContents(Resources.VisualStudioAssemblyInfo_VB.NormalizeLineEndings()); + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal(result.Title, "VisualStudioAssemblyTitle"); + Assert.Equal(result.Description, "VisualStudioAssemblyDescription"); + Assert.Equal(result.Configuration, "VisualStudioConfiguration"); + Assert.Equal(result.Company, "VisualStudioCompany"); + Assert.Equal(result.Product, "VisualStudioProduct"); + Assert.Equal(result.Copyright, "VisualStudioCopyright"); + Assert.Equal(result.Trademark, "VisualStudioTrademark"); + } + [Fact] public void Should_Correctly_Parse_MonoDevelop_AssemblyInfo_File() { @@ -351,6 +479,27 @@ public void Should_Correctly_Parse_MonoDevelop_AssemblyInfo_File() Assert.Equal(result.Copyright, "MonoDevelopCopyright"); Assert.Equal(result.Trademark, "MonoDevelopTrademark"); } + + [Fact] + public void Should_Read_FullyQualifiedAssemblyAttributes() + { + // Given + var fixture = new AssemblyInfoParserFixture(); + fixture.CreateAssemblyInfo = false; + fixture.WithAssemblyInfoContents(Resources.FullyQualifiedAssemblyInfo.NormalizeLineEndings()); + + // When + var result = fixture.Parse(); + + // Then + Assert.Equal("FullyQualifiedCompanyAttribute", result.Company); + Assert.Equal("FullyQualifiedConfigurationAttribute", result.Configuration); + Assert.Equal("1.3.0.0", result.AssemblyFileVersion); + Assert.Equal("1.3.0", result.AssemblyInformationalVersion); + Assert.Equal("FullyQualifiedProductAttribute", result.Product); + Assert.Equal("FullyQualifiedTitleAttribute", result.Title); + Assert.Equal("1.3.0.0", result.AssemblyVersion); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Solution/Project/XmlDoc/XmlDocExampleCodeParserTests.cs b/src/Cake.Common.Tests/Unit/Solution/Project/XmlDoc/XmlDocExampleCodeParserTests.cs index 08333e3f4e..db84653a14 100644 --- a/src/Cake.Common.Tests/Unit/Solution/Project/XmlDoc/XmlDocExampleCodeParserTests.cs +++ b/src/Cake.Common.Tests/Unit/Solution/Project/XmlDoc/XmlDocExampleCodeParserTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; using System.Linq; using Cake.Common.Tests.Fixtures; @@ -12,7 +13,7 @@ public sealed class XmlDocExampleCodeParserTests { public sealed class TheParseMethod { - [Fact] + [Fact] public void Should_Throw_If_Xml_Path_Was_Null() { // Given @@ -23,7 +24,7 @@ public void Should_Throw_If_Xml_Path_Was_Null() var result = Record.Exception(() => fixture.Parse()); // Then - Assert.IsArgumentNullException(result, "xmlFilePath"); + AssertEx.IsArgumentNullException(result, "xmlFilePath"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Xml_Is_Missing() // Then Assert.IsType(result); - Assert.Equal("Supplied xml file not found.", result.Message); + Assert.Equal("Supplied xml file not found.", result?.Message); } [Fact] @@ -57,7 +58,7 @@ public void Should_Return_Correct_Number_Of_Examples() public sealed class TheParseFilesMethod { - [Fact] + [Fact] public void Should_Throw_If_Pattern_Was_Null() { // Given @@ -68,7 +69,7 @@ public void Should_Throw_If_Pattern_Was_Null() var result = Record.Exception(() => fixture.ParseFiles()); // Then - Assert.IsArgumentNullException(result, "pattern"); + AssertEx.IsArgumentNullException(result, "pattern"); } [Fact] @@ -82,7 +83,7 @@ public void Should_Throw_If_Pattern_Is_Empty() var result = Record.Exception(() => fixture.ParseFiles()); // Then - Assert.IsArgumentNullException(result, "pattern"); + AssertEx.IsArgumentNullException(result, "pattern"); } [Fact] @@ -99,4 +100,4 @@ public void Should_Return_Correct_Number_Of_Examples() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/SolutionParserAliasesTests.cs b/src/Cake.Common.Tests/Unit/Solution/SolutionParserAliasesTests.cs new file mode 100644 index 0000000000..3bf9b96d6a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Solution/SolutionParserAliasesTests.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Solution; +using Cake.Core; +using Cake.Core.IO; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Solution +{ + public sealed class SolutionParserAliasesTests + { + public sealed class TheParseSolutionMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => SolutionAliases.ParseSolution(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_SolutionPath_Is_Null() + { + // Given + var cakeContext = Substitute.For(); + var cakeEnvironment = Substitute.For(); + var fileSystem = Substitute.For(); + cakeContext.Environment.Returns(cakeEnvironment); + cakeContext.FileSystem.Returns(fileSystem); + + // When + var result = Record.Exception(() => SolutionAliases.ParseSolution(cakeContext, null)); + + // Then + AssertEx.IsArgumentNullException(result, "solutionPath"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Solution/SolutionParserTests.cs b/src/Cake.Common.Tests/Unit/Solution/SolutionParserTests.cs new file mode 100644 index 0000000000..0197b469f2 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Solution/SolutionParserTests.cs @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Solution; +using Cake.Common.Tests.Fixtures.Solution; +using Cake.Common.Tests.Properties; + +namespace Cake.Common.Tests.Unit.Solution +{ + public sealed class SolutionParserTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given, When + var result = Record.Exception(() => new SolutionParser(null, null)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var fixture = new SolutionParserFixture(); + + // When + var result = Record.Exception(() => new SolutionParser(fixture.FileSystem, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + } + + public sealed class TheParseMethodForSln + { + [Fact] + public void Should_Throw_If_SolutionPath_Is_Null() + { + // Given + var fixture = new SolutionParserFixture(); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = Record.Exception(() => solutionParser.Parse(null)); + + // Then + AssertEx.IsArgumentNullException(result, "solutionPath"); + } + + [Fact] + public async Task Should_Properly_Parse_Projects() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithSolutionFile(Resources.Solution_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Folders() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithSolutionFile(Resources.Solution_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Relation_Between_Project_And_Folder() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithSolutionFile(Resources.Solution_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Projects_With_Empty_Lines() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithSolutionFile(Resources.Solution_WithProjectsAndFoldersAndMissingLine); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Projects_With_Absolute_Path() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithSolutionFile(Resources.Solution_WithProjectUsingAbsolutePath); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + } + + public sealed class TheParseMethodForSlnx + { + [Fact] + public async Task Should_Properly_Parse_Projects() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Folders() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Relation_Between_Project_And_Folder() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectsAndFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Projects_With_Empty_Lines() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectsAndFoldersAndAdditionalLines); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Projects_With_Absolute_Path() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectUsingAbsolutePath); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Projects_With_Different_Type_Id() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithProjectWithDifferentTypeId); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + + [Fact] + public async Task Should_Properly_Parse_Solution_With_Nested_Solution_Folders() + { + // Given + var fixture = new SolutionParserFixture(); + var slnFilePath = fixture.WithXmlSolutionFile(Resources.SolutionXml_WithNestedSolutionFolders); + var solutionParser = new SolutionParser(fixture.FileSystem, fixture.Environment); + + // When + var result = solutionParser.Parse(slnFilePath); + + // Then + await Verify(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Text/TextTransformationAliasesTests.cs b/src/Cake.Common.Tests/Unit/Text/TextTransformationAliasesTests.cs index fb05f20e8e..61140104b2 100644 --- a/src/Cake.Common.Tests/Unit/Text/TextTransformationAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/Text/TextTransformationAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Text; using Cake.Core; using Cake.Core.IO; @@ -24,7 +25,7 @@ public void Should_Throw_If_Context_Is_Null() TextTransformationAliases.TransformText(null, "Hello World")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Template_Is_Null() TextTransformationAliases.TransformText(context, null)); // Then - Assert.IsArgumentNullException(result, "template"); + AssertEx.IsArgumentNullException(result, "template"); } [Fact] @@ -80,7 +81,7 @@ public void Should_Throw_If_Context_Is_Null() TextTransformationAliases.TransformText(null, "Hello World", "{", "}")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -94,7 +95,7 @@ public void Should_Throw_If_Template_Is_Null() TextTransformationAliases.TransformText(context, null, "{", "}")); // Then - Assert.IsArgumentNullException(result, "template"); + AssertEx.IsArgumentNullException(result, "template"); } [Fact] @@ -108,7 +109,7 @@ public void Should_Throw_If_Left_Placeholder_Is_Null() TextTransformationAliases.TransformText(context, null, null, "}")); // Then - Assert.IsArgumentNullException(result, "leftPlaceholder"); + AssertEx.IsArgumentNullException(result, "leftPlaceholder"); } [Fact] @@ -122,7 +123,7 @@ public void Should_Throw_If_Right_Placeholder_Is_Null() TextTransformationAliases.TransformText(context, null, "{", null)); // Then - Assert.IsArgumentNullException(result, "rightPlaceholder"); + AssertEx.IsArgumentNullException(result, "rightPlaceholder"); } [Fact] @@ -168,7 +169,7 @@ public void Should_Throw_If_Context_Is_Null() null, new FilePath("./template.txt"))); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -183,7 +184,7 @@ public void Should_Throw_If_Template_Is_Null() context, null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -240,7 +241,7 @@ public void Should_Throw_If_Context_Is_Null() null, new FilePath("./template.txt"), "{", "}")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -255,7 +256,7 @@ public void Should_Throw_If_Template_Is_Null() context, null, "{", "}")); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -270,7 +271,7 @@ public void Should_Throw_If_Left_Placeholder_Is_Null() context, new FilePath("./template.txt"), null, "}")); // Then - Assert.IsArgumentNullException(result, "leftPlaceholder"); + AssertEx.IsArgumentNullException(result, "leftPlaceholder"); } [Fact] @@ -285,7 +286,7 @@ public void Should_Throw_If_Right_Placeholder_Is_Null() context, new FilePath("./template.txt"), "{", null)); // Then - Assert.IsArgumentNullException(result, "rightPlaceholder"); + AssertEx.IsArgumentNullException(result, "rightPlaceholder"); } [Fact] @@ -326,11 +327,10 @@ public void Should_Transform_Text_From_Disc_Template_Using_Specified_Placeholder // When var result = transform.ToString(); - // Then Assert.Equal("Hello World", result); } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Text/TextTransformationExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Text/TextTransformationExtensionsTests.cs index a432150583..57435b1862 100644 --- a/src/Cake.Common.Tests/Unit/Text/TextTransformationExtensionsTests.cs +++ b/src/Cake.Common.Tests/Unit/Text/TextTransformationExtensionsTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Common.Text; using Cake.Core; using NSubstitute; @@ -42,5 +44,28 @@ public void Should_Return_Same_Instance() Assert.Same(transformation, result); } } + + public sealed class TheWithTokensMethod + { + [Fact] + public void Should_Register_The_Provided_Tokens_With_The_Template() + { + // Given + var context = Substitute.For(); + var transformation = TextTransformationAliases.TransformText( + context, "<%greeting%> World and <%name%>!"); + + // When + var tokens = new Dictionary + { + { "greeting", "Hello" }, + { "name", "Tim" } + }; + transformation.WithTokens(tokens); + + // Then + Assert.Equal("Hello World and Tim!", transformation.ToString()); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Text/TextTransformationTests.cs b/src/Cake.Common.Tests/Unit/Text/TextTransformationTests.cs index 6854d473bc..49de04410e 100644 --- a/src/Cake.Common.Tests/Unit/Text/TextTransformationTests.cs +++ b/src/Cake.Common.Tests/Unit/Text/TextTransformationTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Text; using Cake.Common.Tests.Fixtures; using Cake.Testing; using NSubstitute; @@ -23,7 +25,7 @@ public void Should_Throw_If_Template_Is_Null() var result = Record.Exception(() => fixture.CreateTextTransformation()); // Then - Assert.IsArgumentNullException(result, "template"); + AssertEx.IsArgumentNullException(result, "template"); } } @@ -40,11 +42,11 @@ public void Should_Throw_If_File_Path_Is_Null() var result = Record.Exception(() => transformation.Save(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] - public void Should_Render_Content_To_File() + public void Should_Render_Content_To_File_With_Default_Encoding() { // Given var expectedContent = "Hello World"; @@ -57,8 +59,26 @@ public void Should_Render_Content_To_File() // Then var outputFile = fixture.FileSystem.GetFile("/Working/output.txt"); + Assert.False(outputFile.HasUTF8BOM()); Assert.Equal(expectedContent, outputFile.GetTextContent()); - Assert.Equal(expectedContent.Length, outputFile.Length); + } + + [Fact] + public void Should_Render_Content_To_File_With_Specified_Encoding() + { + // Given + var expectedContent = "Hello World"; + var fixture = new TextTransformationFixture(); + fixture.TransformationTemplate.Render().Returns(expectedContent); + var transformation = fixture.CreateTextTransformation(); + + // When + transformation.Save("./output.txt", Encoding.UTF8); + + // Then + var outputFile = fixture.FileSystem.GetFile("/Working/output.txt"); + Assert.True(outputFile.HasUTF8BOM()); + Assert.Equal(expectedContent, outputFile.GetTextContent(Encoding.UTF8)); } } @@ -80,4 +100,4 @@ public void Should_Render_The_Provided_Template() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Cake/CakeRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Cake/CakeRunnerTests.cs index 58851727c8..660bf299ca 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Cake/CakeRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Cake/CakeRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.IO; using Cake.Common.Tests.Fixtures.Tools; @@ -27,7 +28,7 @@ public void Should_Throw_If_Script_Path_Was_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "scriptPath"); + AssertEx.IsArgumentNullException(result, "scriptPath"); } [Fact] @@ -42,7 +43,7 @@ public void Should_Throw_If_Script_Is_Missing() // Then Assert.IsType(result); - Assert.Equal("Cake script file not found.", result.Message); + Assert.Equal("Cake script file not found.", result?.Message); } [Fact] @@ -57,7 +58,7 @@ public void Should_Throw_If_Cake_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("Cake: Could not locate executable.", result.Message); + Assert.Equal("Cake: Could not locate executable.", result?.Message); } [Theory] @@ -77,21 +78,21 @@ public void Should_Use_Cake_Executable_From_Tool_Path_If_Provided(string toolPat Assert.Equal(expected, result.Path.FullPath); } - [WindowsTheory] - [InlineData("C:/Cake/Cake.exe", "C:/Cake/Cake.exe")] - public void Should_Use_Cake_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) - { - // Given - var fixture = new CakeRunnerFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + [WindowsTheory] + [InlineData("C:/Cake/Cake.exe", "C:/Cake/Cake.exe")] + public void Should_Use_Cake_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new CakeRunnerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); - // When - var result = fixture.Run(); + // When + var result = fixture.Run(); - // Then - Assert.Equal(expected, result.Path.FullPath); - } + // Then + Assert.Equal(expected, result.Path.FullPath); + } [Fact] public void Should_Add_Provided_Script_To_Process_Arguments() @@ -117,7 +118,7 @@ public void Should_Add_Provided_Verbosity_To_Process_Arguments() var result = fixture.Run(); // Then - Assert.Equal("\"/Working/build.cake\" -verbosity=Diagnostic", result.Args); + Assert.Equal("\"/Working/build.cake\" --verbosity=Diagnostic", result.Args); } [Fact] @@ -134,9 +135,9 @@ public void Should_Add_Provided_Arguments_To_Process_Arguments() // Then Assert.Equal("\"/Working/build.cake\" " + - "-target=\"Build\" " + - "-configuration=\"Debug\"", result.Args); + "--target=\"Build\" " + + "--configuration=\"Debug\"", result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterTests.cs index 82ce76f4b2..8b94bbcd95 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetterTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Core.IO; using Cake.Testing; @@ -25,7 +26,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -39,7 +40,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -86,7 +87,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -100,7 +101,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -142,12 +143,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("apikey -s \"source1\" -k \"apikey1\" -y", result.Args); + Assert.Equal("apikey --source=\"source1\" --confirm", result.Args); } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -d -y")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --debug --confirm")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -162,8 +163,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -v -y")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --verbose --confirm")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -178,8 +179,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -y -f")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --trace --confirm")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --no-color --confirm")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --accept-license --confirm")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --force")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -194,8 +243,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -y --noop")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --confirm --what-if")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -210,8 +259,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -y -r")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --confirm --limit-output")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -226,8 +275,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "apikey -s \"source1\" -k \"apikey1\" -y --execution-timeout \"5\"")] - [InlineData(0, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(5, "apikey --source=\"source1\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "apikey --source=\"source1\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -242,8 +291,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "apikey -s \"source1\" -k \"apikey1\" -y -c \"c:\\temp\"")] - [InlineData("", "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(@"c:\temp", "apikey --source=\"source1\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "apikey --source=\"source1\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -258,8 +307,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "apikey -s \"source1\" -k \"apikey1\" -y --allowunofficial")] - [InlineData(false, "apikey -s \"source1\" -k \"apikey1\" -y")] + [InlineData(true, "apikey --source=\"source1\" --confirm --allow-unofficial")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -272,6 +321,198 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --fail-on-error-output")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --use-system-powershell")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --no-progress")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "apikey --source=\"source1\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "apikey --source=\"source1\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "apikey --source=\"source1\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "apikey --source=\"source1\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "apikey --source=\"source1\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --skip-compatibility-checks")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("key1", "apikey --source=\"source1\" --confirm --api-key=\"key1\"")] + [InlineData(null, "apikey --source=\"source1\" --confirm")] + public void Should_Add_ApiKey_Flag_To_Arguments_If_Set(string apiKey, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.ApiKey = apiKey; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "apikey --source=\"source1\" --confirm --remove")] + [InlineData(false, "apikey --source=\"source1\" --confirm")] + public void Should_Add_Remove_Flag_To_Arguments_If_Set(bool remove, string expected) + { + // Given + var fixture = new ChocolateyApiKeySetterFixture(); + fixture.Settings.Remove = remove; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Config/ChocolateyConfigSetterTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Config/ChocolateyConfigSetterTests.cs index de0a8c97b1..1f5d73348b 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Config/ChocolateyConfigSetterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Config/ChocolateyConfigSetterTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Config; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +25,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -37,7 +39,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -84,7 +86,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -98,7 +100,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -124,13 +126,13 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("config set --name \"cacheLocation\" " + - "--value \"c:\\temp\" -y", result.Args); + Assert.Equal("config set --name=\"cacheLocation\" " + + "--value=\"c:\\temp\" --confirm", result.Args); } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -d -y")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --debug --confirm")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -145,8 +147,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -v -y")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --verbose --confirm")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -161,8 +163,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y -f")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --trace --confirm")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --no-color --confirm")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --accept-license --confirm")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --force")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -177,8 +227,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y --noop")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --what-if")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -193,8 +243,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y -r")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --limit-output")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -209,8 +259,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y --execution-timeout \"5\"")] - [InlineData(0, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(5, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -225,8 +275,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "config set --name \"cacheLocation\" --value \"c:\\temp\" -y -c \"c:\\temp\"")] - [InlineData("", "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(@"c:\temp", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -241,8 +291,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y --allowunofficial")] - [InlineData(false, "config set --name \"cacheLocation\" --value \"c:\\temp\" -y")] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --allow-unofficial")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -255,6 +305,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --fail-on-error-output")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --use-system-powershell")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --no-progress")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm --skip-compatibility-checks")] + [InlineData(false, "config set --name=\"cacheLocation\" --value=\"c:\\temp\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyConfigSetterFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Download/ChocolateyDownloadTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Download/ChocolateyDownloadTests.cs new file mode 100644 index 0000000000..db95b4ab9d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Download/ChocolateyDownloadTests.cs @@ -0,0 +1,930 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Download; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Chocolatey.Download +{ + public sealed class ChocolateyDownloadTests + { + public sealed class TheDownloadMethod + { + [Fact] + public void Should_Throw_If_Package_Id_Is_Null() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.PackageId = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageId"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --debug --confirm")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --verbose --confirm")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --trace --confirm")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --no-color --confirm")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --accept-license --confirm")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --force")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --what-if")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --limit-output")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "download \"MyPackage\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "download \"MyPackage\" --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "download \"MyPackage\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "download \"MyPackage\" --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --allow-unofficial")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --fail-on-error-output")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --use-system-powershell")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --no-progress")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "download \"MyPackage\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "download \"MyPackage\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "download \"MyPackage\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "download \"MyPackage\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "download \"MyPackage\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --skip-compatibility-checks")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Source_To_Arguments_If_Set() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Source = "A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm --source=\"A\"", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Version = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm --version=\"1.0.0\"", result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --pre")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Prerelease = prerelease; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("user1", "download \"MyPackage\" --confirm --user=\"user1\"")] + [InlineData("", "download \"MyPackage\" --confirm")] + public void Should_Add_User_To_Arguments_If_Set(string user, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.User = user; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("password1", "download \"MyPackage\" --confirm --password=\"password1\"")] + [InlineData("", "download \"MyPackage\" --confirm")] + public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Password = password; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./mycert.pfx", "download \"MyPackage\" --confirm --cert=\"/Working/mycert.pfx\"")] + [InlineData(null, "download \"MyPackage\" --confirm")] + public void Should_Add_Cert_To_Arguments_If_Set(string certificate, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Certificate = certificate; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("certpassword", "download \"MyPackage\" --confirm --certpassword=\"certpassword\"")] + [InlineData("", "download \"MyPackage\" --confirm")] + public void Should_Add_CertPassword_To_Arguments_If_Set(string certificatePassword, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.CertificatePassword = certificatePassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Output_Directory_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.OutputDirectory = "./Foo"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm --output-directory=\"/Working/Foo\"", result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --ignore-dependencies")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDependencies, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.IgnoreDependencies = ignoreDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --installed-packages")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Installed_Flag_To_Arguments_If_Set(bool installed, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Installed = installed; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --ignore-unfound")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_IgnoreUnfound_Flag_To_Arguments_If_Set(bool ignoreUnfound, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.IgnoreUnfound = ignoreUnfound; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --disable-repository-optimizations")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_DisableRepositoryOptimizations_Flag_To_Arguments_If_Set(bool disableRepositoryOptimizations, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.DisableRepositoryOptimizations = disableRepositoryOptimizations; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --internalize")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_Internalize_Flag_To_Arguments_If_Set(bool internalize, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = internalize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --internalize --internalize-all-urls")] + [InlineData(false, "download \"MyPackage\" --confirm --internalize")] + public void Should_Add_InternalizeAllUrls_Flag_To_Arguments_If_Set(bool internalizeAllUrls, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = true; + fixture.Settings.InternalizeAllUrls = internalizeAllUrls; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Not_Add_InternalizeAllUrls_Flag_To_Arguments_If_Internalize_Is_Not_Set(bool internalizeAllUrls) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = false; + fixture.Settings.InternalizeAllUrls = internalizeAllUrls; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(@"\\foo", "download \"MyPackage\" --confirm --internalize --resources-location=\"\\\\foo\"")] + [InlineData("https://foo.local", "download \"MyPackage\" --confirm --internalize --resources-location=\"https://foo.local\"")] + [InlineData(null, "download \"MyPackage\" --confirm --internalize")] + [InlineData("", "download \"MyPackage\" --confirm --internalize")] + public void Should_Add_ResourceLocation_To_Arguments_If_Set(string resourcesLocation, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = true; + fixture.Settings.ResourcesLocation = resourcesLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"\\foo")] + [InlineData("https://foo.local")] + [InlineData(null)] + [InlineData("")] + public void Should_Not_Add_ResourceLocation_To_Arguments_If_Internalize_Is_Not_Set(string resourcesLocation) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = false; + fixture.Settings.ResourcesLocation = resourcesLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(@"\\foo", "download \"MyPackage\" --confirm --internalize --resources-location=\"\\\\resources\" --download-location=\"\\\\foo\"")] + [InlineData("https://foo.local", "download \"MyPackage\" --confirm --internalize --resources-location=\"\\\\resources\" --download-location=\"https://foo.local\"")] + [InlineData(null, "download \"MyPackage\" --confirm --internalize --resources-location=\"\\\\resources\"")] + [InlineData("", "download \"MyPackage\" --confirm --internalize --resources-location=\"\\\\resources\"")] + public void Should_Add_DownloadLocation_To_Arguments_If_Set(string downloadLocation, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = true; + fixture.Settings.ResourcesLocation = @"\\resources"; + fixture.Settings.DownloadLocation = downloadLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"\\foo")] + [InlineData("https://foo.local")] + [InlineData(null)] + [InlineData("")] + public void Should_Not_Add_DownloadLocation_To_Arguments_If_Internalize_Is_Not_Set(string downloadLocation) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = false; + fixture.Settings.ResourcesLocation = @"\\foo"; + fixture.Settings.DownloadLocation = downloadLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(@"\\foo")] + [InlineData("https://foo.local")] + [InlineData(null)] + [InlineData("")] + public void Should_Not_Add_DownloadLocation_To_Arguments_If_ResourcesLocation_Is_Not_Set(string downloadLocation) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = true; + fixture.Settings.ResourcesLocation = null; + fixture.Settings.DownloadLocation = downloadLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm --internalize", result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --internalize --append-use-original-location")] + [InlineData(false, "download \"MyPackage\" --confirm --internalize")] + public void Should_Add_AppendUseOriginalLocation_Flag_To_Arguments_If_Set(bool appendUseOriginalLocation, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = true; + fixture.Settings.AppendUseOriginalLocation = appendUseOriginalLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Not_Add_AppendUseOriginalLocation_Flag_To_Arguments_If_Internalize_Is_Not_Set(bool appendUseOriginalLocation) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.Internalize = false; + fixture.Settings.AppendUseOriginalLocation = appendUseOriginalLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("download \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --skip-download-cache")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_SkipDownloadCache_Flag_To_Arguments_If_Set(bool skipDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.SkipDownloadCache = skipDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --use-download-cache")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_UseDownloadCache_Flag_To_Arguments_If_Set(bool useDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.UseDownloadCache = useDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --skip-virus-check")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_SkipVirusCheck_Flag_To_Arguments_If_Set(bool skipVirusCheck, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.SkipVirusCheck = skipVirusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "download \"MyPackage\" --confirm --virus-check")] + [InlineData(false, "download \"MyPackage\" --confirm")] + public void Should_Add_VirusCheck_Flag_To_Arguments_If_Set(bool virusCheck, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.VirusCheck = virusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "download \"MyPackage\" --confirm --virus-positives-minimum=\"5\"")] + [InlineData(0, "download \"MyPackage\" --confirm")] + public void Should_Add_VirusPositivesMinimum_To_Arguments_If_Set(int virusPositivesMinimum, string expected) + { + // Given + var fixture = new ChocolateyDownloadFixture(); + fixture.Settings.VirusPositivesMinimum = virusPositivesMinimum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Export/ChocolateyExporterTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Export/ChocolateyExporterTests.cs new file mode 100644 index 0000000000..c9bfdef276 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Export/ChocolateyExporterTests.cs @@ -0,0 +1,501 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Export; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Chocolatey.Export +{ + public sealed class ChocolateyExporterTests + { + public sealed class TheExportMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyExportFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyExportFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --confirm", result.Args); + } + + [Theory] + [InlineData(true, "export --debug --confirm")] + [InlineData(false, "export --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --verbose --confirm")] + [InlineData(false, "export --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --trace --confirm")] + [InlineData(false, "export --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --no-color --confirm")] + [InlineData(false, "export --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --accept-license --confirm")] + [InlineData(false, "export --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --force")] + [InlineData(false, "export --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --what-if")] + [InlineData(false, "export --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --limit-output")] + [InlineData(false, "export --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "export --confirm --execution-timeout=\"5\"")] + [InlineData(0, "export --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "export --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "export --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --allow-unofficial")] + [InlineData(false, "export --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --fail-on-error-output")] + [InlineData(false, "export --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --use-system-powershell")] + [InlineData(false, "export --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --no-progress")] + [InlineData(false, "export --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "export --confirm --proxy=\"proxy\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "export --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "export --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "export --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --proxy-bypass-on-local")] + [InlineData(false, "export --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "export --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --skip-compatibility-checks")] + [InlineData(false, "export --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "export --confirm --output-file-path=\"c:/temp\"")] + [InlineData(null, "export --confirm")] + public void Should_Add_OutputFilePath_To_Arguments_If_Set(string outputFilePath, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.OutputFilePath = outputFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "export --confirm --include-version-numbers")] + [InlineData(false, "export --confirm")] + public void Should_Add_IncludeVersionNumbers_Flag_To_Arguments_If_Set(bool includeVersionNumbers, string expected) + { + // Given + var fixture = new ChocolateyExportFixture(); + fixture.Settings.IncludeVersionNumbers = includeVersionNumbers; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Features/ChocolateyFeatureTogglerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Features/ChocolateyFeatureTogglerTests.cs index fa256444ff..b14e021908 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Features/ChocolateyFeatureTogglerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Features/ChocolateyFeatureTogglerTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Features; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +25,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -37,7 +39,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -84,7 +86,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -98,7 +100,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -124,12 +126,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("feature enable -n \"checkSumFiles\" -y", result.Args); + Assert.Equal("feature enable --name=\"checkSumFiles\" --confirm", result.Args); } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -d -y")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --debug --confirm")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -144,8 +146,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -v -y")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --verbose --confirm")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -160,8 +162,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -y -f")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --trace --confirm")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --no-color --confirm")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --accept-license --confirm")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --force")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -176,8 +226,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -y --noop")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --what-if")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -192,8 +242,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -y -r")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --limit-output")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -208,8 +258,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "feature enable -n \"checkSumFiles\" -y --execution-timeout \"5\"")] - [InlineData(0, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(5, "feature enable --name=\"checkSumFiles\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -224,8 +274,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "feature enable -n \"checkSumFiles\" -y -c \"c:\\temp\"")] - [InlineData("", "feature enable -n \"checkSumFiles\" -y")] + [InlineData(@"c:\temp", "feature enable --name=\"checkSumFiles\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -240,8 +290,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "feature enable -n \"checkSumFiles\" -y --allowunofficial")] - [InlineData(false, "feature enable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --allow-unofficial")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -254,6 +304,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --fail-on-error-output")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --use-system-powershell")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --no-progress")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "feature enable --name=\"checkSumFiles\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "feature enable --name=\"checkSumFiles\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "feature enable --name=\"checkSumFiles\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "feature enable --name=\"checkSumFiles\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "feature enable --name=\"checkSumFiles\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature enable --name=\"checkSumFiles\" --confirm --skip-compatibility-checks")] + [InlineData(false, "feature enable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyEnableFeatureFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } } public sealed class TheDisableFeatureMethod @@ -269,7 +479,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -283,7 +493,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -330,7 +540,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -344,7 +554,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -370,12 +580,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("feature disable -n \"checkSumFiles\" -y", result.Args); + Assert.Equal("feature disable --name=\"checkSumFiles\" --confirm", result.Args); } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -d -y")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --debug --confirm")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -390,8 +600,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -v -y")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --verbose --confirm")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -406,8 +616,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -y -f")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --trace --confirm")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --no-color --confirm")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --accept-license --confirm")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --force")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -422,8 +680,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -y --noop")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --what-if")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -438,8 +696,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -y -r")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --limit-output")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -454,8 +712,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "feature disable -n \"checkSumFiles\" -y --execution-timeout \"5\"")] - [InlineData(0, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(5, "feature disable --name=\"checkSumFiles\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -470,8 +728,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "feature disable -n \"checkSumFiles\" -y -c \"c:\\temp\"")] - [InlineData("", "feature disable -n \"checkSumFiles\" -y")] + [InlineData(@"c:\temp", "feature disable --name=\"checkSumFiles\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -486,8 +744,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "feature disable -n \"checkSumFiles\" -y --allowunofficial")] - [InlineData(false, "feature disable -n \"checkSumFiles\" -y")] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --allow-unofficial")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -497,9 +755,168 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // When var result = fixture.Run(); + // Then + Assert.Equal(expected, result.Args); + } + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --fail-on-error-output")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --use-system-powershell")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --no-progress")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "feature disable --name=\"checkSumFiles\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "feature disable --name=\"checkSumFiles\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "feature disable --name=\"checkSumFiles\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "feature disable --name=\"checkSumFiles\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "feature disable --name=\"checkSumFiles\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "feature disable --name=\"checkSumFiles\" --confirm --skip-compatibility-checks")] + [InlineData(false, "feature disable --name=\"checkSumFiles\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyDisableFeatureFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + // Then Assert.Equal(expected, result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerSettingsTests.cs index 50adc7c490..91556db665 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Install; using Xunit; @@ -18,4 +19,4 @@ public void Should_Set_Prerelease_To_False_By_Default() Assert.False(settings.Prerelease); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerTests.cs index ac51b6af7c..ca5c751ef0 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Install/ChocolateyInstallerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Installer; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_Target_Package_Id_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packageId"); + AssertEx.IsArgumentNullException(result, "packageId"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -51,7 +52,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -98,7 +99,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -112,7 +113,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -138,12 +139,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("install \"Cake\" -y", result.Args); + Assert.Equal("install \"Cake\" --confirm", result.Args); } [Theory] - [InlineData(true, "install \"Cake\" -d -y")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --debug --confirm")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -158,8 +159,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "install \"Cake\" -v -y")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --verbose --confirm")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -174,8 +175,40 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "install \"Cake\" --acceptLicense -y")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --trace --confirm")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --no-color --confirm")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --accept-license --confirm")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) { // Given @@ -190,8 +223,8 @@ public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense } [Theory] - [InlineData(true, "install \"Cake\" -y -f")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --confirm --force")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -206,8 +239,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "install \"Cake\" -y --noop")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --confirm --what-if")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -222,8 +255,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "install \"Cake\" -y -r")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --confirm --limit-output")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -238,8 +271,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "install \"Cake\" -y --execution-timeout \"5\"")] - [InlineData(0, "install \"Cake\" -y")] + [InlineData(5, "install \"Cake\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "install \"Cake\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -254,8 +287,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "install \"Cake\" -y -c \"c:\\temp\"")] - [InlineData("", "install \"Cake\" -y")] + [InlineData(@"c:\temp", "install \"Cake\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "install \"Cake\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -270,8 +303,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "install \"Cake\" -y --allowunofficial")] - [InlineData(false, "install \"Cake\" -y")] + [InlineData(true, "install \"Cake\" --confirm --allow-unofficial")] + [InlineData(false, "install \"Cake\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -285,42 +318,46 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Source_To_Arguments_If_Set() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --fail-on-error-output")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.Source = "A"; + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; // When var result = fixture.Run(); // Then - Assert.Equal("install \"Cake\" -y -s \"A\"", result.Args); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Version_To_Arguments_If_Not_Null() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --use-system-powershell")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.Version = "1.0.0"; + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; // When var result = fixture.Run(); // Then - Assert.Equal("install \"Cake\" -y --version \"1.0.0\"", result.Args); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData(true, "install \"Cake\" -y --pre")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, string expected) + [InlineData(true, "install \"Cake\" --confirm --no-progress")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.Prerelease = prerelease; + fixture.Settings.NoProgress = noProgress; // When var result = fixture.Run(); @@ -330,13 +367,13 @@ public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, stri } [Theory] - [InlineData(true, "install \"Cake\" -y --x86")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string expected) + [InlineData("proxy", "install \"Cake\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.Forcex86 = forcex86; + fixture.Settings.Proxy = proxy; // When var result = fixture.Run(); @@ -346,13 +383,13 @@ public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string e } [Theory] - [InlineData("args1", "install \"Cake\" -y --ia \"args1\"")] - [InlineData("", "install \"Cake\" -y")] - public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, string expected) + [InlineData("proxy-user", "install \"Cake\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.InstallArguments = installArgs; + fixture.Settings.ProxyUser = proxyUser; // When var result = fixture.Run(); @@ -362,13 +399,13 @@ public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, } [Theory] - [InlineData(true, "install \"Cake\" -y -o")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideArguments, string expected) + [InlineData("proxy-password", "install \"Cake\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.OverrideArguments = overrideArguments; + fixture.Settings.ProxyPassword = proxyPassword; // When var result = fixture.Run(); @@ -378,13 +415,13 @@ public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideA } [Theory] - [InlineData(true, "install \"Cake\" -y --notSilent")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string expected) + [InlineData("proxy1,proxy2", "install \"Cake\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.NotSilent = notSilent; + fixture.Settings.ProxyByPassList = proxyByPassList; // When var result = fixture.Run(); @@ -394,13 +431,13 @@ public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string } [Theory] - [InlineData("param1", "install \"Cake\" -y --params \"param1\"")] - [InlineData("", "install \"Cake\" -y")] - public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParameters, string expected) + [InlineData(true, "install \"Cake\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.PackageParameters = packageParameters; + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; // When var result = fixture.Run(); @@ -410,13 +447,13 @@ public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParam } [Theory] - [InlineData(true, "install \"Cake\" -y --allowdowngrade")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngrade, string expected) + [InlineData("./output.log", "install \"Cake\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.AllowDowngrade = allowDowngrade; + fixture.Settings.LogFile = logFilePath; // When var result = fixture.Run(); @@ -426,13 +463,13 @@ public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngra } [Theory] - [InlineData(true, "install \"Cake\" -y -m")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, string expected) + [InlineData(true, "install \"Cake\" --confirm --skip-compatibility-checks")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.SideBySide = sideBySide; + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; // When var result = fixture.Run(); @@ -441,14 +478,42 @@ public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, stri Assert.Equal(expected, result.Args); } + [Fact] + public void Should_Add_Source_To_Arguments_If_Set() + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Source = "A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"Cake\" --confirm --source=\"A\"", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Version = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"Cake\" --confirm --version=\"1.0.0\"", result.Args); + } + [Theory] - [InlineData(true, "install \"Cake\" -y -i")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDependencies, string expected) + [InlineData(true, "install \"Cake\" --confirm --pre")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.IgnoreDependencies = ignoreDependencies; + fixture.Settings.Prerelease = prerelease; // When var result = fixture.Run(); @@ -458,13 +523,13 @@ public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDe } [Theory] - [InlineData(true, "install \"Cake\" -y -x")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_ForceDependencies_Flag_To_Arguments_If_Set(bool forceDependencies, string expected) + [InlineData(true, "install \"Cake\" --confirm --forcex86")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.ForceDependencies = forceDependencies; + fixture.Settings.Forcex86 = forcex86; // When var result = fixture.Run(); @@ -474,13 +539,13 @@ public void Should_Add_ForceDependencies_Flag_To_Arguments_If_Set(bool forceDepe } [Theory] - [InlineData(true, "install \"Cake\" -y -n")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShell, string expected) + [InlineData("args1", "install \"Cake\" --confirm --install-arguments=\"args1\"")] + [InlineData("", "install \"Cake\" --confirm")] + public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.SkipPowerShell = skipPowerShell; + fixture.Settings.InstallArguments = installArgs; // When var result = fixture.Run(); @@ -490,13 +555,13 @@ public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShe } [Theory] - [InlineData("user1", "install \"Cake\" -y -u \"user1\"")] - [InlineData("", "install \"Cake\" -y")] - public void Should_Add_User_To_Arguments_If_Set(string user, string expected) + [InlineData(true, "install \"Cake\" --confirm --override-arguments")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideArguments, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.User = user; + fixture.Settings.OverrideArguments = overrideArguments; // When var result = fixture.Run(); @@ -506,13 +571,13 @@ public void Should_Add_User_To_Arguments_If_Set(string user, string expected) } [Theory] - [InlineData("password1", "install \"Cake\" -y -p \"password1\"")] - [InlineData("", "install \"Cake\" -y")] - public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) + [InlineData(true, "install \"Cake\" --confirm --not-silent")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.Password = password; + fixture.Settings.NotSilent = notSilent; // When var result = fixture.Run(); @@ -522,13 +587,13 @@ public void Should_Add_Password_To_Arguments_If_Set(string password, string expe } [Theory] - [InlineData(true, "install \"Cake\" -y --ignorechecksums")] - [InlineData(false, "install \"Cake\" -y")] - public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreChecksums, string expected) + [InlineData("param1", "install \"Cake\" --confirm --package-parameters=\"param1\"")] + [InlineData("", "install \"Cake\" --confirm")] + public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParameters, string expected) { // Given var fixture = new ChocolateyInstallFixture(); - fixture.Settings.IgnoreChecksums = ignoreChecksums; + fixture.Settings.PackageParameters = packageParameters; // When var result = fixture.Run(); @@ -536,147 +601,159 @@ public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreCheck // Then Assert.Equal(expected, result.Args); } - } - public sealed class TheInstallFromConfigMethod - { - [Fact] - public void Should_Throw_If_Target_Package_Config_Path_Is_Null() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --apply-install-arguments-to-dependencies")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ApplyInstallArgumentsToDependencies_To_Arguments_If_Set(bool applyInstallArgumentsToDependencies, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.PackageConfigPath = null; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ApplyInstallArgumentsToDependencies = applyInstallArgumentsToDependencies; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsArgumentNullException(result, "packageConfigPath"); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Settings_Are_Null() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --apply-package-parameters-to-dependencies")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ApplyPackageParametersToDependencies_To_Arguments_If_Set(bool applyPackageParametersToDependencies, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings = null; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ApplyPackageParametersToDependencies = applyPackageParametersToDependencies; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsArgumentNullException(result, "settings"); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --allow-downgrade")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngrade, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.GivenDefaultToolDoNotExist(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.AllowDowngrade = allowDowngrade; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] - [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] - public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + [InlineData(true, "install \"Cake\" --confirm --side-by-side")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.SideBySide = sideBySide; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [WindowsTheory] - [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] - public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + [Theory] + [InlineData(true, "install \"Cake\" --confirm --ignore-dependencies")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDependencies, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.IgnoreDependencies = ignoreDependencies; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --force-dependencies")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ForceDependencies_Flag_To_Arguments_If_Set(bool forceDependencies, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.GivenProcessCannotStart(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ForceDependencies = forceDependencies; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + [Theory] + [InlineData(true, "install \"Cake\" --confirm --skip-automation-scripts")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShell, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.GivenProcessExitsWithCode(1); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.SkipPowerShell = skipPowerShell; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + [Theory] + [InlineData("user1", "install \"Cake\" --confirm --user=\"user1\"")] + [InlineData("", "install \"Cake\" --confirm")] + public void Should_Add_User_To_Arguments_If_Set(string user, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.User = user; // When var result = fixture.Run(); // Then - Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Mandatory_Arguments() + [Theory] + [InlineData("password1", "install \"Cake\" --confirm --password=\"password1\"")] + [InlineData("", "install \"Cake\" --confirm")] + public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Password = password; // When var result = fixture.Run(); // Then - Assert.Equal("install \"/Working/packages.config\" -y", result.Args); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -d -y")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + [InlineData("./mycert.pfx", "install \"Cake\" --confirm --cert=\"/Working/mycert.pfx\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_Cert_To_Arguments_If_Set(string certificate, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.Debug = debug; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Certificate = certificate; // When var result = fixture.Run(); @@ -686,13 +763,13 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -v -y")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + [InlineData("certpassword", "install \"Cake\" --confirm --certpassword=\"certpassword\"")] + [InlineData("", "install \"Cake\" --confirm")] + public void Should_Add_CertPassword_To_Arguments_If_Set(string certificatePassword, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.Verbose = verbose; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.CertificatePassword = certificatePassword; // When var result = fixture.Run(); @@ -702,13 +779,13 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -y -f")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + [InlineData(true, "install \"Cake\" --confirm --ignore-checksums")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreChecksums, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.Force = force; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.IgnoreChecksums = ignoreChecksums; // When var result = fixture.Run(); @@ -718,13 +795,13 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -y --noop")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + [InlineData(true, "install \"Cake\" --confirm --allow-empty-checksums")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_AllowEmptyChecksums_Flag_To_Arguments_If_Set(bool allowEmptyChecksums, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.Noop = noop; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.AllowEmptyChecksums = allowEmptyChecksums; // When var result = fixture.Run(); @@ -734,13 +811,13 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -y -r")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + [InlineData(true, "install \"Cake\" --confirm --allow-empty-checksums-secure")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_AllowEmptyChecksumsSecure_Flag_To_Arguments_If_Set(bool allowEmptyChecksumsSecure, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.LimitOutput = limitOutput; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.AllowEmptyChecksumsSecure = allowEmptyChecksumsSecure; // When var result = fixture.Run(); @@ -750,13 +827,13 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "install \"/Working/packages.config\" -y --execution-timeout \"5\"")] - [InlineData(0, "install \"/Working/packages.config\" -y")] - public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + [InlineData(true, "install \"Cake\" --confirm --require-checksums")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_RequireChecksums_Flag_To_Arguments_If_Set(bool requireChecksums, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.ExecutionTimeout = executionTimeout; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.RequireChecksums = requireChecksums; // When var result = fixture.Run(); @@ -766,13 +843,13 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "install \"/Working/packages.config\" -y -c \"c:\\temp\"")] - [InlineData("", "install \"/Working/packages.config\" -y")] - public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + [InlineData("abcdef", "install \"Cake\" --confirm --download-checksum=\"abcdef\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_Checksum_Flag_To_Arguments_If_Set(string checksum, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.CacheLocation = cacheLocation; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Checksum = checksum; // When var result = fixture.Run(); @@ -782,13 +859,13 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "install \"/Working/packages.config\" -y --allowunofficial")] - [InlineData(false, "install \"/Working/packages.config\" -y")] - public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + [InlineData("abcdef", "install \"Cake\" --confirm --download-checksum-x64=\"abcdef\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_Checksum64_Flag_To_Arguments_If_Set(string checksum64, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.AllowUnofficial = allowUnofficial; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Checksum64 = checksum64; // When var result = fixture.Run(); @@ -797,19 +874,1591 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Source_To_Arguments_If_Set() + [Theory] + [InlineData("md5", "install \"Cake\" --confirm --download-checksum-type=\"md5\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_ChecksumType_Flag_To_Arguments_If_Set(string checkSumType, string expected) { // Given - var fixture = new ChocolateyInstallFromConfigFixture(); - fixture.Settings.Source = "A"; + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ChecksumType = checkSumType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "install \"Cake\" --confirm --download-checksum-type-x64=\"md5\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_ChecksumType64_Flag_To_Arguments_If_Set(string checkSumType64, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ChecksumType64 = checkSumType64; // When var result = fixture.Run(); // Then - Assert.Equal("install \"/Working/packages.config\" -y -s \"A\"", result.Args); + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --ignore-package-exit-codes")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_IgnorePackageExitCodes_Flag_To_Arguments_If_Set(bool ignorePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.IgnorePackageExitCodes = ignorePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --use-package-exit-codes")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_UsePackageExitCodes_Flag_To_Arguments_If_Set(bool usePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.UsePackageExitCodes = usePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --stop-on-first-package-failure")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_StopOnFirstFailure_Flag_To_Arguments_If_Set(bool stopOnFirstFailure, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.StopOnFirstFailure = stopOnFirstFailure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --exit-when-reboot-detected")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ExitWhenRebootDetected_Flag_To_Arguments_If_Set(bool exitWhenRebootDetected, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ExitWhenRebootDetected = exitWhenRebootDetected; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --ignore-detected-reboot")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_IgnoreDetectedReboot_Flag_To_Arguments_If_Set(bool ignoreDetectedReboot, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.IgnoreDetectedReboot = ignoreDetectedReboot; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --disable-repository-optimizations")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_DisableRepositoryOptimizations_Flag_To_Arguments_If_Set(bool disableRepositoryOptimizations, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.DisableRepositoryOptimizations = disableRepositoryOptimizations; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --pin-package")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_Pin_Flag_To_Arguments_If_Set(bool pin, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.Pin = pin; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --skip-hooks")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_SkipHooks_Flag_To_Arguments_If_Set(bool skipHooks, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.SkipHooks = skipHooks; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --skip-download-cache")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_SkipDownloadCache_Flag_To_Arguments_If_Set(bool skipDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.SkipDownloadCache = skipDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --use-download-cache")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_UseDownloadCache_Flag_To_Arguments_If_Set(bool useDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.UseDownloadCache = useDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --skip-virus-check")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_SkipVirusCheck_Flag_To_Arguments_If_Set(bool skipVirusCheck, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.SkipVirusCheck = skipVirusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --virus-check")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_VirusCheck_Flag_To_Arguments_If_Set(bool virusCheck, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.VirusCheck = virusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "install \"Cake\" --confirm --virus-positives-minimum=\"5\"")] + [InlineData(0, "install \"Cake\" --confirm")] + public void Should_Add_VirusPositivesMinimum_To_Arguments_If_Set(int virusPositivesMinimum, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.VirusPositivesMinimum = virusPositivesMinimum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "install \"Cake\" --confirm --install-arguments-sensitive=\"super-secret\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_InstallArgumentsSensitive_Flag_To_Arguments_If_Set(string installArgumentsSensitive, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.InstallArgumentsSensitive = installArgumentsSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "install \"Cake\" --confirm --package-parameters-sensitive=\"super-secret\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_PackageParametersSensitive_Flag_To_Arguments_If_Set(string packageParametersSensitive, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.PackageParametersSensitive = packageParametersSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./install", "install \"Cake\" --confirm --install-directory=\"/Working/install\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_InstallDirectory_Flag_To_Arguments_If_Set(string installDirectory, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.InstallDirectory = installDirectory; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "install \"Cake\" --confirm --maximum-download-bits-per-second=\"5\"")] + [InlineData(0, "install \"Cake\" --confirm")] + public void Should_Add_MaximumDownloadBitsPerSecond_Flag_To_Arguments_If_Set(int maximumDownloadBitsPerSecond, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.MaximumDownloadBitsPerSecond = maximumDownloadBitsPerSecond; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --reduce-package-size")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ReducePackageSize_Flag_To_Arguments_If_Set(bool reducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ReducePackageSize = reducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --no-reduce-package-size")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_NoReducePackageSize_Flag_To_Arguments_If_Set(bool noReducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.NoReducePackageSize = noReducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"Cake\" --confirm --reduce-nupkg-only")] + [InlineData(false, "install \"Cake\" --confirm")] + public void Should_Add_ReduceNupkgOnly_Flag_To_Arguments_If_Set(bool reduceNupkgOnly, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.ReduceNupkgOnly = reduceNupkgOnly; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("Just because", "install \"Cake\" --confirm --pin-reason=\"Just because\"")] + [InlineData(null, "install \"Cake\" --confirm")] + public void Should_Add_PinReason_Flag_To_Arguments_If_Set(string pinReason, string expected) + { + // Given + var fixture = new ChocolateyInstallFixture(); + fixture.Settings.PinReason = pinReason; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + + public sealed class TheInstallFromConfigMethod + { + [Fact] + public void Should_Throw_If_Target_Package_Config_Path_Is_Null() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.PackageConfigPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageConfigPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"/Working/packages.config\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --debug --confirm")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --verbose --confirm")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --trace --confirm")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --no-color --confirm")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --accept-license --confirm")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --force")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --what-if")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --limit-output")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "install \"/Working/packages.config\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "install \"/Working/packages.config\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --allow-unofficial")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --fail-on-error-output")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --use-system-powershell")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --no-progress")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "install \"/Working/packages.config\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "install \"/Working/packages.config\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "install \"/Working/packages.config\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "install \"/Working/packages.config\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "install \"/Working/packages.config\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --skip-compatibility-checks")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Source_To_Arguments_If_Set() + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Source = "A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"/Working/packages.config\" --confirm --source=\"A\"", result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --pre")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Prerelease = prerelease; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --forcex86")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Forcex86 = forcex86; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("args1", "install \"/Working/packages.config\" --confirm --install-arguments=\"args1\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.InstallArguments = installArgs; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --override-arguments")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideArguments, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.OverrideArguments = overrideArguments; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --not-silent")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.NotSilent = notSilent; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("param1", "install \"/Working/packages.config\" --confirm --package-parameters=\"param1\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParameters, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.PackageParameters = packageParameters; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --apply-install-arguments-to-dependencies")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ApplyInstallArgumentsToDependencies_To_Arguments_If_Set(bool applyInstallArgumentsToDependencies, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ApplyInstallArgumentsToDependencies = applyInstallArgumentsToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --apply-package-parameters-to-dependencies")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ApplyPackageParametersToDependencies_To_Arguments_If_Set(bool applyPackageParametersToDependencies, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ApplyPackageParametersToDependencies = applyPackageParametersToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --allow-downgrade")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngrade, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.AllowDowngrade = allowDowngrade; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --side-by-side")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SideBySide = sideBySide; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --ignore-dependencies")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDependencies, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.IgnoreDependencies = ignoreDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --force-dependencies")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ForceDependencies_Flag_To_Arguments_If_Set(bool forceDependencies, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ForceDependencies = forceDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --skip-automation-scripts")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShell, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SkipPowerShell = skipPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("user1", "install \"/Working/packages.config\" --confirm --user=\"user1\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_User_To_Arguments_If_Set(string user, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.User = user; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("password1", "install \"/Working/packages.config\" --confirm --password=\"password1\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Password = password; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./mycert.pfx", "install \"/Working/packages.config\" --confirm --cert=\"/Working/mycert.pfx\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Cert_To_Arguments_If_Set(string certificate, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Certificate = certificate; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("certpassword", "install \"/Working/packages.config\" --confirm --certpassword=\"certpassword\"")] + [InlineData("", "install \"/Working/packages.config\" --confirm")] + public void Should_Add_CertPassword_To_Arguments_If_Set(string certificatePassword, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.CertificatePassword = certificatePassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --ignore-checksums")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreChecksums, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.IgnoreChecksums = ignoreChecksums; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --allow-empty-checksums")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_AllowEmptyChecksums_Flag_To_Arguments_If_Set(bool allowEmptyChecksums, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.AllowEmptyChecksums = allowEmptyChecksums; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --allow-empty-checksums-secure")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_AllowEmptyChecksumsSecure_Flag_To_Arguments_If_Set(bool allowEmptyChecksumsSecure, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.AllowEmptyChecksumsSecure = allowEmptyChecksumsSecure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --require-checksums")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_RequireChecksums_Flag_To_Arguments_If_Set(bool requireChecksums, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.RequireChecksums = requireChecksums; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "install \"/Working/packages.config\" --confirm --download-checksum=\"abcdef\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Checksum_Flag_To_Arguments_If_Set(string checksum, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Checksum = checksum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "install \"/Working/packages.config\" --confirm --download-checksum-x64=\"abcdef\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Checksum64_Flag_To_Arguments_If_Set(string checksum64, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Checksum64 = checksum64; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "install \"/Working/packages.config\" --confirm --download-checksum-type=\"md5\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ChecksumType_Flag_To_Arguments_If_Set(string checkSumType, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ChecksumType = checkSumType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "install \"/Working/packages.config\" --confirm --download-checksum-type-x64=\"md5\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ChecksumType64_Flag_To_Arguments_If_Set(string checkSumType64, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ChecksumType64 = checkSumType64; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --ignore-package-exit-codes")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_IgnorePackageExitCodes_Flag_To_Arguments_If_Set(bool ignorePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.IgnorePackageExitCodes = ignorePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --use-package-exit-codes")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_UsePackageExitCodes_Flag_To_Arguments_If_Set(bool usePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.UsePackageExitCodes = usePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --stop-on-first-package-failure")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_StopOnFirstFailure_Flag_To_Arguments_If_Set(bool stopOnFirstFailure, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.StopOnFirstFailure = stopOnFirstFailure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --exit-when-reboot-detected")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ExitWhenRebootDetected_Flag_To_Arguments_If_Set(bool exitWhenRebootDetected, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ExitWhenRebootDetected = exitWhenRebootDetected; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --ignore-detected-reboot")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_IgnoreDetectedReboot_Flag_To_Arguments_If_Set(bool ignoreDetectedReboot, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.IgnoreDetectedReboot = ignoreDetectedReboot; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --disable-repository-optimizations")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_DisableRepositoryOptimizations_Flag_To_Arguments_If_Set(bool disableRepositoryOptimizations, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.DisableRepositoryOptimizations = disableRepositoryOptimizations; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --pin-package")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_Pin_Flag_To_Arguments_If_Set(bool pin, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.Pin = pin; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --skip-hooks")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_SkipHooks_Flag_To_Arguments_If_Set(bool skipHooks, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SkipHooks = skipHooks; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --skip-download-cache")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_SkipDownloadCache_Flag_To_Arguments_If_Set(bool skipDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SkipDownloadCache = skipDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --use-download-cache")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_UseDownloadCache_Flag_To_Arguments_If_Set(bool useDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.UseDownloadCache = useDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --skip-virus-check")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_SkipVirusCheck_Flag_To_Arguments_If_Set(bool skipVirusCheck, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.SkipVirusCheck = skipVirusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --virus-check")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_VirusCheck_Flag_To_Arguments_If_Set(bool virusCheck, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.VirusCheck = virusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "install \"/Working/packages.config\" --confirm --virus-positives-minimum=\"5\"")] + [InlineData(0, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_VirusPositivesMinimum_To_Arguments_If_Set(int virusPositivesMinimum, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.VirusPositivesMinimum = virusPositivesMinimum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "install \"/Working/packages.config\" --confirm --install-arguments-sensitive=\"super-secret\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_InstallArgumentsSensitive_Flag_To_Arguments_If_Set(string installArgumentsSensitive, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.InstallArgumentsSensitive = installArgumentsSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "install \"/Working/packages.config\" --confirm --package-parameters-sensitive=\"super-secret\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_PackageParametersSensitive_Flag_To_Arguments_If_Set(string packageParametersSensitive, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.PackageParametersSensitive = packageParametersSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./install", "install \"/Working/packages.config\" --confirm --install-directory=\"/Working/install\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_InstallDirectory_Flag_To_Arguments_If_Set(string installDirectory, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.InstallDirectory = installDirectory; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "install \"/Working/packages.config\" --confirm --maximum-download-bits-per-second=\"5\"")] + [InlineData(0, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_MaximumDownloadBitsPerSecond_Flag_To_Arguments_If_Set(int maximumDownloadBitsPerSecond, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.MaximumDownloadBitsPerSecond = maximumDownloadBitsPerSecond; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --reduce-package-size")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ReducePackageSize_Flag_To_Arguments_If_Set(bool reducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ReducePackageSize = reducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --no-reduce-package-size")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_NoReducePackageSize_Flag_To_Arguments_If_Set(bool noReducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.NoReducePackageSize = noReducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "install \"/Working/packages.config\" --confirm --reduce-nupkg-only")] + [InlineData(false, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_ReduceNupkgOnly_Flag_To_Arguments_If_Set(bool reduceNupkgOnly, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.ReduceNupkgOnly = reduceNupkgOnly; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("Just because", "install \"/Working/packages.config\" --confirm --pin-reason=\"Just because\"")] + [InlineData(null, "install \"/Working/packages.config\" --confirm")] + public void Should_Add_PinReason_Flag_To_Arguments_If_Set(string pinReason, string expected) + { + // Given + var fixture = new ChocolateyInstallFromConfigFixture(); + fixture.Settings.PinReason = pinReason; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/New/ChocolateyNewTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/New/ChocolateyNewTests.cs new file mode 100644 index 0000000000..70df186125 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/New/ChocolateyNewTests.cs @@ -0,0 +1,860 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.New; +using Cake.Common.Tests.Properties; +using Cake.Common.Tools.Chocolatey.New; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Chocolatey.New +{ + public sealed class ChocolateyNewTests + { + public sealed class TheNewMethod + { + [Fact] + public void Should_Throw_If_Package_Id_Is_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.PackageId = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageId"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyNewFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyNewFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --debug --confirm")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --verbose --confirm")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --trace --confirm")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --no-color --confirm")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --accept-license --confirm")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --force")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --what-if")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --limit-output")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "new \"MyPackage\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "new \"MyPackage\" --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "new \"MyPackage\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "new \"MyPackage\" --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --allow-unofficial")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --fail-on-error-output")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --use-system-powershell")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --no-progress")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "new \"MyPackage\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "new \"MyPackage\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "new \"MyPackage\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "new \"MyPackage\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "new \"MyPackage\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --skip-compatibility-checks")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --automaticpackage")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_AutomaticPackage_Flag_To_Arguments_If_Set(bool automaticPackage, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.AutomaticPackage = automaticPackage; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("templateA", "new \"MyPackage\" --confirm --template-name=\"templateA\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_TemplateName_Flag_To_Arguments_If_Set(string templateName, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.TemplateName = templateName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Package_Version_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.PackageVersion = "1.2.3"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm packageversion=\"1.2.3\"", result.Args); + } + + [Fact] + public void Should_Add_Maintainer_Name_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.MaintainerName = "John Doe"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm maintainername=\"John Doe\"", result.Args); + } + + [Fact] + public void Should_Add_Maintainer_Repo_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.MaintainerRepo = "johndoe"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm maintainerrepo=\"johndoe\"", result.Args); + } + + [Fact] + public void Should_Add_Installer_Type_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.InstallerType = "msi"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm installertype=\"msi\"", result.Args); + } + + [Fact] + public void Should_Add_Url_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Url = "https://example.com"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm url=\"https://example.com\"", result.Args); + } + + [Fact] + public void Should_Add_Url64_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Url64 = "https://example.com"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm url64=\"https://example.com\"", result.Args); + } + + [Theory] + [InlineData("-Foo", "new \"MyPackage\" --confirm silentargs=\"-Foo\"")] + [InlineData("--Foo", "new \"MyPackage\" --confirm silentargs=\"--Foo\"")] + [InlineData("/Foo", "new \"MyPackage\" --confirm silentargs=\"/Foo\"")] + [InlineData("-Foo=Bar", "new \"MyPackage\" --confirm silentargs=\"-Foo=Bar\"")] + [InlineData("-Foo --Foo /Foo -Foo=Bar", "new \"MyPackage\" --confirm silentargs=\"-Foo --Foo /Foo -Foo=Bar\"")] + public void Should_Add_Silent_Args_To_Arguments_If_Set(string silentArgs, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.SilentArgs = silentArgs; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("Foo", "Bar", "new \"MyPackage\" --confirm Foo=\"Bar\"")] + [InlineData("Foo", "Foo Bar", "new \"MyPackage\" --confirm Foo=\"Foo Bar\"")] + public void Should_Add_Additional_Property_Value_To_Arguments_If_Set(string additionalPropertyName, string additionalPropertyValue, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.AdditionalPropertyValues.Add(additionalPropertyName, additionalPropertyValue); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Additional_Property_Values_To_Arguments_If_Set() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.AdditionalPropertyValues.Add("Foo1", "Bar1"); + fixture.Settings.AdditionalPropertyValues.Add("Foo2", "Bar2"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm Foo1=\"Bar1\" Foo2=\"Bar2\"", result.Args); + } + + [Fact] + public void Should_Add_Output_Directory_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.OutputDirectory = "./Foo"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("new \"MyPackage\" --confirm --output-directory=\"/Working/Foo\"", result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --built-in-template")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_BuiltInTemplate_Flag_To_Arguments_If_Set(bool builtIn, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.BuiltInTemplate = builtIn; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./input/install.exe", "new \"MyPackage\" --confirm --file=\"./input/install.exe\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_File_Flag_To_Arguments_If_Set(string fileName, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.File = fileName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./input/install.exe", "new \"MyPackage\" --confirm --file64=\"./input/install.exe\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_File64_Flag_To_Arguments_If_Set(string file64Name, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.File64 = file64Name; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --use-original-files-location")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_UseOriginalFilesLocation_Flag_To_Arguments_If_Set(bool useOriginalFilesLocation, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.UseOriginalFilesLocation = useOriginalFilesLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "new \"MyPackage\" --confirm --download-checksum=\"abcdef\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_Checksum_Flag_To_Arguments_If_Set(string checksum, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Checksum = checksum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "new \"MyPackage\" --confirm --download-checksum-x64=\"abcdef\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_Checksum64_Flag_To_Arguments_If_Set(string checksum64, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.Checksum64 = checksum64; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "new \"MyPackage\" --confirm --download-checksum-type=\"md5\"")] + [InlineData(null, "new \"MyPackage\" --confirm")] + public void Should_Add_ChecksumType_Flag_To_Arguments_If_Set(string checkSumType, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.ChecksumType = checkSumType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --pause-on-error")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_PauseOnError_Flag_To_Arguments_If_Set(bool pauseOnError, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.PauseOnError = pauseOnError; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --build-package")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_BuildPackage_Flag_To_Arguments_If_Set(bool buildPackage, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.BuildPackage = buildPackage; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --from-programs-and-features")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_GeneratePackagesFromInstalledSoftware_Flag_To_Arguments_If_Set(bool generatePackagesFromInstalledSoftware, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.GeneratePackagesFromInstalledSoftware = generatePackagesFromInstalledSoftware; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --remove-architecture-from-name")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_RemoveArchitectureFromName_Flag_To_Arguments_If_Set(bool removeArchitectureFromName, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.RemoveArchitectureFromName = removeArchitectureFromName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "new \"MyPackage\" --confirm --include-architecture-in-name")] + [InlineData(false, "new \"MyPackage\" --confirm")] + public void Should_Add_IncludeArchitectureInPackageName_Flag_To_Arguments_If_Set(bool includeArchitectureInPackageName, string expected) + { + // Given + var fixture = new ChocolateyNewFixture(); + fixture.Settings.IncludeArchitectureInPackageName = includeArchitectureInPackageName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pack/ChocolateyPackerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pack/ChocolateyPackerTests.cs index 0207acd1cd..72a3a44a3c 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pack/ChocolateyPackerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pack/ChocolateyPackerTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Packer; using Cake.Common.Tests.Properties; using Cake.Common.Tools.Chocolatey.Pack; @@ -28,7 +30,7 @@ public void Should_Throw_If_Nuspec_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "nuspecFilePath"); + AssertEx.IsArgumentNullException(result, "nuspecFilePath"); } [Fact] @@ -42,7 +44,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -56,7 +58,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -116,7 +118,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -130,7 +132,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -157,7 +159,7 @@ public void Should_Throw_If_Nuspec_Do_Not_Exist() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Could not find nuspec file '/Working/nonexisting.nuspec'."); + AssertEx.IsCakeException(result, "Could not find nuspec file '/Working/nonexisting.nuspec'."); } [Fact] @@ -171,12 +173,12 @@ public void Should_Throw_If_Temporary_Nuspec_Already_Exist() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Could not create the nuspec file '/Working/existing.temp.nuspec' since it already exist."); + AssertEx.IsCakeException(result, "Could not create the nuspec file '/Working/existing.temp.nuspec' since it already exist."); } [Theory] - [InlineData(true, "pack -d -y \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --debug --confirm")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -191,8 +193,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "pack -v -y \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --verbose --confirm")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -207,8 +209,8 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "pack -y -f \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --force")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -223,8 +225,56 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "pack -y --noop \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --trace --confirm")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --no-color --confirm")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --accept-license --confirm")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --what-if")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -239,8 +289,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "pack -y -r \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --limit-output")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -255,8 +305,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "pack -y --execution-timeout \"5\" \"/Working/existing.temp.nuspec\"")] - [InlineData(0, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(5, "pack \"/Working/existing.temp.nuspec\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -271,8 +321,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "pack -y -c \"c:\\temp\" \"/Working/existing.temp.nuspec\"")] - [InlineData("", "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(@"c:\temp", "pack \"/Working/existing.temp.nuspec\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -287,8 +337,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "pack -y --allowunofficial \"/Working/existing.temp.nuspec\"")] - [InlineData(false, "pack -y \"/Working/existing.temp.nuspec\"")] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --allow-unofficial")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -302,6 +352,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi Assert.Equal(expected, result.Args); } + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --fail-on-error-output")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --use-system-powershell")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --no-progress")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "pack \"/Working/existing.temp.nuspec\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "pack \"/Working/existing.temp.nuspec\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "pack \"/Working/existing.temp.nuspec\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "pack \"/Working/existing.temp.nuspec\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "pack \"/Working/existing.temp.nuspec\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pack \"/Working/existing.temp.nuspec\" --confirm --skip-compatibility-checks")] + [InlineData(false, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + [Fact] public void Should_Add_Version_To_Arguments_If_Not_Null() { @@ -313,8 +523,23 @@ public void Should_Add_Version_To_Arguments_If_Not_Null() var result = fixture.Run(); // Then - Assert.Equal("pack -y --version \"1.0.0\" " + - "\"/Working/existing.temp.nuspec\"", result.Args); + Assert.Equal("pack \"/Working/existing.temp.nuspec\" --confirm --version=\"1.0.0\"", result.Args); + } + + [Theory] + [InlineData("./output", "pack \"/Working/existing.temp.nuspec\" --confirm --output-directory=\"/Working/output\"")] + [InlineData(null, "pack \"/Working/existing.temp.nuspec\" --confirm")] + public void Should_Add_OutputDirectory_Flag_To_Arguments_If_Set(string outputDirectory, string expected) + { + // Given + var fixture = new ChocolateyPackerWithNuSpecFixture(); + fixture.Settings.OutputDirectory = outputDirectory; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); } [Fact] @@ -343,6 +568,11 @@ public void Should_Add_Metadata_Element_To_Nuspec_If_Missing() fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Dependencies = new[] + { + new ChocolateyNuSpecDependency { Id = "Dependency1", Version = "1.0.0" }, + new ChocolateyNuSpecDependency { Id = "Dependency2", Version = "[2.0.0]" } + }; // When var result = fixture.Run(); @@ -378,6 +608,11 @@ public void Should_Replace_Template_Tokens_In_Nuspec() fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Dependencies = new[] + { + new ChocolateyNuSpecDependency { Id = "Dependency1", Version = "1.0.0" }, + new ChocolateyNuSpecDependency { Id = "Dependency2", Version = "[2.0.0]" } + }; // When var result = fixture.Run(); @@ -414,6 +649,11 @@ public void Should_Replace_Template_Tokens_In_Nuspec_Without_Namespaces() fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Dependencies = new[] + { + new ChocolateyNuSpecDependency { Id = "Dependency1", Version = "1.0.0" }, + new ChocolateyNuSpecDependency { Id = "Dependency2", Version = "[2.0.0]" } + }; // When var result = fixture.Run(); @@ -454,6 +694,11 @@ public void Should_Replace_Template_Tokens_In_Nuspec_With_Files() { new ChocolateyNuSpecContent { Source = @"tools\**", Target = "tools" }, }; + fixture.Settings.Dependencies = new[] + { + new ChocolateyNuSpecDependency { Id = "Dependency1", Version = "1.0.0" }, + new ChocolateyNuSpecDependency { Id = "Dependency2", Version = "[2.0.0]" } + }; // When var result = fixture.Run(); @@ -494,6 +739,11 @@ public void Should_Replace_Template_Tokens_In_Nuspec_With_Files_Without_Namespac { new ChocolateyNuSpecContent { Source = @"tools\**", Target = "tools" }, }; + fixture.Settings.Dependencies = new[] + { + new ChocolateyNuSpecDependency { Id = "Dependency1", Version = "1.0.0" }, + new ChocolateyNuSpecDependency { Id = "Dependency2", Version = "[2.0.0]" } + }; // When var result = fixture.Run(); @@ -521,8 +771,7 @@ public void Should_Pack_If_Sufficient_Settings_Specified() var result = fixture.Run(); // Then - Assert.Equal("pack -y --version \"1.0.0\" " + - "\"/Working/nonexisting.temp.nuspec\"", result.Args); + Assert.Equal("pack \"/Working/nonexisting.temp.nuspec\" --confirm --version=\"1.0.0\"", result.Args); } [Fact] @@ -535,7 +784,7 @@ public void Should_Throw_If_Id_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Id not specified."); + AssertEx.IsCakeException(result, "Required setting Id not specified."); } [Fact] @@ -549,7 +798,7 @@ public void Should_Throw_If_Version_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Version not specified."); + AssertEx.IsCakeException(result, "Required setting Version not specified."); } [Fact] @@ -564,7 +813,7 @@ public void Should_Throw_If_Authors_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Authors not specified."); + AssertEx.IsCakeException(result, "Required setting Authors not specified."); } [Fact] @@ -580,7 +829,7 @@ public void Should_Throw_If_Description_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Description not specified."); + AssertEx.IsCakeException(result, "Required setting Description not specified."); } [Fact] @@ -597,8 +846,8 @@ public void Should_Throw_If_Files_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Files not specified."); + AssertEx.IsCakeException(result, "Required setting Files not specified."); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pin/ChocolateyPinnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pin/ChocolateyPinnerTests.cs index 0ff45b758c..ed4434f131 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pin/ChocolateyPinnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Pin/ChocolateyPinnerTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Pin; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +25,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -37,7 +39,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -84,7 +86,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -98,7 +100,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -124,12 +126,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("pin add -n \"Cake\" -y", result.Args); + Assert.Equal("pin add --name=\"Cake\" --confirm", result.Args); } [Theory] - [InlineData(true, "pin add -n \"Cake\" -d -y")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --debug --confirm")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -144,8 +146,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "pin add -n \"Cake\" -v -y")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --verbose --confirm")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -160,8 +162,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "pin add -n \"Cake\" -y -f")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --trace --confirm")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --no-color --confirm")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --accept-license --confirm")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --force")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -176,8 +226,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "pin add -n \"Cake\" -y --noop")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --confirm --what-if")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -192,8 +242,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "pin add -n \"Cake\" -y -r")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --confirm --limit-output")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -208,8 +258,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "pin add -n \"Cake\" -y --execution-timeout \"5\"")] - [InlineData(0, "pin add -n \"Cake\" -y")] + [InlineData(5, "pin add --name=\"Cake\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "pin add --name=\"Cake\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -224,8 +274,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "pin add -n \"Cake\" -y -c \"c:\\temp\"")] - [InlineData("", "pin add -n \"Cake\" -y")] + [InlineData(@"c:\temp", "pin add --name=\"Cake\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "pin add --name=\"Cake\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -240,8 +290,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "pin add -n \"Cake\" -y --allowunofficial")] - [InlineData(false, "pin add -n \"Cake\" -y")] + [InlineData(true, "pin add --name=\"Cake\" --confirm --allow-unofficial")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -255,6 +305,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi Assert.Equal(expected, result.Args); } + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --fail-on-error-output")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --use-system-powershell")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --no-progress")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "pin add --name=\"Cake\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "pin add --name=\"Cake\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "pin add --name=\"Cake\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "pin add --name=\"Cake\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "pin add --name=\"Cake\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "pin add --name=\"Cake\" --confirm --skip-compatibility-checks")] + [InlineData(false, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + [Fact] public void Should_Add_Version_To_Arguments_If_Not_Null() { @@ -266,8 +476,24 @@ public void Should_Add_Version_To_Arguments_If_Not_Null() var result = fixture.Run(); // Then - Assert.Equal("pin add -n \"Cake\" --version \"1.0.0\" -y", result.Args); + Assert.Equal("pin add --name=\"Cake\" --confirm --version=\"1.0.0\"", result.Args); + } + + [Theory] + [InlineData("Just because", "pin add --name=\"Cake\" --confirm --pin-reason=\"Just because\"")] + [InlineData(null, "pin add --name=\"Cake\" --confirm")] + public void Should_Add_PinReason_Flag_To_Arguments_If_Set(string pinReason, string expected) + { + // Given + var fixture = new ChocolateyPinnerFixture(); + fixture.Settings.PinReason = pinReason; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Push/ChocolateyPusherTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Push/ChocolateyPusherTests.cs index 98435ade60..2d70f77fbe 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Push/ChocolateyPusherTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Push/ChocolateyPusherTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Push; using Cake.Testing; using Cake.Testing.Xunit; @@ -24,7 +26,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -38,7 +40,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -85,7 +87,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -99,7 +101,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -125,61 +127,65 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("push \"/Working/existing.nupkg\" -y", result.Args); + Assert.Equal("push \"/Working/existing.nupkg\" --confirm", result.Args); } - [Fact] - public void Should_Add_Api_Key_To_Arguments_If_Not_Null() + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --debug --confirm")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given var fixture = new ChocolateyPusherFixture(); - fixture.Settings.ApiKey = "1234"; + fixture.Settings.Debug = debug; // When var result = fixture.Run(); // Then - Assert.Equal("push \"/Working/existing.nupkg\" -k 1234 -y", result.Args); + Assert.Equal(expected, result.Args); } - - [Fact] - public void Should_Add_Source_To_Arguments_If_Not_Null() + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --verbose --confirm")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given var fixture = new ChocolateyPusherFixture(); - fixture.Settings.Source = "http://customsource/"; + fixture.Settings.Verbose = verbose; // When var result = fixture.Run(); // Then - Assert.Equal("push \"/Working/existing.nupkg\" " + - "-s \"http://customsource/\" -y", result.Args); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Timeout_To_Arguments_If_Not_Null() + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --trace --confirm")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) { // Given var fixture = new ChocolateyPusherFixture(); - fixture.Settings.Timeout = TimeSpan.FromSeconds(987); + fixture.Settings.Trace = trace; // When var result = fixture.Run(); // Then - Assert.Equal("push \"/Working/existing.nupkg\" -t 987 -y", result.Args); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -d -y")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] - public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + [InlineData(true, "push \"/Working/existing.nupkg\" --no-color --confirm")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) { // Given var fixture = new ChocolateyPusherFixture(); - fixture.Settings.Debug = debug; + fixture.Settings.NoColor = noColor; // When var result = fixture.Run(); @@ -189,13 +195,13 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -v -y")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] - public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + [InlineData(true, "push \"/Working/existing.nupkg\" --accept-license --confirm")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) { // Given var fixture = new ChocolateyPusherFixture(); - fixture.Settings.Verbose = verbose; + fixture.Settings.AcceptLicense = acceptLicense; // When var result = fixture.Run(); @@ -205,8 +211,8 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -y -f")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --force")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -221,8 +227,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -y --noop")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --what-if")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -237,8 +243,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -y -r")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --limit-output")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -253,8 +259,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "push \"/Working/existing.nupkg\" -y --execution-timeout \"5\"")] - [InlineData(0, "push \"/Working/existing.nupkg\" -y")] + [InlineData(5, "push \"/Working/existing.nupkg\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -269,8 +275,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "push \"/Working/existing.nupkg\" -y -c \"c:\\temp\"")] - [InlineData("", "push \"/Working/existing.nupkg\" -y")] + [InlineData(@"c:\temp", "push \"/Working/existing.nupkg\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -285,8 +291,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "push \"/Working/existing.nupkg\" -y --allowunofficial")] - [InlineData(false, "push \"/Working/existing.nupkg\" -y")] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --allow-unofficial")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -296,9 +302,261 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // When var result = fixture.Run(); + // Then + Assert.Equal(expected, result.Args); + } + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --fail-on-error-output")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --use-system-powershell")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --no-progress")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "push \"/Working/existing.nupkg\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "push \"/Working/existing.nupkg\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "push \"/Working/existing.nupkg\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "push \"/Working/existing.nupkg\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "push \"/Working/existing.nupkg\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --skip-compatibility-checks")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Source_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.Source = "http://customsource/"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("push \"/Working/existing.nupkg\" " + + "--confirm --source=\"http://customsource/\"", result.Args); + } + + [Fact] + public void Should_Add_Api_Key_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ApiKey = "1234"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("push \"/Working/existing.nupkg\" --confirm --api-key=\"1234\"", result.Args); + } + + [Theory] + [InlineData("abcdef", "push \"/Working/existing.nupkg\" --confirm --client-code=\"abcdef\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_ClientCode_Flag_To_Arguments_If_Set(string clientCode, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.ClientCode = clientCode; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("https://test.com", "push \"/Working/existing.nupkg\" --confirm --redirect-url=\"https://test.com\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_RedirectUrl_Flag_To_Arguments_If_Set(string redirectUrl, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.RedirectUrl = redirectUrl; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("IntuneA", "push \"/Working/existing.nupkg\" --confirm --endpoint=\"IntuneA\"")] + [InlineData(null, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_Endpoint_Flag_To_Arguments_If_Set(string endPoint, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.EndPoint = endPoint; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "push \"/Working/existing.nupkg\" --confirm --skip-cleanup")] + [InlineData(false, "push \"/Working/existing.nupkg\" --confirm")] + public void Should_Add_SkipCleanup_Flag_To_Arguments_If_Set(bool skipCleanup, string expected) + { + // Given + var fixture = new ChocolateyPusherFixture(); + fixture.Settings.SkipCleanup = skipCleanup; + + // When + var result = fixture.Run(); + // Then Assert.Equal(expected, result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Sources/ChocolateySourcesTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Sources/ChocolateySourcesTests.cs index 67f020ecaa..a25332dedf 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Sources/ChocolateySourcesTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Sources/ChocolateySourcesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Sources; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -84,7 +85,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -98,7 +99,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -124,12 +125,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("source add -n \"name\" -s \"source\" -y", result.Args); + Assert.Equal("source add --name=\"name\" --source=\"source\" --confirm", result.Args); } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -d -y")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --debug --confirm")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -144,8 +145,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -v -y")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --verbose --confirm")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -160,8 +161,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -y -f")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --trace --confirm")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --no-color --confirm")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --accept-license --confirm")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --force")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -176,8 +225,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -y --noop")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --what-if")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -192,8 +241,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -y -r")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --limit-output")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -208,8 +257,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "source add -n \"name\" -s \"source\" -y --execution-timeout \"5\"")] - [InlineData(0, "source add -n \"name\" -s \"source\" -y")] + [InlineData(5, "source add --name=\"name\" --source=\"source\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -224,8 +273,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "source add -n \"name\" -s \"source\" -y -c \"c:\\temp\"")] - [InlineData("", "source add -n \"name\" -s \"source\" -y")] + [InlineData(@"c:\temp", "source add --name=\"name\" --source=\"source\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -240,8 +289,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "source add -n \"name\" -s \"source\" -y --allowunofficial")] - [InlineData(false, "source add -n \"name\" -s \"source\" -y")] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --allow-unofficial")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -256,13 +305,13 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi } [Theory] - [InlineData(2, "source add -n \"name\" -s \"source\" -y --priority \"2\"")] - [InlineData(0, "source add -n \"name\" -s \"source\" -y")] - public void Should_Add_Priority_To_Arguments_If_Set(int priority, string expected) + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --fail-on-error-output")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) { // Given var fixture = new ChocolateyAddSourceFixture(); - fixture.Settings.Priority = priority; + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; // When var result = fixture.Run(); @@ -270,133 +319,143 @@ public void Should_Add_Priority_To_Arguments_If_Set(int priority, string expecte // Then Assert.Equal(expected, result.Args); } - } - public sealed class TheRemoveSourceMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --use-system-powershell")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings = null; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsArgumentNullException(result, "settings"); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --no-progress")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.GivenDefaultToolDoNotExist(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.NoProgress = noProgress; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] - [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] - public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + [InlineData("proxy", "source add --name=\"name\" --source=\"source\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.Proxy = proxy; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [WindowsTheory] - [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] - public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + [Theory] + [InlineData("proxy-user", "source add --name=\"name\" --source=\"source\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.ProxyUser = proxyUser; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() + [Theory] + [InlineData("proxy-password", "source add --name=\"name\" --source=\"source\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.GivenProcessCannotStart(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.ProxyPassword = proxyPassword; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + [Theory] + [InlineData("proxy1,proxy2", "source add --name=\"name\" --source=\"source\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.GivenProcessExitsWithCode(1); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; // When var result = fixture.Run(); // Then - Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Add_Mandatory_Arguments() + [Theory] + [InlineData("./output.log", "source add --name=\"name\" --source=\"source\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.LogFile = logFilePath; // When var result = fixture.Run(); // Then - Assert.Equal("source remove -n \"name\" -y", result.Args); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData(true, "source remove -n \"name\" -d -y")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --skip-compatibility-checks")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.Debug = debug; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; // When var result = fixture.Run(); @@ -406,13 +465,13 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "source remove -n \"name\" -v -y")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + [InlineData("user1", "source add --name=\"name\" --source=\"source\" --confirm --user=\"user1\"")] + [InlineData("", "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_User_To_Arguments_If_Set(string user, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.Verbose = verbose; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.UserName = user; // When var result = fixture.Run(); @@ -422,13 +481,13 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "source remove -n \"name\" -y -f")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + [InlineData("password1", "source add --name=\"name\" --source=\"source\" --confirm --password=\"password1\"")] + [InlineData("", "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.Force = force; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.Password = password; // When var result = fixture.Run(); @@ -438,13 +497,13 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "source remove -n \"name\" -y --noop")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + [InlineData("./mycert.pfx", "source add --name=\"name\" --source=\"source\" --confirm --cert=\"/Working/mycert.pfx\"")] + [InlineData(null, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Cert_To_Arguments_If_Set(string certificate, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.Noop = noop; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.Certificate = certificate; // When var result = fixture.Run(); @@ -454,13 +513,13 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "source remove -n \"name\" -y -r")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + [InlineData("certpassword", "source add --name=\"name\" --source=\"source\" --confirm --certpassword=\"certpassword\"")] + [InlineData("", "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_CertPassword_To_Arguments_If_Set(string certificatePassword, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.LimitOutput = limitOutput; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.CertificatePassword = certificatePassword; // When var result = fixture.Run(); @@ -470,13 +529,13 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "source remove -n \"name\" -y --execution-timeout \"5\"")] - [InlineData(0, "source remove -n \"name\" -y")] - public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + [InlineData(2, "source add --name=\"name\" --source=\"source\" --confirm --priority=\"2\"")] + [InlineData(0, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_Priority_To_Arguments_If_Set(int priority, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.ExecutionTimeout = executionTimeout; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.Priority = priority; // When var result = fixture.Run(); @@ -486,13 +545,13 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "source remove -n \"name\" -y -c \"c:\\temp\"")] - [InlineData("", "source remove -n \"name\" -y")] - public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --bypass-proxy")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_ByPassProxy_Flag_To_Arguments_If_Set(bool byPassProxy, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.CacheLocation = cacheLocation; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.ByPassProxy = byPassProxy; // When var result = fixture.Run(); @@ -502,13 +561,29 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "source remove -n \"name\" -y --allowunofficial")] - [InlineData(false, "source remove -n \"name\" -y")] - public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --allow-self-service")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_AllowSelfService_Flag_To_Arguments_If_Set(bool allowSelfService, string expected) { // Given - var fixture = new ChocolateyRemoveSourceFixture(); - fixture.Settings.AllowUnofficial = allowUnofficial; + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.AllowSelfService = allowSelfService; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source add --name=\"name\" --source=\"source\" --confirm --admin-only")] + [InlineData(false, "source add --name=\"name\" --source=\"source\" --confirm")] + public void Should_Add_AdminOnly_Flag_To_Arguments_If_Set(bool adminOnly, string expected) + { + // Given + var fixture = new ChocolateyAddSourceFixture(); + fixture.Settings.AdminOnly = adminOnly; // When var result = fixture.Run(); @@ -518,34 +593,34 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi } } - public sealed class TheEnableSourceMethod + public sealed class TheRemoveSourceMethod { [Fact] public void Should_Throw_If_Settings_Are_Null() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings = null; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.GivenDefaultToolDoNotExist(); // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -554,7 +629,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -570,7 +645,7 @@ public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string t public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -585,35 +660,35 @@ public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windo public void Should_Throw_If_Process_Was_Not_Started() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.GivenProcessCannotStart(); // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.GivenProcessExitsWithCode(1); // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); // When var result = fixture.Run(); @@ -626,22 +701,22 @@ public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() public void Should_Add_Mandatory_Arguments() { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); // When var result = fixture.Run(); // Then - Assert.Equal("source enable -n \"name\" -y", result.Args); + Assert.Equal("source remove --name=\"name\" --confirm", result.Args); } [Theory] - [InlineData(true, "source enable -n \"name\" -d -y")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --debug --confirm")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.Debug = debug; // When @@ -652,12 +727,12 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "source enable -n \"name\" -v -y")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --verbose --confirm")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.Verbose = verbose; // When @@ -668,12 +743,60 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "source enable -n \"name\" -y -f")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --trace --confirm")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source remove --name=\"name\" --no-color --confirm")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source remove --name=\"name\" --accept-license --confirm")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source remove --name=\"name\" --confirm --force")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.Force = force; // When @@ -684,12 +807,12 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "source enable -n \"name\" -y --noop")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --confirm --what-if")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.Noop = noop; // When @@ -700,12 +823,12 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "source enable -n \"name\" -y -r")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --confirm --limit-output")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.LimitOutput = limitOutput; // When @@ -716,12 +839,12 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "source enable -n \"name\" -y --execution-timeout \"5\"")] - [InlineData(0, "source enable -n \"name\" -y")] + [InlineData(5, "source remove --name=\"name\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "source remove --name=\"name\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.ExecutionTimeout = executionTimeout; // When @@ -732,12 +855,12 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "source enable -n \"name\" -y -c \"c:\\temp\"")] - [InlineData("", "source enable -n \"name\" -y")] + [InlineData(@"c:\temp", "source remove --name=\"name\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "source remove --name=\"name\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.CacheLocation = cacheLocation; // When @@ -748,12 +871,12 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "source enable -n \"name\" -y --allowunofficial")] - [InlineData(false, "source enable -n \"name\" -y")] + [InlineData(true, "source remove --name=\"name\" --confirm --allow-unofficial")] + [InlineData(false, "source remove --name=\"name\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given - var fixture = new ChocolateyEnableSourceFixture(); + var fixture = new ChocolateyRemoveSourceFixture(); fixture.Settings.AllowUnofficial = allowUnofficial; // When @@ -762,56 +885,670 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // Then Assert.Equal(expected, result.Args); } - } - public sealed class TheDisableSourceMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() + [Theory] + [InlineData(true, "source remove --name=\"name\" --confirm --fail-on-error-output")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) { // Given - var fixture = new ChocolateyDisableSourceFixture(); - fixture.Settings = null; + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsArgumentNullException(result, "settings"); + Assert.Equal(expected, result.Args); } - [Fact] - public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + [Theory] + [InlineData(true, "source remove --name=\"name\" --confirm --use-system-powershell")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) { // Given - var fixture = new ChocolateyDisableSourceFixture(); - fixture.GivenDefaultToolDoNotExist(); + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + Assert.Equal(expected, result.Args); } [Theory] - [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] - [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] - public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + [InlineData(true, "source remove --name=\"name\" --confirm --no-progress")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) { // Given - var fixture = new ChocolateyDisableSourceFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.NoProgress = noProgress; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Path.FullPath); + Assert.Equal(expected, result.Args); } - [WindowsTheory] + [Theory] + [InlineData("proxy", "source remove --name=\"name\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "source remove --name=\"name\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "source remove --name=\"name\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "source remove --name=\"name\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "source remove --name=\"name\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "source remove --name=\"name\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "source remove --name=\"name\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "source remove --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source remove --name=\"name\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "source remove --name=\"name\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "source remove --name=\"name\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source remove --name=\"name\" --confirm --skip-compatibility-checks")] + [InlineData(false, "source remove --name=\"name\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyRemoveSourceFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + + public sealed class TheEnableSourceMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("source enable --name=\"name\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --debug --confirm")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --verbose --confirm")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --trace --confirm")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --no-color --confirm")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --accept-license --confirm")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --force")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --what-if")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --limit-output")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "source enable --name=\"name\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "source enable --name=\"name\" --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "source enable --name=\"name\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "source enable --name=\"name\" --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --allow-unofficial")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --fail-on-error-output")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --use-system-powershell")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --no-progress")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "source enable --name=\"name\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "source enable --name=\"name\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "source enable --name=\"name\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "source enable --name=\"name\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "source enable --name=\"name\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "source enable --name=\"name\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "source enable --name=\"name\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "source enable --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "source enable --name=\"name\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "source enable --name=\"name\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source enable --name=\"name\" --confirm --skip-compatibility-checks")] + [InlineData(false, "source enable --name=\"name\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyEnableSourceFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + + public sealed class TheDisableSourceMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { @@ -838,7 +1575,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -852,7 +1589,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -878,12 +1615,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("source disable -n \"name\" -y", result.Args); + Assert.Equal("source disable --name=\"name\" --confirm", result.Args); } [Theory] - [InlineData(true, "source disable -n \"name\" -d -y")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --debug --confirm")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -898,8 +1635,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "source disable -n \"name\" -v -y")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --verbose --confirm")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -914,8 +1651,56 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "source disable -n \"name\" -y -f")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --trace --confirm")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --no-color --confirm")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --accept-license --confirm")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --force")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -930,8 +1715,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "source disable -n \"name\" -y --noop")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --confirm --what-if")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -946,8 +1731,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "source disable -n \"name\" -y -r")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --confirm --limit-output")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -962,8 +1747,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "source disable -n \"name\" -y --execution-timeout \"5\"")] - [InlineData(0, "source disable -n \"name\" -y")] + [InlineData(5, "source disable --name=\"name\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "source disable --name=\"name\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -978,8 +1763,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "source disable -n \"name\" -y -c \"c:\\temp\"")] - [InlineData("", "source disable -n \"name\" -y")] + [InlineData(@"c:\temp", "source disable --name=\"name\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "source disable --name=\"name\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -994,8 +1779,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "source disable -n \"name\" -y --allowunofficial")] - [InlineData(false, "source disable -n \"name\" -y")] + [InlineData(true, "source disable --name=\"name\" --confirm --allow-unofficial")] + [InlineData(false, "source disable --name=\"name\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -1008,6 +1793,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --fail-on-error-output")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --use-system-powershell")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --no-progress")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "source disable --name=\"name\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "source disable --name=\"name\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "source disable --name=\"name\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "source disable --name=\"name\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "source disable --name=\"name\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "source disable --name=\"name\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "source disable --name=\"name\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "source disable --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "source disable --name=\"name\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "source disable --name=\"name\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "source disable --name=\"name\" --confirm --skip-compatibility-checks")] + [InlineData(false, "source disable --name=\"name\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyDisableSourceFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerSettingsTests.cs new file mode 100644 index 0000000000..d97ded4e53 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerSettingsTests.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.Chocolatey.Uninstall; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Chocolatey.Uninstall +{ + public sealed class ChocolateyUninstallerSettingsTests + { + [Fact] + public void Should_Set_Global_Arguments_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.ApplyInstallArgumentsToDependencies); + } + + [Fact] + public void Should_Set_Global_Package_Arguments_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.ApplyPackageParametersToDependencies); + } + + [Fact] + public void Should_Set_All_Versions_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.AllVersions); + } + + [Fact] + public void Should_Set_Ignore_Package_Exit_Codes_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.IgnorePackageExitCodes); + } + + [Fact] + public void Should_Set_Use_Package_Exit_Codes_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.UsePackageExitCodes); + } + + [Fact] + public void Should_Set_Auto_Uninstaller_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.UseAutoUninstaller); + } + + [Fact] + public void Should_Set_Skip_Auto_Uninstaller_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.SkipAutoUninstaller); + } + + [Fact] + public void Should_Set_Fail_On_Auto_Uninstaller_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.FailOnAutoUninstaller); + } + + [Fact] + public void Should_Set_Ignore_Auto_Uninstaller_To_False_By_Default() + { + // Given, When + var settings = new ChocolateyUninstallSettings(); + + // Then + Assert.False(settings.IgnoreAutoUninstallerFailure); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerTests.cs new file mode 100644 index 0000000000..c42a8b037b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Uninstall/ChocolateyUninstallerTests.cs @@ -0,0 +1,848 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.ApiKey; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Installer; +using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Uninstaller; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Chocolatey.Uninstall +{ + public sealed class ChocolateyUninstallerTests + { + public sealed class TheUninstallMethod + { + [Fact] + public void Should_Throw_If_Traget_Package_Id_Is_Null() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.PackageIds = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageIds"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/chocolatey/choco.exe", "/bin/chocolatey/choco.exe")] + [InlineData("./chocolatey/choco.exe", "/Working/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/ProgramData/chocolatey/choco.exe", "C:/ProgramData/chocolatey/choco.exe")] + public void Should_Use_Chocolatey_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_Chocolatey_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/choco.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("uninstall \"Cake\" --confirm", result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --debug --confirm")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Debug = debug; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --verbose --confirm")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Verbose = verbose; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --trace --confirm")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --no-color --confirm")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --accept-license --confirm")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.AcceptLicense = acceptLicense; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --force")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Force = force; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --what-if")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Noop = noop; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --limit-output")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.LimitOutput = limitOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "uninstall \"Cake\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "uninstall \"Cake\" --confirm")] + public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ExecutionTimeout = executionTimeout; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(@"c:\temp", "uninstall \"Cake\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "uninstall \"Cake\" --confirm")] + public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.CacheLocation = cacheLocation; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --allow-unofficial")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.AllowUnofficial = allowUnofficial; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --fail-on-error-output")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --use-system-powershell")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --no-progress")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "uninstall \"Cake\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "uninstall \"Cake\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "uninstall \"Cake\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "uninstall \"Cake\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "uninstall \"Cake\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "uninstall \"Cake\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "uninstall \"Cake\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "uninstall \"Cake\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "uninstall \"Cake\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "uninstall \"Cake\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --skip-compatibility-checks")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Source_To_Arguments_If_Set() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Source = "A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("uninstall \"Cake\" --confirm --source=\"A\"", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Not_Null() + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.Version = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("uninstall \"Cake\" --confirm --version=\"1.0.0\"", result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --all-versions")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_AllVersions_To_Arguments_If_Set(bool allVersions, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.AllVersions = allVersions; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("arg1, arg2", "uninstall \"Cake\" --confirm --uninstall-arguments=\"arg1, arg2\"")] + [InlineData("", "uninstall \"Cake\" --confirm")] + public void Should_Add_UninstallArguments_Flag_To_Arguments_If_Set(string uninstallArguments, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.UninstallArguments = uninstallArguments; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --override-arguments")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideArguments, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.OverrideArguments = overrideArguments; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --not-silent")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.NotSilent = notSilent; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("param1", "uninstall \"Cake\" --confirm --package-parameters=\"param1\"")] + [InlineData("", "uninstall \"Cake\" --confirm")] + public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParameters, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.PackageParameters = packageParameters; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --apply-install-arguments-to-dependencies")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_ApplyInstallArgumentsToDependencies_To_Arguments_If_Set(bool applyInstallArgumentsToDependencies, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ApplyInstallArgumentsToDependencies = applyInstallArgumentsToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --apply-package-parameters-to-dependencies")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_ApplyPackageParametersToDependencies_To_Arguments_If_Set(bool applyPackageParametersToDependencies, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ApplyPackageParametersToDependencies = applyPackageParametersToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --side-by-side")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.SideBySide = sideBySide; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --force-dependencies")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_ForceDependencies_Flag_To_Arguments_If_Set(bool forceDependencies, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ForceDependencies = forceDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --skip-automation-scripts")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShell, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.SkipPowerShell = skipPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --ignore-package-exit-codes")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_IgnorePackageExitCoedes_To_Arguments_If_Set(bool ignorePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.IgnorePackageExitCodes = ignorePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --use-package-exit-codes")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_UsePackageExitCodes_To_Arguments_If_Set(bool usePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.UsePackageExitCodes = usePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --use-autouninstaller")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_UseAutoUninstaller_To_Arguments_If_Set(bool useAutoUninstaller, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.UseAutoUninstaller = useAutoUninstaller; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --skip-autouninstaller")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_SkipAutoUninstaller_To_Arguments_If_Set(bool skipAutoUninstaller, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.SkipAutoUninstaller = skipAutoUninstaller; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --fail-on-autouninstaller")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_FailOnAutoUninstaller_To_Arguments_If_Set(bool failOnAutoUninstaller, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.FailOnAutoUninstaller = failOnAutoUninstaller; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --ignore-autouninstaller-failure")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_IgnoreAutoUninstallerFailure_To_Arguments_If_Set(bool ignoreAutoUninstaller, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.IgnoreAutoUninstallerFailure = ignoreAutoUninstaller; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --stop-on-first-package-failure")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_StopOnFirstFailure_Flag_To_Arguments_If_Set(bool stopOnFirstFailure, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.StopOnFirstFailure = stopOnFirstFailure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --exit-when-reboot-detected")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_ExitWhenRebootDetected_Flag_To_Arguments_If_Set(bool exitWhenRebootDetected, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.ExitWhenRebootDetected = exitWhenRebootDetected; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --ignore-detected-reboot")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_IgnoreDetectedReboot_Flag_To_Arguments_If_Set(bool ignoreDetectedReboot, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.IgnoreDetectedReboot = ignoreDetectedReboot; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --skip-hooks")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_SkipHooks_Flag_To_Arguments_If_Set(bool skipHooks, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.SkipHooks = skipHooks; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "uninstall \"Cake\" --confirm --from-programs-and-features")] + [InlineData(false, "uninstall \"Cake\" --confirm")] + public void Should_Add_FromProgramsAndFeatures_Flag_To_Arguments_If_Set(bool fromProgramsAndFeatures, string expected) + { + // Given + var fixture = new ChocolateyUninstallerFixture(); + fixture.Settings.FromProgramsAndFeatures = fromProgramsAndFeatures; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettingsTests.cs index e204a7714a..957e512936 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.Chocolatey.Upgrade; using Xunit; @@ -18,4 +19,4 @@ public void Should_Set_Prerelease_To_False_By_Default() Assert.False(settings.Prerelease); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgraderTests.cs b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgraderTests.cs index 9267d1a878..f8da86e23e 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgraderTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Chocolatey/Upgrade/ChocolateyUpgraderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.Chocolatey.Upgrade; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_Target_Package_Id_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packageId"); + AssertEx.IsArgumentNullException(result, "packageId"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -51,7 +52,7 @@ public void Should_Throw_If_Chocolatey_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Could not locate executable."); + AssertEx.IsCakeException(result, "Chocolatey: Could not locate executable."); } [Theory] @@ -98,7 +99,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process was not started."); + AssertEx.IsCakeException(result, "Chocolatey: Process was not started."); } [Fact] @@ -112,7 +113,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Chocolatey: Process returned an error (exit code 1)."); } [Fact] @@ -138,12 +139,12 @@ public void Should_Add_Mandatory_Arguments() var result = fixture.Run(); // Then - Assert.Equal("upgrade \"Cake\" -y", result.Args); + Assert.Equal("upgrade \"Cake\" --confirm", result.Args); } [Theory] - [InlineData(true, "upgrade \"Cake\" -d -y")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --debug --confirm")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expected) { // Given @@ -158,8 +159,8 @@ public void Should_Add_Debug_Flag_To_Arguments_If_Set(bool debug, string expecte } [Theory] - [InlineData(true, "upgrade \"Cake\" -v -y")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --verbose --confirm")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string expected) { // Given @@ -174,8 +175,40 @@ public void Should_Add_Verbose_Flag_To_Arguments_If_Set(bool verbose, string exp } [Theory] - [InlineData(true, "upgrade \"Cake\" --acceptLicense -y")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --trace --confirm")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_Trace_Flag_To_Arguments_If_Set(bool trace, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Trace = trace; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --no-color --confirm")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_NoColor_Flag_To_Arguments_If_Set(bool noColor, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.NoColor = noColor; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --accept-license --confirm")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense, string expected) { // Given @@ -190,8 +223,8 @@ public void Should_Add_AcceptLicense_Flag_To_Arguments_If_Set(bool acceptLicense } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -f")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --force")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expected) { // Given @@ -206,8 +239,8 @@ public void Should_Add_Force_Flag_To_Arguments_If_Set(bool force, string expecte } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --noop")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --what-if")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) { // Given @@ -222,8 +255,8 @@ public void Should_Add_Noop_Flag_To_Arguments_If_Set(bool noop, string expected) } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -r")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --limit-output")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, string expected) { // Given @@ -238,8 +271,8 @@ public void Should_Add_LimitOutput_Flag_To_Arguments_If_Set(bool limitOutput, st } [Theory] - [InlineData(5, "upgrade \"Cake\" -y --execution-timeout \"5\"")] - [InlineData(0, "upgrade \"Cake\" -y")] + [InlineData(5, "upgrade \"Cake\" --confirm --execution-timeout=\"5\"")] + [InlineData(0, "upgrade \"Cake\" --confirm")] public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout, string expected) { // Given @@ -254,8 +287,8 @@ public void Should_Add_ExecutionTimeout_To_Arguments_If_Set(int executionTimeout } [Theory] - [InlineData(@"c:\temp", "upgrade \"Cake\" -y -c \"c:\\temp\"")] - [InlineData("", "upgrade \"Cake\" -y")] + [InlineData(@"c:\temp", "upgrade \"Cake\" --confirm --cache-location=\"c:\\temp\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocation, string expected) { // Given @@ -270,8 +303,8 @@ public void Should_Add_CacheLocation_Flag_To_Arguments_If_Set(string cacheLocati } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --allowunofficial")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --allow-unofficial")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnofficial, string expected) { // Given @@ -285,6 +318,166 @@ public void Should_Add_AllowUnofficial_Flag_To_Arguments_If_Set(bool allowUnoffi Assert.Equal(expected, result.Args); } + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --fail-on-error-output")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_FailOnErrorOutput_Flag_To_Arguments_If_Set(bool failOnErrorOutput, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.FailOnErrorOutput = failOnErrorOutput; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --use-system-powershell")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_UseSystemPowerShell_Flag_To_Arguments_If_Set(bool useSystemPowerShell, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.UseSystemPowerShell = useSystemPowerShell; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --no-progress")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_NoProgress_Flag_To_Arguments_If_Set(bool noProgress, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.NoProgress = noProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy", "upgrade \"Cake\" --confirm --proxy=\"proxy\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Proxy_Flag_To_Arguments_If_Set(string proxy, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Proxy = proxy; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-user", "upgrade \"Cake\" --confirm --proxy-user=\"proxy-user\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_ProxyUser_Flag_To_Arguments_If_Set(string proxyUser, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ProxyUser = proxyUser; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy-password", "upgrade \"Cake\" --confirm --proxy-password=\"proxy-password\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_ProxyPassword_Flag_To_Arguments_If_Set(string proxyPassword, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ProxyPassword = proxyPassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("proxy1,proxy2", "upgrade \"Cake\" --confirm --proxy-bypass-list=\"proxy1,proxy2\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_ProxyByPassList_Flag_To_Arguments_If_Set(string proxyByPassList, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ProxyByPassList = proxyByPassList; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --proxy-bypass-on-local")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ProxyByPassOnLocal_Flag_To_Arguments_If_Set(bool proxyBypassOnLocal, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ProxyBypassOnLocal = proxyBypassOnLocal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./output.log", "upgrade \"Cake\" --confirm --log-file=\"/Working/output.log\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Log_File_Flag_To_Arguments_If_Set(string logFilePath, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.LogFile = logFilePath; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-compatibility-checks")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_Skip_Compatibility_Flag_To_Arguments_If_Set(bool skipCompatibiity, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.SkipCompatibilityChecks = skipCompatibiity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + [Fact] public void Should_Add_Source_To_Arguments_If_Set() { @@ -296,7 +489,7 @@ public void Should_Add_Source_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("upgrade \"Cake\" -y -s \"A\"", result.Args); + Assert.Equal("upgrade \"Cake\" --confirm --source=\"A\"", result.Args); } [Fact] @@ -310,12 +503,12 @@ public void Should_Add_Version_To_Arguments_If_Not_Null() var result = fixture.Run(); // Then - Assert.Equal("upgrade \"Cake\" -y --version \"1.0.0\"", result.Args); + Assert.Equal("upgrade \"Cake\" --confirm --version=\"1.0.0\"", result.Args); } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --pre")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --pre")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, string expected) { // Given @@ -330,8 +523,8 @@ public void Should_Add_Prerelease_Flag_To_Arguments_If_Set(bool prerelease, stri } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --x86")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --forcex86")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string expected) { // Given @@ -346,8 +539,8 @@ public void Should_Add_Forcex86_Flag_To_Arguments_If_Set(bool forcex86, string e } [Theory] - [InlineData("args1", "upgrade \"Cake\" -y --ia \"args1\"")] - [InlineData("", "upgrade \"Cake\" -y")] + [InlineData("args1", "upgrade \"Cake\" --confirm --install-arguments=\"args1\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, string expected) { // Given @@ -362,8 +555,8 @@ public void Should_Add_InstallArguments_To_Arguments_If_Set(string installArgs, } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -o")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --override-arguments")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideArguments, string expected) { // Given @@ -378,8 +571,8 @@ public void Should_Add_OverrideArguments_Flag_To_Arguments_If_Set(bool overrideA } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --notSilent")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --not-silent")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string expected) { // Given @@ -394,8 +587,8 @@ public void Should_Add_NotSilent_Flag_To_Arguments_If_Set(bool notSilent, string } [Theory] - [InlineData("param1", "upgrade \"Cake\" -y --params \"param1\"")] - [InlineData("", "upgrade \"Cake\" -y")] + [InlineData("param1", "upgrade \"Cake\" --confirm --package-parameters=\"param1\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParameters, string expected) { // Given @@ -410,8 +603,40 @@ public void Should_Add_PackageParameters_To_Arguments_If_Set(string packageParam } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --allowdowngrade")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --apply-install-arguments-to-dependencies")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ApplyInstallArgumentsToDependencies_To_Arguments_If_Set(bool applyInstallArgumentsToDependencies, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ApplyInstallArgumentsToDependencies = applyInstallArgumentsToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --apply-package-parameters-to-dependencies")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ApplyPackageParametersToDependencies_To_Arguments_If_Set(bool applyPackageParametersToDependencies, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ApplyPackageParametersToDependencies = applyPackageParametersToDependencies; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --allow-downgrade")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngrade, string expected) { // Given @@ -426,8 +651,8 @@ public void Should_Add_AllowDowngrade_Flag_To_Arguments_If_Set(bool allowDowngra } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -m")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --side-by-side")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, string expected) { // Given @@ -442,8 +667,8 @@ public void Should_Add_SideBySide_Flag_To_Arguments_If_Set(bool sideBySide, stri } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -i")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-dependencies")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDependencies, string expected) { // Given @@ -458,8 +683,8 @@ public void Should_Add_IgnoreDependencies_Flag_To_Arguments_If_Set(bool ignoreDe } [Theory] - [InlineData(true, "upgrade \"Cake\" -y -n")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-automation-scripts")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShell, string expected) { // Given @@ -474,8 +699,8 @@ public void Should_Add_SkipPowerShell_Flag_To_Arguments_If_Set(bool skipPowerShe } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --failonunfound")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --fail-on-unfound")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_FailOnUnfound_Flag_To_Arguments_If_Set(bool failOnUnfound, string expected) { // Given @@ -490,8 +715,24 @@ public void Should_Add_FailOnUnfound_Flag_To_Arguments_If_Set(bool failOnUnfound } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --failonnotinstalled")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-unfound")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_IgnoreUnfound_Flag_To_Arguments_If_Set(bool ignoreUnfound, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.IgnoreUnfound = ignoreUnfound; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --fail-on-not-installed")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_FailOnNotInstalled_Flag_To_Arguments_If_Set(bool failOnNotInstalled, string expected) { // Given @@ -506,8 +747,8 @@ public void Should_Add_FailOnNotInstalled_Flag_To_Arguments_If_Set(bool failOnNo } [Theory] - [InlineData("user1", "upgrade \"Cake\" -y -u \"user1\"")] - [InlineData("", "upgrade \"Cake\" -y")] + [InlineData("user1", "upgrade \"Cake\" --confirm --user=\"user1\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] public void Should_Add_User_To_Arguments_If_Set(string user, string expected) { // Given @@ -522,8 +763,8 @@ public void Should_Add_User_To_Arguments_If_Set(string user, string expected) } [Theory] - [InlineData("password1", "upgrade \"Cake\" -y -p \"password1\"")] - [InlineData("", "upgrade \"Cake\" -y")] + [InlineData("password1", "upgrade \"Cake\" --confirm --password=\"password1\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] public void Should_Add_Password_To_Arguments_If_Set(string password, string expected) { // Given @@ -538,8 +779,40 @@ public void Should_Add_Password_To_Arguments_If_Set(string password, string expe } [Theory] - [InlineData(true, "upgrade \"Cake\" -y --ignorechecksums")] - [InlineData(false, "upgrade \"Cake\" -y")] + [InlineData("./mycert.pfx", "upgrade \"Cake\" --confirm --cert=\"/Working/mycert.pfx\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Cert_To_Arguments_If_Set(string certificate, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Certificate = certificate; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("certpassword", "upgrade \"Cake\" --confirm --certpassword=\"certpassword\"")] + [InlineData("", "upgrade \"Cake\" --confirm")] + public void Should_Add_CertPassword_To_Arguments_If_Set(string certificatePassword, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.CertificatePassword = certificatePassword; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-checksums")] + [InlineData(false, "upgrade \"Cake\" --confirm")] public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreChecksums, string expected) { // Given @@ -552,6 +825,582 @@ public void Should_Add_IgnoreChecksums_Flag_To_Arguments_If_Set(bool ignoreCheck // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --allow-empty-checksums")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_AllowEmptyChecksums_Flag_To_Arguments_If_Set(bool allowEmptyChecksums, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.AllowEmptyChecksums = allowEmptyChecksums; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --allow-empty-checksums-secure")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_AllowEmptyChecksumsSecure_Flag_To_Arguments_If_Set(bool allowEmptyChecksumsSecure, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.AllowEmptyChecksumsSecure = allowEmptyChecksumsSecure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --require-checksums")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_RequireChecksums_Flag_To_Arguments_If_Set(bool requireChecksums, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.RequireChecksums = requireChecksums; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "upgrade \"Cake\" --confirm --download-checksum=\"abcdef\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Checksum_Flag_To_Arguments_If_Set(string checksum, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Checksum = checksum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("abcdef", "upgrade \"Cake\" --confirm --download-checksum-x64=\"abcdef\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Checksum64_Flag_To_Arguments_If_Set(string checksum64, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Checksum64 = checksum64; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "upgrade \"Cake\" --confirm --download-checksum-type=\"md5\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_ChecksumType_Flag_To_Arguments_If_Set(string checkSumType, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ChecksumType = checkSumType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("md5", "upgrade \"Cake\" --confirm --download-checksum-type-x64=\"md5\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_ChecksumType64_Flag_To_Arguments_If_Set(string checkSumType64, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ChecksumType64 = checkSumType64; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-package-exit-codes")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_IgnorePackageExitCodes_Flag_To_Arguments_If_Set(bool ignorePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.IgnorePackageExitCodes = ignorePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --use-package-exit-codes")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_UsePackageExitCodes_Flag_To_Arguments_If_Set(bool usePackageExitCodes, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.UsePackageExitCodes = usePackageExitCodes; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("packageA,packageB", "upgrade \"Cake\" --confirm --except=\"packageA,packageB\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_Except_Flag_To_Arguments_If_Set(string except, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Except = except; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --stop-on-first-package-failure")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_StopOnFirstFailure_Flag_To_Arguments_If_Set(bool stopOnFirstFailure, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.StopOnFirstFailure = stopOnFirstFailure; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-if-not-installed")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_SkipIfNotInstalled_Flag_To_Arguments_If_Set(bool skipIfNotInstalled, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.SkipIfNotInstalled = skipIfNotInstalled; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --install-if-not-installed")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_InstallIfNotInstalled_Flag_To_Arguments_If_Set(bool installIfNotInstalled, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.InstallIfNotInstalled = installIfNotInstalled; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --exclude-prerelease")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ExcludePrerelease_Flag_To_Arguments_If_Set(bool excludePrerelease, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ExcludePrerelease = excludePrerelease; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --use-remembered-arguments")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_UseRememberedArguments_Flag_To_Arguments_If_Set(bool useRememberedArguments, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.UseRememberedArguments = useRememberedArguments; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-remembered-arguments")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_IgnoreRememeredArguments_Flag_To_Arguments_If_Set(bool ignoreRememeredArguments, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.IgnoreRememeredArguments = ignoreRememeredArguments; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --exit-when-reboot-detected")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ExitWhenRebootDetected_Flag_To_Arguments_If_Set(bool exitWhenRebootDetected, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ExitWhenRebootDetected = exitWhenRebootDetected; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --ignore-detected-reboot")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_IgnoreDetectedReboot_Flag_To_Arguments_If_Set(bool ignoreDetectedReboot, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.IgnoreDetectedReboot = ignoreDetectedReboot; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --disable-repository-optimizations")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_DisableRepositoryOptimizations_Flag_To_Arguments_If_Set(bool disableRepositoryOptimizations, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.DisableRepositoryOptimizations = disableRepositoryOptimizations; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --pin-package")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_Pin_Flag_To_Arguments_If_Set(bool pin, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.Pin = pin; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-hooks")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_SkipHooks_Flag_To_Arguments_If_Set(bool skipHooks, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.SkipHooks = skipHooks; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-download-cache")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_SkipDownloadCache_Flag_To_Arguments_If_Set(bool skipDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.SkipDownloadCache = skipDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --use-download-cache")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_UseDownloadCache_Flag_To_Arguments_If_Set(bool useDownloadCache, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.UseDownloadCache = useDownloadCache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --skip-virus-check")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_SkipVirusCheck_Flag_To_Arguments_If_Set(bool skipVirusCheck, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.SkipVirusCheck = skipVirusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --virus-check")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_VirusCheck_Flag_To_Arguments_If_Set(bool virusCheck, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.VirusCheck = virusCheck; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "upgrade \"Cake\" --confirm --virus-positives-minimum=\"5\"")] + [InlineData(0, "upgrade \"Cake\" --confirm")] + public void Should_Add_VirusPositivesMinimum_To_Arguments_If_Set(int virusPositivesMinimum, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.VirusPositivesMinimum = virusPositivesMinimum; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "upgrade \"Cake\" --confirm --install-arguments-sensitive=\"super-secret\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_InstallArgumentsSensitive_Flag_To_Arguments_If_Set(string installArgumentsSensitive, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.InstallArgumentsSensitive = installArgumentsSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("super-secret", "upgrade \"Cake\" --confirm --package-parameters-sensitive=\"super-secret\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_PackageParametersSensitive_Flag_To_Arguments_If_Set(string packageParametersSensitive, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.PackageParametersSensitive = packageParametersSensitive; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./install", "upgrade \"Cake\" --confirm --install-directory=\"/Working/install\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_InstallDirectory_Flag_To_Arguments_If_Set(string installDirectory, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.InstallDirectory = installDirectory; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(5, "upgrade \"Cake\" --confirm --maximum-download-bits-per-second=\"5\"")] + [InlineData(0, "upgrade \"Cake\" --confirm")] + public void Should_Add_MaximumDownloadBitsPerSecond_Flag_To_Arguments_If_Set(int maximumDownloadBitsPerSecond, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.MaximumDownloadBitsPerSecond = maximumDownloadBitsPerSecond; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --reduce-package-size")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ReducePackageSize_Flag_To_Arguments_If_Set(bool reducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ReducePackageSize = reducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --no-reduce-package-size")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_NoReducePackageSize_Flag_To_Arguments_If_Set(bool noReducePackageSize, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.NoReducePackageSize = noReducePackageSize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --reduce-nupkg-only")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ReduceNupkgOnly_Flag_To_Arguments_If_Set(bool reduceNupkgOnly, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ReduceNupkgOnly = reduceNupkgOnly; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --exclude-chocolatey-packages-during-upgrade-all")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_ExcludeChocolateyPackagesDuringUpgradeAll_Flag_To_Arguments_If_Set(bool excludeChocolateyPackagesDuringUpgradeAll, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.ExcludeChocolateyPackagesDuringUpgradeAll = excludeChocolateyPackagesDuringUpgradeAll; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(true, "upgrade \"Cake\" --confirm --include-chocolatey-packages-during-upgrade-all")] + [InlineData(false, "upgrade \"Cake\" --confirm")] + public void Should_Add_IncludeChocolateyPackagesDuringUpgradeAll_Flag_To_Arguments_If_Set(bool includeChocolateyPackagesDuringUpgradeAll, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.IncludeChocolateyPackagesDuringUpgradeAll = includeChocolateyPackagesDuringUpgradeAll; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("Just because", "upgrade \"Cake\" --confirm --pin-reason=\"Just because\"")] + [InlineData(null, "upgrade \"Cake\" --confirm")] + public void Should_Add_PinReason_Flag_To_Arguments_If_Set(string pinReason, string expected) + { + // Given + var fixture = new ChocolateyUpgraderFixture(); + fixture.Settings.PinReason = pinReason; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.cs new file mode 100644 index 0000000000..58c423a434 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/Command/CommandRunnerTests.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.Command; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.Command +{ + public sealed class CommandRunnerTests + { + public sealed class TheRunCommandMethod + { + [Fact] + public void Should_Throw_If_Arguments_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + Arguments = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "arguments"); + } + + [Fact] + public void Should_Throw_If_Settings_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + Settings = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_ToolName_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolName = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolName"); + } + } + + [Fact] + public void Should_Throw_If_ToolExecutableNames_Was_Null() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolExecutableNames = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolExecutableNames"); + } + + [Fact] + public void Should_Throw_If_ToolExecutableNames_Was_Empty() + { + // Given + var fixture = new CommandRunnerFixture + { + ToolExecutableNames = Array.Empty() + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "ToolExecutableNames"); + } + + [Fact] + public void Should_Call_Settings_PostAction() + { + // Given + var called = false; + var fixture = new CommandRunnerFixture + { + Settings = { PostAction = _ => called = true } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.True(called, "Settings PostAction not called"); + } + + [Fact] + public void Should_Return_StandardOutput() + { + // Given + const string expectedStandardOutput = "LINE1"; + const int expectedExitCode = 0; + + var fixture = new CommandRunnerStandardOutputFixture() + .GivenStandardOutput(expectedStandardOutput); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardOutput_ExitCode() + { + // Given + const string expectedStandardOutput = "LINE1"; + const int expectedExitCode = 1337; + + var fixture = new CommandRunnerStandardOutputFixture + { + Settings = + { + HandleExitCode = exitCode => exitCode == expectedExitCode + } + } + .GivenStandardOutput(expectedStandardOutput); + + fixture.ProcessRunner.Process.SetExitCode(expectedExitCode); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardError() + { + // Given + const string expectedStandardOutput = "LINE1"; + const string expectedStandardError = "ERRORLINE1"; + const int expectedExitCode = 0; + + var fixture = new CommandRunnerStandardErrorFixture() + .GivenStandardError(expectedStandardError) + .GivenStandardOutput(expectedStandardOutput); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedStandardError, fixture.StandardError); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + + [Fact] + public void Should_Return_StandardError_ExitCode() + { + // Given + const string expectedStandardOutput = "LINE1"; + const string expectedStandardError = "ERRORLINE1"; + const int expectedExitCode = 1337; + + var fixture = new CommandRunnerStandardErrorFixture + { + Settings = + { + HandleExitCode = exitCode => exitCode == expectedExitCode + } + } + .GivenStandardError(expectedStandardError) + .GivenStandardOutput(expectedStandardOutput); + + fixture.ProcessRunner.Process.SetExitCode(expectedExitCode); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardOutput, fixture.StandardOutput); + Assert.Equal(expectedStandardError, fixture.StandardError); + Assert.Equal(expectedExitCode, fixture.ExitCode); + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DNU/Build/DNUBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DNU/Build/DNUBuilderTests.cs deleted file mode 100644 index 0e17604b74..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DNU/Build/DNUBuilderTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DNU.Build; -using Cake.Common.Tools.DNU.Build; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DNU.Build -{ - public sealed class DNUBuilderTests - { - public sealed class TheBuildMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Path_Are_Null() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Settings = new DNUBuildSettings(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "path"); - } - - [Fact] - public void Should_Throw_If_DNU_Executable_Was_Not_Found() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Could not locate executable."); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\"", result.Args); - } - - [Fact] - public void Should_Add_Frameworks_If_Set() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUBuildSettings - { - Frameworks = new[] { "dnx451", "dnxcore50" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\" --framework \"dnx451\" --framework \"dnxcore50\"", result.Args); - } - - [Fact] - public void Should_Add_Configurations_If_Set() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUBuildSettings - { - Configurations = new[] { "Debug", "Release" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\" --configuration \"Debug\" --configuration \"Release\"", result.Args); - } - - [Fact] - public void Should_Add_Proxy_If_Set() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUBuildSettings - { - OutputDirectory = "./artifacts/" - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\" --out \"/Working/artifacts\"", result.Args); - } - - [Fact] - public void Should_Add_Quiet_If_Set() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUBuildSettings - { - Quiet = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\" --quiet", result.Args); - } - - [Fact] - public void Should_Add_Arguments_And_Settings_If_Set() - { - // Given - var fixture = new DNUBuilderFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUBuildSettings - { - Frameworks = new[] { "dnx451", "dnxcore50" }, - Configurations = new[] { "Debug", "Release" }, - OutputDirectory = "./artifacts/", - Quiet = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build \"./src/*\"" + - " --framework \"dnx451\" --framework \"dnxcore50\"" + - " --configuration \"Debug\" --configuration \"Release\"" + - " --out \"/Working/artifacts\"" + - " --quiet", - result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DNU/Pack/DNUPackerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DNU/Pack/DNUPackerTests.cs deleted file mode 100644 index b3a9efa4a6..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DNU/Pack/DNUPackerTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DNU.Pack; -using Cake.Common.Tools.DNU.Pack; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DNU.Pack -{ - public sealed class DNUPackerTests - { - public sealed class ThePackMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Path_Are_Null() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Settings = new DNUPackSettings(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "path"); - } - - [Fact] - public void Should_Throw_If_DNU_Executable_Was_Not_Found() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Could not locate executable."); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\"", result.Args); - } - - [Fact] - public void Should_Add_Frameworks_If_Set() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUPackSettings - { - Frameworks = new[] { "dnx451", "dnxcore50" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\" --framework \"dnx451\" --framework \"dnxcore50\"", result.Args); - } - - [Fact] - public void Should_Add_Configurations_If_Set() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUPackSettings - { - Configurations = new[] { "Debug", "Release" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\" --configuration \"Debug\" --configuration \"Release\"", result.Args); - } - - [Fact] - public void Should_Add_Proxy_If_Set() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUPackSettings - { - OutputDirectory = "./artifacts/" - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\" --out \"/Working/artifacts\"", result.Args); - } - - [Fact] - public void Should_Add_Quiet_If_Set() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUPackSettings - { - Quiet = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\" --quiet", result.Args); - } - - [Fact] - public void Should_Add_Arguments_And_Settings_If_Set() - { - // Given - var fixture = new DNUPackerFixture(); - fixture.Path = "./src/*"; - fixture.Settings = new DNUPackSettings - { - Frameworks = new[] { "dnx451", "dnxcore50" }, - Configurations = new[] { "Debug", "Release" }, - OutputDirectory = "./artifacts/", - Quiet = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack \"./src/*\"" + - " --framework \"dnx451\" --framework \"dnxcore50\"" + - " --configuration \"Debug\" --configuration \"Release\"" + - " --out \"/Working/artifacts\"" + - " --quiet", - result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DNU/Restore/DNURestorerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DNU/Restore/DNURestorerTests.cs deleted file mode 100644 index 820cc63185..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DNU/Restore/DNURestorerTests.cs +++ /dev/null @@ -1,311 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DNU.Restorer; -using Cake.Common.Tools.DNU.Restore; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DNU.Restore -{ - public sealed class DNURestorerTests - { - public sealed class TheRestoreMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_DNU_Executable_Was_Not_Found() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Could not locate executable."); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "DNU: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DNURestorerFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore", result.Args); - } - - [Fact] - public void Should_Add_FilePath_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.FilePath = "./project.json"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore \"/Working/project.json\"", result.Args); - } - - [Fact] - public void Should_Add_Sources_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Sources = new[] { "https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --source \"https://www.example.com/nugetfeed\" --source \"https://www.example.com/nugetfeed2\"", result.Args); - } - - [Fact] - public void Should_Add_FallbackSources_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - FallbackSources = new[] { "https://www.example.com/fallbacknugetfeed" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --fallbacksource \"https://www.example.com/fallbacknugetfeed\"", result.Args); - } - - [Fact] - public void Should_Add_Proxy_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Proxy = "exampleproxy" - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --proxy \"exampleproxy\"", result.Args); - } - - [Fact] - public void Should_Add_NoCache_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - NoCache = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --no-cache", result.Args); - } - - [Fact] - public void Should_Add_Packages_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Packages = "./packages" - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --packages \"/Working/packages\"", result.Args); - } - - [Fact] - public void Should_Add_IgnoreFailedSources_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - IgnoreFailedSources = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --ignore-failed-sources", result.Args); - } - - [Fact] - public void Should_Add_Quiet_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Quiet = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --quiet", result.Args); - } - - [Fact] - public void Should_Add_Parallel_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Parallel = true - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --parallel", result.Args); - } - - [Theory] - [InlineData(DNULocked.Lock, "--lock")] - [InlineData(DNULocked.Unlock, "--unlock")] - public void Should_Add_Locked_If_Set(DNULocked locked, string arg) - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Locked = locked - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore " + arg, result.Args); - } - - [Fact] - public void Should_Add_Runtime_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.Settings = new DNURestoreSettings - { - Runtimes = new[] { "runtime1", "runtime2" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore --runtime \"runtime1\" --runtime \"runtime2\"", result.Args); - } - - [Fact] - public void Should_Add_Arguments_And_Settings_If_Set() - { - // Given - var fixture = new DNURestorerFixture(); - fixture.FilePath = "./project.json"; - fixture.Settings = new DNURestoreSettings - { - Sources = new[] { "https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2" }, - FallbackSources = new[] { "https://www.example.com/fallbacknugetfeed" }, - Proxy = "exampleproxy", - NoCache = true, - Packages = "./packages", - IgnoreFailedSources = true, - Quiet = true, - Parallel = true, - Locked = DNULocked.Lock, - Runtimes = new[] { "runtime1", "runtime2" } - }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore \"/Working/project.json\"" + - " --source \"https://www.example.com/nugetfeed\"" + - " --source \"https://www.example.com/nugetfeed2\"" + - " --fallbacksource \"https://www.example.com/fallbacknugetfeed\"" + - " --proxy \"exampleproxy\"" + - " --no-cache" + - " --packages \"/Working/packages\"" + - " --ignore-failed-sources" + - " --quiet" + - " --parallel" + - " --lock" + - " --runtime \"runtime1\" --runtime \"runtime2\"", - result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyseSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyseSettingsTests.cs index c9e2b78ca6..eb4d247075 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyseSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyseSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.DotCover; using Cake.Common.Tools.DotCover.Analyse; using Xunit; @@ -32,4 +33,4 @@ public void Should_Use_XML_Report_Type_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyserTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyserTests.cs index 2f12ea4963..d3a39025ac 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyserTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Analyse/DotCoverAnalyserTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.DotCover.Analyse; using Cake.Common.Tools.DotCover; using Cake.Common.Tools.NUnit; @@ -26,7 +27,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -40,8 +41,7 @@ public void Should_Throw_If_Action_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "action"); - + AssertEx.IsArgumentNullException(result, "action"); } [Fact] @@ -55,7 +55,7 @@ public void Should_Throw_If_Output_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputPath"); + AssertEx.IsArgumentNullException(result, "outputPath"); } [Fact] @@ -69,7 +69,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -83,7 +83,7 @@ public void Should_Throw_If_No_Tool_Was_Intercepted() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "No tool was started."); + AssertEx.IsCakeException(result, "No tool was started."); } [Fact] @@ -235,6 +235,23 @@ public void Should_Append_DisableDefaultFilters() "/DisableDefaultFilters", result.Args); } + [Fact] + public void Should_Append_LogFile() + { + // Given + var fixture = new DotCoverAnalyserFixture(); + fixture.Settings.LogFile = "./logfile.log"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Analyse /TargetExecutable=\"/Working/tools/Test.exe\" " + + "/TargetArguments=\"-argument\" " + + "/Output=\"/Working/result.xml\" " + + "/LogFile=\"/Working/logfile.log\"", result.Args); + } + [Fact] public void Should_Capture_XUnit() { @@ -278,6 +295,22 @@ public void Should_Capture_NUnit() "/TargetArguments=\"\\\"/Working/Test.dll\\\" -noshadow\" " + "/Output=\"/Working/result.xml\"", result.Args); } + + [Fact] + public void Should_Append_ConfigurationFile() + { + // Given + var fixture = new DotCoverAnalyserFixture(); + fixture.Settings.WithConfigFile(new FilePath("./config.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Analyse \"/Working/config.xml\" /TargetExecutable=\"/Working/tools/Test.exe\" " + + "/TargetArguments=\"-argument\" " + + "/Output=\"/Working/result.xml\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs index 262c432d40..f98ab9b6f9 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.DotCover.Cover; using Cake.Common.Tools.DotCover; +using Cake.Common.Tools.DotCover.Cover; using Cake.Common.Tools.NUnit; using Cake.Common.Tools.XUnit; using Cake.Core.IO; @@ -26,7 +28,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -40,8 +42,7 @@ public void Should_Throw_If_Action_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "action"); - + AssertEx.IsArgumentNullException(result, "action"); } [Fact] @@ -55,7 +56,7 @@ public void Should_Throw_If_Output_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputPath"); + AssertEx.IsArgumentNullException(result, "outputPath"); } [Fact] @@ -69,7 +70,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -83,7 +84,7 @@ public void Should_Throw_If_No_Tool_Was_Intercepted() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "No tool was started."); + AssertEx.IsCakeException(result, "No tool was started."); } [Fact] @@ -96,9 +97,9 @@ public void Should_Capture_Tool_And_Arguments_From_Action() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Theory] @@ -122,8 +123,8 @@ public void Should_Not_Capture_Arguments_From_Action_If_Excluded(string argument var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Fact] @@ -137,10 +138,10 @@ public void Should_Append_TargetWorkingDir() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + - "/TargetWorkingDir=\"/Working\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--target-working-directory \"/Working\"", result.Args); } [Fact] @@ -155,9 +156,9 @@ public void Should_Append_Scope() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/Scope=\"/Working/*.dll;/Some/**/Other/*.dll\"", result.Args); } @@ -173,9 +174,9 @@ public void Should_Append_Filters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/Filters=\"+:module=Test.*;-:myassembly\"", result.Args); } @@ -191,9 +192,9 @@ public void Should_Append_AttributeFilters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/AttributeFilters=\"filter1;filter2\"", result.Args); } @@ -208,12 +209,30 @@ public void Should_Append_DisableDefaultFilters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/DisableDefaultFilters", result.Args); } + [Fact] + public void Should_Append_ProcessFilters() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithProcessFilter("+:test.exe") + .WithProcessFilter("-:sqlservr.exe"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "/ProcessFilters=\"+:test.exe;-:sqlservr.exe\"", result.Args); + } + [Fact] public void Should_Capture_XUnit() { @@ -231,9 +250,9 @@ public void Should_Capture_XUnit() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/xunit.console.exe\" " + - "/TargetArguments=\"\\\"/Working/Test.dll\\\" -noshadow\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/xunit.console.exe\" " + + "--target-arguments \"\\\"/Working/Test.dll\\\" -noshadow\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Fact] @@ -253,10 +272,268 @@ public void Should_Capture_NUnit() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/nunit-console.exe\" " + - "/TargetArguments=\"\\\"/Working/Test.dll\\\" -noshadow\" " + + Assert.Equal("cover --target-executable \"/Working/tools/nunit-console.exe\" " + + "--target-arguments \"\\\"/Working/Test.dll\\\" -noshadow\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); + } + + [Fact] + public void Should_Append_ConfigurationFile() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithConfigFile(new FilePath("./config.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover \"/Working/config.xml\" --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeAssemblies() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeAssembly("*.Tests") + .WithExcludeAssembly("Test.*"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-assemblies \"*.Tests,Test.*\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeAttributes() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeAttribute("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute") + .WithExcludeAttribute("Custom.*Attribute"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-attributes \"System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute,Custom.*Attribute\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeProcesses() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeProcess("test.exe") + .WithExcludeProcess("*.vshost.exe"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-processes \"test.exe,*.vshost.exe\"", result.Args); + } + + [Fact] + public void Should_Append_JsonReportOutput() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportOutput(new FilePath("/Working/coverage.json")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--json-report-output \"/Working/coverage.json\"", result.Args); + } + + [Fact] + public void Should_Append_JsonReportCoveringTestsScope() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportCoveringTestsScope(DotCoverReportScope.Method); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--json-report-covering-tests-scope \"method\"", result.Args); + } + + [Fact] + public void Should_Append_XmlReportOutput() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithXmlReportOutput(new FilePath("/Working/coverage.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--xml-report-output \"/Working/coverage.xml\"", result.Args); + } + + [Fact] + public void Should_Append_XmlReportCoveringTestsScope() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithXmlReportCoveringTestsScope(DotCoverReportScope.Statement); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--xml-report-covering-tests-scope \"statement\"", result.Args); + } + + [Fact] + public void Should_Append_TemporaryDirectory() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithTemporaryDirectory(new DirectoryPath("/Working/temp")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--temporary-directory \"/Working/temp\"", result.Args); + } + + [Fact] + public void Should_Append_UseApi() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithUseApi(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--use-api", result.Args); + } + + [Fact] + public void Should_Append_NoNGen() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithNoNGen(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--no-ngen", result.Args); + } + + [Fact] + public void Should_Use_Legacy_Syntax_When_Enabled() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithLegacySyntax(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover /TargetExecutable=\"/Working/tools/Test.exe\" " + + "/TargetArguments=\"-argument\" " + "/Output=\"/Working/result.dcvr\"", result.Args); } + + [Fact] + public void Should_Use_New_Syntax_By_Default() + { + // Given + var fixture = new DotCoverCovererFixture(); + // Don't set UseLegacySyntax - should default to false + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); + } + + [Fact] + public void Should_Not_Support_New_Features_In_Legacy_Mode() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithLegacySyntax() + .WithJsonReportOutput(new FilePath("/Working/report.json")) + .WithExcludeAssembly("*.Tests"); + + // When + var result = fixture.Run(); + + // Then - New format features should not appear in legacy mode + Assert.Equal("cover /TargetExecutable=\"/Working/tools/Test.exe\" " + + "/TargetArguments=\"-argument\" " + + "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.DoesNotContain("--json-report-output", result.Args); + Assert.DoesNotContain("--exclude-assemblies", result.Args); + } + + [Fact] + public void Should_Support_New_Features_In_New_Mode() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportOutput(new FilePath("/Working/report.json")) + .WithExcludeAssembly("*.Tests"); + + // When + var result = fixture.Run(); + + // Then - New format features should appear + Assert.Contains("--json-report-output \"/Working/report.json\"", result.Args); + Assert.Contains("--exclude-assemblies \"*.Tests\"", result.Args); + Assert.Contains("--snapshot-output", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Merge/DotCoverMergerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Merge/DotCoverMergerTests.cs new file mode 100644 index 0000000000..238584413e --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Merge/DotCoverMergerTests.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tests.Fixtures.Tools.DotCover.Merge; +using Cake.Common.Tools.DotCover; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotCover.Merge +{ + public sealed class DotCoverMergerTests + { + public sealed class TheMergeMethod + { + [Fact] + public void Should_Throw_If_Source_Files_Is_Null() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.SourceFiles = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "sourceFiles"); + } + + [Fact] + public void Should_Throw_If_Source_Files_Is_Empty() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.SourceFiles = new List(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "sourceFiles"); + } + + [Fact] + public void Should_Throw_If_Output_File_Is_Null() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.OutputFile = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "outputFile"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Append_LogFile() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.Settings.LogFile = "./logfile.log"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Merge " + + "/Source=\"/Working/result1.dcvr;/Working/result2.dcvr\" " + + "/Output=\"/Working/result.dcvr\" " + + "/LogFile=\"/Working/logfile.log\"", result.Args); + } + + [Fact] + public void Should_Append_ConfigurationFile() + { + // Given + var fixture = new DotCoverMergerFixture(); + fixture.Settings.WithConfigFile(new FilePath("./config.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Merge \"/Working/config.xml\" " + + "/Source=\"/Working/result1.dcvr;/Working/result2.dcvr\" " + + "/Output=\"/Working/result.dcvr\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Report/DotCoverReporterTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Report/DotCoverReporterTests.cs new file mode 100644 index 0000000000..468f0c17a5 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Report/DotCoverReporterTests.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotCover.Report; +using Cake.Common.Tools.DotCover; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotCover.Report +{ + public sealed class DotCoverReporterTests + { + public sealed class TheReportMethod + { + [Fact] + public void Should_Throw_If_Source_File_Is_Null() + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.SourceFile = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "sourceFile"); + } + + [Fact] + public void Should_Throw_If_Output_File_Is_Null() + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.OutputFile = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "outputFile"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Theory] + [InlineData(DotCoverReportType.DetailedXML, "DetailedXML")] + [InlineData(DotCoverReportType.HTML, "HTML")] + [InlineData(DotCoverReportType.JSON, "JSON")] + [InlineData(DotCoverReportType.NDependXML, "NDependXML")] + public void Should_Append_ReportType(DotCoverReportType reportType, string reportTypeString) + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.Settings.ReportType = reportType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Report " + + "/Source=\"/Working/result.dcvr\" " + + "/Output=\"/Working/result.xml\" " + + "/ReportType=" + reportTypeString, result.Args); + } + + [Fact] + public void Should_Append_LogFile() + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.Settings.LogFile = "./logfile.log"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Report " + + "/Source=\"/Working/result.dcvr\" " + + "/Output=\"/Working/result.xml\" " + + "/LogFile=\"/Working/logfile.log\"", result.Args); + } + + [Fact] + public void Should_Append_ConfigurationFile() + { + // Given + var fixture = new DotCoverReporterFixture(); + fixture.Settings.WithConfigFile(new FilePath("./config.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("Report \"/Working/config.xml\" " + + "/Source=\"/Working/result.dcvr\" " + + "/Output=\"/Working/result.xml\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Build/DotNetBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Build/DotNetBuilderTests.cs new file mode 100644 index 0000000000..9d60f32a44 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Build/DotNetBuilderTests.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Build; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Build; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Build +{ + public sealed class DotNetBuilderTests + { + public sealed class TheBuildMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Project_Is_Null() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Settings = new DotNetBuildSettings(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "project"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("build \"./src/*\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Settings.Framework = "net451"; + fixture.Settings.Runtime = "runtime1"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.VersionSuffix = "rc1"; + fixture.Settings.NoLogo = true; + fixture.Project = "./src/*"; + fixture.Settings.Verbosity = DotNetVerbosity.Minimal; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("build \"./src/*\" --runtime runtime1 --framework net451 --configuration Release --version-suffix rc1 --nologo --source \"https://api.nuget.org/v3/index.json\" --verbosity minimal", result.Args); + } + + [Fact] + public void Should_Add_OutputPath_Arguments() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("build \"./src/*\" --output \"/Working/artifacts\"", result.Args); + } + + [Theory] + [InlineData("./src/*", "build \"./src/*\"")] + [InlineData("./src/cake build/", "build \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "build \"./src/cake build/cake cli\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Build_Arguments() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Settings.NoIncremental = true; + fixture.Settings.NoDependencies = true; + fixture.Settings.NoRestore = true; + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("build \"./src/*\" --no-incremental --no-dependencies --no-restore", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetBuilderFixture(); + fixture.Project = "./src/*"; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics build \"./src/*\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/BuildServer/DotNetBuildServerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/BuildServer/DotNetBuildServerTests.cs new file mode 100644 index 0000000000..3535da1f41 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/BuildServer/DotNetBuildServerTests.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Build; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.BuildServer; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.BuildServer +{ + public sealed class DotNetBuildServerTests + { + public sealed class TheShutdownMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetBuildServerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetBuildServerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetBuildServerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetBuildServerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("build-server shutdown", result.Args); + } + + [Theory] + [InlineData(true, null, null, "build-server shutdown --msbuild")] + [InlineData(null, true, null, "build-server shutdown --razor")] + [InlineData(null, null, true, "build-server shutdown --vbcscompiler")] + [InlineData(true, true, true, "build-server shutdown --msbuild --razor --vbcscompiler")] + public void Should_Add_Settings_Arguments(bool? msBuild, bool? razor, bool? vbcscompiler, string expected) + { + // Given + var fixture = new DotNetBuildServerFixture(); + fixture.Settings.MSBuild = msBuild; + fixture.Settings.Razor = razor; + fixture.Settings.VBCSCompiler = vbcscompiler; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetBuildServerFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics build-server shutdown", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Clean/DotNetCleanTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Clean/DotNetCleanTests.cs new file mode 100644 index 0000000000..4ea4227892 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Clean/DotNetCleanTests.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Clean; +using Cake.Common.Tools.DotNet.Clean; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Clean +{ + public sealed class DotNetCleanTests + { + public sealed class TheCleanMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = "./src/project"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Project_Is_Null() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Settings = new DotNetCleanSettings(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "project"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = "./src/project"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = "./src/project"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = "./src/project"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("clean \"./src/project\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Settings.Framework = "net451"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.Runtime = "win7-x86"; + fixture.Settings.NoLogo = true; + fixture.Project = "./src/project"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("clean \"./src/project\" --framework net451 --runtime win7-x86 --configuration Release --nologo", result.Args); + } + + [Fact] + public void Should_Add_OutputPath_Arguments() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Project = "./src/project"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("clean \"./src/project\" --output \"/Working/artifacts\"", result.Args); + } + + [Theory] + [InlineData("./src/project", "clean \"./src/project\"")] + [InlineData("./src/cake build/", "clean \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "clean \"./src/cake build/cake cli\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetCleanerFixture(); + fixture.Project = "./src/project"; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics clean \"./src/project\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Execute/DotNetExecutorTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Execute/DotNetExecutorTests.cs new file mode 100644 index 0000000000..17117aa950 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Execute/DotNetExecutorTests.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Execute; +using Cake.Common.Tools.DotNet.Execute; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Execute +{ + public sealed class DotNetExecutorTests + { + public sealed class TheExecuteMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./bin/Debug/app.dll"; + fixture.Arguments = "--args"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./bin/Debug/app.dll"; + fixture.Arguments = "--args"; + fixture.Settings = new DotNetExecuteSettings(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./bin/Debug/app.dll"; + fixture.Arguments = "--args"; + fixture.Settings = new DotNetExecuteSettings(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./bin/Debug/app.dll"; + fixture.Settings = new DotNetExecuteSettings(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/bin/Debug/app.dll\"", result.Args); + } + + [Fact] + public void Should_Add_Famework_Version() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./Some Folder/Debug/app.dll"; + fixture.Settings.FrameworkVersion = "1.0.3"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--fx-version 1.0.3 \"/Working/Some Folder/Debug/app.dll\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetExecutorFixture(); + fixture.AssemblyPath = "./bin/Debug/app.dll"; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics \"/Working/bin/Debug/app.dll\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Format/DotNetFormatTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Format/DotNetFormatTests.cs new file mode 100644 index 0000000000..4921371013 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Format/DotNetFormatTests.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Format; +using Cake.Common.Tools.DotNet.Format; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Format +{ + public sealed class DotNetFormatTests + { + public sealed class TheFormatMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = "./src/project"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Root_Is_Null() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Settings = new DotNetFormatSettings(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "root"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = "./src/project"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = "./src/project"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = "./src/project"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("format \"./src/project\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Settings.Diagnostics.Add("CS123"); + fixture.Settings.Diagnostics.Add("CA555"); + fixture.Settings.Severity = DotNetFormatSeverity.Warning; + fixture.Settings.NoRestore = true; + fixture.Settings.VerifyNoChanges = true; + fixture.Settings.Include.Add("./src/"); + fixture.Settings.Include.Add("./tests/"); + fixture.Settings.Exclude.Add("./src/submodule-a/"); + fixture.Settings.IncludeGenerated = true; + fixture.Settings.Verbosity = Common.Tools.DotNet.DotNetVerbosity.Diagnostic; + fixture.Settings.BinaryLog = "./temp/b.log"; + fixture.Settings.Report = "./temp/report.json"; + fixture.Root = "./src/project"; + + // When + var result = fixture.Run(); + + // Then + var expected = "format \"./src/project\" --diagnostics CS123 CA555 --severity warn --no-restore --verify-no-changes --include ./src/ ./tests/ --exclude ./src/submodule-a/ --include-generated"; + expected += " --binarylog \"/Working/temp/b.log\" --report \"/Working/temp/report.json\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./src/project", "format \"./src/project\"")] + [InlineData("./src/cake build/", "format \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "format \"./src/cake build/cake cli\"")] + public void Should_Quote_Root_Path(string text, string expected) + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Subcommand() + { + // Given + var fixture = new DotNetFormatterFixture(); + fixture.Root = "./src/project"; + fixture.Subcommand = "style"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("format style \"./src/project\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs new file mode 100644 index 0000000000..3c40ab56e4 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildBuilderTests.cs @@ -0,0 +1,1478 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tests.Fixtures.Tools.DotNet.MSBuild; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Common.Tools.MSBuild; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.MSBuild +{ + public sealed class DotNetMSBuildBuilderTests + { + public sealed class TheBuildMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Not_Throw_If_Project_Is_Null() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture() + { + Settings = new DotNetMSBuildSettings() + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture() + { + Project = "./src/*", + Settings = new DotNetMSBuildSettings() + }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture() + { + Project = "./src/*" + }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Use_Project_Path_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Project = "./src/foo/foo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild \"./src/foo/foo.csproj\"", result.Args); + } + + [Fact] + public void Should_Use_Directory_Path_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Project = "./src/"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild \"./src/\"", result.Args); + } + + [Fact] + public void Should_Add_Target_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Targets.Add("A"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /target:A", result.Args); + } + + [Fact] + public void Should_Append_Target_Argument_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Targets.Add("A"); + fixture.Settings.Targets.Add("B"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /target:A;B", result.Args); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_If_Target_Has_No_Value(string targetValue) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Targets.Add(targetValue); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "Targets", "Specify the name of the target"); + } + + [Fact] + public void Should_Add_Property_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Properties.Add("A", new[] { "B" }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:A=B", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Property_Arguments_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Properties.Add("A", new[] { "B", "E" }); + fixture.Settings.Properties.Add("C", new[] { "D" }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:A=B;E /property:C=D", result.Args); + } + + [Theory] + [InlineData(null)] + [InlineData(new object[] { new string[] { } })] + [InlineData(new object[] { new string[] { null } })] + public void Should_Throw_If_Property_Has_Null_ValueOrIsEmpty(string[] propertyValues) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Properties.Add("F", propertyValues); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "Properties", "A property must have at least one non-null value"); + } + + [Fact] + public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetProperty("A"); + fixture.Settings.WithGetProperty("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Properties\": {", + " \"A\": \"A value\",", + " \"B\": \"B value\"", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getProperty:A /getProperty:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetItem("A"); + fixture.Settings.WithGetItem("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Items\": {", + " \"A\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " \"B\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getItem:A /getItem:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.WithGetTargetResult("A"); + fixture.Settings.WithGetTargetResult("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"TargetResults\": {", + " \"A\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " },", + " \"B\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " }", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /getTargetResult:A /getTargetResult:B", + result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Theory] + [InlineData(0)] + [InlineData(-10)] + public void Should_Use_As_Many_Processors_As_Possible_If_MaxCpuCount_Is_Zero_Or_Less(int maxCpuCount) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.MaxCpuCount = maxCpuCount; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /maxcpucount", result.Args); + } + + [Fact] + public void Should_Use_Specified_Number_Of_Max_Processors() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.MaxCpuCount = 4; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /maxcpucount:4", result.Args); + } + + [Theory] + [InlineData(MSBuildVersion.MSBuild20, "2.0")] + [InlineData(MSBuildVersion.MSBuild35, "3.5")] + [InlineData(MSBuildVersion.MSBuild4, "4.0")] + [InlineData(MSBuildVersion.MSBuild12, "12.0")] + [InlineData(MSBuildVersion.MSBuild14, "14.0")] + [InlineData(MSBuildVersion.MSBuild15, "15.0")] + public void Should_Add_ToolVersion_Argument_If_Specified(MSBuildVersion toolVersion, string expectedToolVersion) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ToolVersion = toolVersion; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /toolsversion:{expectedToolVersion}", result.Args); + } + + [Theory] + [InlineData(100)] + [InlineData(-8)] + [InlineData(0)] + public void Should_Throw_If_ToolVersion_Is_Invalid(int toolVersion) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ToolVersion = (MSBuildVersion)toolVersion; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("toolVersion", ((ArgumentOutOfRangeException)result)?.ParamName); + Assert.Equal(toolVersion, (int)((ArgumentOutOfRangeException)result)?.ActualValue); + } + + [Fact] + public void Should_Add_NoConsoleLogger_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DisableConsoleLogger = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /noconsolelogger", result.Args); + } + + [Fact] + public void Should_Ignore_Console_Logger_Settings_If_No_Console_Logger_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DisableConsoleLogger = true; + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /noconsolelogger", result.Args); + } + + [Fact] + public void Should_Add_ConsoleLoggerParameters_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + PerformanceSummary = true, + SummaryOutputLevel = MSBuildLoggerOutputLevel.ErrorsOnly, + Verbosity = DotNetVerbosity.Diagnostic + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:PerformanceSummary;ErrorsOnly;Verbosity=Diagnostic", result.Args); + } + + [Fact] + public void Should_Add_FileLogger_Arguments() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { AppendToLogFile = false, PerformanceSummary = true, Verbosity = DotNetVerbosity.Diagnostic }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(@"msbuild /fileLogger /fileloggerparameters:PerformanceSummary;Verbosity=Diagnostic", result.Args); + } + + [Fact] + public void Should_Throw_Exception_For_Too_Many_FileLoggers() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings()); + + // When + var ex = Assert.Throws(() => fixture.Run()); + + // Then + Assert.Equal(@"Too Many FileLoggers", ex.Message); + } + + [Fact] + public void Should_Add_DistributedLogger_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DistributedLoggers.Add(new MSBuildDistributedLogger + { + CentralLogger = new MSBuildLogger { Assembly = "A" }, + ForwardingLogger = new MSBuildLogger { Assembly = "B" } + }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /distributedlogger:\"A\"*\"B\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_DistributedLogger_Arguments_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DistributedLoggers.Add(new MSBuildDistributedLogger + { + CentralLogger = new MSBuildLogger { Assembly = "A" }, + ForwardingLogger = new MSBuildLogger { Assembly = "B" } + }); + + fixture.Settings.DistributedLoggers.Add(new MSBuildDistributedLogger + { + CentralLogger = new MSBuildLogger { Class = "C", Assembly = "D" }, + ForwardingLogger = new MSBuildLogger { Class = "E", Assembly = "F", Parameters = "g" } + }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /distributedlogger:\"A\"*\"B\" /distributedlogger:C,D*E,F;g", result.Args); + } + + [Theory] + [InlineData(null, null)] + [InlineData(null, "A")] + [InlineData("A", null)] + [InlineData("", "")] + [InlineData("", "A")] + [InlineData("A", "")] + [InlineData(" ", " ")] + [InlineData(" ", "A")] + [InlineData("A", " ")] + public void Should_Throw_If_DistributedLogger_Has_No_Assembly_Value(string centralLoggerAssembly, string forwardingLoggerAssembly) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DistributedLoggers.Add(new MSBuildDistributedLogger + { + CentralLogger = new MSBuildLogger { Assembly = centralLoggerAssembly }, + ForwardingLogger = new MSBuildLogger { Assembly = forwardingLoggerAssembly } + }); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "Assembly"); + } + + [Fact] + public void Should_Add_DistributedFileLogger_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DistributedFileLogger = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /distributedfilelogger", result.Args); + } + + [Fact] + public void Should_Add_Logger_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Loggers.Add(new MSBuildLogger { Assembly = "A" }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /logger:\"A\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Logger_Arguments_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Loggers.Add(new MSBuildLogger { Assembly = "A" }); + fixture.Settings.Loggers.Add(new MSBuildLogger { Class = "C", Assembly = "B" }); + fixture.Settings.Loggers.Add(new MSBuildLogger { Class = "E", Assembly = "F", Parameters = "g" }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /logger:\"A\" /logger:C,B /logger:E,F;g", result.Args); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_If_Logger_Has_No_Assembly_Value(string loggerAssembly) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Loggers.Add(new MSBuildLogger { Assembly = loggerAssembly }); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "Assembly"); + } + + [Theory] + [InlineData(MSBuildTreatAllWarningsAs.Default, new[] { "Err1" }, ":Err1")] + [InlineData(MSBuildTreatAllWarningsAs.Default, new[] { "Err1", "Err2" }, ":Err1;Err2")] + [InlineData(MSBuildTreatAllWarningsAs.Error, new[] { "Err1", "Err2" }, "")] + [InlineData(MSBuildTreatAllWarningsAs.Error, new string[] { }, "")] + public void Should_Add_WarnAsError_Argument(MSBuildTreatAllWarningsAs treatAllWarningsAs, string[] errorCodes, string expectedValue) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.TreatAllWarningsAs = treatAllWarningsAs; + + foreach (var errorCode in errorCodes) + { + fixture.Settings.WarningCodesAsError.Add(errorCode); + } + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /warnaserror{expectedValue}", result.Args); + } + + [Theory] + [InlineData(MSBuildTreatAllWarningsAs.Default, new[] { "Err1" }, ":Err1")] + [InlineData(MSBuildTreatAllWarningsAs.Default, new[] { "Err1", "Err2" }, ":Err1;Err2")] + [InlineData(MSBuildTreatAllWarningsAs.Message, new[] { "Err1", "Err2" }, "")] + [InlineData(MSBuildTreatAllWarningsAs.Message, new string[] { }, "")] + public void Should_Add_WarnAsMessage_Argument(MSBuildTreatAllWarningsAs treatAllWarningsAs, string[] errorCodes, string expectedValue) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.TreatAllWarningsAs = treatAllWarningsAs; + + foreach (var errorCode in errorCodes) + { + fixture.Settings.WarningCodesAsMessage.Add(errorCode); + } + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /warnasmessage{expectedValue}", result.Args); + } + + [Fact] + public void Should_Add_IgnoreProjectExtensions_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.IgnoreProjectExtensions.Add(".sln"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /ignoreprojectextensions:.sln", result.Args); + } + + [Fact] + public void Should_Append_IgnoreProjectExtensions_Argument_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.IgnoreProjectExtensions.Add(".vcproj"); + fixture.Settings.IgnoreProjectExtensions.Add(".sln"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /ignoreprojectextensions:.vcproj,.sln", result.Args); + } + + [Fact] + public void Should_Add_DetailedSummary_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DetailedSummary = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /detailedsummary", result.Args); + } + + [Fact] + public void Should_Add_NoAutoResponse_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ExcludeAutoResponseFiles = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /noautoresponse", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /nologo", result.Args); + } + + [Fact] + public void Should_Add_Version_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Version = "1.0.0-test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:Version=1.0.0-test", result.Args); + } + + [Fact] + public void Should_Add_VersionPrefix_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.VersionPrefix = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:VersionPrefix=1.0.0", result.Args); + } + + [Fact] + public void Should_Add_VersionSuffix_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.VersionSuffix = "test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:VersionSuffix=test", result.Args); + } + + [Fact] + public void Should_Add_FileVersion_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileVersion = "1.0.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:FileVersion=1.0.0.0", result.Args); + } + + [Fact] + public void Should_Add_AssemblyVersion_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.AssemblyVersion = "1.0.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:AssemblyVersion=1.0.0.0", result.Args); + } + + [Fact] + public void Should_Add_InformationalVersion_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.InformationalVersion = "1.0.0-test+7ad03d0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:InformationalVersion=1.0.0-test+7ad03d0", result.Args); + } + + [Fact] + public void Should_Add_PackageVersion_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.PackageVersion = "1.0.0-test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:PackageVersion=1.0.0-test", result.Args); + } + + [Fact] + public void Should_Add_PackageReleaseNotes_If_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.PackageReleaseNotes = "https://"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:PackageReleaseNotes=https://", result.Args); + } + + [Fact] + public void Should_Add_ContinuousIntegrationBuild_If_Set_To_True() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ContinuousIntegrationBuild = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:ContinuousIntegrationBuild=true", result.Args); + } + + [Fact] + public void Should_Add_ContinuousIntegrationBuild_If_Set_To_False() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ContinuousIntegrationBuild = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /property:ContinuousIntegrationBuild=false", result.Args); + } + + [Fact] + public void Should_Not_Add_ContinuousIntegrationBuild_If_Not_Set() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.DoesNotContain("/property:ContinuousIntegrationBuild=", result.Args); + } + + [Fact] + public void Should_Add_ResponseFile_Argument() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ResponseFiles.Add("/src/inputs.rsp"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild @\"/src/inputs.rsp\"", result.Args); + } + + [Fact] + public void Should_Append_ResponseFile_Argument_When_Multiple_Values() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ResponseFiles.Add("/src/inputs1.rsp"); + fixture.Settings.ResponseFiles.Add("/src/inputs_2.rsp"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild @\"/src/inputs1.rsp\" @\"/src/inputs_2.rsp\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics msbuild", result.Args); + } + + [Theory] + [InlineData(DotNetVerbosity.Quiet, "/verbosity:quiet")] + [InlineData(DotNetVerbosity.Minimal, "/verbosity:minimal")] + [InlineData(DotNetVerbosity.Normal, "/verbosity:normal")] + [InlineData(DotNetVerbosity.Detailed, "/verbosity:detailed")] + [InlineData(DotNetVerbosity.Diagnostic, "/verbosity:diagnostic")] + public void Should_Use_MSBuild_Verbosity_Not_DotNet_Verbosity_When_Verbosity_Is_Specified(DotNetVerbosity verbosity, string expectedMsBuildVerbosity) + { + // Given: DotNetMSBuildSettings.Verbosity (not ConsoleLoggerSettings) causes MSB1016 if passed as dotnet --verbosity + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then: must use MSBuild /verbosity switch, not dotnet --verbosity (see https://github.com/cake-build/cake/issues/4456) + Assert.Contains(expectedMsBuildVerbosity, result.Args); + Assert.DoesNotContain("--verbosity", result.Args); + } + + [Fact] + public void Should_Use_Node_Reuse_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.NodeReuse = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /nodeReuse:true", result.Args); + } + } + + public class TheConsoleLoggerSettingsProperty + { + [Fact] + public void Should_Append_PerformanceSummary_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + PerformanceSummary = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:PerformanceSummary", result.Args); + } + + [Fact] + public void Should_Append_NoSummary_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + NoSummary = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:NoSummary", result.Args); + } + + [Theory] + [InlineData(MSBuildLoggerOutputLevel.ErrorsOnly)] + [InlineData(MSBuildLoggerOutputLevel.WarningsOnly)] + public void Should_Append_SummaryOutputLevel_If_Specified(MSBuildLoggerOutputLevel outputLevel) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + SummaryOutputLevel = outputLevel + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /consoleloggerparameters:{outputLevel}", result.Args); + } + + [Fact] + public void Should_Not_Append_SummaryOutputLevel_If_Default() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + SummaryOutputLevel = MSBuildLoggerOutputLevel.Default + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild", result.Args); + } + + [Fact] + public void Should_Append_HideItemAndPropertyList_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + HideItemAndPropertyList = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:NoItemAndPropertyList", result.Args); + } + + [Fact] + public void Should_Append_ShowCommandLine_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ShowCommandLine = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:ShowCommandLine", result.Args); + } + + [Fact] + public void Should_Append_ShowTimestamp_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ShowTimestamp = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:ShowTimestamp", result.Args); + } + + [Fact] + public void Should_Append_ForceNoAlign_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ForceNoAlign = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:ForceNoAlign", result.Args); + } + + [Fact] + public void Should_Append_ShowEventId_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ShowEventId = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:ShowEventId", result.Args); + } + + [Fact] + public void Should_Append_DisableConsoleColor_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ConsoleColorType = MSBuildConsoleColorType.Disabled + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:DisableConsoleColor", result.Args); + } + + [Fact] + public void Should_Append_ForceConsoleColor_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + ConsoleColorType = MSBuildConsoleColorType.ForceAnsi + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:ForceConsoleColor", result.Args); + } + + [Fact] + public void Should_Append_DisableMultiprocessorLogging_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + DisableMultiprocessorLogging = true + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /consoleloggerparameters:DisableMPLogging", result.Args); + } + + [Theory] + [InlineData(DotNetVerbosity.Quiet)] + [InlineData(DotNetVerbosity.Minimal)] + [InlineData(DotNetVerbosity.Normal)] + [InlineData(DotNetVerbosity.Detailed)] + [InlineData(DotNetVerbosity.Diagnostic)] + public void Should_Append_Verbosity_If_Specified(DotNetVerbosity verbosity) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.ConsoleLoggerSettings = new MSBuildLoggerSettings + { + Verbosity = verbosity + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /consoleloggerparameters:Verbosity={verbosity}", result.Args); + } + } + + public sealed class TheFileLoggerSettingsProperty + { + public string FileEncoding { get; set; } + + [Fact] + public void Should_Append_LogFile_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings + { + LogFile = "msbuild.log" + }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:LogFile=\"/Working/msbuild.log\"", result.Args); + } + + [Fact] + public void Should_Append_LogFile_If_Empty() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { LogFile = string.Empty }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:LogFile=\"\"", result.Args); + } + + [Fact] + public void Should_Append_AppendToLogFile_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { AppendToLogFile = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:Append", result.Args); + } + + [Fact] + public void Should_Append_FileEncoding_If_Specified() + { + // Given + const string fileEncoding = "UTF8"; + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { FileEncoding = fileEncoding }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /fileLogger /fileloggerparameters:Encoding={fileEncoding}", result.Args); + } + + [Fact] + public void Should_Append_PerformanceSummary_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { PerformanceSummary = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:PerformanceSummary", result.Args); + } + + [Fact] + public void Should_Append_NoSummary_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { NoSummary = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:NoSummary", result.Args); + } + + [Theory] + [InlineData(MSBuildLoggerOutputLevel.ErrorsOnly)] + [InlineData(MSBuildLoggerOutputLevel.WarningsOnly)] + public void Should_Append_SummaryOutputLevel_If_Specified(MSBuildLoggerOutputLevel outputLevel) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { SummaryOutputLevel = outputLevel }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /fileLogger /fileloggerparameters:{outputLevel}", result.Args); + } + + [Fact] + public void Should_Not_Append_SummaryOutputLevel_If_Default() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { SummaryOutputLevel = MSBuildLoggerOutputLevel.Default }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild", result.Args); + } + + [Fact] + public void Should_Append_HideItemAndPropertyList_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { HideItemAndPropertyList = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:NoItemAndPropertyList", result.Args); + } + + [Fact] + public void Should_Append_ShowCommandLine_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ShowCommandLine = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:ShowCommandLine", result.Args); + } + + [Fact] + public void Should_Append_ShowTimestamp_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ShowTimestamp = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:ShowTimestamp", result.Args); + } + + [Fact] + public void Should_Append_ForceNoAlign_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ForceNoAlign = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:ForceNoAlign", result.Args); + } + + [Fact] + public void Should_Append_ShowEventId_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ShowEventId = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:ShowEventId", result.Args); + } + + [Fact] + public void Should_Append_DisableConsoleColor_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ConsoleColorType = MSBuildConsoleColorType.Disabled }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:DisableConsoleColor", result.Args); + } + + [Fact] + public void Should_Append_ForceConsoleColor_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { ConsoleColorType = MSBuildConsoleColorType.ForceAnsi }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:ForceConsoleColor", result.Args); + } + + [Fact] + public void Should_Append_DisableMultiprocessorLogging_If_Specified() + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { DisableMultiprocessorLogging = true }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("msbuild /fileLogger /fileloggerparameters:DisableMPLogging", result.Args); + } + + [Theory] + [InlineData(DotNetVerbosity.Quiet)] + [InlineData(DotNetVerbosity.Minimal)] + [InlineData(DotNetVerbosity.Normal)] + [InlineData(DotNetVerbosity.Detailed)] + [InlineData(DotNetVerbosity.Diagnostic)] + public void Should_Append_Verbosity_If_Specified(DotNetVerbosity verbosity) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.FileLoggers.Add(new MSBuildFileLoggerSettings { Verbosity = verbosity }); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"msbuild /fileLogger /fileloggerparameters:Verbosity={verbosity}", result.Args); + } + } + + public class TheBinaryLoggerProperty + { + [Theory] + [InlineData(false, null, MSBuildBinaryLoggerImports.Unspecified, "msbuild")] + [InlineData(true, null, MSBuildBinaryLoggerImports.Unspecified, "msbuild /binarylogger")] + [InlineData(true, null, MSBuildBinaryLoggerImports.None, "msbuild /binarylogger:ProjectImports=None")] + [InlineData(true, "mylog.binlog", MSBuildBinaryLoggerImports.Unspecified, "msbuild /binarylogger:\"mylog.binlog\"")] + [InlineData(true, "mylog.binlog", MSBuildBinaryLoggerImports.Embed, "msbuild /binarylogger:\"mylog.binlog\";ProjectImports=Embed")] + public void Should_Append_Binary_Logging_If_Specified(bool enabled, string fileName, MSBuildBinaryLoggerImports imports, string args) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.BinaryLogger = new MSBuildBinaryLoggerSettings + { + Enabled = enabled, + FileName = fileName, + Imports = imports + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(null, MSBuildBinaryLoggerImports.Unspecified, "msbuild /binarylogger")] + [InlineData(null, MSBuildBinaryLoggerImports.None, "msbuild /binarylogger:ProjectImports=None")] + [InlineData("mylog.binlog", MSBuildBinaryLoggerImports.Unspecified, "msbuild /binarylogger:\"mylog.binlog\"")] + [InlineData("mylog.binlog", MSBuildBinaryLoggerImports.Embed, "msbuild /binarylogger:\"mylog.binlog\";ProjectImports=Embed")] + public void Should_Append_Binary_Logging_If_Enabled(string fileName, MSBuildBinaryLoggerImports imports, string args) + { + // Given + var fixture = new DotNetMSBuildBuilderFixture(); + fixture.Settings.EnableBinaryLogger(fileName, imports); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensionsTests.cs new file mode 100644 index 0000000000..655125e255 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensionsTests.cs @@ -0,0 +1,1141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Common.Tools.MSBuild; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.MSBuild +{ + public sealed class DotNetMSBuildSettingsExtensionsTests + { + public sealed class TheWithTargetMethod + { + [Fact] + public void Should_Add_Target_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.WithTarget("Target"); + + // Then + Assert.True(settings.Targets.Contains("Target")); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithTarget("Target"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithPropertyMethod + { + [Fact] + public void Should_Add_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.WithProperty("PropertyName", "Value"); + + // Then + Assert.True(settings.Properties.ContainsKey("PropertyName")); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithProperty("PropertyName", "Value"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheDetailedSummaryMethod + { + [Fact] + public void Should_Set_Detailed_Summary() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.ShowDetailedSummary(); + + // Then + Assert.True(settings.DetailedSummary); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.ShowDetailedSummary(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithIgnoredProjectExtensionMethod + { + [Fact] + public void Should_Add_Ignored_Extension_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.WithIgnoredProjectExtension(".sln"); + + // Then + Assert.True(settings.IgnoreProjectExtensions.Contains(".sln")); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithIgnoredProjectExtension(".sln"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetMaxCpuCountMethod + { + [Fact] + public void Should_Set_MaxCpuCount() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetMaxCpuCount(4); + + // Then + Assert.Equal(4, settings.MaxCpuCount); + } + + [Fact] + public void Should_Set_MaxCpuCount_To_Zero_If_Negative_Value() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetMaxCpuCount(-1); + + // Then + Assert.Equal(0, settings.MaxCpuCount); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetMaxCpuCount(4); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheExcludeAutoResponseFilesMethod + { + [Fact] + public void Should_Set_Exclude_Auto_Response_Files() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.ExcludeAutoResponseFiles(); + + // Then + Assert.True(settings.ExcludeAutoResponseFiles); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.ExcludeAutoResponseFiles(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheHideLogoMethod + { + [Fact] + public void Should_Set_No_Logo() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.HideLogo(); + + // Then + Assert.True(settings.NoLogo); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.HideLogo(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetContinuousIntegrationBuildMethod + { + [Theory] + [InlineData(null)] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_ContinuousIntegrationBuild(bool? continuousIntegrationBuild) + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetContinuousIntegrationBuild(continuousIntegrationBuild); + + // Then + Assert.Equal(continuousIntegrationBuild, settings.ContinuousIntegrationBuild); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetContinuousIntegrationBuild(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheUseToolVersionMethod + { + [Fact] + public void Should_Set_Tool_Version() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.UseToolVersion(MSBuildVersion.MSBuild35); + + // Then + Assert.Equal(MSBuildVersion.MSBuild35, settings.ToolVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.UseToolVersion(MSBuildVersion.MSBuild35); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheValidateProjectFileMethod + { + [Fact] + public void Should_Set_Validate_Project_File() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.ValidateProjectFile(); + + // Then + Assert.True(settings.ValidateProjectFile); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.ValidateProjectFile(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithResponseFileMethod + { + [Fact] + public void Should_Add_Response_File_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + var filePath = FilePath.FromString(".sln"); + + // When + settings.WithResponseFile(filePath); + + // Then + Assert.True(settings.ResponseFiles.Contains(filePath)); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithResponseFile(".sln"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheUseDistributedFileLoggerMethod + { + [Fact] + public void Should_Set_Distributed_File_Logger() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.UseDistributedFileLogger(); + + // Then + Assert.True(settings.DistributedFileLogger); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.UseDistributedFileLogger(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithDistributedLoggerMethod + { + [Fact] + public void Should_Add_Distributed_Logger_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + var distributedLogger = new MSBuildDistributedLogger + { + CentralLogger = new MSBuildLogger + { + Assembly = "LoggerAssembly1", + Class = "LoggerClass1", + Parameters = "LoggerParameters1" + }, + ForwardingLogger = new MSBuildLogger + { + Assembly = "LoggerAssembly2", + Class = "LoggerClass2", + Parameters = "LoggerParameters2" + } + }; + + // When + settings.WithDistributedLogger(distributedLogger); + + // Then + Assert.True(settings.DistributedLoggers.Contains(distributedLogger)); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithDistributedLogger(new MSBuildDistributedLogger()); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetConsoleLoggerSettingsMethod + { + [Fact] + public void Should_Set_Console_Logger_Settings() + { + // Given + var settings = new DotNetMSBuildSettings(); + var consoleLoggerParameters = new MSBuildLoggerSettings + { + PerformanceSummary = true, + ConsoleColorType = MSBuildConsoleColorType.ForceAnsi + }; + + // When + settings.SetConsoleLoggerSettings(consoleLoggerParameters); + + // Then + Assert.Same(consoleLoggerParameters, settings.ConsoleLoggerSettings); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetConsoleLoggerSettings(new MSBuildLoggerSettings()); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheAddFileLoggerMethod + { + [Fact] + public void Should_Add_Logger() + { + // Given + var settings = new DotNetMSBuildSettings(); + var fileLogger = new MSBuildFileLoggerSettings(); + var fileLogger2 = new MSBuildFileLoggerSettings() { LogFile = "A" }; + + // When + settings.AddFileLogger(fileLogger); + settings.AddFileLogger(fileLogger2); + + // Then + var loggers = settings.FileLoggers.ToArray(); + Assert.Equal(2, loggers.Length); + Assert.Equal(fileLogger, loggers[0]); + Assert.Equal(fileLogger2, loggers[1]); + Assert.Equal("A", loggers[1].LogFile); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.AddFileLogger(new MSBuildFileLoggerSettings()); + var result1 = settings.AddFileLogger(); + + // Then + Assert.Equal(settings, result); + Assert.Equal(settings, result1); + } + } + + public sealed class TheEnableBinaryLoggerMethod + { + [Fact] + public void Should_Enable_BinaryLogger() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.EnableBinaryLogger(); + + // Then + Assert.NotNull(settings.BinaryLogger); + Assert.True(settings.BinaryLogger.Enabled); + Assert.Null(settings.BinaryLogger.FileName); + Assert.Equal(MSBuildBinaryLoggerImports.Unspecified, settings.BinaryLogger.Imports); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.EnableBinaryLogger(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithLoggerMethod + { + [Fact] + public void Should_Add_Logger() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.WithLogger("LoggerAssembly1", "LoggerClass1", "LoggerParameters1"); + settings.WithLogger("LoggerAssembly2", "LoggerClass2", "LoggerParameters2"); + + // Then + var loggers = settings.Loggers.ToArray(); + Assert.Equal(2, loggers.Length); + Assert.Equal("LoggerAssembly1", loggers[0].Assembly); + Assert.Equal("LoggerClass1", loggers[0].Class); + Assert.Equal("LoggerParameters1", loggers[0].Parameters); + Assert.Equal("LoggerAssembly2", loggers[1].Assembly); + Assert.Equal("LoggerClass2", loggers[1].Class); + Assert.Equal("LoggerParameters2", loggers[1].Parameters); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.WithLogger("Logger"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheDisableConsoleLoggerMethod + { + [Fact] + public void Should_Set_Disable_Console_Logger() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.DisableConsoleLogger(); + + // Then + Assert.True(settings.DisableConsoleLogger); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.DisableConsoleLogger(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetWarningCodeAsErrorMethod + { + [Fact] + public void Should_Add_Warning_Code_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetWarningCodeAsError("ERR1"); + + // Then + Assert.True(settings.WarningCodesAsError.Contains("ERR1")); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetWarningCodeAsError("ERR1"); + + // Then + Assert.Equal(settings, result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_Exception_If_Null_Or_Whitespace(string warningCode) + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = Record.Exception(() => settings.SetWarningCodeAsError(warningCode)); + + // Then + AssertEx.IsArgumentException(result, "warningCode", "Warning code cannot be null or empty"); + } + } + + public sealed class TheSetWarningCodeAsMessageMethod + { + [Fact] + public void Should_Add_Warning_Code_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetWarningCodeAsMessage("ERR1"); + + // Then + Assert.True(settings.WarningCodesAsMessage.Contains("ERR1")); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetWarningCodeAsMessage("ERR1"); + + // Then + Assert.Equal(settings, result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_Exception_If_Null_Or_Whitespace(string warningCode) + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = Record.Exception(() => settings.SetWarningCodeAsMessage(warningCode)); + + // Then + AssertEx.IsArgumentException(result, "warningCode", "Warning code cannot be null or empty"); + } + } + + public sealed class TheTreatAllWarningsAsMethod + { + [Theory] + [InlineData(MSBuildTreatAllWarningsAs.Message)] + [InlineData(MSBuildTreatAllWarningsAs.Error)] + public void Should_Set_Warning_Code_Behaviour(MSBuildTreatAllWarningsAs treatAllAs) + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.TreatAllWarningsAs(treatAllAs); + + // Then + Assert.Equal(treatAllAs, settings.TreatAllWarningsAs); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheNodeReuseMethod + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_Node_Reuse(bool reuse) + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.SetNodeReuse(reuse); + + // Then + Assert.Equal(reuse, settings.NodeReuse); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetNodeReuse(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetConfigurationMethod + { + private const string Configuration = "TheConfiguration"; + + [Fact] + public void Should_Set_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "Configuration"; + + // When + settings.SetConfiguration(Configuration); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(Configuration, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetConfiguration(Configuration); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionMethod + { + private const string Version = "1.0.0-test"; + + [Fact] + public void Should_Set_Version() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "Version"; + + // When + settings.SetVersion(Version); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(Version, settings.Properties[key].FirstOrDefault()); + Assert.Equal(Version, settings.Version); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetVersion(Version); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetFileVersionMethod + { + private const string FileVersion = "1.0.0-test"; + + [Fact] + public void Should_Set_FileVersion() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "FileVersion"; + + // When + settings.SetFileVersion(FileVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(FileVersion, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetFileVersion(FileVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetAssemblyVersionMethod + { + private const string AssemblyVersion = "1.0.0.0"; + + [Fact] + public void Should_Set_AssemblyVersion() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "AssemblyVersion"; + + // When + settings.SetAssemblyVersion(AssemblyVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(AssemblyVersion, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetAssemblyVersion(AssemblyVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetInformationalVersionMethod + { + private const string InformationalVersion = "1.0.0-test"; + + [Fact] + public void Should_Set_InformationalVersion() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "InformationalVersion"; + + // When + settings.SetInformationalVersion(InformationalVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(InformationalVersion, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetInformationalVersion(InformationalVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetPackageVersionMethod + { + private const string PackageVersion = "1.0.0-test"; + + [Fact] + public void Should_Set_PackageVersion() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "PackageVersion"; + + // When + settings.SetPackageVersion(PackageVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(PackageVersion, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetPackageVersion(PackageVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetPackageReleaseNotesMethod + { + private const string PackageReleaseNotes = "https://"; + + [Fact] + public void Should_Set_PackageReleaseNotes() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "PackageReleaseNotes"; + + // When + settings.SetPackageReleaseNotes(PackageReleaseNotes); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(PackageReleaseNotes, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetPackageReleaseNotes(PackageReleaseNotes); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSuppressVersionRecommendedFormatWarningMethod + { + [Fact] + public void Should_Set_NoWarn7035() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "nowarn"; + + // When + settings.SuppressVersionRecommendedFormatWarning(); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal("7035", settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SuppressVersionRecommendedFormatWarning(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionPrefixMethod + { + private const string VersionPrefix = "1.0.0"; + + [Fact] + public void Should_Set_Version_Prefix() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "VersionPrefix"; + + // When + settings.SetVersionPrefix(VersionPrefix); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(VersionPrefix, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetVersionPrefix(VersionPrefix); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionSuffixMethod + { + private const string VersionSuffix = "test"; + + [Fact] + public void Should_Set_Version_Suffix() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "VersionSuffix"; + + // When + settings.SetVersionSuffix(VersionSuffix); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(VersionSuffix, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetVersionSuffix(VersionSuffix); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetTargetFrameworkMethod + { + private const string TargetFramework = "netstandard2.0"; + + [Fact] + public void Should_Set_Target_Framework() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "TargetFrameworks"; + + // When + settings.SetTargetFramework(TargetFramework); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(TargetFramework, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetTargetFramework(TargetFramework); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetRuntimeMethod + { + private const string Runtime = "ubuntu.16.10-x64"; + + [Fact] + public void Should_Set_Runtime() + { + // Given + var settings = new DotNetMSBuildSettings(); + const string key = "RuntimeIdentifiers"; + + // When + settings.SetRuntime(Runtime); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(Runtime, settings.Properties[key].FirstOrDefault()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + var result = settings.SetRuntime(Runtime); + + // Then + Assert.Equal(settings, result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsTests.cs new file mode 100644 index 0000000000..0162cbc19c --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/MSBuild/DotNetMSBuildSettingsTests.cs @@ -0,0 +1,501 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.MSBuild; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.MSBuild +{ + public sealed class DotNetMSBuildSettingsTests + { + public sealed class TheVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.Version); + } + + [Fact] + public void Should_Add_Version_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.Version = "1.0.0-test"; + + // Then + Assert.True(settings.Properties.ContainsKey("Version")); + Assert.True(settings.Properties["Version"].Contains("1.0.0-test")); + } + } + + public sealed class TheVersionPrefixProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.VersionPrefix); + } + + [Fact] + public void Should_Add_VersionPrefix_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.VersionPrefix = "1.0.0"; + + // Then + Assert.True(settings.Properties.ContainsKey("VersionPrefix")); + Assert.True(settings.Properties["VersionPrefix"].Contains("1.0.0")); + } + } + + public sealed class TheVersionSuffixProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.VersionSuffix); + } + + [Fact] + public void Should_Add_VersionSuffix_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.VersionSuffix = "test"; + + // Then + Assert.True(settings.Properties.ContainsKey("VersionSuffix")); + Assert.True(settings.Properties["VersionSuffix"].Contains("test")); + } + } + + public sealed class TheFileVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.FileVersion); + } + + [Fact] + public void Should_Add_FileVersion_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.FileVersion = "1.0.0.0"; + + // Then + Assert.True(settings.Properties.ContainsKey("FileVersion")); + Assert.True(settings.Properties["FileVersion"].Contains("1.0.0.0")); + } + } + + public sealed class TheAssemblyVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.AssemblyVersion); + } + + [Fact] + public void Should_Add_AssemblyVersion_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.AssemblyVersion = "1.0.0.0"; + + // Then + Assert.True(settings.Properties.ContainsKey("AssemblyVersion")); + Assert.True(settings.Properties["AssemblyVersion"].Contains("1.0.0.0")); + } + } + + public sealed class TheInformationalVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.InformationalVersion); + } + + [Fact] + public void Should_Add_InformationalVersion_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.InformationalVersion = "1.0.0-test+7ad03d0"; + + // Then + Assert.True(settings.Properties.ContainsKey("InformationalVersion")); + Assert.True(settings.Properties["InformationalVersion"].Contains("1.0.0-test+7ad03d0")); + } + } + + public sealed class ThePackageVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.PackageVersion); + } + + [Fact] + public void Should_Add_PackageVersion_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.PackageVersion = "1.0.0-test"; + + // Then + Assert.True(settings.Properties.ContainsKey("PackageVersion")); + Assert.True(settings.Properties["PackageVersion"].Contains("1.0.0-test")); + } + } + + public sealed class ThePackageReleaseNotesProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.PackageReleaseNotes); + } + + [Fact] + public void Should_Add_PackageReleaseNotes_Property_To_Configuration() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.PackageReleaseNotes = "https://..."; + + // Then + Assert.True(settings.Properties.ContainsKey("PackageReleaseNotes")); + Assert.True(settings.Properties["PackageReleaseNotes"].Contains("https://...")); + } + } + + public sealed class TheDistributedFileLoggerProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.False(settings.DistributedFileLogger); + } + } + + public sealed class TheValidateProjectFileProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.False(settings.ValidateProjectFile); + } + } + + public sealed class TheExcludeAutoResponseFilesProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.False(settings.ExcludeAutoResponseFiles); + } + } + + public sealed class TheTreatAllWarningsAsProperty + { + [Fact] + public void Should_Be_Default_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Equal(MSBuildTreatAllWarningsAs.Default, settings.TreatAllWarningsAs); + } + } + + public sealed class TheWarningCodesAsErrorProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.WarningCodesAsError); + } + } + + public sealed class TheWarningCodesAsMessageProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.WarningCodesAsMessage); + } + } + + public sealed class TheConsoleLoggerSettingsProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.ConsoleLoggerSettings); + } + } + + public sealed class TheVerbosityProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.Verbosity); + } + } + + public sealed class TheToolVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.ToolVersion); + } + } + + public sealed class TheTargetsProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.Targets); + } + } + + public sealed class ThePropertiesProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.Properties); + } + + [Fact] + public void Should_Return_A_Dictionary_That_Is_Case_Insensitive() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // When + settings.Properties.Add("THEKEY", new[] { "THEVALUE" }); + + // Then + Assert.True(settings.Properties.ContainsKey("thekey")); + } + } + + public sealed class TheMaxCpuCountProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.MaxCpuCount); + } + } + + public sealed class TheDetailedSummaryProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.False(settings.DetailedSummary); + } + } + + public sealed class TheDisableConsoleLoggerProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.False(settings.DisableConsoleLogger); + } + } + + public sealed class TheLoggersProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.Loggers); + } + } + + public sealed class TheFileLoggersProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.FileLoggers); + } + } + + public sealed class TheIgnoreProjectExtensionsProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.IgnoreProjectExtensions); + } + } + + public sealed class TheResponseFilesProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.ResponseFiles); + } + } + + public sealed class TheDistributedLoggersProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Empty(settings.DistributedLoggers); + } + } + + public sealed class TheNodeReuseProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new DotNetMSBuildSettings(); + + // Then + Assert.Null(settings.NodeReuse); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleterTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleterTests.cs new file mode 100644 index 0000000000..3576f74653 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleterTests.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Delete; +using Cake.Common.Tools.DotNet.NuGet.Delete; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.NuGet.Delete +{ + public sealed class DotNetNuGetDeleterTests + { + public sealed class TheDeleteMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Not_Throw_If_PackageName_Is_Empty(string packageName) + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.PackageName = packageName; + fixture.Settings = new DotNetNuGetDeleteSettings(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget delete", result.Args); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Not_Throw_If_PackageVersion_Is_Empty(string packageVersion) + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.PackageName = "name"; + fixture.PackageVersion = packageVersion; + fixture.Settings = new DotNetNuGetDeleteSettings(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget delete name", result.Args); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + const string packageName = "name"; + const string packageVersion = "1.2.3"; + const string source = "http://www.nuget.org/api/v2/package"; + const string apiKey = "key1234"; + + var fixture = new DotNetNuGetDeleterFixture(); + fixture.Settings.Source = source; + fixture.Settings.NoServiceEndpoint = true; + fixture.Settings.Interactive = true; + fixture.Settings.NonInteractive = true; + fixture.Settings.ApiKey = apiKey; + fixture.Settings.ForceEnglishOutput = true; + fixture.PackageName = packageName; + fixture.PackageVersion = packageVersion; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(string.Format("nuget delete {0} {1} --source \"{2}\" --no-service-endpoint --interactive --non-interactive --api-key \"{3}\" --force-english-output", packageName, packageVersion, source, apiKey), result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetDeleterFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget delete", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Push/DotNetNuGetPusherTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Push/DotNetNuGetPusherTests.cs new file mode 100644 index 0000000000..6052c2ffd9 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Push/DotNetNuGetPusherTests.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Push; +using Cake.Common.Tools.DotNet.NuGet.Push; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.NuGet.Push +{ + public sealed class DotNetNuGetPusherTests + { + public sealed class ThePushMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetPusherFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Should_Throw_If_PackageName_Is_Empty(string packageName) + { + // Given + var fixture = new DotNetNuGetPusherFixture(); + fixture.PackageName = packageName; + fixture.Settings = new DotNetNuGetPushSettings(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageName"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetPusherFixture(); + fixture.PackageName = "foo.nupkg"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetPusherFixture(); + fixture.PackageName = "foo.nupkg"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + const string packageName = "foo.nupkg"; + + var fixture = new DotNetNuGetPusherFixture(); + fixture.PackageName = packageName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(string.Format("nuget push \"{0}\"", packageName), result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + const string packageName = "foo.nupkg"; + const int timeout = 60; + const string source = "http://www.nuget.org/api/v2/package"; + const string apiKey = "key1234"; + const string symbolSource = "http://www.symbolserver.org/"; + const string symbolApiKey = "key5678"; + + var fixture = new DotNetNuGetPusherFixture(); + fixture.Settings.Source = source; + fixture.Settings.ApiKey = apiKey; + fixture.Settings.SymbolSource = symbolSource; + fixture.Settings.SymbolApiKey = symbolApiKey; + fixture.Settings.NoServiceEndpoint = true; + fixture.Settings.Interactive = true; + fixture.Settings.Timeout = timeout; + fixture.Settings.DisableBuffering = true; + fixture.Settings.IgnoreSymbols = true; + fixture.Settings.SkipDuplicate = true; + fixture.Settings.ForceEnglishOutput = true; + fixture.PackageName = packageName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(string.Format("nuget push \"{0}\" --source {1} --api-key \"{2}\" --symbol-source {3} --symbol-api-key \"{4}\" --no-service-endpoint --interactive --timeout {5} --disable-buffering --no-symbols --skip-duplicate --force-english-output", packageName, source, apiKey, symbolSource, symbolApiKey, timeout), result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + const string packageName = "foo.nupkg"; + + var fixture = new DotNetNuGetPusherFixture(); + fixture.Settings.DiagnosticOutput = true; + fixture.PackageName = packageName; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(string.Format("--diagnostics nuget push \"{0}\"", packageName), result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Source/DotNetNuGetSourcerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Source/DotNetNuGetSourcerTests.cs new file mode 100644 index 0000000000..f1d8965b29 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/NuGet/Source/DotNetNuGetSourcerTests.cs @@ -0,0 +1,720 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.NuGet.Source; +using Cake.Common.Tools.DotNet.NuGet.Source; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.NuGet.Source +{ + public sealed class DotNetNuGetSourcerTests + { + public sealed class TheAddSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Settings_Source_Is_Null_Or_WhiteSpace(string source) + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { Source = source }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "Source", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { Source = "source" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { Source = "source" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { Source = "source" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget add source \"source\" --name \"name\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings + { + Source = "source", + UserName = "username", + Password = "password", + StorePasswordInClearText = true, + ValidAuthenticationTypes = "basic,negotiate" + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget add source \"source\" --name \"name\" --username \"username\" --password \"password\" --store-password-in-clear-text --valid-authentication-types \"basic,negotiate\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetAddSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true, Source = "source" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget add source \"source\" --name \"name\"", result.Args); + } + } + + public sealed class TheDisableSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget disable source \"name\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetDisableSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget disable source \"name\"", result.Args); + } + } + + public sealed class TheEnableSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget enable source \"name\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetEnableSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget enable source \"name\"", result.Args); + } + } + + public sealed class TheHasSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget list source --format \"detailed\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget list source --format \"detailed\"", result.Args); + } + + [Theory] + [InlineData("nuget.org", "Enabled")] + [InlineData("nuget.org", "Disabled")] + [InlineData("Package source {0}1", "Enabled")] + [InlineData("Package source {0}1", "Disabled")] + public void Should_Return_True_For_Configured_Source(string name, string status) + { + // Given + var fixture = new DotNetNuGetHasSourceFixture(); + fixture.Name = name; + fixture.GivenProcessOutput(new[] + { + "Registered Sources:", + $" 1. {name} [{status}]", + " https://api.myfeed.org/v3/index.json" + }); + + // When + var result = fixture.Run(); + + // Then + Assert.True(fixture.HasSource); + } + } + + public sealed class TheListSourceMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget list source", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + fixture.Format = "detailed"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget list source --format \"detailed\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetListSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget list source", result.Args); + } + } + + public sealed class TheRemoveSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget remove source \"name\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetRemoveSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget remove source \"name\"", result.Args); + } + } + + public sealed class TheUpdateSourceMethod + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + public void Should_Throw_If_Name_Is_Null_Or_WhiteSpace(string name) + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.Name = name; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentException(result, "name", "Value cannot be null or whitespace."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget update source \"name\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings + { + Source = "source", + UserName = "username", + Password = "password", + StorePasswordInClearText = true, + ValidAuthenticationTypes = "basic,negotiate" + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("nuget update source \"name\" --source \"source\" --username \"username\" --password \"password\" --store-password-in-clear-text --valid-authentication-types \"basic,negotiate\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetNuGetUpdateSourceFixture(); + fixture.Settings = new DotNetNuGetSourceSettings { DiagnosticOutput = true }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics nuget update source \"name\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Pack/DotNetPackerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Pack/DotNetPackerTests.cs new file mode 100644 index 0000000000..a8a7016e11 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Pack/DotNetPackerTests.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Pack; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Pack +{ + public sealed class DotNetPackerTests + { + public sealed class ThePackMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetPackFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack", result.Args); + } + + [Fact] + public void Should_Add_Project() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack \"./src/*\"", result.Args); + } + + [Theory] + [InlineData("./src/*", "pack \"./src/*\"")] + [InlineData("./src/cake artifacts/", "pack \"./src/cake artifacts/\"")] + [InlineData("./src/nuget/cake build", "pack \"./src/nuget/cake build\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetPackFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Settings() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.Settings.NoBuild = true; + fixture.Settings.NoDependencies = true; + fixture.Settings.NoRestore = true; + fixture.Settings.NoLogo = true; + fixture.Settings.Configuration = "Release"; + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Settings.VersionSuffix = "rc1"; + fixture.Settings.IncludeSource = true; + fixture.Settings.IncludeSymbols = true; + fixture.Settings.SymbolPackageFormat = "snupkg"; + fixture.Settings.Serviceable = true; + fixture.Settings.Runtime = "win7-x86"; + fixture.Settings.Verbosity = DotNetVerbosity.Minimal; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --output \"/Working/artifacts\" --no-build --no-dependencies --no-restore --nologo --include-symbols -p:SymbolPackageFormat=snupkg --include-source --configuration Release --version-suffix rc1 --serviceable --runtime win7-x86 --source \"https://api.nuget.org/v3/index.json\" --verbosity minimal", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetPackFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics pack", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs new file mode 100644 index 0000000000..ac19bb0f41 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Add/DotNetPackageAdderTests.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Add; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.Add +{ + public sealed class DotNetPackageAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_PackageName_Is_Null() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageName"); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"ToDo.csproj\" package Microsoft.AspNetCore.StaticFiles", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetPackageAdderFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Settings.Framework = "net8.0"; + fixture.Settings.Interactive = true; + fixture.Settings.NoRestore = true; + fixture.Settings.PackageDirectory = "./src/project"; + fixture.Settings.Prerelease = true; + fixture.Settings.Source = "http://www.nuget.org/api/v2/package"; + fixture.Settings.Version = "1.0.0"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "add package Microsoft.AspNetCore.StaticFiles --framework net8.0 --interactive --no-restore --package-directory \"/Working/src/project\" --prerelease --source \"http://www.nuget.org/api/v2/package\" --version \"1.0.0\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/List/DotNetPackageListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/List/DotNetPackageListerTests.cs new file mode 100644 index 0000000000..3444eab001 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/List/DotNetPackageListerTests.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Package.List; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.List +{ + public sealed class DotNetPackageListerTests + { + public sealed class TheListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.Project = "ToDo.csproj"; + fixture.GivenPackgeListResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"ToDo.csproj\" package --format json --output-version 1", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.Deprecated = true; + fixture.Settings.Framework = "net8.0"; + fixture.Settings.HighestMinor = true; + fixture.Settings.HighestPatch = true; + fixture.Settings.Prerelease = true; + fixture.Settings.Transitive = true; + fixture.Settings.Interactive = true; + fixture.Settings.Outdated = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.Vulnerable = true; + fixture.GivenPackgeListResult(); + + // When + var result = fixture.Run(); + + // Then + var expected = "list package --config \"/Working/nuget.config\" --deprecated --framework net8.0 --highest-minor --highest-patch --include-prerelease --include-transitive --interactive --outdated "; + expected += "--source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --vulnerable --format json --output-version 1"; + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Return_Correct_Result() + { + // Given + var fixture = new DotNetPackageListerFixture(); + fixture.GivenPackgeListResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(1, fixture.Result.Version); + Assert.Contains(fixture.Result.Projects, item => item.Path == "src/lib/MyProject.csproj"); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Remove/DotNetPackageRemoverTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Remove/DotNetPackageRemoverTests.cs new file mode 100644 index 0000000000..b97f3d37b3 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Remove/DotNetPackageRemoverTests.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Remove; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.Remove +{ + public sealed class DotNetPackageRemoverTests + { + public sealed class TheRemoveMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPackageRemoverFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPackageRemoverFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_PackageName_Is_Null() + { + // Given + var fixture = new DotNetPackageRemoverFixture(); + fixture.PackageName = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageName"); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetPackageRemoverFixture(); + fixture.PackageName = "Microsoft.AspNetCore.StaticFiles"; + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("remove \"ToDo.csproj\" package Microsoft.AspNetCore.StaticFiles", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearchSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearchSettingsTests.cs new file mode 100644 index 0000000000..62ddc07c44 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearchSettingsTests.cs @@ -0,0 +1,51 @@ +using Cake.Common.Tools.DotNet.Package.Search; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.Search +{ + public sealed class DotNetPackageSearchSettingsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Set_ExactMatch_To_False_By_Default() + { + // Given, When + var settings = new DotNetPackageSearchSettings(); + + // Then + Assert.False(settings.ExactMatch); + } + + [Fact] + public void Should_Set_Take_To_Null_By_Default() + { + // Given, When + var settings = new DotNetPackageSearchSettings(); + + // Then + Assert.Null(settings.Take); + } + + [Fact] + public void Should_Set_Skip_To_Null_By_Default() + { + // Given, When + var settings = new DotNetPackageSearchSettings(); + + // Then + Assert.Null(settings.Skip); + } + + [Fact] + public void Should_Set_Prerelease_To_False_By_Default() + { + // Given, When + var settings = new DotNetPackageSearchSettings(); + + // Then + Assert.False(settings.Prerelease); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearcherTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearcherTests.cs new file mode 100644 index 0000000000..6725488e0b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Package/Search/DotNetPackageSearcherTests.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Package.Search; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Package.Search +{ + public sealed class DotNetPackageSearcherTests + { + public sealed class TheSearchMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_ExactMatch_To_Arguments_If_True() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.ExactMatch = true; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --exact-match --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_True() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.Prerelease = true; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --prerelease --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_Take_To_Arguments_If_True() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.Take = 10; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --take 10 --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_Skip_To_Arguments_If_True() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.Skip = 10; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --skip 10 --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_Sources_To_Arguments_If_Set() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.Sources = new[] { "A", "B", "C", }; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --source \"A\" --source \"B\" --source \"C\" --verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("package search \"Cake\" --configfile \"/Working/nuget.config\" " + + "--verbosity normal --format json", result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_DotNetPackageSearchItems() + { + // Given + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Cake"; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Result, + item => + { + Assert.Equal(item.Name, "Cake"); + Assert.Equal(item.Version, "0.22.2"); + }, + item => + { + Assert.Equal(item.Name, "Cake.Core"); + Assert.Equal(item.Version, "0.22.2"); + }, + item => + { + Assert.Equal(item.Name, "Cake.CoreCLR"); + Assert.Equal(item.Version, "0.22.2"); + }); + } + + [Fact] + public void Should_Return_Versions_When_ExactMatch_Json_Uses_Version_Property() + { + // Given: dotnet package search --exact-match --format json returns "version" not "latestVersion" + var fixture = new DotNetPackageSearcherFixture(); + fixture.SearchTerm = "Refit.Newtonsoft.Json"; + fixture.Settings.ExactMatch = true; + fixture.GivenExactMatchPackageResult(); + + // When + fixture.Run(); + + // Then: Version must be populated from "version" property (issue #4454) + Assert.Collection(fixture.Result, + item => + { + Assert.Equal("Refit.Newtonsoft.Json", item.Name); + Assert.Equal("7.0.0", item.Version); + }, + item => + { + Assert.Equal("Refit.Newtonsoft.Json", item.Name); + Assert.Equal("6.3.0", item.Version); + }); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs new file mode 100644 index 0000000000..5532ee7752 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Publish/DotNetPublisherTests.cs @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Publish; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Publish +{ + public sealed class DotNetPublisherTests + { + public sealed class ThePublishMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetPublisherFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish", result.Args); + } + + [Fact] + public void Should_Add_Path() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish \"./src/*\"", result.Args); + } + + [Theory] + [InlineData("./src/*", "publish \"./src/*\"")] + [InlineData("./src/cake artifacts/", "publish \"./src/cake artifacts/\"")] + [InlineData("./src/cake artifacts/cake binaries", "publish \"./src/cake artifacts/cake binaries\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Settings() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.NoBuild = true; + fixture.Settings.NoDependencies = true; + fixture.Settings.NoRestore = true; + fixture.Settings.NoLogo = true; + fixture.Settings.Framework = "dnxcore50"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.Runtime = "runtime1"; + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Settings.VersionSuffix = "rc1"; + fixture.Settings.Verbosity = DotNetVerbosity.Minimal; + fixture.Settings.Force = true; + fixture.Settings.SelfContained = true; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --output \"/Working/artifacts\" --runtime runtime1 --framework dnxcore50 --configuration Release --version-suffix rc1 --no-build --no-dependencies --no-restore --nologo --force --self-contained --source \"https://api.nuget.org/v3/index.json\" --verbosity minimal", result.Args); + } + + [Fact] + public void Should_Add_SelfContained_False_Settings() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.NoDependencies = true; + fixture.Settings.NoRestore = true; + fixture.Settings.Framework = "dnxcore50"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.Runtime = "runtime1"; + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Settings.VersionSuffix = "rc1"; + fixture.Settings.Verbosity = DotNetVerbosity.Minimal; + fixture.Settings.Force = true; + fixture.Settings.SelfContained = false; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --output \"/Working/artifacts\" --runtime runtime1 --framework dnxcore50 --configuration Release --version-suffix rc1 --no-dependencies --no-restore --force --no-self-contained --source \"https://api.nuget.org/v3/index.json\" --verbosity minimal", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics publish", result.Args); + } + + [Fact] + public void Should_Add_PublishSingleFile() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.PublishSingleFile = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:PublishSingleFile=true", result.Args); + } + + [Fact] + public void Should_Add_PublishTrimmed() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.PublishTrimmed = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:PublishTrimmed=true", result.Args); + } + + [Fact] + public void Should_Add_TieredCompilationQuickJit() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.TieredCompilationQuickJit = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:TieredCompilationQuickJit=false", result.Args); + } + + [Fact] + public void Should_Add_TieredCompilation() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.TieredCompilation = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:TieredCompilation=false", result.Args); + } + + [Fact] + public void Should_Add_PublishReadyToRun() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.PublishReadyToRun = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:PublishReadyToRun=true", result.Args); + } + + [Fact] + public void Should_Add_PublishReadyToRunShowWarnings() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.PublishReadyToRunShowWarnings = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:PublishReadyToRunShowWarnings=true", result.Args); + } + + [Fact] + public void Should_Add_IncludeNativeLibrariesForSelfExtract() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.IncludeNativeLibrariesForSelfExtract = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:IncludeNativeLibrariesForSelfExtract=true", result.Args); + } + + [Fact] + public void Should_Add_IncludeAllContentForSelfExtract() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.IncludeAllContentForSelfExtract = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:IncludeAllContentForSelfExtract=true", result.Args); + } + + [Fact] + public void Should_Add_EnableCompressionInSingleFile() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.EnableCompressionInSingleFile = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -p:EnableCompressionInSingleFile=true", result.Args); + } + + [Fact] + public void Should_Add_Os() + { + // Given + var fixture = new DotNetPublisherFixture(); + fixture.Settings.OS = "linux"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --os linux", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Add/DotNetReferenceAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Add/DotNetReferenceAdderTests.cs new file mode 100644 index 0000000000..89efc4345d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Add/DotNetReferenceAdderTests.cs @@ -0,0 +1,159 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.Add; +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Reference.Add +{ + public sealed class DotNetReferenceAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_ProjectReferences_Is_Null() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectReferences"); + } + + [Fact] + public void Should_Throw_If_ProjectReferences_Is_Empty() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = Array.Empty(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectReferences"); + } + + [Fact] + public void Should_Not_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add reference \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_All_Project_References() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj", "./lib2/*.csproj" }; + fixture.Project = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add reference \"/Working/lib1.csproj\" \"/Working/lib2/*.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"ToDo.csproj\" reference \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetReferenceAdderFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = "ToDo.csproj"; + fixture.Settings.Framework = "net8.0"; + fixture.Settings.Interactive = true; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "add \"ToDo.csproj\" reference \"/Working/lib1.csproj\" --framework net8.0 --interactive --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/List/DotNetReferenceListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/List/DotNetReferenceListerTests.cs new file mode 100644 index 0000000000..e5fd3c1528 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/List/DotNetReferenceListerTests.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.List; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Reference.List +{ + public sealed class DotNetReferenceListerTests + { + public sealed class TheListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Not_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.Project = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list reference", result.Args); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"ToDo.csproj\" reference", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.Project = "ToDo.csproj"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "list \"ToDo.csproj\" reference --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_References() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.GivenProjectReferencesResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.References, + item => + { + Assert.Equal(item, "..\\..\\Common\\Common.AspNetCore\\Common.AspNetCore.csproj"); + }, + item => + { + Assert.Equal(item, "..\\..\\Common\\Common.Messaging\\Common.Messaging.csproj"); + }, + item => + { + Assert.Equal(item, "..\\..\\Common\\Common.Utilities\\Common.Utilities.csproj"); + }); + } + + [Fact] + public void Should_Return_Empty_List_Of_References() + { + // Given + var fixture = new DotNetReferenceListerFixture(); + fixture.GivenEmptyProjectReferencesResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Empty(fixture.References); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverTests.cs new file mode 100644 index 0000000000..d8e0ddda51 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Reference/Remove/DotNetReferenceRemoverTests.cs @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.DotNet.Reference.Remove; +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Reference.Remove +{ + public sealed class DotNetReferenceRemoverTests + { + public sealed class TheRemoveMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./test/unit.tests.csproj" }; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_ProjectReferences_Is_Null() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectReferences"); + } + + [Fact] + public void Should_Throw_If_ProjectReferences_Is_Empty() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = Array.Empty(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectReferences"); + } + + [Fact] + public void Should_Not_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("remove reference \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_All_Project_References() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj", "./lib2/*.csproj" }; + fixture.Project = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("remove reference \"/Working/lib1.csproj\" \"/Working/lib2/*.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Project_Argument() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = "ToDo.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("remove \"ToDo.csproj\" reference \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetReferenceRemoverFixture(); + fixture.ProjectReferences = new[] { (FilePath)"./lib1.csproj" }; + fixture.Project = "ToDo.csproj"; + fixture.Settings.Framework = "net8.0"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "remove \"ToDo.csproj\" reference \"/Working/lib1.csproj\" --framework net8.0 --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs new file mode 100644 index 0000000000..a86e6f1d9b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Restore/DotNetRestorerTests.cs @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Restore; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Restore; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Restore +{ + public sealed class DotNetRestorerTests + { + public sealed class TheRestoreMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Root = "./src/*"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetRestorerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore", result.Args); + } + + [Fact] + public void Should_Add_Path() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Root = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore \"./src/*\"", result.Args); + } + + [Theory] + [InlineData("./src/*", "restore \"./src/*\"")] + [InlineData("./src/cake build/", "restore \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "restore \"./src/cake build/cake cli\"")] + public void Should_Quote_Root_Path(string text, string expected) + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Root = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Settings() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.Sources = new[] { "https://www.example.com/source1", "https://www.example.com/source2" }; + fixture.Settings.NoCache = true; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.ConfigFile = "./NuGet.config"; + fixture.Settings.PackagesDirectory = "./packages/"; + fixture.Settings.Runtime = "runtime1"; + fixture.Settings.NoDependencies = true; + fixture.Settings.Force = true; + fixture.Settings.Verbosity = DotNetVerbosity.Minimal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore" + + " --runtime runtime1" + + " --packages \"/Working/packages\"" + + " --source \"https://www.example.com/source1\"" + + " --source \"https://www.example.com/source2\"" + + " --configfile \"/Working/NuGet.config\"" + + " --no-cache --disable-parallel --ignore-failed-sources --no-dependencies --force" + + " --verbosity minimal", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics restore", result.Args); + } + + [Fact] + public void Should_Add_Interactive() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.Interactive = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($@"restore --interactive", result.Args); + } + + [Fact] + public void Should_Add_UseLockFile() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.UseLockFile = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($@"restore --use-lock-file", result.Args); + } + + [Fact] + public void Should_Add_LockedMode() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.LockedMode = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($@"restore --locked-mode", result.Args); + } + + [Fact] + public void Should_Add_LockFilePath() + { + // Given + var fixture = new DotNetRestorerFixture(); + const string lockfile = @"mypackages.lock.json"; + fixture.Settings.LockFilePath = lockfile; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($@"restore --lock-file-path ""/Working/{lockfile}""", result.Args); + } + + [Fact] + public void Should_Add_ForceEvaluate() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.ForceEvaluate = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($@"restore --force-evaluate", result.Args); + } + + [Fact] + public void Should_Add_PublishReadyToRun() + { + // Given + var fixture = new DotNetRestorerFixture(); + fixture.Settings.PublishReadyToRun = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore -p:PublishReadyToRun=true", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Run/DotNetRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Run/DotNetRunnerTests.cs new file mode 100644 index 0000000000..ba6914c251 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Run/DotNetRunnerTests.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Run; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Run +{ + public sealed class DotNetRunnerTests + { + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetRunnerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("run", result.Args); + } + + [Fact] + public void Should_Add_Path_Arguments() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Project = "./tools/tool/"; + fixture.Arguments = "--args=\"value\""; + // When + var result = fixture.Run(); + + // Then + Assert.Equal("run --project \"./tools/tool/\" -- --args=\"value\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Settings() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Settings.Framework = "dnxcore50"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.Runtime = "win7-x86"; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + fixture.Settings.RollForward = DotNetRollForward.Major; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("run --framework dnxcore50 --configuration Release --runtime win7-x86 --source \"https://api.nuget.org/v3/index.json\" --roll-forward Major", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics run", result.Args); + } + + [Theory] + [InlineData(DotNetRollForward.Minor, "run --roll-forward Minor")] + [InlineData(DotNetRollForward.LatestPatch, "run --roll-forward LatestPatch")] + [InlineData(DotNetRollForward.Major, "run --roll-forward Major")] + [InlineData(DotNetRollForward.LatestMinor, "run --roll-forward LatestMinor")] + [InlineData(DotNetRollForward.LatestMajor, "run --roll-forward LatestMajor")] + [InlineData(DotNetRollForward.Disable, "run --roll-forward Disable")] + public void Should_Add_RollForward_Arguments(DotNetRollForward rollForward, string expected) + { + // Given + var fixture = new DotNetRunnerFixture(); + fixture.Settings.RollForward = rollForward; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/SDKCheck/DotNetSDKCheckTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/SDKCheck/DotNetSDKCheckTests.cs new file mode 100644 index 0000000000..b902ddbe03 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/SDKCheck/DotNetSDKCheckTests.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.SDKCheck; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.SDKCheck +{ + public sealed class DotNetSDKCheckTests + { + public sealed class TheSDKCheckMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSDKCheckerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetSDKCheckerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs new file mode 100644 index 0000000000..22094b0180 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Add/DotNetSlnAdderTests.cs @@ -0,0 +1,220 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.Add; +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Sln.Add +{ + public sealed class DotNetSlnAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_ProjectPath_Is_Null() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_ProjectPath_Is_Empty() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new FilePath[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_InRoot_And_SolutionFolder_Are_Used_Together() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.InRoot = true; + fixture.Settings.SolutionFolder = "mylibs"; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("InRoot and SolutionFolder cannot be used together.", result.Message); + } + + [Fact] + public void Should_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Not_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = null; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_ProjectPath_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_All_ProjectPath() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj", "./lib2.csproj", "./lib3.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add \"/Working/lib1.csproj\" \"/Working/lib2.csproj\" \"/Working/lib3.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_InRoot_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.InRoot = true; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add --in-root \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_SolutionFolder_Argument() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.SolutionFolder = "mylibs"; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln add --solution-folder \"/Working/mylibs\" \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetSlnAdderFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.Verbosity = DotNetVerbosity.Detailed; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" add \"/Working/lib1.csproj\" --verbosity detailed", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs new file mode 100644 index 0000000000..c39a97a093 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/List/DotNetSlnListerTests.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.List; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Sln.List +{ + public sealed class DotNetSlnListerTests + { + public sealed class TheListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Not_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sln list", result.Args); + } + + [Fact] + public void Should_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = "ToDo.sln"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sln \"/Working/ToDo.sln\" list", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.Solution = "ToDo.sln"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "sln \"/Working/ToDo.sln\" list --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_Projects() + { + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.GivenProjectsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Projects, + item => + { + Assert.Equal(item, "Common\\Common.AspNetCore\\Common.AspNetCore.csproj"); + }, + item => + { + Assert.Equal(item, "Common\\Common.Messaging\\Common.Messaging.csproj"); + }, + item => + { + Assert.Equal(item, "Common\\Common.Utilities\\Common.Utilities.csproj"); + }); + } + + [Fact] + public void Should_Return_StandardError_ExitCode() + { + const string expectedStandardError = "Specified solution file C:\\Cake\\Cake.Core\\ does not exist, or there is no solution file in the directory."; + + // Given + var fixture = new DotNetSlnListerFixture(); + fixture.StandardError = expectedStandardError; + fixture.GivenErrorResult(); + + // When + fixture.Run(); + + // Then + Assert.Equal(expectedStandardError, fixture.StandardError); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Remove/DotNetSlnRemoverTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Remove/DotNetSlnRemoverTests.cs new file mode 100644 index 0000000000..408afcae04 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Sln/Remove/DotNetSlnRemoverTests.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Sln.Remove; +using Cake.Common.Tools.DotNet; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Sln.Remove +{ + public sealed class DotNetSlnRemoverTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_ProjectPath_Is_Null() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_ProjectPath_Is_Empty() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new FilePath[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectPath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" remove \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Not_Add_Solution_Argument() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.Solution = null; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln remove \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_ProjectPath_Argument() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln remove \"/Working/lib1.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_All_ProjectPath() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj", "./lib2.csproj", "./lib3.csproj" }; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln remove \"/Working/lib1.csproj\" \"/Working/lib2.csproj\" \"/Working/lib3.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetSlnRemoverFixture(); + fixture.Solution = (FilePath)"test.sln"; + fixture.ProjectPath = new[] { (FilePath)"./lib1.csproj" }; + fixture.Settings.Verbosity = DotNetVerbosity.Detailed; + + // When + var result = fixture.Run(); + + // Then + Assert.NotNull(result); + Assert.Equal("sln \"/Working/test.sln\" remove \"/Working/lib1.csproj\" --verbosity detailed", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Test/DotNetTesterTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Test/DotNetTesterTests.cs new file mode 100644 index 0000000000..81b5030a55 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Test/DotNetTesterTests.cs @@ -0,0 +1,292 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Test; +using Cake.Common.Tools.DotNet.Test; +using Cake.Core; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Test +{ + public sealed class DotNetTesterTests + { + public sealed class TheTestMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetTesterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test", result.Args); + } + + [Fact] + public void Should_Add_Path() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/Project.Tests/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test \"./test/Project.Tests/*\"", result.Args); + } + + [Theory] + [InlineData("./test/*", "test \"./test/*\"")] + [InlineData("./test/cake unit tests/", "test \"./test/cake unit tests/\"")] + [InlineData("./test/cake unit tests/cake core tests", "test \"./test/cake unit tests/cake core tests\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_RunSettings_Arguments() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Arguments = new[] { "MSTest.DeploymentEnabled=false", "MSTest.MapInconclusiveToFailed=true" }.ToProcessArguments(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test -- MSTest.DeploymentEnabled=false MSTest.MapInconclusiveToFailed=true", result.Args); + } + + [Fact] + public void Should_Add_Additional_Settings() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Settings.NoBuild = true; + fixture.Settings.NoRestore = true; + fixture.Settings.NoLogo = true; + fixture.Settings.Framework = "dnxcore50"; + fixture.Settings.Configuration = "Release"; + fixture.Settings.Collectors = new[] { "XPlat Code Coverage" }; + fixture.Settings.OutputDirectory = "./artifacts/"; + fixture.Settings.Settings = "./demo.runsettings"; + fixture.Settings.Filter = "Priority = 1"; + fixture.Settings.TestAdapterPath = @"/Working/custom-test-adapter"; + fixture.Settings.Loggers = new[] { "html;LogFileName=/Working/logfile.html" }; + fixture.Settings.DiagnosticFile = "./artifacts/logging/diagnostics.txt"; + fixture.Settings.ResultsDirectory = "./tests/"; + fixture.Settings.VSTestReportPath = "./tests/TestResults.xml"; + fixture.Settings.Runtime = "win-x64"; + fixture.Settings.Blame = true; + fixture.Settings.Sources = new[] { "https://api.nuget.org/v3/index.json" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test --settings \"/Working/demo.runsettings\" --filter \"Priority = 1\" --test-adapter-path \"/Working/custom-test-adapter\" --logger \"html;LogFileName=/Working/logfile.html\" --output \"/Working/artifacts\" --framework dnxcore50 --configuration Release --collect \"XPlat Code Coverage\" --diag \"/Working/artifacts/logging/diagnostics.txt\" --no-build --no-restore --nologo --results-directory \"/Working/tests\" --logger trx;LogFileName=\"/Working/tests/TestResults.xml\" --runtime win-x64 --source \"https://api.nuget.org/v3/index.json\" --blame", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics test", result.Args); + } + + [Fact] + public void Should_Add_Project_Path_With_Explicit_Project_Type() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/Project.Tests.csproj"; + fixture.Settings.PathType = DotNetTestPathType.Project; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test --project \"./test/Project.Tests.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Solution_Path_With_Explicit_Solution_Type() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/TestSolution.sln"; + fixture.Settings.PathType = DotNetTestPathType.Solution; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test --solution \"./test/TestSolution.sln\"", result.Args); + } + + [Theory] + [InlineData("./test/Project.csproj", "test --project \"./test/Project.csproj\"")] + [InlineData("./test/Project.vbproj", "test --project \"./test/Project.vbproj\"")] + [InlineData("./test/Project.fsproj", "test --project \"./test/Project.fsproj\"")] + [InlineData("./test/Project.vcxproj", "test --project \"./test/Project.vcxproj\"")] + public void Should_Auto_Detect_Project_Files(string projectPath, string expected) + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = projectPath; + fixture.Settings.PathType = DotNetTestPathType.Auto; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Auto_Detect_Solution_Files() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/TestSolution.sln"; + fixture.Settings.PathType = DotNetTestPathType.Auto; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test --solution \"./test/TestSolution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_Legacy_Behavior_For_Unknown_Extensions_With_Auto() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/UnknownFile.xyz"; + fixture.Settings.PathType = DotNetTestPathType.Auto; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test \"./test/UnknownFile.xyz\"", result.Args); + } + + [Fact] + public void Should_Use_Legacy_Behavior_When_PathType_Is_None() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/Project.csproj"; + fixture.Settings.PathType = DotNetTestPathType.None; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test \"./test/Project.csproj\"", result.Args); + } + + [Fact] + public void Should_Use_Legacy_Behavior_When_PathType_Is_Default() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/Project.csproj"; + // PathType defaults to None + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test \"./test/Project.csproj\"", result.Args); + } + + [Fact] + public void Should_Combine_PathType_With_Other_Settings() + { + // Given + var fixture = new DotNetTesterFixture(); + fixture.Project = "./test/Project.csproj"; + fixture.Settings.PathType = DotNetTestPathType.Project; + fixture.Settings.NoBuild = true; + fixture.Settings.Configuration = "Release"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("test --project \"./test/Project.csproj\" --configuration Release --no-build", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Tool/DotNetToolTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Tool/DotNetToolTests.cs new file mode 100644 index 0000000000..ab018bae02 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Tool/DotNetToolTests.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Tool; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Tool; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Tool +{ + public sealed class DotNetToolTests + { + public sealed class TheToolMethod + { + [Fact] + public void Should_Not_Throw_If_ProjectPath_IsNull() + { + // Given + var fixture = new DotNetToolFixture(); + fixture.Command = "cake"; + fixture.ProjectPath = null; + + // When + fixture.Run(); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void Should_Throw_If_Command_IsNull(string command) + { + // Given + var fixture = new DotNetToolFixture(); + fixture.ProjectPath = "./tests/Cake.Common.Tests/"; + fixture.Command = command; + + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "command"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetToolFixture(); + fixture.ProjectPath = "./tests/Cake.Common.Tests/"; + fixture.Command = "xunit"; + fixture.Settings = null; + + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetToolFixture(); + fixture.ProjectPath = "./tests/Cake.Common.Tests/"; + fixture.Command = "xunit"; + fixture.Settings = new DotNetToolSettings(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetToolFixture(); + fixture.ProjectPath = "./tests/Cake.Common.Tests/"; + fixture.Command = "xunit"; + fixture.Settings = new DotNetToolSettings(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Wrap_Command_In_Quotes() + { + // Given + var fixture = new DotNetToolFixture(); + fixture.ProjectPath = "./tests/Cake.Common.Tests/"; + fixture.Command = "C:\\example\\path with\\spaces"; + fixture.Settings = new DotNetToolSettings(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"C:\\example\\path with\\spaces\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/VSTest/DotNetVSTesterTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/VSTest/DotNetVSTesterTests.cs new file mode 100644 index 0000000000..711709b06d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/VSTest/DotNetVSTesterTests.cs @@ -0,0 +1,390 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.VSTest; +using Cake.Common.Tools.DotNet.VSTest; +using Cake.Common.Tools.VSTest; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.VSTest +{ + public sealed class DotNetVSTesterTests + { + public sealed class TheTestMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = null + }; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetVSTesterFixture { TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" } }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetVSTesterFixture { TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" } }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetVSTesterFixture { TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" } }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Test_File_Arguments() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] + { + (FilePath)"./test1/unit.tests.csproj", + (FilePath)"./test2/unit.tests.csproj", + (FilePath)"./test3/unit.tests.csproj", + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test1/unit.tests.csproj\" \"/Working/test2/unit.tests.csproj\" \"/Working/test3/unit.tests.csproj\"", result.Args); + } + + [Fact] + public void Should_Quote_Test_File_Path() + { + // Given + var fixture = new DotNetVSTesterFixture { TestFiles = new[] { (FilePath)"./test/cake unit tests/cake core tests.csproj" } }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/cake unit tests/cake core tests.csproj\"", result.Args); + } + + [Fact] + public void Should_Add_Settings_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Settings = "./test/demo.runsettings" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Settings:\"/Working/test/demo.runsettings\"", result.Args); + } + + [Fact] + public void Should_Add_Tests_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + TestsToRun = new[] { "TestMethod1", "TestMethod2" } + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Tests:TestMethod1,TestMethod2", result.Args); + } + + [Fact] + public void Should_Add_TestAdapter_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + TestAdapterPath = @"/Working/custom-test-adapter" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --TestAdapterPath:\"/Working/custom-test-adapter\"", result.Args); + } + + [Fact] + public void Should_Add_Platform_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Platform = VSTestPlatform.x64 + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Platform:x64", result.Args); + } + + [Fact] + public void Should_Add_Framework_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Framework = "dnxcore50" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Framework:dnxcore50", result.Args); + } + + [Fact] + public void Should_Add_Parallel_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Parallel = true + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Parallel", result.Args); + } + + [Fact] + public void Should_Add_TestCaseFilter_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + TestCaseFilter = "FullyQualifiedName~Cake.Common.Core.DotNet" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --TestCaseFilter:\"FullyQualifiedName~Cake.Common.Core.DotNet\"", result.Args); + } + + [Fact] + public void Should_Add_Logger_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Logger = @"trx;LogFileName=/Working/logfile.trx" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --logger:\"trx;LogFileName=/Working/logfile.trx\"", result.Args); + } + + [Fact] + public void Should_Add_ParentProcessId_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + ParentProcessId = @"100" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --ParentProcessId:100", result.Args); + } + + [Fact] + public void Should_Add_Port_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Port = 8000 + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Port:8000", result.Args); + } + + [Fact] + public void Should_Add_Diag_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + DiagnosticFile = "./artifacts/logging/diagnostics.txt" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --Diag:\"/Working/artifacts/logging/diagnostics.txt\"", result.Args); + } + + [Fact] + public void Should_Add_ResultsDirectory_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + ResultsDirectory = "./artifacts/TestResults" + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" --ResultsDirectory:\"/Working/artifacts/TestResults\"", result.Args); + } + + [Fact] + public void Should_Add_Extra_Argument() + { + // Given + var fixture = new DotNetVSTesterFixture + { + TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" }, + Settings = new DotNetVSTestSettings + { + Arguments = + { + { "Arg1", "Value1" }, + { "Arg2", "Value2" }, + } + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("vstest \"/Working/test/unit.tests.csproj\" Arg1:\"Value1\" Arg2:\"Value2\"", result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetVSTesterFixture { TestFiles = new[] { (FilePath)"./test/unit.tests.csproj" } }; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics vstest \"/Working/test/unit.tests.csproj\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs new file mode 100644 index 0000000000..fe2a1c6061 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Install/DotNetWorkloadInstallTests.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Install; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Install +{ + public sealed class DotNetWorkloadInstallTests + { + public sealed class TheWorkloadInstallMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_WorkloadIds_Argument() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui-android", "maui-ios" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload install maui-android maui-ios", result.Args); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Empty() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Null() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadInstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.SkipManifestUpdate = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = Common.Tools.DotNet.DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload install maui --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache --skip-manifest-update"; + expected += " --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.cs new file mode 100644 index 0000000000..4dd79f8dbd --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/List/DotNetWorkloadListTests.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.List; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.List +{ + public sealed class DotNetWorkloadListTests + { + public sealed class TheWorkloadListMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Verbosity_Argument() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.Settings.Verbosity = Common.Tools.DotNet.DotNetVerbosity.Normal; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload list --verbosity normal", result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_Workloads() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenInstalledWorkloadsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Workloads, + item => + { + Assert.Equal(item.Id, "maui-ios"); + Assert.Equal(item.ManifestVersion, "6.0.312/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }, + item => + { + Assert.Equal(item.Id, "maui-windows"); + Assert.Equal(item.ManifestVersion, "6.0.312/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }, + item => + { + Assert.Equal(item.Id, "android"); + Assert.Equal(item.ManifestVersion, "32.0.301/6.0.300"); + Assert.Equal(item.InstallationSource, "VS 17.3.32804.467, VS 17.4.32804.182"); + }); + } + + [Fact] + public void Should_Return_Empty_List_Of_Workloads() + { + // Given + var fixture = new DotNetWorkloadListerFixture(); + fixture.GivenEmptyInstalledWorkloadsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Empty(fixture.Workloads); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs new file mode 100644 index 0000000000..bbd4efda00 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairTests.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Repair; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Repair +{ + public sealed class DotNetWorkloadRepairTests + { + public sealed class TheWorkloadRepairMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadRepairerFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload repair --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache"; + expected += " --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs new file mode 100644 index 0000000000..492be031a4 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreTests.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Restore; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Workload.Restore; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Restore +{ + public sealed class DotNetWorkloadRestoreTests + { + public sealed class TheBuildMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Project_Is_Null() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Settings = new DotNetWorkloadRestoreSettings(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "project"); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload restore \"./src/*\"", result.Args); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.SkipManifestUpdate = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload restore \"./src/*\" --configfile \"/Working/nuget.config\" --disable-parallel --ignore-failed-sources --include-previews --interactive --no-cache --skip-manifest-update --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("./src/*", "workload restore \"./src/*\"")] + [InlineData("./src/cake build/", "workload restore \"./src/cake build/\"")] + [InlineData("./src/cake build/cake cli", "workload restore \"./src/cake build/cake cli\"")] + public void Should_Quote_Project_Path(string text, string expected) + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = text; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Host_Arguments() + { + // Given + var fixture = new DotNetWorkloadRestorerFixture(); + fixture.Project = "./src/*"; + fixture.Settings.DiagnosticOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--diagnostics workload restore \"./src/*\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Search/DotNetWorkloadSearchTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Search/DotNetWorkloadSearchTests.cs new file mode 100644 index 0000000000..d6413d83f7 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Search/DotNetWorkloadSearchTests.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Search; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Search +{ + public sealed class DotNetWorkloadSearchTests + { + public sealed class TheWorkloadSearchMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadSearcherFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadSearcherFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_SearchString_Argument() + { + // Given + var fixture = new DotNetWorkloadSearcherFixture(); + fixture.SearchString = "maui"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload search maui", result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_Workloads() + { + // Given + var fixture = new DotNetWorkloadSearcherFixture(); + fixture.SearchString = "maui"; + fixture.GivenAvailableWorkloadsResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Workloads, + item => + { + Assert.Equal(item.Id, "maui"); + Assert.Equal(item.Description, ".NET MAUI SDK for all platforms"); + }, + item => + { + Assert.Equal(item.Id, "maui-desktop"); + Assert.Equal(item.Description, ".NET MAUI SDK for Desktop"); + }, + item => + { + Assert.Equal(item.Id, "maui-mobile"); + Assert.Equal(item.Description, ".NET MAUI SDK for Mobile"); + }); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs new file mode 100644 index 0000000000..a1f31ae00d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallTests.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Uninstall; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Uninstall +{ + public sealed class DotNetWorkloadUninstallTests + { + public sealed class TheWorkloadSearchMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui" }; + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_WorkloadIds_Argument() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { "maui-android", "maui-ios" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("workload uninstall maui-android maui-ios", result.Args); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Empty() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = new string[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + + [Fact] + public void Should_Throw_If_WorkloadIds_Is_Null() + { + // Given + var fixture = new DotNetWorkloadUninstallerFixture(); + fixture.WorkloadIds = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "workloadIds"); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs new file mode 100644 index 0000000000..764bbea4a3 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateTests.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.DotNet.Workload.Update; +using Cake.Common.Tools.DotNet; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.DotNet.Workload.Update +{ + public sealed class DotNetWorkloadUpdateTests + { + public sealed class TheWorkloadUpdateMethod + { + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, ".NET CLI: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + fixture.Settings = null; + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Additional_Arguments() + { + // Given + var fixture = new DotNetWorkloadUpdaterFixture(); + fixture.Settings.AdvertisingManifestsOnly = true; + fixture.Settings.ConfigFile = "./nuget.config"; + fixture.Settings.DisableParallel = true; + fixture.Settings.FromPreviousSdk = true; + fixture.Settings.IgnoreFailedSources = true; + fixture.Settings.IncludePreviews = true; + fixture.Settings.Interactive = true; + fixture.Settings.NoCache = true; + fixture.Settings.Source.Add("http://www.nuget.org/api/v2/package"); + fixture.Settings.Source.Add("http://www.symbolserver.org/"); + fixture.Settings.TempDir = "./src/project"; + fixture.Settings.Verbosity = DotNetVerbosity.Diagnostic; + + // When + var result = fixture.Run(); + + // Then + var expected = "workload update --advertising-manifests-only --configfile \"/Working/nuget.config\" --disable-parallel --from-previous-sdk --ignore-failed-sources --include-previews --interactive --no-cache --source \"http://www.nuget.org/api/v2/package\" --source \"http://www.symbolserver.org/\" --temp-dir \"/Working/src/project\" --verbosity diagnostic"; + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsExtensionsTests.cs deleted file mode 100644 index 869523f868..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsExtensionsTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tools; -using Cake.Core.Diagnostics; -using Cake.Core.IO; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools -{ - public sealed class DotNetBuildSettingsExtensionsTests - { - public sealed class TheWithTargetMethod - { - [Fact] - public void Should_Add_Target_To_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.WithTarget("Target"); - - // Then - Assert.True(settings.Targets.Contains("Target")); - } - - [Fact] - public void Should_Return_The_Same_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - var result = settings.WithTarget("Target"); - - // Then - Assert.Equal(settings, result); - } - } - - public sealed class TheWithPropertyMethod - { - [Fact] - public void Should_Add_Property_To_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.WithProperty("PropertyName", "Value"); - - // Then - Assert.True(settings.Properties.ContainsKey("PropertyName")); - } - - [Fact] - public void Should_Return_The_Same_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - var result = settings.WithProperty("PropertyName", "Value"); - - // Then - Assert.Equal(settings, result); - } - } - - public sealed class TheSetConfigurationMethod - { - [Fact] - public void Should_Set_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.SetConfiguration("TheConfiguration"); - - // Then - Assert.Equal("TheConfiguration", settings.Configuration); - } - - [Fact] - public void Should_Return_The_Same_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - var result = settings.SetConfiguration("TheConfiguration"); - - // Then - Assert.Equal(settings, result); - } - } - - public sealed class TheSetVerbosityMethod - { - [Theory] - [InlineData(Verbosity.Quiet)] - [InlineData(Verbosity.Minimal)] - [InlineData(Verbosity.Normal)] - [InlineData(Verbosity.Verbose)] - [InlineData(Verbosity.Diagnostic)] - public void Should_Set_Verbosity(Verbosity verbosity) - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.SetVerbosity(verbosity); - - // Then - Assert.Equal(verbosity, settings.Verbosity); - } - - [Fact] - public void Should_Return_The_Same_Configuration() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - var result = settings.SetVerbosity(Verbosity.Normal); - - // Then - Assert.Equal(settings, result); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsTests.cs deleted file mode 100644 index fcbf0b16f0..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetBuildSettingsTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tools; -using Cake.Core.Diagnostics; -using Cake.Core.IO; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools -{ - public sealed class DotNetBuildSettingsTests - { - public sealed class TheConstructor - { - [Fact] - public void Should_Set_Default_Verbosity_To_Normal() - { - // Given, When - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // Then - Assert.Equal(Verbosity.Normal, settings.Verbosity); - } - - [Fact] - public void Should_Throw_If_Solution_Is_Null() - { - // Given, When - var result = Record.Exception(() => new DotNetBuildSettings(null)); - - // Then - Assert.IsArgumentNullException(result, "solution"); - } - } - - public sealed class TheTargetsProperty - { - [Fact] - public void Should_Return_A_Set_That_Is_Case_Insensitive() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.Targets.Add("TARGET"); - - // Then - Assert.True(settings.Targets.Contains("target")); - } - } - - public sealed class ThePropertiesProperty - { - [Fact] - public void Should_Return_A_Dictionary_That_Is_Case_Insensitive() - { - // Given - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // When - settings.Properties.Add("THEKEY", new []{"THEVALUE"}); - - // Then - Assert.True(settings.Properties.ContainsKey("thekey")); - } - } - - public sealed class TheConfigurationProperty - { - [Fact] - public void Should_Be_Empty_By_Default() - { - // Given, When - var settings = new DotNetBuildSettings(new FilePath("./Test.sln")); - - // Then - Assert.Equal(string.Empty, settings.Configuration); - } - } - - public sealed class TheSolutionProperty - { - [Fact] - public void Should_Return_Passed_Constructor_Argument() - { - // Given - var solution = new FilePath("./Test.sln"); - - // When - var settings = new DotNetBuildSettings(solution); - - // Then - Assert.Equal(solution, settings.Solution); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Build/DotNetCoreBuilderTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Build/DotNetCoreBuilderTests.cs deleted file mode 100644 index cb95a33b84..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Build/DotNetCoreBuilderTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Build; -using Cake.Common.Tools.DotNetCore.Build; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Build -{ - public sealed class DotNetCoreBuilderTests - { - public sealed class TheBuildMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Project = "./src/*"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Project_Is_Null() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Settings = new DotNetCoreBuildSettings(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "project"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build ./src/*", result.Args); - } - - [Fact] - public void Should_Add_Additional_Arguments() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Settings.Framework = "net451"; - fixture.Settings.Runtime = "runtime1"; - fixture.Settings.Configuration = "Release"; - fixture.Settings.VersionSuffix = "rc1"; - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build ./src/* --runtime runtime1 --framework net451 --configuration Release --version-suffix rc1", result.Args); - } - - [Fact] - public void Should_Add_OutputPath_Arguments() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Settings.OutputDirectory = "./artifacts/"; - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build ./src/* --output \"/Working/artifacts\"", result.Args); - } - - [Fact] - public void Should_Add_Build_Arguments() - { - // Given - var fixture = new DotNetCoreBuilderFixture(); - fixture.Settings.BuildBasePath = "./temp/"; - fixture.Settings.BuildProfile = true; - fixture.Settings.NoIncremental = true; - fixture.Settings.NoDependencies = true; - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("build ./src/* --build-base-path \"/Working/temp\" --build-profile --no-incremental --no-dependencies", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Execute/DotNetCoreExecutorTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Execute/DotNetCoreExecutorTests.cs deleted file mode 100644 index ebaeea9335..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Execute/DotNetCoreExecutorTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Execute; -using Cake.Common.Tools.DotNetCore.Execute; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Execute -{ - public sealed class DotNetCoreExecutorTests - { - public sealed class TheExecuteMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCoreExecutorFixture(); - fixture.AssemblyPath = "./bin/Debug/app.dll"; - fixture.Arguments = "--args"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCoreExecutorFixture(); - fixture.AssemblyPath = "./bin/Debug/app.dll"; - fixture.Arguments = "--args"; - fixture.Settings = new DotNetCoreExecuteSettings(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCoreExecutorFixture(); - fixture.AssemblyPath = "./bin/Debug/app.dll"; - fixture.Arguments = "--args"; - fixture.Settings = new DotNetCoreExecuteSettings(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCoreExecutorFixture(); - fixture.AssemblyPath = "./bin/Debug/app.dll"; - fixture.Settings = new DotNetCoreExecuteSettings(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("/Working/bin/Debug/app.dll", result.Args); - } - - [Fact] - public void Should_Add_Verbose() - { - // Given - var fixture = new DotNetCoreExecutorFixture(); - fixture.AssemblyPath = "./bin/Debug/app.dll"; - fixture.Settings.Verbose = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("--verbose /Working/bin/Debug/app.dll", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Pack/DotNetCorePackerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Pack/DotNetCorePackerTests.cs deleted file mode 100644 index 09daf4fd26..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Pack/DotNetCorePackerTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Pack; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Pack -{ - public sealed class DotNetCorePackerTests - { - public sealed class ThePackMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCorePackFixture(); - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCorePackFixture(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCorePackFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCorePackFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack", result.Args); - } - - [Fact] - public void Should_Add_Project() - { - // Given - var fixture = new DotNetCorePackFixture(); - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack ./src/*", result.Args); - } - - [Fact] - public void Should_Add_Settings() - { - // Given - var fixture = new DotNetCorePackFixture(); - fixture.Settings.BuildBasePath = "./temp/"; - fixture.Settings.NoBuild = true; - fixture.Settings.Configuration = "Release"; - fixture.Settings.OutputDirectory = "./artifacts/"; - fixture.Settings.VersionSuffix = "rc1"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("pack --output \"/Working/artifacts\" --build-base-path \"/Working/temp\" --no-build --configuration Release --version-suffix rc1", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Publish/DotNetCorePublisherTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Publish/DotNetCorePublisherTests.cs deleted file mode 100644 index 9874c530b0..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Publish/DotNetCorePublisherTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Publish; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Publish -{ - public sealed class DotNetCorePublisherTests - { - public sealed class ThePublishMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("publish", result.Args); - } - - [Fact] - public void Should_Add_Path() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - fixture.Project = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("publish ./src/*", result.Args); - } - - [Fact] - public void Should_Add_Settings() - { - // Given - var fixture = new DotNetCorePublisherFixture(); - fixture.Settings.Framework = "dnxcore50"; - fixture.Settings.Configuration = "Release"; - fixture.Settings.Runtime = "runtime1"; - fixture.Settings.BuildBasePath = "./temp/"; - fixture.Settings.OutputDirectory = "./artifacts/"; - fixture.Settings.NoBuild = true; - fixture.Settings.NativeSubDirectory = true; - fixture.Settings.VersionSuffix = "rc1"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("publish --output \"/Working/artifacts\" --build-base-path \"/Working/temp\" --runtime runtime1 --framework dnxcore50 --configuration Release --version-suffix rc1 --native-subdirectory --no-build", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Restore/DotNetCoreRestorerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Restore/DotNetCoreRestorerTests.cs deleted file mode 100644 index cd43505727..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Restore/DotNetCoreRestorerTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Restore; -using Cake.Common.Tools.DotNetCore.Restore; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Restore -{ - public sealed class DotNetCoreRestorerTests - { - public sealed class TheRestoreMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - fixture.Root = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore", result.Args); - } - - [Fact] - public void Should_Add_Path() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - fixture.Root = "./src/*"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore ./src/*", result.Args); - } - - [Fact] - public void Should_Add_Settings() - { - // Given - var fixture = new DotNetCoreRestorerFixture(); - fixture.Settings.Sources = new[] { "https://www.example.com/source1", "https://www.example.com/source2" }; - fixture.Settings.FallbackSources = new[] { "https://www.example.com/fallback1", "https://www.example.com/fallback2" }; - fixture.Settings.Quiet = true; - fixture.Settings.NoCache = true; - fixture.Settings.DisableParallel = true; - fixture.Settings.ForceEnglishOutput = true; - fixture.Settings.IgnoreFailedSources = true; - fixture.Settings.InferRuntimes = new[] { "runtime1", "runtime2" }; - fixture.Settings.ConfigFile = "./NuGet.config"; - fixture.Settings.PackagesDirectory = "./packages/"; - fixture.Settings.Verbosity = DotNetCoreRestoreVerbosity.Information; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("restore" + - " --packages \"/Working/packages\"" + - " --source \"https://www.example.com/source1\"" + - " --source \"https://www.example.com/source2\"" + - " --fallbacksource \"https://www.example.com/fallback1\"" + - " --fallbacksource \"https://www.example.com/fallback2\"" + - " --configfile \"/Working/NuGet.config\"" + - " --infer-runtimes \"runtime1\"" + - " --infer-runtimes \"runtime2\"" + - " --quiet --no-cache --disable-parallel --ignore-failed-sources --force-english-output" + - " --verbosity Information", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Run/DotNetCoreRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Run/DotNetCoreRunnerTests.cs deleted file mode 100644 index cda39980f0..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Run/DotNetCoreRunnerTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Run; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Run -{ - public sealed class DotNetCoreRunnerTests - { - public sealed class TheRunMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - fixture.Project = "./src/*"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("run", result.Args); - } - - [Fact] - public void Should_Add_Path_Arguments() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - fixture.Project = "./tools/tool/"; - fixture.Arguments = "--args=\"value\""; - // When - var result = fixture.Run(); - - // Then - Assert.Equal("run --project \"./tools/tool/\" -- --args=\"value\"", result.Args); - } - - [Fact] - public void Should_Add_Additional_Settings() - { - // Given - var fixture = new DotNetCoreRunnerFixture(); - fixture.Settings.Framework = "dnxcore50"; - fixture.Settings.Configuration = "Release"; - // When - var result = fixture.Run(); - - // Then - Assert.Equal("run --framework dnxcore50 --configuration Release", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Test/DotNetCoreTesterTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Test/DotNetCoreTesterTests.cs deleted file mode 100644 index e738390308..0000000000 --- a/src/Cake.Common.Tests/Unit/Tools/DotNetCore/Test/DotNetCoreTesterTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Common.Tests.Fixtures.Tools.DotNetCore.Test; -using Cake.Testing; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.DotNetCore.Test -{ - public sealed class DotNetCoreTesterTests - { - public sealed class TheTestMethod - { - [Fact] - public void Should_Throw_If_Settings_Are_Null() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - fixture.Project = "./src/*"; - fixture.Settings = null; - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process was not started."); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - fixture.Project = "./src/*"; - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, ".NET Core CLI: Process returned an error (exit code 1)."); - } - - [Fact] - public void Should_Add_Mandatory_Arguments() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("test", result.Args); - } - - [Fact] - public void Should_Add_Path() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - fixture.Project = "./test/Project.Tests/"; - // When - var result = fixture.Run(); - - // Then - Assert.Equal("test ./test/Project.Tests/", result.Args); - } - - [Fact] - public void Should_Add_Additional_Settings() - { - // Given - var fixture = new DotNetCoreTesterFixture(); - fixture.Settings.BuildBasePath = "./temp/"; - fixture.Settings.NoBuild = true; - fixture.Settings.Framework = "dnxcore50"; - fixture.Settings.Runtime = "runtime1"; - fixture.Settings.Configuration = "Release"; - fixture.Settings.OutputDirectory = "./artifacts/"; - // When - var result = fixture.Run(); - - // Then - Assert.Equal("test --output \"/Working/artifacts\" --build-base-path \"/Working/temp\" --runtime runtime1 --framework dnxcore50 --configuration Release --no-build", result.Args); - } - } - } -} diff --git a/src/Cake.Common.Tests/Unit/Tools/DupFinder/DupFinderRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/DupFinder/DupFinderRunnerTests.cs index 2e5f2a3837..f83361425f 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DupFinder/DupFinderRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DupFinder/DupFinderRunnerTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; +using System.Linq; using Cake.Common.Tests.Fixtures.Tools.DupFinder; using Cake.Core; using Cake.Core.IO; @@ -25,7 +27,7 @@ public void Should_Throw_If_Files_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "filePaths"); + AssertEx.IsArgumentNullException(result, "filePaths"); } [Fact] @@ -114,7 +116,7 @@ public void Should_Throw_If_OutputFile_Contains_Duplicates_And_Set_To_Throw() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Duplicates found in code base."); + AssertEx.IsCakeException(result, "Duplicates found in code base."); } [Fact] @@ -331,6 +333,51 @@ public void Should_Set_Show_Text_Switch() // Then Assert.Equal("/show-text \"/Working/Test.sln\"", result.Args); } + + [Fact] + public void Should_Analyze_Output() + { + var log = new FakeLog(); + + // Given + var fixture = new DupFinderRunnerFixture + { + Log = log + }; + fixture.Settings.OutputFile = new FilePath("build/duplicates.xml"); + + // When + fixture.Run(); + + // Then + var logContainsInspectionResults = + log.Entries.Any(p => p.Message.StartsWith("Duplicate Located with a cost of")); + + Assert.True(logContainsInspectionResults); + } + + [Fact] + public void Should_Not_Analyze_Output() + { + var log = new FakeLog(); + + // Given + var fixture = new DupFinderRunnerFixture + { + Log = log + }; + fixture.Settings.OutputFile = new FilePath("build/duplicates.xml"); + fixture.Settings.SkipOutputAnalysis = true; + + // When + fixture.Run(); + + // Then + var logContainsInspectionResults = + log.Entries.Any(p => p.Message.StartsWith("Duplicate Located with a cost of")); + + Assert.False(logContainsInspectionResults); + } } public sealed class TheRunFromConfigMethod @@ -346,7 +393,7 @@ public void Should_Throw_If_Config_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "configFile"); + AssertEx.IsArgumentNullException(result, "configFile"); } [Fact] @@ -363,4 +410,4 @@ public void Should_Use_Provided_Config_File() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Fixie/FixieRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Fixie/FixieRunnerTests.cs index de7527aaea..a5d1346d5a 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Fixie/FixieRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Fixie/FixieRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.Fixie; using Cake.Core; @@ -25,7 +26,7 @@ public void Should_Throw_If_Assembly_Paths_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -40,7 +41,7 @@ public void Should_Throw_If_Fixie_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("Fixie: Could not locate executable.", result.Message); + Assert.Equal("Fixie: Could not locate executable.", result?.Message); } [Theory] @@ -143,7 +144,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("Fixie: Process was not started.", result.Message); + Assert.Equal("Fixie: Process was not started.", result?.Message); } [Fact] @@ -158,7 +159,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("Fixie: Process returned an error (exit code 1).", result.Message); + Assert.Equal("Fixie: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -194,9 +195,9 @@ public void Should_Set_xUnitXml_Output_File() } [Theory] - [InlineData(true, "on", "\"/Working/Test1.dll\" --TeamCity on")] - [InlineData(false, "off", "\"/Working/Test1.dll\" --TeamCity off")] - public void Should_Set_TeamCity_Value(bool teamCityOutput, string teamCityValue, string expected) + [InlineData(true, "\"/Working/Test1.dll\" --TeamCity on")] + [InlineData(false, "\"/Working/Test1.dll\" --TeamCity off")] + public void Should_Set_TeamCity_Value(bool teamCityOutput, string expected) { // Given var fixture = new FixieRunnerFixture(); @@ -245,4 +246,4 @@ public void Should_Set_Multiple_Options() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/GitLink/Gitlink3RunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitLink/Gitlink3RunnerTests.cs new file mode 100644 index 0000000000..8a42f847cf --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/GitLink/Gitlink3RunnerTests.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.GitLink +{ + public sealed class Gitlink3RunnerTests + { + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Pdb_Path_Is_Null() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.PdbFilePath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "pdbFile"); + } + + [Fact] + public void Should_Find_GitLink_Runner() + { + // Given + var fixture = new GitLink3Fixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/gitlink.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("GitLink: Process was not started.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("GitLink: Process returned an error (exit code 1).", result?.Message); + } + + [Fact] + public void Should_Use_Provided_Pdb_File_Path_In_Process_Arguments() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.PdbFilePath = "source/my.pdb"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/source/my.pdb\"", result.Args); + } + + [WindowsFact] + public void Should_Set_RepositoryUrl() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.Settings.RepositoryUrl = "http://mydomain.com"; + + // Then + var result = fixture.Run(); + + // Then + Assert.Equal("-u \"http://mydomain.com\" \"c:/temp/my.pdb\"", result.Args); + } + + [WindowsFact] + public void Should_Set_ShaHash() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.Settings.ShaHash = "abcdef"; + + // Then + var result = fixture.Run(); + + // Then + Assert.Equal("--commit \"abcdef\" \"c:/temp/my.pdb\"", result.Args); + } + + [WindowsFact] + public void Should_Set_BaseDirectory() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.Settings.BaseDir = DirectoryPath.FromString("pdb/"); + + // Then + var result = fixture.Run(); + + // Then + Assert.Equal("--baseDir \"/Working/pdb\" \"c:/temp/my.pdb\"", result.Args); + } + + [WindowsFact] + public void Should_Set_PowerShell_Switch() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.Settings.UsePowerShell = true; + + // Then + var result = fixture.Run(); + + // Then + Assert.Equal("-m Powershell \"c:/temp/my.pdb\"", result.Args); + } + + [WindowsFact] + public void Should_Set_SkipVerify_Switch() + { + // Given + var fixture = new GitLink3Fixture(); + fixture.Settings.SkipVerify = true; + + // Then + var result = fixture.Run(); + + // Then + Assert.Equal("-s \"c:/temp/my.pdb\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/GitLink/GitlinkRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitLink/GitlinkRunnerTests.cs index 9f57ceebf8..61fcdd7da2 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitLink/GitlinkRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitLink/GitlinkRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Core; using Cake.Core.IO; @@ -25,7 +26,7 @@ public void Should_Throw_If_Repository_Root_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repositoryRootPath"); + AssertEx.IsArgumentNullException(result, "repositoryRootPath"); } [Fact] @@ -53,7 +54,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("GitLink: Process was not started.", result.Message); + Assert.Equal("GitLink: Process was not started.", result?.Message); } [Fact] @@ -68,7 +69,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("GitLink: Process returned an error (exit code 1).", result.Message); + Assert.Equal("GitLink: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -240,4 +241,4 @@ public void Should_Set_IsDebug_Switch() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdderTests.cs index cdde32a5f3..0c21ea4471 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdderTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_UserName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "userName"); + AssertEx.IsArgumentNullException(result, "userName"); } [Fact] @@ -37,7 +38,22 @@ public void Should_Throw_If_Password_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "password"); + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); } [Fact] @@ -51,7 +67,22 @@ public void Should_Throw_If_Owner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "owner"); + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); } [Fact] @@ -65,7 +96,22 @@ public void Should_Throw_If_Repository_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repository"); + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); } [Fact] @@ -79,7 +125,22 @@ public void Should_Throw_If_TagName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "tagName"); + AssertEx.IsArgumentNullException(result, "tagName"); + } + + [Fact] + public void Should_Throw_If_TagName_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.TagName = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "tagName"); } [Fact] @@ -93,7 +154,22 @@ public void Should_Throw_If_Assets_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assets"); + AssertEx.IsArgumentNullException(result, "assets"); + } + + [Fact] + public void Should_Throw_If_Assets_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Assets = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "assets"); } [Fact] @@ -107,7 +183,22 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -121,13 +212,62 @@ public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); } [Theory] [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerAssetsAdderFixture(); @@ -143,10 +283,11 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPa [WindowsTheory] [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -168,7 +309,22 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process was not started."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); } [Fact] @@ -182,7 +338,22 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); } [Fact] @@ -198,6 +369,20 @@ public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + [Fact] public void Should_Add_Mandatory_Arguments() { @@ -213,6 +398,22 @@ public void Should_Add_Mandatory_Arguments() "-a \"/temp/asset1.txt\"", result.Args); } + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\"", result.Args); + } + [Fact] public void Should_Add_TargetDirectory_To_Arguments_If_Set() { @@ -229,6 +430,23 @@ public void Should_Add_TargetDirectory_To_Arguments_If_Set() "-a \"/temp/asset1.txt\" -d \"/temp\"", result.Args); } + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" -d \"/temp\"", result.Args); + } + [Fact] public void Should_Add_LogFilePath_To_Arguments_If_Set() { @@ -245,6 +463,129 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() "-a \"/temp/asset1.txt\" " + "-l \"/temp/log.txt\"", result.Args); } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerAssetsAdderFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("addasset --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-a \"/temp/asset1.txt\" " + + "--no-logo", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloserTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloserTests.cs index 860d023f76..6cb7ee9032 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloserTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloserTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_UserName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "userName"); + AssertEx.IsArgumentNullException(result, "userName"); } [Fact] @@ -37,7 +38,22 @@ public void Should_Throw_If_Password_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "password"); + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); } [Fact] @@ -51,7 +67,22 @@ public void Should_Throw_If_Owner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "owner"); + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); } [Fact] @@ -65,7 +96,22 @@ public void Should_Throw_If_Repository_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repository"); + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); } [Fact] @@ -79,7 +125,22 @@ public void Should_Throw_If_Milestone_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "milestone"); + AssertEx.IsArgumentNullException(result, "milestone"); + } + + [Fact] + public void Should_Throw_If_Milestone_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Milestone = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "milestone"); } [Fact] @@ -93,7 +154,22 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -107,16 +183,49 @@ public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); } [Theory] [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -129,7 +238,7 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPa [WindowsTheory] [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerMilestoneCloserFixture(); @@ -143,6 +252,23 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(st Assert.Equal(expected, result.Path.FullPath); } + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + [Fact] public void Should_Throw_If_Process_Was_Not_Started() { @@ -154,7 +280,22 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process was not started."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); } [Fact] @@ -168,7 +309,22 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); } [Fact] @@ -184,6 +340,20 @@ public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + [Fact] public void Should_Add_Mandatory_Arguments() { @@ -198,6 +368,21 @@ public void Should_Add_Mandatory_Arguments() "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\"", result.Args); } + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\"", result.Args); + } + [Fact] public void Should_Add_TargetDirectory_To_Arguments_If_Set() { @@ -214,6 +399,23 @@ public void Should_Add_TargetDirectory_To_Arguments_If_Set() "-d \"/temp\"", result.Args); } + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "-d \"/temp\"", result.Args); + } + [Fact] public void Should_Add_LogFilePath_To_Arguments_If_Set() { @@ -229,6 +431,122 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + "-l \"/temp/log.txt\"", result.Args); } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerMilestoneCloserFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("close --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--no-logo", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Create/GitReleaseManagerCreatorTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Create/GitReleaseManagerCreatorTests.cs index 8e88a8a04f..87a528f209 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Create/GitReleaseManagerCreatorTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Create/GitReleaseManagerCreatorTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_UserName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "userName"); + AssertEx.IsArgumentNullException(result, "userName"); } [Fact] @@ -37,7 +38,22 @@ public void Should_Throw_If_Password_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "password"); + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); } [Fact] @@ -51,7 +67,22 @@ public void Should_Throw_If_Owner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "owner"); + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); } [Fact] @@ -65,7 +96,22 @@ public void Should_Throw_If_Repository_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repository"); + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); } [Fact] @@ -79,7 +125,22 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -93,13 +154,62 @@ public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); } [Theory] [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerCreatorFixture(); @@ -115,10 +225,11 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPa [WindowsTheory] [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -140,7 +251,22 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process was not started."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); } [Fact] @@ -154,7 +280,22 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); } [Fact] @@ -170,6 +311,20 @@ public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + [Fact] public void Should_Add_Mandatory_Arguments() { @@ -184,6 +339,21 @@ public void Should_Add_Mandatory_Arguments() "-o \"repoOwner\" -r \"repo\"", result.Args); } + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\"", result.Args); + } + [Fact] public void Should_Add_Milestone_To_Arguments_If_True() { @@ -199,6 +369,22 @@ public void Should_Add_Milestone_To_Arguments_If_True() "-o \"repoOwner\" -r \"repo\" -m \"1.0.0\"", result.Args); } + [Fact] + public void Should_Add_Milestone_To_Arguments_If_True_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Milestone = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"1.0.0\"", result.Args); + } + [Fact] public void Should_Add_Name_To_Arguments_If_Set() { @@ -214,6 +400,22 @@ public void Should_Add_Name_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -n \"1.0.0\"", result.Args); } + [Fact] + public void Should_Add_Name_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Name = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -n \"1.0.0\"", result.Args); + } + [Fact] public void Should_Add_InputFilePath_To_Arguments_If_Set() { @@ -229,6 +431,22 @@ public void Should_Add_InputFilePath_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -i \"/temp\"", result.Args); } + [Fact] + public void Should_Add_InputFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.InputFilePath = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -i \"/temp\"", result.Args); + } + [Fact] public void Should_Add_Prerelease_To_Arguments_If_True() { @@ -244,6 +462,22 @@ public void Should_Add_Prerelease_To_Arguments_If_True() "-o \"repoOwner\" -r \"repo\" -e", result.Args); } + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_True_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Prerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -e", result.Args); + } + [Fact] public void Should_Add_Assets_To_Arguments_If_Set() { @@ -260,6 +494,23 @@ public void Should_Add_Assets_To_Arguments_If_Set() "-a \"asset1,asset2\"", result.Args); } + [Fact] + public void Should_Add_Assets_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Assets = "asset1,asset2"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "-a \"asset1,asset2\"", result.Args); + } + [Fact] public void Should_Add_TargetCommitish_To_Arguments_If_Set() { @@ -275,6 +526,22 @@ public void Should_Add_TargetCommitish_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -c \"master\"", result.Args); } + [Fact] + public void Should_Add_TargetCommitish_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.TargetCommitish = "master"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -c \"master\"", result.Args); + } + [Fact] public void Should_Add_TargetDirectory_To_Arguments_If_Set() { @@ -290,6 +557,22 @@ public void Should_Add_TargetDirectory_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -d \"/temp\"", result.Args); } + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -d \"/temp\"", result.Args); + } + [Fact] public void Should_Add_LogFilePath_To_Arguments_If_Set() { @@ -305,6 +588,122 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" " + "-l \"/temp/log.txt\"", result.Args); } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerCreatorFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--no-logo", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarderTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarderTests.cs new file mode 100644 index 0000000000..f7aaaaed47 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarderTests.cs @@ -0,0 +1,269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.GitReleaseManager.Discard +{ + public sealed class GitReleaseManagerDiscarderTests + { + public sealed class TheDiscardMethod + { + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\"", result.Args); + } + + [Fact] + public void Should_Add_Milestone_To_Arguments_If_True() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Milestone = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"1.0.0\"", result.Args); + } + + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" -d \"/temp\"", result.Args); + } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerDiscarderFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("discard --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--no-logo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Export/GitReleaseManagerExporterTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Export/GitReleaseManagerExporterTests.cs index 435887211b..7947301f70 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Export/GitReleaseManagerExporterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Export/GitReleaseManagerExporterTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_UserName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "userName"); + AssertEx.IsArgumentNullException(result, "userName"); } [Fact] @@ -37,7 +38,22 @@ public void Should_Throw_If_Password_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "password"); + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); } [Fact] @@ -51,7 +67,22 @@ public void Should_Throw_If_Owner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "owner"); + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); } [Fact] @@ -65,7 +96,22 @@ public void Should_Throw_If_Repository_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repository"); + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); } [Fact] @@ -79,7 +125,22 @@ public void Should_Throw_If_FileOutputPath_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "fileOutputPath"); + AssertEx.IsArgumentNullException(result, "fileOutputPath"); + } + + [Fact] + public void Should_Throw_If_FileOutputPath_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.FileOutputPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "fileOutputPath"); } [Fact] @@ -93,7 +154,22 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -107,16 +183,49 @@ public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); } [Theory] [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -129,7 +238,7 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPa [WindowsTheory] [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerExporterFixture(); @@ -143,6 +252,23 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(st Assert.Equal(expected, result.Path.FullPath); } + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + [Fact] public void Should_Throw_If_Process_Was_Not_Started() { @@ -154,7 +280,22 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process was not started."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); } [Fact] @@ -168,7 +309,22 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); } [Fact] @@ -184,6 +340,20 @@ public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + [Fact] public void Should_Add_Mandatory_Arguments() { @@ -199,6 +369,22 @@ public void Should_Add_Mandatory_Arguments() "-f \"/temp\"", result.Args); } + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "-f \"/temp\"", result.Args); + } + [Fact] public void Should_Add_TagName_To_Arguments_If_Set() { @@ -215,6 +401,23 @@ public void Should_Add_TagName_To_Arguments_If_Set() "-t \"0.1.0\"", result.Args); } + [Fact] + public void Should_Add_TagName_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.TagName = "0.1.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "-t \"0.1.0\"", result.Args); + } + [Fact] public void Should_Add_TargetDirectory_To_Arguments_If_Set() { @@ -231,6 +434,23 @@ public void Should_Add_TargetDirectory_To_Arguments_If_Set() "-d \"/temp\"", result.Args); } + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "-d \"/temp\"", result.Args); + } + [Fact] public void Should_Add_LogFilePath_To_Arguments_If_Set() { @@ -246,6 +466,122 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + "-l \"/temp/log.txt\"", result.Args); } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerExporterFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("export --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -f \"/temp\" " + + "--no-logo", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Label/GitReleaseManagerLabellerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Label/GitReleaseManagerLabellerTests.cs new file mode 100644 index 0000000000..2737e57cbe --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Label/GitReleaseManagerLabellerTests.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.GitReleaseManager.Label +{ + public sealed class GitReleaseManagerLabellerTests + { + public sealed class TheLabelMethod + { + [Fact] + public void Should_Throw_If_UserName_Is_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UserName = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "userName"); + } + + [Fact] + public void Should_Throw_If_Password_Is_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Password = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\"", result.Args); + } + + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\"", result.Args); + } + + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" -d \"/temp\"", result.Args); + } + + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -d \"/temp\"", result.Args); + } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label -u \"bob\" -p \"password\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerLabellerFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("label --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" " + + "--no-logo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpenerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpenerTests.cs new file mode 100644 index 0000000000..3d5760e0f6 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpenerTests.cs @@ -0,0 +1,269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.GitReleaseManager.Open +{ + public sealed class GitReleaseManagerMilestoneOpenerTests + { + public sealed class TheOpenMethod + { + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Milestone_Is_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Milestone = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "milestone"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/GitReleaseManager.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\"", result.Args); + } + + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "-d \"/temp\"", result.Args); + } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerMilestoneOpenerFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("open --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -m \"0.1.0\" " + + "--no-logo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisherTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisherTests.cs index ee92edada6..59eeb534a9 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisherTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisherTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.GitReleaseManager; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_UserName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "userName"); + AssertEx.IsArgumentNullException(result, "userName"); } [Fact] @@ -37,7 +38,22 @@ public void Should_Throw_If_Password_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "password"); + AssertEx.IsArgumentNullException(result, "password"); + } + + [Fact] + public void Should_Throw_If_Token_Is_Null() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Token = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "token"); } [Fact] @@ -51,7 +67,22 @@ public void Should_Throw_If_Owner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "owner"); + AssertEx.IsArgumentNullException(result, "owner"); + } + + [Fact] + public void Should_Throw_If_Owner_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Owner = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "owner"); } [Fact] @@ -65,7 +96,22 @@ public void Should_Throw_If_Repository_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "repository"); + AssertEx.IsArgumentNullException(result, "repository"); + } + + [Fact] + public void Should_Throw_If_Repository_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Repository = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "repository"); } [Fact] @@ -79,7 +125,22 @@ public void Should_Throw_If_TagName_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "tagName"); + AssertEx.IsArgumentNullException(result, "tagName"); + } + + [Fact] + public void Should_Throw_If_TagName_Is_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.TagName = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "tagName"); } [Fact] @@ -93,7 +154,22 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -107,13 +183,62 @@ public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Fact] + public void Should_Throw_If_GitReleaseManager_Executable_Was_Not_Found_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] + [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); } [Theory] [InlineData("/bin/tools/GitReleaseManager/GitReleaseManager.exe", "/bin/tools/GitReleaseManager/GitReleaseManager.exe")] [InlineData("./tools/GitReleaseManager/GitReleaseManager.exe", "/Working/tools/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_When_Using_Token(string toolPath, string expected) + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerPublisherFixture(); @@ -129,10 +254,11 @@ public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPa [WindowsTheory] [InlineData("C:/GitReleaseManager/GitReleaseManager.exe", "C:/GitReleaseManager/GitReleaseManager.exe")] - public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + public void Should_Use_GitReleaseManager_Executable_From_Tool_Path_If_Provided_On_Windows_When_Using_Token(string toolPath, string expected) { // Given var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); fixture.Settings.ToolPath = toolPath; fixture.GivenSettingsToolPathExist(); @@ -154,7 +280,22 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process was not started."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process was not started."); } [Fact] @@ -168,7 +309,22 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "GitReleaseManager: Process returned an error (exit code 1)."); } [Fact] @@ -186,6 +342,22 @@ public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided() result.Path.FullPath); } + [Fact] + public void Should_Find_GitReleaseManager_Executable_If_Tool_Path_Not_Provided_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + "/Working/tools/GitReleaseManager.exe", + result.Path.FullPath); + } + [Fact] public void Should_Add_Mandatory_Arguments() { @@ -201,6 +373,22 @@ public void Should_Add_Mandatory_Arguments() result.Args); } + [Fact] + public void Should_Add_Mandatory_Arguments_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\"", + result.Args); + } + [Fact] public void Should_Add_TargetDirectory_To_Arguments_If_Set() { @@ -217,6 +405,23 @@ public void Should_Add_TargetDirectory_To_Arguments_If_Set() "-d \"/temp\"", result.Args); } + [Fact] + public void Should_Add_TargetDirectory_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.TargetDirectory = @"/temp"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" " + + "-o \"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-d \"/temp\"", result.Args); + } + [Fact] public void Should_Add_LogFilePath_To_Arguments_If_Set() { @@ -232,6 +437,122 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + "-l \"/temp/log.txt\"", result.Args); } + + [Fact] + public void Should_Add_LogFilePath_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.LogFilePath = @"/temp/log.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "-l \"/temp/log.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -u \"bob\" -p \"password\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_All_Debug_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.Debug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--debug", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -u \"bob\" -p \"password\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_All_Verbose_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--verbose", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Set() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish -u \"bob\" -p \"password\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--no-logo", result.Args); + } + + [Fact] + public void Should_All_NoLogo_To_Arguments_If_Set_When_Using_Token() + { + // Given + var fixture = new GitReleaseManagerPublisherFixture(); + fixture.UseToken(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("publish --token \"token\" -o " + + "\"repoOwner\" -r \"repo\" -t \"0.1.0\" " + + "--no-logo", result.Args); + } } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/GitReleaseNotes/GitReleaseNotesRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitReleaseNotes/GitReleaseNotesRunnerTests.cs index 52e5f824a5..7fc7d5c68d 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitReleaseNotes/GitReleaseNotesRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitReleaseNotes/GitReleaseNotesRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.GitReleaseNotes; using Cake.Core; @@ -24,7 +25,7 @@ public void Should_Throw_If_OutputFile_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputFile"); + AssertEx.IsArgumentNullException(result, "outputFile"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -66,7 +67,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("GitReleaseNotes: Process was not started.", result.Message); + Assert.Equal("GitReleaseNotes: Process was not started.", result?.Message); } [Fact] @@ -81,7 +82,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("GitReleaseNotes: Process returned an error (exit code 1).", result.Message); + Assert.Equal("GitReleaseNotes: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -354,4 +355,4 @@ public void Should_Add_AllLabels_To_Arguments_If_Set() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs index d0b2a8e4ed..5b45ee21a8 100644 --- a/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/GitVersion/GitVersionRunnerTests.cs @@ -1,10 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.GitVersion; using Cake.Core; +using Cake.Core.Diagnostics; using Cake.Testing; +using Cake.Testing.Extensions; using Xunit; namespace Cake.Common.Tests.Unit.Tools.GitVersion @@ -24,7 +28,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -52,7 +56,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("GitVersion: Process was not started.", result.Message); + Assert.Equal("GitVersion: Process was not started.", result?.Message); } [Fact] @@ -67,7 +71,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("GitVersion: Process returned an error (exit code 1).", result.Message); + Assert.Equal("GitVersion: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -82,18 +86,35 @@ public void Should_Set_Working_Directory() Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); } + [Theory] + [InlineData(GitVersionOutput.Json, "-output json")] + [InlineData(GitVersionOutput.File, "-output file")] + public void Should_Add_OutputType_To_Arguments_If_Set(GitVersionOutput outputType, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.OutputType = outputType; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + [Fact] - public void Should_Add_OutputType_To_Arguments_If_Set() + public void Should_Add_OutputFile_If_Set_With_OutputType_File() { // Given var fixture = new GitVersionRunnerFixture(); - fixture.Settings.OutputType = GitVersionOutput.Json; + fixture.Settings.OutputType = GitVersionOutput.File; + fixture.Settings.OutputFile = "GitVersion.json"; // When var result = fixture.Run(); // Then - Assert.Equal("/output json", result.Args); + Assert.Equal("-output file -outputfile \"GitVersion.json\"", result.Args); } [Fact] @@ -107,7 +128,7 @@ public void Should_Add_ShowVariable_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/showvariable FullSemVer", result.Args); + Assert.Equal("-showvariable FullSemVer", result.Args); } [Fact] @@ -122,7 +143,7 @@ public void Should_Add_Username_And_Password_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/u \"bob\" /p \"password\"", result.Args); + Assert.Equal("-u \"bob\" -p \"password\"", result.Args); } [Fact] @@ -136,7 +157,7 @@ public void Should_Add_UpdateAssemblyInfo_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/updateassemblyinfo", result.Args); + Assert.Equal("-updateassemblyinfo", result.Args); } [Fact] @@ -151,7 +172,21 @@ public void Should_Add_UpdateAssemblyInfoFilePath_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/updateassemblyinfo \"c:/temp/assemblyinfo.cs\"", result.Args); + Assert.Equal("-updateassemblyinfo \"c:/temp/assemblyinfo.cs\"", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.ConfigFile = "c:/temp/gitversion.yml"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-config \"c:/temp/gitversion.yml\"", result.Args); } [Fact] @@ -165,7 +200,7 @@ public void Should_Add_RepositoryPath_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/targetpath \"c:/temp\"", result.Args); + Assert.Equal("-targetpath \"c:/temp\"", result.Args); } [Fact] @@ -182,7 +217,7 @@ public void Should_Add_DynamicRepoSettings_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/url \"http://mygitrepo.co.uk\" /b master /c \"abcdef\" /dynamicRepoLocation \"c:/temp\"", result.Args); + Assert.Equal("-url \"http://mygitrepo.co.uk\" -b master -c \"abcdef\" -dynamicRepoLocation \"c:/temp\"", result.Args); } [Fact] @@ -196,8 +231,357 @@ public void Should_Add_LogFilePath_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("/l \"c:/temp/gitversion.log\"", result.Args); + Assert.Equal("-l \"c:/temp/gitversion.log\"", result.Args); + } + + [Theory] + [InlineData(true, "-nofetch")] + [InlineData(false, "")] + public void Should_Add_NoFetch_To_Arguments_If_Set(bool nofetch, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.NoFetch = nofetch; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-nocache")] + [InlineData(false, "")] + public void Should_Add_NoCache_To_Arguments_If_Set(bool nocache, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.NoCache = nocache; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-nonormalize")] + [InlineData(false, "")] + public void Should_Add_NoNormalize_To_Arguments_If_Set(bool nonormalize, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.NoNormalize = nonormalize; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-diag")] + [InlineData(false, "")] + public void Should_Add_Diag_To_Arguments_If_Set(bool diag, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.Diag = diag; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-updateprojectfiles")] + [InlineData(false, "")] + public void Should_Add_UpdateProjectFiles_To_Arguments_If_Set(bool updateProjectFiles, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.UpdateProjectFiles = updateProjectFiles; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-ensureassemblyinfo")] + [InlineData(false, "")] + public void Should_Add_EnsureAssemblyInfo_To_Arguments_If_Set(bool ensureAssemblyInfo, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.EnsureAssemblyInfo = ensureAssemblyInfo; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(true, "-updatewixversionfile")] + [InlineData(false, "")] + public void Should_Add_UpdateWixVersionFile_To_Arguments_If_Set(bool updateWixVersionFile, string args) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.UpdateWixVersionFile = updateWixVersionFile; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(args, result.Args); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public void Should_Log_GitVersion_Errors_When_Verbosity_Less_Than_Diagnostic(Verbosity verbosity) + { + // Given + var log = new FakeLog { Verbosity = verbosity }; + var fixture = new GitVersionRunnerFixture(new[] + { + " INFO [02/29/20 20:29:12:17] No local branch pointing at the commit '57a682f6012d1f27255de86240fa98e87fe1f765'. Fake branch needs to be created. INFO [02/29/20 20:29:12:17] Fetching remote refs to see if there is a pull request ref INFO [02/29/20 20:29:12:20] End: Normalizing git directory for branch '' (Took: 89.28ms) ERROR [02/29/20 20:29:12:21] An unexpected error occurred:", + "LibGit2Sharp.LibGit2SharpException: this remote has never connected", + " at LibGit2Sharp.Core.Ensure.HandleError(Int32 result)", + "...", + " INFO [02/29/20 20:43:18:60] No local branch pointing at the commit 'c9d51bc9836a310145b3d8976a69b1859be36a35'. Fake branch needs to be created.", + " INFO [02/29/20 20:43:18:60] Fetching remote refs to see if there is a pull request ref", + " INFO [02/29/20 20:43:18:66] End: Normalizing git directory for branch '' (Took: 124.33ms)", + " ERROR [02/29/20 20:43:18:68] An unexpected error occurred:", + "LibGit2Sharp.LibGit2SharpException: this remote has never connected", + " at LibGit2Sharp.Core.Ensure.HandleError(Int32 result)", + "..." + }); + fixture.Log = log; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.Equal(verbosity < Verbosity.Diagnostic + ? " ERROR [02/29/20 20:29:12:21] An unexpected error occurred:" + Environment.NewLine + + "LibGit2Sharp.LibGit2SharpException: this remote has never connected" + Environment.NewLine + + " ERROR [02/29/20 20:43:18:68] An unexpected error occurred:" + Environment.NewLine + + "LibGit2Sharp.LibGit2SharpException: this remote has never connected" + Environment.NewLine + : string.Empty, + log.AggregateLogMessages()); + } + + [Fact] + public void Should_Tolerate_Bad_Json_Set() + { + // Given + var expect = new Common.Tools.GitVersion.GitVersion + { + Major = 0, + Minor = 1, + Patch = 1, + PreReleaseTag = "PreReleaseTag", + PreReleaseTagWithDash = "PreReleaseTagWithDash", + PreReleaseLabel = "PreReleaseLabel", + PreReleaseLabelWithDash = "-PreReleaseLabel", + PreReleaseNumber = null, + WeightedPreReleaseNumber = null, + BuildMetaData = "BuildMetaData", + BuildMetaDataPadded = "BuildMetaDataPadded", + FullBuildMetaData = "Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", + MajorMinorPatch = "0.1.1", + SemVer = "0.1.1", + LegacySemVer = "0.1.1", + LegacySemVerPadded = "0.1.1", + AssemblySemVer = "0.1.1.0", + AssemblySemFileVer = "0.1.1.0", + FullSemVer = "0.1.1", + InformationalVersion = "0.1.1+Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", + BranchName = "master", + EscapedBranchName = "master", + Sha = "f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", + ShortSha = "f2467748", + NuGetVersionV2 = "0.1.1", + NuGetVersion = "0.1.1", + NuGetPreReleaseTagV2 = "tag", + NuGetPreReleaseTag = "tag", + VersionSourceSha = "f2467748c78b3c8b37972ad0b30df2e15dfbf2cb", + CommitsSinceVersionSource = null, + CommitsSinceVersionSourcePadded = "0002", + UncommittedChanges = 0, + CommitDate = "2017-09-13", + } + ; + var fixture = new GitVersionRunnerFixture( + new[] + { + "{", + " \"Major\":0,", + " \"Minor\":1,", + " \"Patch\":1,", + " \"PreReleaseTag\":\"PreReleaseTag\",", + " \"PreReleaseTagWithDash\":\"PreReleaseTagWithDash\",", + " \"PreReleaseLabel\":\"PreReleaseLabel\",", + " \"PreReleaseLabelWithDash\":\"-PreReleaseLabel\",", + " \"PreReleaseNumber\":\"\",", + " \"WeightedPreReleaseNumber\":\"\",", + " \"BuildMetaData\":\"BuildMetaData\",", + " \"BuildMetaDataPadded\":\"BuildMetaDataPadded\",", + " \"FullBuildMetaData\":\"Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", + " \"MajorMinorPatch\":\"0.1.1\",", + " \"SemVer\":\"0.1.1\",", + " \"LegacySemVer\":\"0.1.1\",", + " \"LegacySemVerPadded\":\"0.1.1\",", + " \"AssemblySemVer\":\"0.1.1.0\",", + " \"AssemblySemFileVer\":\"0.1.1.0\",", + " \"FullSemVer\":\"0.1.1\",", + " \"InformationalVersion\":\"0.1.1+Branch.master.Sha.f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", + " \"BranchName\":\"master\",", + " \"EscapedBranchName\":\"master\",", + " \"Sha\":\"f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", + " \"ShortSha\":\"f2467748\",", + " \"NuGetVersionV2\":\"0.1.1\",", + " \"NuGetVersion\":\"0.1.1\",", + " \"NuGetPreReleaseTagV2\":\"tag\",", + " \"NuGetPreReleaseTag\":\"tag\",", + " \"VersionSourceSha\":\"f2467748c78b3c8b37972ad0b30df2e15dfbf2cb\",", + " \"CommitsSinceVersionSource\":\"\",", + " \"CommitsSinceVersionSourcePadded\":\"0002\",", + " \"UncommittedChanges\":\"0\",", + " \"CommitDate\":\"2017-09-13\"", + "}" + }); + fixture.Settings.OutputType = GitVersionOutput.Json; + + // When + var result = fixture.RunGitVersion(); + + // Then + Assert.Equal(expect.Major, result.Major); + Assert.Equal(expect.Minor, result.Minor); + Assert.Equal(expect.Patch, result.Patch); + Assert.Equal(expect.PreReleaseTag, result.PreReleaseTag); + Assert.Equal(expect.PreReleaseTagWithDash, result.PreReleaseTagWithDash); + Assert.Equal(expect.PreReleaseLabel, result.PreReleaseLabel); + Assert.Equal(expect.PreReleaseLabelWithDash, result.PreReleaseLabelWithDash); + Assert.Equal(expect.PreReleaseNumber, result.PreReleaseNumber); + Assert.Equal(expect.WeightedPreReleaseNumber, result.WeightedPreReleaseNumber); + Assert.Equal(expect.BuildMetaData, result.BuildMetaData); + Assert.Equal(expect.BuildMetaDataPadded, result.BuildMetaDataPadded); + Assert.Equal(expect.FullBuildMetaData, result.FullBuildMetaData); + Assert.Equal(expect.MajorMinorPatch, result.MajorMinorPatch); + Assert.Equal(expect.SemVer, result.SemVer); + Assert.Equal(expect.LegacySemVer, result.LegacySemVer); + Assert.Equal(expect.LegacySemVerPadded, result.LegacySemVerPadded); + Assert.Equal(expect.AssemblySemVer, result.AssemblySemVer); + Assert.Equal(expect.AssemblySemFileVer, result.AssemblySemFileVer); + Assert.Equal(expect.FullSemVer, result.FullSemVer); + Assert.Equal(expect.InformationalVersion, result.InformationalVersion); + Assert.Equal(expect.BranchName, result.BranchName); + Assert.Equal(expect.EscapedBranchName, result.EscapedBranchName); + Assert.Equal(expect.Sha, result.Sha); + Assert.Equal(expect.ShortSha, result.ShortSha); + Assert.Equal(expect.NuGetVersionV2, result.NuGetVersionV2); + Assert.Equal(expect.NuGetVersion, result.NuGetVersion); + Assert.Equal(expect.NuGetPreReleaseTagV2, result.NuGetPreReleaseTagV2); + Assert.Equal(expect.NuGetPreReleaseTag, result.NuGetPreReleaseTag); + Assert.Equal(expect.VersionSourceSha, result.VersionSourceSha); + Assert.Equal(expect.CommitsSinceVersionSource, result.CommitsSinceVersionSource); + Assert.Equal(expect.CommitsSinceVersionSourcePadded, result.CommitsSinceVersionSourcePadded); + Assert.Equal(expect.UncommittedChanges, result.UncommittedChanges); + Assert.Equal(expect.CommitDate, result.CommitDate); + } + + [Fact] + public void Should_Populate_Legacy_Properties_When_Not_Returned_By_GitVersion_6() + { + // GitVersion 6+ omits legacy output variables; Cake best-effort populates them from remaining properties. + var fixture = new GitVersionRunnerFixture( + new[] + { + "{", + " \"Major\":6,", + " \"Minor\":1,", + " \"Patch\":0,", + " \"PreReleaseTag\":\"alpha.41\",", + " \"PreReleaseTagWithDash\":\"-alpha.41\",", + " \"PreReleaseLabel\":\"alpha\",", + " \"PreReleaseLabelWithDash\":\"-alpha\",", + " \"PreReleaseNumber\":\"41\",", + " \"BuildMetaData\":\"41.Branch.feature.Sha.abc123\",", + " \"MajorMinorPatch\":\"6.1.0\",", + " \"SemVer\":\"6.1.0-alpha.41\",", + " \"FullSemVer\":\"6.1.0-alpha.41+41\",", + " \"BranchName\":\"feature/gh-4575\",", + " \"Sha\":\"abc123def456\",", + " \"CommitsSinceVersionSource\":\"41\"", + "}" + }); + fixture.Settings.OutputType = GitVersionOutput.Json; + + var result = fixture.RunGitVersion(); + + Assert.Equal("6.1.0", result.MajorMinorPatch); + Assert.Equal("0041", result.CommitsSinceVersionSourcePadded); + Assert.Equal("0041", result.BuildMetaDataPadded); + Assert.Equal("6.1.0-alpha41", result.LegacySemVer); + Assert.Equal("6.1.0-alpha0041", result.LegacySemVerPadded); + Assert.Equal("6.1.0-alpha0041", result.NuGetVersionV2); + Assert.Equal("6.1.0-alpha0041", result.NuGetVersion); + Assert.Equal("-alpha", result.NuGetPreReleaseTagV2); + Assert.Equal("-alpha", result.NuGetPreReleaseTag); + } + + [Theory] + [InlineData(GitVersionVerbosity.Quiet, nameof(GitVersionVerbosity.Quiet))] + [InlineData(GitVersionVerbosity.Diagnostic, nameof(GitVersionVerbosity.Diagnostic))] + [InlineData(GitVersionVerbosity.Verbose, nameof(GitVersionVerbosity.Verbose))] + [InlineData(GitVersionVerbosity.Normal, nameof(GitVersionVerbosity.Normal))] + [InlineData(GitVersionVerbosity.Minimal, nameof(GitVersionVerbosity.Minimal))] + public void Should_Add_Verbosity_To_Arguments_If_Set(GitVersionVerbosity verbosity, string arg) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"-verbosity {arg}", result.Args); + } + + [Theory] + [InlineData(Verbosity.Quiet, nameof(Verbosity.Quiet))] + [InlineData(Verbosity.Diagnostic, nameof(Verbosity.Diagnostic))] + [InlineData(Verbosity.Verbose, nameof(Verbosity.Verbose))] + [InlineData(Verbosity.Minimal, nameof(Verbosity.Minimal))] + public void Should_Add_Default_Verbosity_To_Arguments_If_Not_Set(Verbosity verbosity, string arg) + { + // Given + var fixture = new GitVersionRunnerFixture(); + fixture.SetLogVerbosity(verbosity); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"-verbosity {arg}", result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeRunnerTests.cs index 28588aec32..c3640bfad3 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.ILMerge; using Cake.Core; @@ -26,7 +27,7 @@ public void Should_Throw_If_Output_Assembly_Path_Was_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputAssemblyPath"); + AssertEx.IsArgumentNullException(result, "outputAssemblyPath"); } [Fact] @@ -40,7 +41,7 @@ public void Should_Throw_If_Primary_Assembly_Path_Was_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "primaryAssemblyPath"); + AssertEx.IsArgumentNullException(result, "primaryAssemblyPath"); } [Fact] @@ -54,7 +55,7 @@ public void Should_Throw_If_Assembly_Paths_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -69,7 +70,7 @@ public void Should_Throw_If_ILMerge_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("ILMerge: Could not locate executable.", result.Message); + Assert.Equal("ILMerge: Could not locate executable.", result?.Message); } [Theory] @@ -160,7 +161,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("ILMerge: Process was not started.", result.Message); + Assert.Equal("ILMerge: Process was not started.", result?.Message); } [Fact] @@ -175,7 +176,133 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("ILMerge: Process returned an error (exit code 1).", result.Message); + Assert.Equal("ILMerge: Process returned an error (exit code 1).", result?.Message); + } + + [Fact] + public void Should_Add_SearchDirectories_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.SearchDirectories = new DirectoryPath[] { "/Working/DirectoryA", "/Working/DirectoryB" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/lib:\"/Working/DirectoryA\" " + + "/lib:\"/Working/DirectoryB\" " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_Log_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Log = true; + fixture.Settings.LogFile = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/log " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_LogFile_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Log = false; + fixture.Settings.LogFile = "/Working/output.log"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/log:\"/Working/output.log\" " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_KeyFile_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.KeyFile = "/Working/input.key"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/keyfile:\"/Working/input.key\" " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_KeyFile_And_DelaySign_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.KeyFile = "/Working/input.key"; + fixture.Settings.DelaySign = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/keyfile:\"/Working/input.key\" " + + "/delaysign " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_KeyContainer_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.KeyContainer = "myContainer"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/keycontainer:\"myContainer\" " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_KeyContainer_And_DelaySign_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.KeyContainer = "myContainer"; + fixture.Settings.DelaySign = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/keycontainer:\"myContainer\" " + + "/delaysign " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Not_Add_DelaySign_If_KeyFile_Or_KeyContainer_Not_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.DelaySign = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); } [Fact] @@ -189,14 +316,14 @@ public void Should_Internalize_If_Enabled_In_Settings() var result = fixture.Run(); // Then - Assert.Equal("/out:\"/Working/output.exe\" " + - "/internalize \"/Working/input.exe\"", result.Args); + Assert.Equal("/internalize " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); } [Theory] - [InlineData(TargetKind.Dll, "/out:\"/Working/output.exe\" /target:\"dll\" \"/Working/input.exe\"")] - [InlineData(TargetKind.Exe, "/out:\"/Working/output.exe\" /target:\"exe\" \"/Working/input.exe\"")] - [InlineData(TargetKind.WinExe, "/out:\"/Working/output.exe\" /target:\"winexe\" \"/Working/input.exe\"")] + [InlineData(TargetKind.Dll, "/target:\"dll\" /out:\"/Working/output.exe\" \"/Working/input.exe\"")] + [InlineData(TargetKind.Exe, "/target:\"exe\" /out:\"/Working/output.exe\" \"/Working/input.exe\"")] + [InlineData(TargetKind.WinExe, "/target:\"winexe\" /out:\"/Working/output.exe\" \"/Working/input.exe\"")] [InlineData(TargetKind.Default, "/out:\"/Working/output.exe\" \"/Working/input.exe\"")] public void Should_Set_Target_Kind_If_Enabled_In_Settings(TargetKind kind, string expected) { @@ -211,6 +338,160 @@ public void Should_Set_Target_Kind_If_Enabled_In_Settings(TargetKind kind, strin Assert.Equal(expected, result.Args); } + [Fact] + public void Should_Add_Closed_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Closed = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/closed " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_NDebug_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.NDebug = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/ndebug " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_Version_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Version = "1.2.3.4"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/ver:1.2.3.4 " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_CopyAttributes_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.CopyAttributes = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/copyattrs " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_CopyAttributes_And_AllowMultiple_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.CopyAttributes = true; + fixture.Settings.AllowMultiple = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/copyattrs /allowMultiple " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_CopyAttributes_And_KeepFirst_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.CopyAttributes = true; + fixture.Settings.KeepFirst = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/copyattrs /keepFirst " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_CopyAttributes_And_AllowMultiple_And_KeepFirst_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.CopyAttributes = true; + fixture.Settings.AllowMultiple = true; + fixture.Settings.KeepFirst = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/copyattrs /allowMultiple /keepFirst " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Not_Add_AllowMultiple_And_KeepFirst_If_CopyAttributes_Not_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.AllowMultiple = true; + fixture.Settings.KeepFirst = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_XmlDocumentation_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.XmlDocumentation = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/xmldocs " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_AttributeFile_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.AttributeFile = "/Working/input.attributes"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/attr:\"/Working/input.attributes\" " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + [Fact] public void Should_Set_Target_Platform_If_Enabled_In_Settings() { @@ -224,8 +505,8 @@ public void Should_Set_Target_Platform_If_Enabled_In_Settings() var result = fixture.Run(); // Then - Assert.Equal("/out:\"/Working/output.exe\" " + - "/targetPlatform:v4,\"/NetFramework\" " + + Assert.Equal("/targetPlatform:v4,\"/NetFramework\" " + + "/out:\"/Working/output.exe\" " + "\"/Working/input.exe\"", result.Args); } @@ -240,8 +521,8 @@ public void Should_Set_Target_Platform_Without_NETFramework_Path_If_Enabled_In_S var result = fixture.Run(); // Then - Assert.Equal("/out:\"/Working/output.exe\" " + - "/targetPlatform:v4 \"/Working/input.exe\"", result.Args); + Assert.Equal("/targetPlatform:v4 " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); } [Fact] @@ -257,6 +538,113 @@ public void Should_Not_Set_Target_Platform_If_Not_Enabled_In_Settings() Assert.Equal("/out:\"/Working/output.exe\" " + "\"/Working/input.exe\"", result.Args); } + + [Fact] + public void Should_Add_UseFullPublicKeyForReferences_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.UseFullPublicKeyForReferences = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/useFullPublicKeyForReferences " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_WildCards_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Wildcards = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/wildcards " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_ZeroPeKind_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.ZeroPeKind = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/zeroPeKind " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_AllowDuplicates_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.AllowDuplicateTypes = true; + fixture.Settings.DuplicateTypes = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/allowDup " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_DuplicateType_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.AllowDuplicateTypes = false; + fixture.Settings.DuplicateTypes = new string[] { "typeA", "typeB" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/allowDup:typeA /allowDup:typeB " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_Union_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Union = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/union " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } + + [Fact] + public void Should_Add_Align_If_Enabled_In_Settings() + { + // Given + var fixture = new ILMergeRunnerFixture(); + fixture.Settings.Align = 13; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/align:13 " + + "/out:\"/Working/output.exe\" \"/Working/input.exe\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeSettingsTests.cs index f771614e29..1831deb0eb 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ILMerge/ILMergeSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.ILMerge; using Xunit; @@ -31,4 +32,4 @@ public void Should_Set_Target_Kind_To_Default_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackRunnerTests.cs index 712e65b7e2..0e0074f272 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; @@ -28,7 +29,7 @@ public void Should_Throw_If_Output_Assembly_Path_Was_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputAssemblyPath"); + AssertEx.IsArgumentNullException(result, "outputAssemblyPath"); } [Fact] @@ -42,7 +43,7 @@ public void Should_Throw_If_Primary_Assembly_Path_Was_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "primaryAssemblyPath"); + AssertEx.IsArgumentNullException(result, "primaryAssemblyPath"); } [Fact] @@ -56,7 +57,7 @@ public void Should_Throw_If_Assembly_Paths_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -71,7 +72,7 @@ public void Should_Throw_If_ILRepack_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("ILRepack: Could not locate executable.", result.Message); + Assert.Equal("ILRepack: Could not locate executable.", result?.Message); } [Theory] @@ -162,7 +163,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("ILRepack: Process was not started.", result.Message); + Assert.Equal("ILRepack: Process was not started.", result?.Message); } [Fact] @@ -177,7 +178,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("ILRepack: Process returned an error (exit code 1).", result.Message); + Assert.Equal("ILRepack: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -367,13 +368,13 @@ public void Should_Set_Libs_If_Enabled_In_Settings() { // Given var fixture = new ILRepackRunnerFixture(); - fixture.Settings.Libs = new List { "/lib1.dll", "/lib2.dll" }; + fixture.Settings.Libs = new List { "/path1", "/path2" }; // When var result = fixture.Run(); // Then - Assert.Equal("/lib:\"/lib1.dll\" /lib:\"/lib2.dll\" /out:\"/Working/output.exe\" " + + Assert.Equal("/lib:\"/path1\" /lib:\"/path2\" /out:\"/Working/output.exe\" " + "\"/Working/input.exe\"", result.Args); } @@ -513,4 +514,4 @@ public void Should_Set_Verbose_If_Enabled_In_Settings() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackSettingsTests.cs index 40d586f152..50cc136779 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ILRepack/ILRepackSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.ILMerge; using Cake.Common.Tools.ILRepack; using Xunit; @@ -32,4 +33,4 @@ public void Should_Set_Target_Kind_To_Default_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupAliasesTests.cs b/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupAliasesTests.cs new file mode 100644 index 0000000000..2f3d7e91df --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupAliasesTests.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.InnoSetup; +using Cake.Core; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.InnoSetup +{ + public class InnoSetupAliasesTests + { + public sealed class TheInnoSetupMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given, When + var result = Record.Exception(() => InnoSetupAliases.InnoSetup(null, "some file.iss")); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Script_Path_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => InnoSetupAliases.InnoSetup(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "scriptFile"); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupRunnerTests.cs new file mode 100644 index 0000000000..cfd433c5d0 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/InnoSetup/InnoSetupRunnerTests.cs @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tools.InnoSetup; +using Cake.Core; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.InnoSetup +{ + public class InnoSetupRunnerTests + { + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Script_File_Is_Null() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.ScriptPath = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "scriptFile"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_InnoSetup_Runner_Was_Not_Found() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("InnoSetup: Could not locate executable.", result?.Message); + } + + [Theory] + [InlineData("/bin/inno/iscc.exe", "/bin/inno/iscc.exe")] + [InlineData("./tools/inno/iscc.exe", "/Working/tools/inno/iscc.exe")] + public void Should_Use_InnoSetup_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/inno/iscc.exe", "C:/inno/iscc.exe")] + public void Should_Use_InnoSetup_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_InnoSetup_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/iscc.exe", result.Path.FullPath); + } + + [WindowsTheory] + [InlineData(true, InnoSetupVersion.InnoSetup5)] + [InlineData(true, InnoSetupVersion.InnoSetup6)] + [InlineData(false, InnoSetupVersion.InnoSetup5)] + [InlineData(false, InnoSetupVersion.InnoSetup6)] + public void Should_Find_InnoSetup_Runner_In_Installation_Path_If_Tool_Path_Not_Provided(bool is64Bit, InnoSetupVersion version) + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenToolIsInstalled(is64Bit, version); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(fixture.InstalledToolPaths[version].FullPath, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData(true)] + [InlineData(false)] + public void Should_Use_Newest_InnoSetup_Version_If_Version_And_Tool_Path_Are_Not_Provided(bool is64Bit) + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenToolIsInstalled(is64Bit, InnoSetupVersion.InnoSetup5); + fixture.GivenToolIsInstalled(is64Bit, InnoSetupVersion.InnoSetup6); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(fixture.InstalledToolPaths[InnoSetupVersion.InnoSetup6].FullPath, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData(true, InnoSetupVersion.InnoSetup5)] + [InlineData(true, InnoSetupVersion.InnoSetup6)] + [InlineData(false, InnoSetupVersion.InnoSetup5)] + [InlineData(false, InnoSetupVersion.InnoSetup6)] + public void Should_Use_Provided_InnoSetup_Version_When_Tool_Path_Is_Not_Provided(bool is64Bit, InnoSetupVersion version) + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenToolIsInstalled(is64Bit, InnoSetupVersion.InnoSetup5); + fixture.GivenToolIsInstalled(is64Bit, InnoSetupVersion.InnoSetup6); + fixture.Settings.Version = version; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(fixture.InstalledToolPaths[version].FullPath, result.Path.FullPath); + } + + [Fact] + public void Should_Set_Working_Directory() + { + // Given + var fixture = new InnoSetupFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("InnoSetup: Process was not started.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("InnoSetup: Process returned an error (exit code 1).", result?.Message); + } + + [Fact] + public void Should_Add_Defines_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.Defines = new Dictionary(); + fixture.Settings.Defines.Add("Foo", "Bar"); + fixture.Settings.Defines.Add("Test", null); + fixture.Settings.Defines.Add("Test2", string.Empty); + fixture.Settings.Defines.Add("Test3", "hello world"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/DFoo=\"Bar\" /DTest /DTest2 /DTest3=\"hello world\" \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_OutputOn_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.EnableOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/O+ \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_OutputOff_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.EnableOutput = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/O- \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_OutputDir_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.OutputDirectory = "SetupOutput"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/O\"/Working/SetupOutput\" \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_OutputBaseFilename_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.OutputBaseFilename = "Test-setup"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/F\"Test-setup\" \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_QuietMode_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.QuietMode = InnoSetupQuietMode.Quiet; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Q \"/Working/Test.iss\"", result.Args); + } + + [Fact] + public void Should_Add_QuietModeWithProgress_To_Arguments_If_Provided() + { + // Given + var fixture = new InnoSetupFixture(); + fixture.Settings.QuietMode = InnoSetupQuietMode.QuietWithProgress; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Qp \"/Working/Test.iss\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/InspectCode/InspectCodeRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/InspectCode/InspectCodeRunnerTests.cs index 86ef04412a..5cedfad02e 100644 --- a/src/Cake.Common.Tests/Unit/Tools/InspectCode/InspectCodeRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/InspectCode/InspectCodeRunnerTests.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Linq; using Cake.Common.Tests.Fixtures.Tools.InspectCode; using Cake.Common.Tools.InspectCode; using Cake.Core; @@ -27,7 +29,7 @@ public void Should_Throw_If_Solution_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "solution"); + AssertEx.IsArgumentNullException(result, "solution"); } [Fact] @@ -43,6 +45,19 @@ public void Should_Find_Inspect_Code_Runner() Assert.Equal("/Working/tools/inspectcode.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_Inspect_Code_Runner_X86() + { + // Given + var fixture = new InspectCodeRunFixture(true); + fixture.Settings.UseX86Tool = true; + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/inspectcode.x86.exe", result.Path.FullPath); + } + [Fact] public void Should_Use_Provided_Solution_In_Process_Arguments() { @@ -68,7 +83,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("InspectCode: Process was not started.", result.Message); + Assert.Equal("InspectCode: Process was not started.", result?.Message); } [Fact] @@ -83,7 +98,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("InspectCode: Process returned an error (exit code 1).", result.Message); + Assert.Equal("InspectCode: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -113,7 +128,7 @@ public void Should_Throw_If_OutputFile_Contains_Violations_And_Set_To_Throw() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Code Inspection Violations found in code base."); + AssertEx.IsCakeException(result, "Code Inspection Violations found in code base."); } [Fact] @@ -124,12 +139,12 @@ public void Should_Throw_If_Solution_Wide_Analysis_Is_Both_Disabled_And_Enabled( fixture.Settings.SolutionWideAnalysis = true; fixture.Settings.NoSolutionWideAnalysis = true; - //When + // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("InspectCode: You can't set both SolutionWideAnalysis and NoSolutionWideAnalysis to true", result.Message); + Assert.Equal("InspectCode: You can't set both SolutionWideAnalysis and NoSolutionWideAnalysis to true", result?.Message); } [Fact] @@ -208,7 +223,7 @@ public void Should_Set_Caches_Home() } [Fact] - public void Should_Set_Resharper_Plugins() + public void Should_Set_ReSharper_Plugins() { // Given var fixture = new InspectCodeRunFixture(); @@ -289,6 +304,79 @@ public void Should_Set_Profile() Assert.Equal("\"/profile=/Working/profile.DotSettings\" " + "\"/Working/Test.sln\"", result.Args); } + + [Fact] + public void Should_Set_Verbosity() + { + // Given + var fixture = new InspectCodeRunFixture(); + fixture.Settings.Verbosity = InspectCodeVerbosity.Error; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/verbosity=ERROR\" \"/Working/Test.sln\"", result.Args); + } + + [Fact] + public void Should_Set_Severity() + { + // Given + var fixture = new InspectCodeRunFixture(); + fixture.Settings.Severity = InspectCodeSeverity.Hint; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/severity=HINT\" \"/Working/Test.sln\"", result.Args); + } + + [Fact] + public void Should_Analyze_Output() + { + var log = new FakeLog(); + + // Given + var fixture = new InspectCodeRunFixture + { + Log = log + }; + fixture.Settings.OutputFile = new FilePath("build/violations.xml"); + + // When + fixture.Run(); + + // Then + var logContainsInspectionResults = + log.Entries.Any(p => p.Message.StartsWith("Code Inspection Error(s) Located.")); + + Assert.True(logContainsInspectionResults); + } + + [Fact] + public void Should_Not_Analyze_Output() + { + var log = new FakeLog(); + + // Given + var fixture = new InspectCodeRunFixture + { + Log = log + }; + fixture.Settings.OutputFile = new FilePath("build/violations.xml"); + fixture.Settings.SkipOutputAnalysis = true; + + // When + fixture.Run(); + + // Then + var logContainsInspectionResults = + log.Entries.Any(p => p.Message.StartsWith("Code Inspection Error(s) Located.")); + + Assert.False(logContainsInspectionResults); + } } public sealed class TheRunFromConfigMethod @@ -304,7 +392,7 @@ public void Should_Throw_If_Config_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "configFile"); + AssertEx.IsArgumentNullException(result, "configFile"); } [Fact] @@ -320,6 +408,48 @@ public void Should_Use_Provided_Config_File() // Then Assert.Equal("\"/config=/Working/config.xml\"", result.Args); } + + [Fact] + public void Should_Not_Contain_Build_Or_NoBuild_If_Build_Is_Not_Set() + { + // Given + var fixture = new InspectCodeRunFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.DoesNotContain("--build", result.Args); + Assert.DoesNotContain("--no-build", result.Args); + } + + [Fact] + public void Should_Contain_Build_If_Build_Is_Set_To_True() + { + // Given + var fixture = new InspectCodeRunFixture(); + fixture.Settings.Build = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Contains("--build", result.Args); + } + + [Fact] + public void Should_Contain_NoBuild_If_Build_Is_Set_To_False() + { + // Given + var fixture = new InspectCodeRunFixture(); + fixture.Settings.Build = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Contains("--no-build", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs index ec0acb9e88..655f5f65f6 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildRunnerTests.cs @@ -1,12 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.MSBuild; +using Cake.Core; using Cake.Core.Diagnostics; using Cake.Testing; using Xunit; +#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to + namespace Cake.Common.Tests.Unit.Tools.MSBuild { public sealed class MSBuildRunnerTests @@ -17,7 +22,7 @@ public sealed class TheRunMethod public void Should_Return_The_Highest_MSBuild_Version_If_Tool_Version_Is_Set_To_Default() { // Given - var fixture = new MSBuildRunnerFixture(true); + var fixture = new MSBuildRunnerFixture(true, PlatformFamily.Windows); fixture.Settings.PlatformTarget = PlatformTarget.x64; fixture.Settings.ToolVersion = MSBuildToolVersion.Default; @@ -67,7 +72,7 @@ public void Should_Return_The_Highest_MSBuild_Version_If_Tool_Version_Is_Set_To_ public void Should_Get_Correct_Path_To_MSBuild_Version_2(MSBuildToolVersion version, PlatformTarget target, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.PlatformTarget = target; @@ -102,10 +107,165 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_2(MSBuildToolVersion vers public void Should_Get_Correct_Path_To_MSBuild_Version_35(MSBuildToolVersion version, PlatformTarget target, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2017, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2017/Enterprise/MSBuild/15.0/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2017, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2017/Enterprise/MSBuild/15.0/Bin/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2017, PlatformTarget.x64, PlatformFamily.Linux, false, "/usr/bin/msbuild")] + [InlineData(MSBuildToolVersion.VS2017, PlatformTarget.x64, PlatformFamily.OSX, false, "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild")] + public void Should_Get_Correct_Path_To_MSBuild_Version_15(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2019, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2019, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2019, PlatformTarget.x64, PlatformFamily.Linux, false, "/usr/bin/msbuild")] + [InlineData(MSBuildToolVersion.VS2019, PlatformTarget.x64, PlatformFamily.OSX, false, "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild")] + public void Should_Get_Correct_Path_To_MSBuild_Version_16(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.Linux, false, "/usr/bin/msbuild")] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.OSX, false, "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild")] + public void Should_Get_Correct_Path_To_MSBuild_Version_17(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2022/BuildTools/MSBuild/Current/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/2022/BuildTools/MSBuild/Current/Bin/MSBuild.exe")] + public void Should_Get_Correct_Path_To_MSBuild_Version_17_When_Only_Build_Tools_Are_Installed(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenMSBuildIsNotInstalled(); + fixture.FileSystem.CreateFile("/Program86/Microsoft Visual Studio/2022/BuildTools/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program86/Microsoft Visual Studio/2022/BuildTools/MSBuild/Current/Bin/MSBuild.exe"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.Windows, true)] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x86, PlatformFamily.Windows, true)] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x64, PlatformFamily.Windows, false)] + [InlineData(MSBuildToolVersion.VS2022, PlatformTarget.x86, PlatformFamily.Windows, false)] + public void Should_Get_Correct_Path_To_MSBuild_Version_17Preview_When_Preview_Is_Set(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool allowPreview) + { + // Given + var is64BitOperativeSystem = target == PlatformTarget.x64; + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + fixture.Settings.AllowPreviewVersion = allowPreview; + + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenMSBuildIsNotInstalled(); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/2022/Preview/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/2022/Preview/MSBuild/Current/Bin/MSBuild.exe"); + + // When + var result = fixture.Run(); + + // Then + if (allowPreview) + { + Assert.Contains("2022/Preview", result.Path.FullPath); + } + else + { + Assert.DoesNotContain("2022/Preview", result.Path.FullPath); + } + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.Linux, false, "/usr/bin/msbuild")] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.OSX, false, "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild")] + public void Should_Get_Correct_Path_To_MSBuild_Version_18(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Theory] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/18/BuildTools/MSBuild/Current/Bin/amd64/MSBuild.exe")] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x86, PlatformFamily.Windows, false, "/Program86/Microsoft Visual Studio/18/BuildTools/MSBuild/Current/Bin/MSBuild.exe")] + public void Should_Get_Correct_Path_To_MSBuild_Version_18_When_Only_Build_Tools_Are_Installed(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool is64BitOperativeSystem, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); fixture.Settings.ToolVersion = version; fixture.Settings.PlatformTarget = target; + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenMSBuildIsNotInstalled(); + fixture.FileSystem.CreateFile("/Program86/Microsoft Visual Studio/18/BuildTools/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program86/Microsoft Visual Studio/18/BuildTools/MSBuild/Current/Bin/MSBuild.exe"); + // When var result = fixture.Run(); @@ -113,6 +273,41 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_35(MSBuildToolVersion ver Assert.Equal(expected, result.Path.FullPath); } + [Theory] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.Windows, true)] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x86, PlatformFamily.Windows, true)] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x64, PlatformFamily.Windows, false)] + [InlineData(MSBuildToolVersion.VS2026, PlatformTarget.x86, PlatformFamily.Windows, false)] + public void Should_Get_Correct_Path_To_MSBuild_Version_18Insider_When_Preview_Is_Set(MSBuildToolVersion version, PlatformTarget target, PlatformFamily family, bool allowPreview) + { + // Given + var is64BitOperativeSystem = target == PlatformTarget.x64; + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, family); + fixture.Settings.ToolVersion = version; + fixture.Settings.PlatformTarget = target; + fixture.Settings.AllowPreviewVersion = allowPreview; + + fixture.GivenDefaultToolDoNotExist(); + fixture.GivenMSBuildIsNotInstalled(); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/18/Enterprise/MSBuild/Current/Bin/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/18/Insiders/MSBuild/Current/Bin/amd64/MSBuild.exe"); + fixture.FileSystem.CreateFile("/Program/Microsoft Visual Studio/18/Insiders/MSBuild/Current/Bin/MSBuild.exe"); + + // When + var result = fixture.Run(); + + // Then + if (allowPreview) + { + Assert.Contains("18/Insiders", result.Path.FullPath); + } + else + { + Assert.DoesNotContain("18/Insiders", result.Path.FullPath); + } + } + [Theory] [InlineData(MSBuildToolVersion.NET40, PlatformTarget.MSIL, true, "/Windows/Microsoft.NET/Framework64/v4.0.30319/MSBuild.exe")] [InlineData(MSBuildToolVersion.NET40, PlatformTarget.MSIL, false, "/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe")] @@ -167,7 +362,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_35(MSBuildToolVersion ver public void Should_Get_Correct_Path_To_MSBuild_Version_4(MSBuildToolVersion version, PlatformTarget target, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.PlatformTarget = target; @@ -212,7 +407,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_4(MSBuildToolVersion vers public void Should_Get_Correct_Path_To_MSBuild_Version_12(MSBuildToolVersion version, PlatformTarget target, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.PlatformTarget = target; @@ -247,7 +442,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_12(MSBuildToolVersion ver public void Should_Get_Correct_Path_To_MSBuild_Version_14(MSBuildToolVersion version, PlatformTarget target, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.PlatformTarget = target; @@ -292,7 +487,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_14(MSBuildToolVersion ver public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Build_Platform_Set_To_Automatic(MSBuildToolVersion version, MSBuildPlatform buildPlatform, PlatformTarget platformTarget, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = buildPlatform; fixture.Settings.PlatformTarget = platformTarget; @@ -328,7 +523,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Build_Platform_Set public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Build_Platform_Explicitly_Set(MSBuildToolVersion version, MSBuildPlatform buildPlatform, PlatformTarget platformTarget, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = buildPlatform; fixture.Settings.PlatformTarget = platformTarget; @@ -362,7 +557,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Build_Platform_Exp public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Platform_Set(MSBuildToolVersion version, MSBuildPlatform platform, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = platform; @@ -389,7 +584,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_2_When_Platform_Set(MSBui public void Should_Get_Correct_Path_To_MSBuild_Version_35_When_Platform_Set(MSBuildToolVersion version, MSBuildPlatform platform, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = platform; @@ -434,7 +629,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_35_When_Platform_Set(MSBu public void Should_Get_Correct_Path_To_MSBuild_Version_4_When_Platform_Set(MSBuildToolVersion version, MSBuildPlatform platform, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = platform; @@ -467,7 +662,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_4_When_Platform_Set(MSBui public void Should_Get_Correct_Path_To_MSBuild_Version_12_When_Platform_Set(MSBuildToolVersion version, MSBuildPlatform platform, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = platform; @@ -494,7 +689,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_12_When_Platform_Set(MSBu public void Should_Get_Correct_Path_To_MSBuild_Version_14_When_Platform_Set(MSBuildToolVersion version, MSBuildPlatform platform, bool is64BitOperativeSystem, string expected) { // Given - var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem); + var fixture = new MSBuildRunnerFixture(is64BitOperativeSystem, PlatformFamily.Windows); fixture.Settings.ToolVersion = version; fixture.Settings.MSBuildPlatform = platform; @@ -509,7 +704,7 @@ public void Should_Get_Correct_Path_To_MSBuild_Version_14_When_Platform_Set(MSBu public void Should_Throw_If_MSBuild_Executable_Did_Not_Exist() { // Given - var fixture = new MSBuildRunnerFixture(true); + var fixture = new MSBuildRunnerFixture(true, PlatformFamily.Windows); fixture.Settings.PlatformTarget = PlatformTarget.x86; fixture.Settings.ToolVersion = MSBuildToolVersion.NET20; @@ -520,14 +715,14 @@ public void Should_Throw_If_MSBuild_Executable_Did_Not_Exist() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "MSBuild: Could not locate executable."); + AssertEx.IsCakeException(result, "MSBuild: Could not locate executable."); } [Fact] public void Should_Use_As_Many_Processors_As_Possible_If_MaxCpuCount_Is_Zero() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); fixture.Settings.MaxCpuCount = 0; // When @@ -535,14 +730,14 @@ public void Should_Use_As_Many_Processors_As_Possible_If_MaxCpuCount_Is_Zero() // Then Assert.Equal("/m /v:normal /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] public void Should_Use_Specified_Number_Of_Max_Processors() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); fixture.Settings.MaxCpuCount = 4; // When @@ -550,43 +745,134 @@ public void Should_Use_Specified_Number_Of_Max_Processors() // Then Assert.Equal("/m:4 /v:normal /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] public void Should_Use_Correct_Default_Target_In_Process_Arguments() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Not_Append_Targets_If_No_Implicit_Target_Is_True_And_No_Targets_Provided() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.NoImplicitTarget = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Append_Targets_If_No_Implicit_Target_Is_True_And_Targets_Provided() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.NoImplicitTarget = true; + fixture.Settings.WithTarget("A"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] public void Should_Use_Node_Reuse_If_Specified() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); fixture.Settings.NodeReuse = true; // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /nr:true /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /nr:true /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_Detailed_Summary_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.DetailedSummary = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/ds /v:normal /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_No_Console_Logger_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.NoConsoleLogger = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/noconlog /v:normal /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_No_Logo_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/nologo /v:normal /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Single_Target_With_AddTarget() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithTarget("A"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Append_Targets_To_Process_Arguments() + public void Should_Add_Multiple_Targets_With_AddTarget() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); fixture.Settings.WithTarget("A"); fixture.Settings.WithTarget("B"); @@ -594,196 +880,929 @@ public void Should_Append_Targets_To_Process_Arguments() var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /target:A;B " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /target:A;B " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Append_Property_To_Process_Arguments() + public void Should_Not_Add_Duplicate_Target_With_AddTarget() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.WithProperty("A", "B"); - fixture.Settings.WithProperty("C", "D"); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithTarget("A"); + fixture.Settings.WithTarget("A"); // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /p:A=B /p:C=D /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Append_Property_With_Multiple_Values_To_Process_Arguments() + public void Should_Add_Single_Target_With_Initializer() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.WithProperty("A", "B", "E"); - fixture.Settings.WithProperty("C", "D"); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /p:A=B /p:A=E /p:C=D /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); } - [Theory] - [InlineData("Release", "/m /v:normal /p:Configuration=\"Release\" /target:Build \"/Working/src/Solution.sln\"")] - [InlineData("Custom Spaced", "/m /v:normal /p:Configuration=\"Custom Spaced\" /target:Build \"/Working/src/Solution.sln\"")] - public void Should_Append_Configuration_As_Property_To_Process_Arguments(string configuration, string expected) + [Fact] + public void Should_Remove_Existing_Targets_When_Set_After_Initializer() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.SetConfiguration(configuration); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; + + fixture.Settings.Target = "B"; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Args); + Assert.Equal("/v:normal /target:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); } - [Theory] - [InlineData(PlatformTarget.MSIL, "/m /v:normal /p:Platform=\"Any CPU\" /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(PlatformTarget.x86, "/m /v:normal /p:Platform=x86 /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(PlatformTarget.x64, "/m /v:normal /p:Platform=x64 /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(PlatformTarget.ARM, "/m /v:normal /p:Platform=arm /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(PlatformTarget.Win32, "/m /v:normal /p:Platform=Win32 /target:Build \"/Working/src/Solution.sln\"")] - public void Should_Append_Platform_As_Property_To_Process_Arguments(PlatformTarget platform, string expected) + [Fact] + public void Should_Add_Multiple_Targets_With_Initializer() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.SetPlatformTarget(platform); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A;B", + } + }; // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Args); + Assert.Equal("/v:normal /target:A;B " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Append_MSIL_Platform_As_AnyCPU_For_Project() + public void Should_Add_Multiple_Targets_With_Initializer_And_AddTarget() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.SetPlatformTarget(PlatformTarget.MSIL); - fixture.Solution = "/Working/src/Project.csproj"; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A;B", + } + }; + + fixture.Settings.WithTarget("C"); + fixture.Settings.WithTarget("D"); // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /p:Platform=AnyCPU /target:Build \"/Working/src/Project.csproj\"", result.Args); + Assert.Equal("/v:normal /target:A;B;C;D " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Build_Arm_With_x86_When_Specified() + public void Should_Not_Add_Duplicate_Target_With_Initializer() { // Given - var fixture = new MSBuildRunnerFixture(true); - fixture.Settings.PlatformTarget = PlatformTarget.ARM; - fixture.Settings.MSBuildPlatform = MSBuildPlatform.x86; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = "A", + } + }; + + fixture.Settings.Target = "A"; // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /p:Platform=arm /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); - Assert.Equal("/Program86/MSBuild/12.0/Bin/MSBuild.exe", result.Path.FullPath); + Assert.Equal("/v:normal /target:A " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Omit_Platform_Property_In_Process_Arguments_If_It_Is_Null() + public void Should_Remove_Whitespace_From_Targets_With_Initializer() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows) + { + Settings = new MSBuildSettings + { + Target = " A ; B ;;", + } + }; // When var result = fixture.Run(); // Then - Assert.Equal("/m /v:normal /target:Build " + - "\"/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/v:normal /target:A;B " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Set_Working_Directory() + public void Should_Append_Property_To_Process_Arguments() { // Given - var fixture = new MSBuildRunnerFixture(false); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("A", "B"); + fixture.Settings.WithProperty("C", "D"); // When var result = fixture.Run(); // Then - Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + Assert.Equal("/v:normal /p:A=B /p:C=D /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Throw_If_Process_Was_Not_Started() + public void Should_Append_Property_With_Multiple_Values_To_Process_Arguments() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.GivenProcessCannotStart(); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("A", "B", "E"); + fixture.Settings.WithProperty("C", "D"); // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "MSBuild: Process was not started."); + Assert.Equal("/v:normal /p:A=B;E /p:C=D /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + public void Should_Concatenate_Property_With_Multiple_Arguments_To_Process_Argument() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.GivenProcessExitsWithCode(1); + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("A", "B", "E"); + fixture.Settings.WithProperty("C", "D", "F", "G"); // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "MSBuild: Process returned an error (exit code 1)."); + Assert.Equal("/v:normal /p:A=B;E /p:C=D;F;G /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); } - [Theory] - [InlineData(Verbosity.Quiet, "/m /v:quiet /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(Verbosity.Minimal, "/m /v:minimal /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(Verbosity.Normal, "/m /v:normal /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(Verbosity.Verbose, "/m /v:detailed /target:Build \"/Working/src/Solution.sln\"")] - [InlineData(Verbosity.Diagnostic, "/m /v:diagnostic /target:Build \"/Working/src/Solution.sln\"")] - public void Should_Append_Correct_Verbosity(Verbosity verbosity, string expected) + [Fact] + public void Should_Not_Escape_Semicolons_For_Specified_Property_Arguments_When_Appending_To_Process_Argument() { // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.Verbosity = verbosity; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("DefineConstants", "A;B"); + fixture.Settings.WithProperty("A", "A;B"); // When var result = fixture.Run(); // Then - Assert.Equal(expected, result.Args); + Assert.Equal("/v:normal /p:DefineConstants=A;B /p:A=A%3BB /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); } [Fact] - public void Should_Throw_If_Verbosity_Is_Unknown() + public void Should_Append_GetProperty_To_Process_Arguments_And_Collects_Output() { + IEnumerable msbuildOutput = null; + // Given - var fixture = new MSBuildRunnerFixture(false); - fixture.Settings.Verbosity = (Verbosity)int.MaxValue; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetProperty("A"); + fixture.Settings.WithGetProperty("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Properties\": {", + " \"A\": \"A value\",", + " \"B\": \"B value\"", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); // When - var result = Record.Exception(() => fixture.Run()); + var result = fixture.Run(); // Then - Assert.IsCakeException(result, "Encountered unknown MSBuild build log verbosity."); + Assert.Equal("/v:normal /target:Build /getProperty:A /getProperty:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); } - } - } -} + + [Fact] + public void Should_Append_GetItem_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetItem("A"); + fixture.Settings.WithGetItem("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"Items\": {", + " \"A\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " \"B\": [", + " {", + " \"Identity\": \"Identity value\"", + " }", + " ],", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /getItem:A /getItem:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Fact] + public void Should_Append_GetTargetResult_To_Process_Arguments_And_Collects_Output() + { + IEnumerable msbuildOutput = null; + + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithGetTargetResult("A"); + fixture.Settings.WithGetTargetResult("B"); + fixture.StandardOutputAction = lines => msbuildOutput = lines; + var standardOutput = new string[] + { + "{", + " \"TargetResults\": {", + " \"A\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " },", + " \"B\": {", + " \"Result\": \"Success\"", + " \"Items\": []", + " }", + " }", + "}", + }; + fixture.ProcessRunner.Process.SetStandardOutput(standardOutput); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /getTargetResult:A /getTargetResult:B " + + "\"C:/Working/src/Solution.sln\"", result.Args); + + Assert.Equal(standardOutput, msbuildOutput); + } + + [Theory] + [InlineData("Release", "/v:normal /p:Configuration=\"Release\" /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData("Custom Spaced", "/v:normal /p:Configuration=\"Custom Spaced\" /target:Build \"C:/Working/src/Solution.sln\"")] + public void Should_Append_Configuration_As_Property_To_Process_Arguments(string configuration, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.SetConfiguration(configuration); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(PlatformTarget.MSIL, "/v:normal /p:Platform=\"Any CPU\" /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(PlatformTarget.x86, "/v:normal /p:Platform=x86 /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(PlatformTarget.x64, "/v:normal /p:Platform=x64 /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(PlatformTarget.ARM, "/v:normal /p:Platform=arm /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(PlatformTarget.Win32, "/v:normal /p:Platform=Win32 /target:Build \"C:/Working/src/Solution.sln\"")] + public void Should_Append_Platform_As_Property_To_Process_Arguments(PlatformTarget platform, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.SetPlatformTarget(platform); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Append_MSIL_Platform_As_AnyCPU_For_Project() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.SetPlatformTarget(PlatformTarget.MSIL); + fixture.Solution = "/Working/src/Project.csproj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:Platform=AnyCPU /target:Build \"/Working/src/Project.csproj\"", result.Args); + } + + [Fact] + public void Should_Build_Arm_With_x86_When_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(true, PlatformFamily.Windows); + fixture.Settings.PlatformTarget = PlatformTarget.ARM; + fixture.Settings.MSBuildPlatform = MSBuildPlatform.x86; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:Platform=arm /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + Assert.Equal("/Program86/MSBuild/12.0/Bin/MSBuild.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Omit_Platform_Property_In_Process_Arguments_If_It_Is_Null() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Set_Working_Directory() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Linux); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "MSBuild: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "MSBuild: Process returned an error (exit code 1)."); + } + + [Theory] + [InlineData(Verbosity.Quiet, "/v:quiet /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(Verbosity.Minimal, "/v:minimal /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(Verbosity.Normal, "/v:normal /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(Verbosity.Verbose, "/v:detailed /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData(Verbosity.Diagnostic, "/v:diagnostic /target:Build \"C:/Working/src/Solution.sln\"")] + public void Should_Append_Correct_Verbosity(Verbosity verbosity, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Throw_If_Verbosity_Is_Unknown() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.Verbosity = (Verbosity)int.MaxValue; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Encountered unknown MSBuild build log verbosity."); + } + + [Fact] + public void Should_Use_IncludeSymbols_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.IncludeSymbols = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:IncludeSymbols=true /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_SymbolPackageFormat_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.IncludeSymbols = true; + fixture.Settings.SymbolPackageFormat = "snupkg"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_Version_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.Version = "1.0.0-test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:Version=1.0.0-test /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_VersionPrefix_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.VersionPrefix = "1.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:VersionPrefix=1.0.0 /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_VersionSuffix_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.VersionSuffix = "test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:VersionSuffix=test /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_FileVersion_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.FileVersion = "1.0.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:FileVersion=1.0.0.0 /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_AssemblyVersion_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.AssemblyVersion = "1.0.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:AssemblyVersion=1.0.0.0 /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_InformationalVersion_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.InformationalVersion = "1.0.0-test+7ad03d0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:InformationalVersion=1.0.0-test+7ad03d0 /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_PackageVersion_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.PackageVersion = "1.0.0-test"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:PackageVersion=1.0.0-test /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_PackageReleaseNotes_If_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.PackageReleaseNotes = "https://"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:PackageReleaseNotes=https:// /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_ContinuousIntegrationBuild_If_Set_To_True() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.ContinuousIntegrationBuild = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:ContinuousIntegrationBuild=true /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Add_ContinuousIntegrationBuild_If_Set_To_False() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.ContinuousIntegrationBuild = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /p:ContinuousIntegrationBuild=false /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Not_Add_ContinuousIntegrationBuild_If_Not_Set() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + + // When + var result = fixture.Run(); + + // Then + Assert.DoesNotContain("/p:ContinuousIntegrationBuild=", result.Args); + } + + [Fact] + public void Should_Append_Logger_To_Process_Arguments() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithLogger("A", "B", "C"); + fixture.Settings.WithLogger("D", "E"); + fixture.Settings.WithLogger("F"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /logger:B,\"A\";C /logger:E,\"D\" /logger:\"F\" " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Append_FileLogger_To_Process_Arguments() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { AppendToLogFile = false, Encoding = "E", HideVerboseItemAndPropertyList = false, LogFile = "A", MSBuildFileLoggerOutput = MSBuildFileLoggerOutput.All, PerformanceSummaryEnabled = false, ShowCommandLine = false, ShowEventId = false, ShowTimestamp = false, SummaryDisabled = false, Verbosity = Verbosity.Diagnostic }); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { AppendToLogFile = true, HideVerboseItemAndPropertyList = true, MSBuildFileLoggerOutput = MSBuildFileLoggerOutput.ErrorsOnly, PerformanceSummaryEnabled = true, ShowCommandLine = true, ShowEventId = true, ShowTimestamp = true, SummaryDisabled = true, Verbosity = Verbosity.Minimal }); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { MSBuildFileLoggerOutput = MSBuildFileLoggerOutput.WarningsOnly, Verbosity = Verbosity.Normal }); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { Verbosity = Verbosity.Quiet }); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { Verbosity = Verbosity.Verbose }); + fixture.Settings.AddFileLogger(new MSBuildFileLogger { }); + fixture.Settings.AddFileLogger(); + + // When + var result = fixture.Run(); + // Then + Assert.Equal(@"/v:normal /target:Build /fl /flp:logfile=""C:/Working/A"";Encoding=E;Verbosity=diagnostic /fl1 /flp1:Append;PerformanceSummary;NoSummary;ErrorsOnly;NoItemAndPropertyList;ShowCommandLine;ShowTimestamp;ShowEventId;Verbosity=minimal /fl2 /flp2:WarningsOnly;Verbosity=normal /fl3 /flp3:Verbosity=quiet /fl4 /flp4:Verbosity=detailed /fl5 /fl6 ""C:/Working/src/Solution.sln""", result.Args); + } + + [Fact] + public void Should_Append_Default_FileLogger_To_Process_Arguments() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.AddFileLogger(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(@"/v:normal /target:Build /fl ""C:/Working/src/Solution.sln""", result.Args); + } + + [Fact] + public void Should_Throw_Exception_For_Too_Many_FileLoggers() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + fixture.Settings.AddFileLogger(); + + // When + var ex = Assert.Throws(() => fixture.Run()); + + // Then + Assert.Equal(@"Too Many FileLoggers", ex.Message); + } + + [Fact] + public void Should_Use_Binary_Logging_If_Specified() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.BinaryLogger = new MSBuildBinaryLogSettings() + { + Enabled = true, + FileName = "mylog.binlog", + Imports = MSBuildBinaryLogImports.ZipFile + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /bl:\"mylog.binlog\";ProjectImports=ZipFile \"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_Binary_Logging_If_Enabled() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.EnableBinaryLogger("mylog.binlog", MSBuildBinaryLogImports.ZipFile); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /bl:\"mylog.binlog\";ProjectImports=ZipFile \"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Quote_Binary_Logging_FilePath() + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.BinaryLogger = new MSBuildBinaryLogSettings() + { + Enabled = true, + FileName = "C:/Working Directory/src folder/mylog.binlog" + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/v:normal /target:Build /bl:\"C:/Working Directory/src folder/mylog.binlog\" \"C:/Working/src/Solution.sln\"", result.Args); + } + + [Theory] + [InlineData("Value1,Value2", "/v:normal /p:Property=Value1%2CValue2 /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData("Value1;Value2", "/v:normal /p:Property=Value1%3BValue2 /target:Build \"C:/Working/src/Solution.sln\"")] + [InlineData("Value1 Value2", "/v:normal /p:Property=Value1%20Value2 /target:Build \"C:/Working/src/Solution.sln\"")] + public void Should_Escape_Special_Characters_In_Property_Value(string propertyValue, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("Property", propertyValue); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Use_WarnAsError_If_Specified() + { + // Given + var expected = "/v:normal /target:Build /warnaserror \"C:/Working/src/Solution.sln\""; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithWarningsAsError(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Use_WarnAsError_Codes_If_Specified() + { + // Given + const string expected = "/v:normal /target:Build /warnaserror:\"12345\" \"C:/Working/src/Solution.sln\""; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithWarningsAsError("12345"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Use_WarnAsMessage_Codes_If_Specified() + { + // Given + const string expected = "/v:normal /target:Build /warnasmessage:\"12345\" \"C:/Working/src/Solution.sln\""; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithWarningsAsMessage("12345"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Use_WarnAsError_And_WarnAsMessage_Codes_If_Specified() + { + // Given + var expected = "/v:normal /target:Build /warnaserror /warnasmessage:\"12345\" \"C:/Working/src/Solution.sln\""; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithWarningsAsError().WithWarningsAsMessage("12345"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData("\r", "%0D")] + [InlineData("\n", "%0A")] + [InlineData("\r\n", "%0D%0A")] + public void Should_URL_Convert_NewLine_Characters_In_Properties(string input, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithProperty("Foo", input); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"/v:normal /p:Foo={expected} /target:Build " + + "\"C:/Working/src/Solution.sln\"", result.Args); + } + + [Fact] + public void Should_Use_Restore_If_Specified() + { + // Given + var expected = "/v:normal /target:Build /restore \"C:/Working/src/Solution.sln\""; + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + fixture.Settings.WithRestore(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Theory] + [InlineData(new string[] { }, @"/v:normal /target:Build ""C:/Working/src/Solution.sln""")] + [InlineData(new string[] { "ForceConsoleColor" }, @"/v:normal /target:Build /clp:ForceConsoleColor ""C:/Working/src/Solution.sln""")] + [InlineData(new string[] { "ForceConsoleColor", "ShowCommandLine" }, @"/v:normal /target:Build /clp:ForceConsoleColor;ShowCommandLine ""C:/Working/src/Solution.sln""")] + public void Should_Append_ConsoleLoggerParameters(string[] parameters, string expected) + { + // Given + var fixture = new MSBuildRunnerFixture(false, PlatformFamily.Windows); + foreach (var parameter in parameters) + { + fixture.Settings.ConsoleLoggerParameters.Add(parameter); + } + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} + +#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs index 3073cae1ad..11ad8d9478 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsExtensionsTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Linq; using Cake.Common.Tools.MSBuild; using Cake.Core.Diagnostics; using Xunit; @@ -65,6 +67,79 @@ public void Should_Return_The_Same_Configuration() // Then Assert.Equal(settings, result); } + + [Fact] + public void Should_Set_Tool_Version_FromStringOverride() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.UseToolVersion("net20"); + + // Then + Assert.Equal(MSBuildToolVersion.NET20, settings.ToolVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration_FromStringOverride() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.UseToolVersion("net35"); + + // Then + Assert.Equal(settings, result); + } + + [Fact] + public void Should_ThrowArgumetnException_When_String_Is_Null() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Throws(() => settings.UseToolVersion(null)); + } + + [Fact] + public void Should_Return_Default_When_Input_Is_Bad() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.UseToolVersion("fjaldskjflaksdjflkas"); + + // Then + Assert.Equal(MSBuildToolVersion.Default, settings.ToolVersion); + } + + [Fact] + public void Should_Return_VSCustom_When_VsVersion_CantBeMatch() + { + var settings = new MSBuildSettings(); + + // When + settings.UseToolVersion("2022"); + + // Then + Assert.Equal(MSBuildToolVersion.VSCustom, settings.ToolVersion); + } + + [Fact] + public void Should_Return_VSCustom_When_NETFramework_CantBeMatch() + { + var settings = new MSBuildSettings(); + + // When + settings.UseToolVersion("5"); + + // Then + Assert.Equal(MSBuildToolVersion.NETCustom, settings.ToolVersion); + } } public sealed class TheSetPlatformTargetMethod @@ -96,6 +171,36 @@ public void Should_Return_The_Same_Configuration() } } + public sealed class TheSetPlatformTargetStringMethod + { + [Fact] + public void Should_Set_Platform_Target_Via_Property() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetPlatformTarget("Custom"); + + // Then + Assert.True(settings.Properties.ContainsKey("Platform")); + Assert.Equal("Custom", string.Concat(settings.Properties["Platform"])); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetPlatformTarget("Custom"); + + // Then + Assert.Equal(settings, result); + } + } + public sealed class TheWithPropertyMethod { [Fact] @@ -227,24 +332,21 @@ public void Should_Return_The_Same_Configuration() } } - public sealed class TheSetVerbosityMethod + public sealed class TheDetailedSummaryMethod { [Theory] - [InlineData(Verbosity.Quiet)] - [InlineData(Verbosity.Minimal)] - [InlineData(Verbosity.Normal)] - [InlineData(Verbosity.Verbose)] - [InlineData(Verbosity.Diagnostic)] - public void Should_Set_Verbosity(Verbosity verbosity) + [InlineData(true)] + [InlineData(false)] + public void Should_Set_Detailed_Summary(bool detailedSummary) { // Given var settings = new MSBuildSettings(); // When - settings.SetVerbosity(verbosity); + settings.SetDetailedSummary(detailedSummary); // Then - Assert.Equal(verbosity, settings.Verbosity); + Assert.Equal(detailedSummary, settings.DetailedSummary); } [Fact] @@ -254,11 +356,717 @@ public void Should_Return_The_Same_Configuration() var settings = new MSBuildSettings(); // When - var result = settings.SetVerbosity(Verbosity.Normal); + var result = settings.SetDetailedSummary(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheNoConsoleLoggerMethod + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_No_Console_Logger(bool noConsoleLog) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetNoConsoleLogger(noConsoleLog); + + // Then + Assert.Equal(noConsoleLog, settings.NoConsoleLogger); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetNoConsoleLogger(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheNoLogoMethod + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_No_Logo(bool noLogo) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetNoLogo(noLogo); + + // Then + Assert.Equal(noLogo, settings.NoLogo); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetNoLogo(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionMethod + { + private const string Version = "1.0.0-test"; + + [Fact] + public void Should_Set_Version() + { + // Given + var settings = new MSBuildSettings(); + const string key = "Version"; + + // When + settings.SetVersion(Version); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(Version, settings.Properties[key].FirstOrDefault()); + Assert.Equal(Version, settings.Version); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetVersion(Version); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionPrefixMethod + { + private const string VersionPrefix = "1.0.0"; + + [Fact] + public void Should_Set_VersionPrefix() + { + // Given + var settings = new MSBuildSettings(); + const string key = "VersionPrefix"; + + // When + settings.SetVersionPrefix(VersionPrefix); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(VersionPrefix, settings.Properties[key].FirstOrDefault()); + Assert.Equal(VersionPrefix, settings.VersionPrefix); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetVersionPrefix(VersionPrefix); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVersionSuffixMethod + { + private const string VersionSuffix = "test"; + + [Fact] + public void Should_Set_VersionSuffix() + { + // Given + var settings = new MSBuildSettings(); + const string key = "VersionSuffix"; + + // When + settings.SetVersionSuffix(VersionSuffix); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(VersionSuffix, settings.Properties[key].FirstOrDefault()); + Assert.Equal(VersionSuffix, settings.VersionSuffix); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetVersionSuffix(VersionSuffix); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetFileVersionMethod + { + private const string FileVersion = "1.0.0.0"; + + [Fact] + public void Should_Set_FileVersion() + { + // Given + var settings = new MSBuildSettings(); + const string key = "FileVersion"; + + // When + settings.SetFileVersion(FileVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(FileVersion, settings.Properties[key].FirstOrDefault()); + Assert.Equal(FileVersion, settings.FileVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetFileVersion(FileVersion); // Then Assert.Equal(settings, result); } } + + public sealed class TheSetAssemblyVersionMethod + { + private const string AssemblyVersion = "1.0.0.0"; + + [Fact] + public void Should_Set_AssemblyVersion() + { + // Given + var settings = new MSBuildSettings(); + const string key = "AssemblyVersion"; + + // When + settings.SetAssemblyVersion(AssemblyVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(AssemblyVersion, settings.Properties[key].FirstOrDefault()); + Assert.Equal(AssemblyVersion, settings.AssemblyVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetAssemblyVersion(AssemblyVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetInformationalVersionMethod + { + private const string InformationalVersion = "1.0.0-test+7ad03d0"; + + [Fact] + public void Should_Set_InformationalVersion() + { + // Given + var settings = new MSBuildSettings(); + const string key = "InformationalVersion"; + + // When + settings.SetInformationalVersion(InformationalVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(InformationalVersion, settings.Properties[key].FirstOrDefault()); + Assert.Equal(InformationalVersion, settings.InformationalVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetInformationalVersion(InformationalVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetPackageVersionMethod + { + private const string PackageVersion = "1.0.0-test"; + + [Fact] + public void Should_Set_PackageVersion() + { + // Given + var settings = new MSBuildSettings(); + const string key = "PackageVersion"; + + // When + settings.SetPackageVersion(PackageVersion); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(PackageVersion, settings.Properties[key].FirstOrDefault()); + Assert.Equal(PackageVersion, settings.PackageVersion); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetPackageVersion(PackageVersion); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetPackageReleaseNotesMethod + { + private const string PackageReleaseNotes = "1.0.0-test"; + + [Fact] + public void Should_Set_PackageReleaseNotes() + { + // Given + var settings = new MSBuildSettings(); + const string key = "PackageReleaseNotes"; + + // When + settings.SetPackageReleaseNotes(PackageReleaseNotes); + + // Then + Assert.True(settings.Properties.ContainsKey(key)); + Assert.Equal(PackageReleaseNotes, settings.Properties[key].FirstOrDefault()); + Assert.Equal(PackageReleaseNotes, settings.PackageReleaseNotes); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetPackageReleaseNotes(PackageReleaseNotes); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetContinuousIntegrationBuildMethod + { + [Theory] + [InlineData(null)] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_ContinuousIntegrationBuild(bool? continuousIntegrationBuild) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetContinuousIntegrationBuild(continuousIntegrationBuild); + + // Then + Assert.Equal(continuousIntegrationBuild, settings.ContinuousIntegrationBuild); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetContinuousIntegrationBuild(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheNoImplicitTargetMethod + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_No_Implicit_Target(bool noImplicitTarget) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetNoImplicitTarget(noImplicitTarget); + + // Then + Assert.Equal(noImplicitTarget, settings.NoImplicitTarget); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetNoImplicitTarget(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetRestoreLockedModeMethod + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Set_RestoreLockedMode(bool restoreLockedMode) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetRestoreLockedMode(restoreLockedMode); + + // Then + Assert.Equal(restoreLockedMode, settings.RestoreLockedMode); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetRestoreLockedMode(true); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheSetVerbosityMethod + { + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public void Should_Set_Verbosity(Verbosity verbosity) + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.SetVerbosity(verbosity); + + // Then + Assert.Equal(verbosity, settings.Verbosity); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.SetVerbosity(Verbosity.Normal); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithLoggersMethod + { + [Fact] + public void Should_Add_Logger() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithLogger("LoggerAssembly1", "LoggerClass1", "LoggerParameters1"); + settings.WithLogger("LoggerAssembly2", "LoggerClass2", "LoggerParameters2"); + + // Then + var loggers = settings.Loggers.ToArray(); + Assert.Equal(2, loggers.Length); + Assert.Equal("LoggerAssembly1", loggers[0].Assembly); + Assert.Equal("LoggerClass1", loggers[0].Class); + Assert.Equal("LoggerParameters1", loggers[0].Parameters); + Assert.Equal("LoggerAssembly2", loggers[1].Assembly); + Assert.Equal("LoggerClass2", loggers[1].Class); + Assert.Equal("LoggerParameters2", loggers[1].Parameters); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.WithLogger("Logger"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheAddFileLoggersMethod + { + [Fact] + public void Should_Add_Logger() + { + // Given + var settings = new MSBuildSettings(); + var fileLogger = new MSBuildFileLogger(); + var fileLogger2 = new MSBuildFileLogger { LogFile = "A" }; + + // When + settings.AddFileLogger(fileLogger); + settings.AddFileLogger(fileLogger2); + + // Then + var loggers = settings.FileLoggers.ToArray(); + Assert.Equal(2, loggers.Length); + Assert.Equal(fileLogger, loggers[0]); + Assert.Equal(fileLogger2, loggers[1]); + Assert.Equal("A", loggers[1].LogFile.FullPath); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.AddFileLogger(new MSBuildFileLogger()); + var result1 = settings.AddFileLogger(); + + // Then + Assert.Equal(settings, result); + Assert.Equal(settings, result1); + } + } + + public sealed class TheEnableBinaryLoggerMethod + { + [Fact] + public void Should_Enable_BinaryLogger() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.EnableBinaryLogger(); + + // Then + Assert.NotNull(settings.BinaryLogger); + Assert.True(settings.BinaryLogger.Enabled); + Assert.Null(settings.BinaryLogger.FileName); + Assert.Equal(MSBuildBinaryLogImports.Unspecified, settings.BinaryLogger.Imports); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.EnableBinaryLogger(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithWarningsAsErrorMethod + { + [Fact] + public void Should_Set_WarningsAsError() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithWarningsAsError(); + + // Then + Assert.True(settings.WarningsAsError); + Assert.Empty(settings.WarningsAsErrorCodes); + } + + [Fact] + public void Should_Add_Codes() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithWarningsAsError("12345"); + + // Then + Assert.True(settings.WarningsAsError); + Assert.Single(settings.WarningsAsErrorCodes); + Assert.Equal("12345", settings.WarningsAsErrorCodes.First()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.WithWarningsAsError("12345"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithWarningsAsMessageMethod + { + [Fact] + public void Should_Add_Codes() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithWarningsAsMessage("12345"); + + // Then + Assert.Single(settings.WarningsAsMessageCodes); + Assert.Equal("12345", settings.WarningsAsMessageCodes.First()); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.WithWarningsAsMessage("12345"); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithRestoreMethod + { + [Fact] + public void Should_Set_Restore_To_True() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithRestore(); + + // Then + Assert.Equal(true, settings.Restore); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.WithRestore(); + + // Then + Assert.Equal(settings, result); + } + } + + public sealed class TheWithConsoleLoggerParameterMethod + { + [Fact] + public void Should_Add_Console_Logger_Parameter() + { + // Given + var settings = new MSBuildSettings(); + + // When + settings.WithConsoleLoggerParameter("ForceConsoleColor"); + settings.WithConsoleLoggerParameter("ShowCommandLine"); + + // Then + Assert.Contains("ForceConsoleColor", settings.ConsoleLoggerParameters); + Assert.Contains("ShowCommandLine", settings.ConsoleLoggerParameters); + } + + [Fact] + public void Should_Return_The_Same_Configuration() + { + // Given + var settings = new MSBuildSettings(); + + // When + var result = settings.WithConsoleLoggerParameter("ForceConsoleColor"); + var result1 = settings.WithConsoleLoggerParameter("ShowCommandLine"); + + // Then + Assert.Equal(settings, result); + Assert.Equal(settings, result1); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsTests.cs index 15172fc6ea..5acf90d505 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.MSBuild; using Cake.Core.Diagnostics; using Xunit; @@ -21,7 +22,7 @@ public void Should_Set_Default_Tools_Version_To_Default() Assert.Equal(MSBuildToolVersion.Default, settings.ToolVersion); } - [Fact()] + [Fact] public void Should_Set_Default_Platform_Target_To_Null() { // Given, When @@ -67,7 +68,7 @@ public void Should_Return_A_Dictionary_That_Is_Case_Insensitive() var settings = new MSBuildSettings(); // When - settings.Properties.Add("THEKEY", new []{"THEVALUE"}); + settings.Properties.Add("THEKEY", new[] { "THEVALUE" }); // Then Assert.True(settings.Properties.ContainsKey("thekey")); @@ -101,6 +102,162 @@ public void Should_Be_Null_By_Default() } public sealed class TheMaxCpuCountProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.MaxCpuCount); + } + } + + public sealed class TheDetailedSummaryProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.DetailedSummary); + } + } + + public sealed class TheNoConsoleLogProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.NoConsoleLogger); + } + } + + public sealed class TheNoLogoProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.NoLogo); + } + } + + public sealed class TheContinuousIntegrationBuildProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.ContinuousIntegrationBuild); + } + } + + public sealed class TheVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.Version); + } + } + + public sealed class TheVersionPrefixProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.VersionPrefix); + } + } + + public sealed class TheVersionSuffixProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.VersionSuffix); + } + } + + public sealed class TheFileVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.FileVersion); + } + } + + public sealed class TheAssemblyVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.AssemblyVersion); + } + } + + public sealed class ThePackageVersionProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.PackageVersion); + } + } + + public sealed class ThePackageReleaseNotesProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.PackageReleaseNotes); + } + } + + public sealed class TheLoggersProperty { [Fact] public void Should_Be_Empty_By_Default() @@ -109,8 +266,86 @@ public void Should_Be_Empty_By_Default() var settings = new MSBuildSettings(); // Then - Assert.Equal(0, settings.MaxCpuCount); + Assert.Empty(settings.Loggers); + } + } + + public sealed class TheFileLoggersProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Empty(settings.FileLoggers); + } + } + + public sealed class TheWarningsAsErrorCodesProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Empty(settings.WarningsAsErrorCodes); + } + } + + public sealed class TheWarningsAsMessageCodesProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Empty(settings.WarningsAsMessageCodes); + } + } + + public sealed class TheRestoreProperty + { + [Fact] + public void Should_Be_False_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.False(settings.Restore); + } + } + + public sealed class TheConsoleLoggerParametersProperty + { + [Fact] + public void Should_Be_Empty_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Empty(settings.ConsoleLoggerParameters); + } + } + + public sealed class TheRestoreLockedModeProperty + { + [Fact] + public void Should_Be_Null_By_Default() + { + // Given + var settings = new MSBuildSettings(); + + // Then + Assert.Null(settings.RestoreLockedMode); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildVerbosityExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildVerbosityExtensionsTests.cs new file mode 100644 index 0000000000..381855a2d9 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/MSBuild/MSBuildVerbosityExtensionsTests.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Cake.Common.Tools.MSBuild; +using Cake.Core; +using Cake.Core.Diagnostics; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.MSBuild +{ + public sealed class MSBuildVerbosityExtensionsTests + { + public sealed class GetMSBuildVerbosityMethod + { + [Theory] + [InlineData("quiet", Verbosity.Quiet)] + [InlineData("minimal", Verbosity.Minimal)] + [InlineData("normal", Verbosity.Normal)] + [InlineData("detailed", Verbosity.Verbose)] + [InlineData("diagnostic", Verbosity.Diagnostic)] + public void Should_Convert_Valid_String_To_Verbosity_Enum(string verbosityName, Verbosity expectedVerbosity) + { + // Given + + // When + var actualVerbosity = MSBuildVerbosityExtensions.GetMSBuildVerbosity(verbosityName); + + // Then + Assert.Equal(expectedVerbosity, actualVerbosity); + } + + [Theory] + [InlineData("QUIET", Verbosity.Quiet)] + [InlineData("MINIMAL", Verbosity.Minimal)] + [InlineData("NORMAL", Verbosity.Normal)] + [InlineData("DETAILED", Verbosity.Verbose)] + [InlineData("DIAGNOSTIC", Verbosity.Diagnostic)] + public void Should_Convert_Valid_UpperCase_String_To_Verbosity_Enum(string verbosityName, Verbosity expectedVerbosity) + { + // Given + + // When + var actualVerbosity = MSBuildVerbosityExtensions.GetMSBuildVerbosity(verbosityName); + + // Then + Assert.Equal(expectedVerbosity, actualVerbosity); + } + + [Fact] + public void Should_Throws_Exception_When_Parameter_Is_Null() + { + // Given + + // When + var actualException = Assert.Throws(() => MSBuildVerbosityExtensions.GetMSBuildVerbosity(null)); + + // Then + Assert.Equal("Encountered unknown MSBuild build log verbosity ''. Valid values are 'quiet', 'minimal', 'normal', 'detailed' and 'diagnostic'.", actualException.Message); + } + + [Theory] + [InlineData("", "Encountered unknown MSBuild build log verbosity ''. Valid values are 'quiet', 'minimal', 'normal', 'detailed' and 'diagnostic'.")] + [InlineData("invalid", "Encountered unknown MSBuild build log verbosity 'invalid'. Valid values are 'quiet', 'minimal', 'normal', 'detailed' and 'diagnostic'.")] + public void Should_Throws_Exception_When_Parameter_Has_Invalid_Value(string invalidVerbosityValue, string expectedMessage) + { + // Given + + // When + var actualException = Assert.Throws(() => MSBuildVerbosityExtensions.GetMSBuildVerbosity(invalidVerbosityValue)); + + // Then + Assert.Equal(expectedMessage, actualException.Message); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSTest/MSTestRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSTest/MSTestRunnerTests.cs index d08be50018..d7e7d783d5 100644 --- a/src/Cake.Common.Tests/Unit/Tools/MSTest/MSTestRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/MSTest/MSTestRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Core; using Cake.Core.IO; @@ -23,7 +24,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -52,7 +53,7 @@ public void Should_Throw_If_Tool_Path_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("MSTest: Could not locate executable.", result.Message); + Assert.Equal("MSTest: Could not locate executable.", result?.Message); } [Theory] @@ -93,6 +94,12 @@ public void Should_Use_MSTest_From_Tool_Path_If_Provided_On_Windows(string toolP [InlineData("/ProgramFilesX86/Microsoft Visual Studio 12.0/Common7/IDE/mstest.exe")] [InlineData("/ProgramFilesX86/Microsoft Visual Studio 11.0/Common7/IDE/mstest.exe")] [InlineData("/ProgramFilesX86/Microsoft Visual Studio 10.0/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Enterprise/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Professional/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Community/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Professional/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Community/Common7/IDE/mstest.exe")] public void Should_Use_Available_Tool_Path(string existingToolPath) { // Given @@ -107,6 +114,24 @@ public void Should_Use_Available_Tool_Path(string existingToolPath) Assert.Equal(existingToolPath, result.Path.FullPath); } + [Theory] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/mstest.exe", "/ProgramFilesX86/Microsoft Visual Studio/2017/Enterprise/Common7/IDE/mstest.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Professional/Common7/IDE/mstest.exe", "/ProgramFilesX86/Microsoft Visual Studio/2017/Enterprise/Common7/IDE/mstest.exe")] + public void Should_Use_Highest_Available_Version_Tool_Path(string prioritizedPath, string lesserPath) + { + // Given + var fixture = new MSTestRunnerFixture(); + fixture.GivenDefaultToolDoNotExist(); + fixture.FileSystem.CreateFile(lesserPath); + fixture.FileSystem.CreateFile(prioritizedPath); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(prioritizedPath, result.Path.FullPath); + } + [Fact] public void Should_Set_Working_Directory() { @@ -132,7 +157,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("MSTest: Process was not started.", result.Message); + Assert.Equal("MSTest: Process was not started.", result?.Message); } [Fact] @@ -147,7 +172,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("MSTest: Process returned an error (exit code 1).", result.Message); + Assert.Equal("MSTest: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -180,25 +205,41 @@ public void Should_Use_Isolation_If_Disabled_In_Settings() [Fact] public void Should_Use_TestCategoryFilter_If_Provided() { - //Given + // Given var fixture = new MSTestRunnerFixture(); fixture.Settings.Category = "!Network"; - //When + // When var result = fixture.Run(); + // Then Assert.Equal("\"/testcontainer:/Working/Test1.dll\" /category:\"!Network\" /noisolation", result.Args); } + [Fact] + public void Should_Use_TestResultsFile_If_Provided() + { + // Given + var fixture = new MSTestRunnerFixture(); + fixture.Settings.ResultsFile = @"c:\temp\myresults.trx"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/testcontainer:/Working/Test1.dll\" /resultsfile:\"c:\\temp\\myresults.trx\" /noisolation", result.Args); + } + [Fact] public void Should_Not_Use_TestCategoryFilter_If_Not_Provided() { - //Given + // Given var fixture = new MSTestRunnerFixture(); - //When + // When var result = fixture.Run(); + // Then Assert.Equal("\"/testcontainer:/Working/Test1.dll\" /noisolation", result.Args); } @@ -250,5 +291,57 @@ public void Should_Use_Available_Environment_Tool_Path(string existingToolPath, // Then Assert.Equal(existingToolPath, result.Path.FullPath); } + + [Theory] + [InlineData(SpecialPath.ProgramFilesX86, "2017", "Enterprise")] + [InlineData(SpecialPath.ProgramFilesX86, "2017", "Professional")] + [InlineData(SpecialPath.ProgramFilesX86, "2017", "Community")] + [InlineData(SpecialPath.ProgramFilesX86, "2019", "Enterprise")] + [InlineData(SpecialPath.ProgramFilesX86, "2019", "Professional")] + [InlineData(SpecialPath.ProgramFilesX86, "2019", "Community")] + [InlineData(SpecialPath.ProgramFiles, "2022", "Enterprise")] + [InlineData(SpecialPath.ProgramFiles, "2022", "Professional")] + [InlineData(SpecialPath.ProgramFiles, "2022", "Community")] + public void Should_Use_Tool_Path_For_YearAndEdition_Versions(SpecialPath programFiles, string year, string edition) + { + // Given + var fixture = new MSTestRunnerFixture(); + fixture.GivenDefaultToolDoNotExist(); + var toolPath = fixture.Environment.GetSpecialPath(programFiles) + .CombineWithFilePath($"Microsoft Visual Studio/{year}/{edition}/Common7/IDE/mstest.exe"); + fixture.FileSystem.CreateFile(toolPath); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(toolPath, result.Path.FullPath); + } + + [Theory] + [InlineData("2022", true)] + [InlineData("2022", false)] + public void Should_Use_Tool_Path_For_Preview_Version_Only_If_PreviewIsSet(string year, bool allowPreview) + { + // Given + var fixture = new MSTestRunnerFixture(); + var previewToolPath = fixture.Environment.GetSpecialPath(SpecialPath.ProgramFiles) + .CombineWithFilePath($"Microsoft Visual Studio/{year}/Preview/Common7/IDE/mstest.exe"); + fixture.FileSystem.CreateFile(previewToolPath); + fixture.Settings.AllowPreviewVersion = allowPreview; + + // When + var result = fixture.Run(); + + // Then + if (allowPreview) + { + Assert.Equal(previewToolPath, result.Path.FullPath); + } + else + { + Assert.NotEqual(previewToolPath, result.Path.FullPath); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/MSpec/MSpecRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/MSpec/MSpecRunnerTests.cs new file mode 100644 index 0000000000..f302e6116b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/MSpec/MSpecRunnerTests.cs @@ -0,0 +1,404 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.MSpec +{ + public sealed class MSpecRunnerTests + { + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Assembly_Path_Is_Null() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "assemblyPaths"); + } + + [Fact] + public void Should_Throw_If_MSpec_Runner_Was_Not_Found() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("MSpec: Could not locate executable.", result?.Message); + } + + [Theory] + [InlineData("/bin/MSpec/mspec.exe", "/bin/MSpec/mspec.exe")] + [InlineData("./tools/MSpec/mspec.exe", "/Working/tools/MSpec/mspec.exe")] + public void Should_Use_MSpec_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/MSpec/mspec.exe", "C:/MSpec/mspec.exe")] + public void Should_Use_MSpec_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_MSpec_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new MSpecRunnerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/mspec-clr4.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Use_Provided_Assembly_Path_In_Process_Arguments() + { + // Given + var fixture = new MSpecRunnerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Use_Provided_Assembly_Paths_In_Process_Arguments() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies.Clear(); + fixture.Assemblies.Add(new FilePath("./Test1.dll")); + fixture.Assemblies.Add(new FilePath("./Test2.dll")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); + } + + [Fact] + public void Should_Set_Working_Directory() + { + // Given + var fixture = new MSpecRunnerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("MSpec: Process was not started.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("MSpec: Process returned an error (exit code 1).", result?.Message); + } + + [Fact] + public void Should_Throw_If_HtmlReport_Is_Set_But_OutputDirectory_Is_Null() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.HtmlReport = true; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot generate HTML report when no output directory has been set.", result?.Message); + } + + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Single_Assembly() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--html \"/Output/Test1.dll.html\" \"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + fixture.Settings.ReportName = "MSpecUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--html \"/Output/MSpecUnitReport.html\" \"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Multiple_Assemblies() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies.Clear(); + fixture.Assemblies.AddRange(new FilePath[] { "./Test1.dll", "./Test2.dll" }); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--html \"/Output/TestResults.html\" " + + "\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies.Clear(); + fixture.Assemblies.AddRange(new FilePath[] { "./Test1.dll", "./Test2.dll" }); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + fixture.Settings.ReportName = "MSpecUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--html \"/Output/MSpecUnitReport.html\" " + + "\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); + } + + [Fact] + public void Should_Throw_If_XmlReport_Is_Set_But_OutputDirectory_Is_Null() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.XmlReport = true; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot generate XML report when no output directory has been set.", result?.Message); + } + + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Single_Assembly() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--xml \"/Output/Test1.dll.xml\" \"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + fixture.Settings.ReportName = "MSpecUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--xml \"/Output/MSpecUnitReport.xml\" \"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Multiple_Assemblies() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies.Clear(); + fixture.Assemblies.AddRange(new FilePath[] { "./Test1.dll", "./Test2.dll" }); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--xml \"/Output/TestResults.xml\" " + + "\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); + } + + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Assemblies.Clear(); + fixture.Assemblies.AddRange(new FilePath[] { "./Test1.dll", "./Test2.dll" }); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + fixture.Settings.ReportName = "MSpecUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--xml \"/Output/MSpecUnitReport.xml\" " + + "\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); + } + + [Fact] + public void Should_Set_Commandline_Switches() + { + // Given + var fixture = new MSpecRunnerFixture(); + fixture.Settings.Filters = "Filters.txt"; + fixture.Settings.Include = "foo,bar,foo_bar"; + fixture.Settings.Exclude = "foo,bar,foo_bar"; + fixture.Settings.TimeInfo = true; + fixture.Settings.Silent = true; + fixture.Settings.Progress = true; + fixture.Settings.NoColor = true; + fixture.Settings.Wait = true; + fixture.Settings.TeamCity = true; + fixture.Settings.NoTeamCity = true; + fixture.Settings.AppVeyor = true; + fixture.Settings.NoAppVeyor = true; + fixture.Settings.HtmlReport = true; + fixture.Settings.XmlReport = true; + fixture.Settings.ReportName = "UnitTests"; + fixture.Settings.OutputDirectory = "Reports"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-f \"/Working/Filters.txt\" " + + "-i \"foo,bar,foo_bar\" " + + "-x \"foo,bar,foo_bar\" " + + "-t " + + "-s " + + "-p " + + "-c " + + "-w " + + "--teamcity " + + "--no-teamcity-autodetect " + + "--appveyor " + + "--no-appveyor-autodetect " + + "--html \"/Working/Reports/UnitTests.html\" " + + "--xml \"/Working/Reports/UnitTests.xml\" " + + "\"/Working/Test1.dll\"", result.Args); + } + + [Fact] + public void Should_Throw_If_Mspec_X86_Flag_Is_Not_Set_To_True() + { + // Given + var fixture = new MSpecRunnerFixture("mspec-x86-clr4.exe"); + fixture.Settings.UseX86 = false; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("MSpec: Could not locate executable.", result?.Message); + } + + [Fact] + public void Should_Find_MSpec_X86_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new MSpecRunnerFixture("mspec-x86-clr4.exe"); + fixture.Settings.UseX86 = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/mspec-x86-clr4.exe", result.Path.FullPath); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NSIS/MakeNSISRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NSIS/MakeNSISRunnerTests.cs index 596d606a04..ccd9a346f2 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NSIS/MakeNSISRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NSIS/MakeNSISRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Core; @@ -26,7 +27,7 @@ public void Should_Throw_If_Script_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "scriptFile"); + AssertEx.IsArgumentNullException(result, "scriptFile"); } [Fact] @@ -40,7 +41,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -55,7 +56,7 @@ public void Should_Throw_If_MakeNSIS_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("MakeNSIS: Could not locate executable.", result.Message); + Assert.Equal("MakeNSIS: Could not locate executable.", result?.Message); } [Theory] @@ -129,7 +130,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("MakeNSIS: Process was not started.", result.Message); + Assert.Equal("MakeNSIS: Process was not started.", result?.Message); } [Fact] @@ -144,7 +145,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("MakeNSIS: Process returned an error (exit code 1).", result.Message); + Assert.Equal("MakeNSIS: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -193,4 +194,4 @@ public void Should_Add_NoConfig_To_Arguments_If_Provided() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NSIS/NSISAliasesTests.cs b/src/Cake.Common.Tests/Unit/Tools/NSIS/NSISAliasesTests.cs index f06474c8e6..434540b248 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NSIS/NSISAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NSIS/NSISAliasesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NSIS; using Cake.Core; using NSubstitute; @@ -21,7 +22,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => NSISAliases.MakeNSIS(null, "some file.nsi")); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -34,8 +35,8 @@ public void Should_Throw_If_Script_Path_Is_Null() var result = Record.Exception(() => NSISAliases.MakeNSIS(context, null)); // Then - Assert.IsArgumentNullException(result, "scriptFile"); + AssertEx.IsArgumentNullException(result, "scriptFile"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3RunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3RunnerTests.cs index 25f08ac2a8..3b8a89f598 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3RunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3RunnerTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.NUnit; using Cake.Core; @@ -27,7 +29,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -42,7 +44,7 @@ public void Should_Throw_If_NUnit3_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("NUnit3: Could not locate executable.", result.Message); + Assert.Equal("NUnit3: Could not locate executable.", result?.Message); } [Theory] @@ -145,22 +147,29 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("NUnit3: Process was not started.", result.Message); + Assert.Equal("NUnit3: Process was not started.", result?.Message); } - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + [Theory] + [InlineData(10, "NUnit3: 10 test(s) failed (exit code 10).")] + [InlineData(-1, "NUnit3: Invalid argument (exit code -1).")] + [InlineData(-2, "NUnit3: Invalid assembly (exit code -2).")] + [InlineData(-4, "NUnit3: Invalid test fixture (exit code -4).")] + [InlineData(-5, "NUnit3: Unload error (exit code -5).")] + [InlineData(-100, "NUnit3: Unexpected error (exit code -100).")] + [InlineData(-10, "NUnit3: Unrecognised error (exit code -10).")] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code(int exitCode, string expectedMessage) { // Given var fixture = new NUnit3RunnerFixture(); - fixture.GivenProcessExitsWithCode(1); + fixture.GivenProcessExitsWithCode(exitCode); // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("NUnit3: Process returned an error (exit code 1).", result.Message); + Assert.Equal(expectedMessage, result?.Message); } [Fact] @@ -168,7 +177,7 @@ public void Should_Not_Allow_NoResults_And_ResultsFile() { // Given var fixture = new NUnit3RunnerFixture(); - fixture.Settings.Results = "NewResults.xml"; + fixture.Settings.Results = new[] { new NUnit3Result { FileName = "NewResults.xml" } }; fixture.Settings.NoResults = true; // When @@ -176,7 +185,7 @@ public void Should_Not_Allow_NoResults_And_ResultsFile() // Then Assert.IsType(result); - Assert.Equal("NUnit3: You can't specify both a results file and set NoResults to true.", result.Message); + Assert.Equal("NUnit3: You can't specify both a results file and set NoResults to true.", result?.Message); } [Fact] @@ -184,7 +193,7 @@ public void Should_Set_Result_Switch() { // Given var fixture = new NUnit3RunnerFixture(); - fixture.Settings.Results = "NewTestResult.xml"; + fixture.Settings.Results = new[] { new NUnit3Result { FileName = "NewTestResult.xml" } }; // When var result = fixture.Run(); @@ -207,18 +216,18 @@ public void Should_Set_Commandline_Switches() fixture.Settings.Seed = 6; fixture.Settings.Workers = 7; fixture.Settings.StopOnError = true; + fixture.Settings.SkipNonTestAssemblies = true; fixture.Settings.Work = "out"; fixture.Settings.OutputFile = "stdout.txt"; - fixture.Settings.ErrorOutputFile = "stderr.txt"; - fixture.Settings.Full = true; - fixture.Settings.Results = "NewTestResult.xml"; - fixture.Settings.ResultFormat = "nunit2"; - fixture.Settings.ResultTransform = "nunit2.xslt"; + fixture.Settings.Results = new[] + { + new NUnit3Result { FileName = "NewTestResult.xml", Format = "nunit2", Transform = "nunit2.xslt" }, + new NUnit3Result { FileName = "NewTestResult2.xml", Format = "nunit3" } + }; fixture.Settings.Labels = NUnit3Labels.All; fixture.Settings.TeamCity = true; fixture.Settings.NoHeader = true; fixture.Settings.NoColor = true; - fixture.Settings.Verbose = true; fixture.Settings.Configuration = "Debug"; fixture.Settings.Process = NUnit3ProcessOption.InProcess; fixture.Settings.AppDomainUsage = NUnit3AppDomainUsage.Single; @@ -227,6 +236,20 @@ public void Should_Set_Commandline_Switches() fixture.Settings.DisposeRunners = true; fixture.Settings.ShadowCopy = true; fixture.Settings.Agents = 3; + fixture.Settings.TraceLevel = NUnitInternalTraceLevel.Debug; + fixture.Settings.ConfigFile = "app.config"; + fixture.Settings.Params = new Dictionary + { + ["one"] = "1", + ["two"] = "2", + ["three"] = "3" + }; + fixture.Settings.TestParams = new Dictionary + { + ["uno"] = "1", + ["dos"] = "2", + ["tres"] = "3" + }; // When var result = fixture.Run(); @@ -234,13 +257,21 @@ public void Should_Set_Commandline_Switches() // Then Assert.Equal("\"/Working/Test1.dll\" --test=Test1,Test2 \"--testlist=/Working/Testlist.txt\" " + "--where \"cat==Data\" --timeout=5 --seed=6 --workers=7 " + - "--stoponerror \"--work=/Working/out\" \"--out=/Working/stdout.txt\" " + - "\"--err=/Working/stderr.txt\" --full " + + "--stoponerror --skipnontestassemblies \"--work=/Working/out\" \"--out=/Working/stdout.txt\" " + "\"--result=/Working/NewTestResult.xml;format=nunit2;transform=/Working/nunit2.xslt\" " + - "--labels=All --teamcity --noheader --nocolor --verbose " + + "\"--result=/Working/NewTestResult2.xml;format=nunit3\" " + + "--labels=All --teamcity --noheader --nocolor " + "\"--config=Debug\" \"--framework=net3.5\" --x86 " + "--dispose-runners --shadowcopy --agents=3 " + - "--process=InProcess --domain=Single", result.Args); + "--process=InProcess --domain=Single " + + "--trace=verbose " + + "\"--configfile=/Working/app.config\" " + + "\"--params=one=1\" " + + "\"--params=two=2\" " + + "\"--params=three=3\" " + + "\"--testparam:uno=1\" " + + "\"--testparam:dos=2\" " + + "\"--testparam:tres=3\"", result.Args); } [Fact] @@ -257,6 +288,34 @@ public void Should_Not_Set_Switch_For_Default_Labels() Assert.Equal("\"/Working/Test1.dll\"", result.Args); } + [Fact] + public void Should_Set_Switch_For_After_Labels() + { + // Given + var fixture = new NUnit3RunnerFixture(); + fixture.Settings.Labels = NUnit3Labels.After; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" --labels=After", result.Args); + } + + [Fact] + public void Should_Set_Switch_For_Before_Labels() + { + // Given + var fixture = new NUnit3RunnerFixture(); + fixture.Settings.Labels = NUnit3Labels.Before; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" --labels=Before", result.Args); + } + [Fact] public void Should_Not_Set_Process_Switch_For_DefaultMultipleValue() { @@ -284,6 +343,20 @@ public void Should_Not_Set_Switch_For_Default_AppDomainUsage() // Then Assert.Equal("\"/Working/Test1.dll\"", result.Args); } + + [Fact] + public void Should_Not_Set_Switch_For_Default_TraceLevel() + { + // Given + var fixture = new NUnit3RunnerFixture(); + fixture.Settings.TraceLevel = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3SettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3SettingsTests.cs index 4fbcdd5a65..c2dccaeed2 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3SettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnit3SettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NUnit; using Xunit; @@ -49,6 +50,16 @@ public void Should_Use_Default_AppDomainUsage_By_Default() // Then Assert.Equal(settings.AppDomainUsage, NUnit3AppDomainUsage.Default); } + + [Fact] + public void Should_Use_No_Internal_Trace_By_Default() + { + // Given, When + var settings = new NUnit3Settings(); + + // Then + Assert.Equal(settings.TraceLevel, null); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitInternalTraceLevelExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitInternalTraceLevelExtensionsTests.cs new file mode 100644 index 0000000000..02a12ff823 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitInternalTraceLevelExtensionsTests.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.NUnit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NUnit +{ + public sealed class NUnitInternalTraceLevelExtensionsTests + { + public sealed class TheGetArgumentValueMethod + { + [Theory] + [InlineData(NUnitInternalTraceLevel.Off, "off")] + [InlineData(NUnitInternalTraceLevel.Error, "error")] + [InlineData(NUnitInternalTraceLevel.Warning, "warning")] + [InlineData(NUnitInternalTraceLevel.Info, "info")] + [InlineData(NUnitInternalTraceLevel.Debug, "verbose")] +#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to + [InlineData(NUnitInternalTraceLevel.Verbose, "verbose")] +#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to + public void Should_Return_Correct_Value(NUnitInternalTraceLevel level, string expected) + { + // Given, When + var result = level.GetArgumentValue(); + + // Then + Assert.Equal(expected, result); + } + + [Fact] + public void Should_Throw_On_Unexpected_Value() + { + // Given + var level = (NUnitInternalTraceLevel)128; + + // When + var ex = Record.Exception(() => level.GetArgumentValue()); + + // Then + Assert.IsType(ex); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitRunnerTests.cs index e974df9b01..9b7195c172 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.NUnit; @@ -27,7 +28,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -42,7 +43,7 @@ public void Should_Throw_If_NUnit_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("NUnit: Could not locate executable.", result.Message); + Assert.Equal("NUnit: Could not locate executable.", result?.Message); } [Theory] @@ -91,6 +92,20 @@ public void Should_Find_NUnit_Runner_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/nunit-console.exe", result.Path.FullPath); } + [Fact] + public void Should_Find_X86NUnit_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new NUnitRunnerFixture(@"nunit-console-x86.exe"); + fixture.Settings.X86 = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/nunit-console-x86.exe", result.Path.FullPath); + } + [Fact] public void Should_Use_Provided_Assembly_Path_In_Process_Arguments() { @@ -145,22 +160,28 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("NUnit: Process was not started.", result.Message); + Assert.Equal("NUnit: Process was not started.", result?.Message); } - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + [Theory] + [InlineData(10, "NUnit: 10 test(s) failed (exit code 10).")] + [InlineData(-1, "NUnit: Invalid argument (exit code -1).")] + [InlineData(-2, "NUnit: File not found (exit code -2).")] + [InlineData(-3, "NUnit: Test fixture not found (exit code -3).")] + [InlineData(-100, "NUnit: Unexpected error (exit code -100).")] + [InlineData(-10, "NUnit: Unrecognised error (exit code -10).")] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code(int exitCode, string expectedMessage) { // Given var fixture = new NUnitRunnerFixture(); - fixture.GivenProcessExitsWithCode(1); + fixture.GivenProcessExitsWithCode(exitCode); // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("NUnit: Process returned an error (exit code 1).", result.Message); + Assert.Equal(expectedMessage, result?.Message); } [Fact] @@ -190,7 +211,7 @@ public void Should_Not_Allow_NoResults_And_ResultsFile() // Then Assert.IsType(result); - Assert.Equal("NUnit: You can't specify both a results file and set NoResults to true.", result.Message); + Assert.Equal("NUnit: You can't specify both a results file and set NoResults to true.", result?.Message); } [Fact] @@ -223,6 +244,7 @@ public void Should_Set_Commandline_Switches() fixture.Settings.Include = "Database"; fixture.Settings.Exclude = "Database_Users"; fixture.Settings.Framework = "net1_1"; + fixture.Settings.Labels = true; fixture.Settings.OutputFile = "stdout.txt"; fixture.Settings.ErrorOutputFile = "stderr.txt"; fixture.Settings.Process = NUnitProcessOption.Multiple; @@ -236,7 +258,7 @@ public void Should_Set_Commandline_Switches() Assert.Equal("\"/Working/Test1.dll\" \"-framework:net1_1\" " + "\"-include:Database\" \"-exclude:Database_Users\" " + "-timeout:5 -nologo -nothread -stoponerror " + - "-trace:Debug \"-output:/Working/stdout.txt\" " + + "-trace:Debug -labels \"-output:/Working/stdout.txt\" " + "\"-err:/Working/stderr.txt\" " + "\"-result:/Working/NewTestResult.xml\" " + "\"-process:Multiple\" \"-apartment:STA\" " + @@ -272,4 +294,4 @@ public void Should_Not_Set_Switch_For_Default_AppDomainUsage() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitSettingsTests.cs index f5003fcbc8..595036de63 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NUnit/NUnitSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NUnit; using Xunit; @@ -51,4 +52,4 @@ public void Should_Use_Default_AppDomainUsage_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAddSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAddSettingsTests.cs new file mode 100644 index 0000000000..c2b412fa59 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAddSettingsTests.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.NuGet.Add; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.Add +{ + public sealed class NuGetAddSettingsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Set_Expand_To_False_By_Default() + { + // Given, When + var settings = new NuGetAddSettings(); + + // Then + Assert.False(settings.Expand); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAdderTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAdderTests.cs new file mode 100644 index 0000000000..959c1ff45c --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Add/NuGetAdderTests.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.NuGet.Add; +using Cake.Common.Tools.NuGet; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.Add +{ + public sealed class NuGetAdderTests + { + public sealed class TheAddMethod + { + [Fact] + public void Should_Throw_If_Target_Package_Id_Is_Null() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.PackageId = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageId"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_NuGet_Executable_Was_Not_Found() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] + [InlineData("./tools/nuget/nuget.exe", "/Working/tools/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/nuget/nuget.exe", "C:/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_NuGet_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new NuGetAdderFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/NuGet.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new NuGetAdderFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"Cake\" -Source \"/Working/NuGet/localfeed\" -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Expand_To_Arguments_If_True() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings.Expand = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"Cake\" -Source \"/Working/NuGet/localfeed\" -Expand -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("add \"Cake\" -Source \"/Working/NuGet/localfeed\" -ConfigFile \"/Working/nuget.config\" " + + "-NonInteractive", result.Args); + } + + [Theory] + [InlineData(NuGetVerbosity.Detailed, "add \"Cake\" -Source \"/Working/NuGet/localfeed\" -Verbosity detailed -NonInteractive")] + [InlineData(NuGetVerbosity.Normal, "add \"Cake\" -Source \"/Working/NuGet/localfeed\" -Verbosity normal -NonInteractive")] + [InlineData(NuGetVerbosity.Quiet, "add \"Cake\" -Source \"/Working/NuGet/localfeed\" -Verbosity quiet -NonInteractive")] + public void Should_Add_Verbosity_To_Arguments_If_Set(NuGetVerbosity verbosity, string expected) + { + // Given + var fixture = new NuGetAdderFixture(); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Delete/NuGetDeleterTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Delete/NuGetDeleterTests.cs new file mode 100644 index 0000000000..14ceb22089 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Delete/NuGetDeleterTests.cs @@ -0,0 +1,279 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools.NuGet.Deleter; +using Cake.Common.Tools.NuGet; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.Delete +{ + public sealed class NuGetDeleterTests + { + public sealed class TheDeleteMethod + { + [Fact] + public void Should_Throw_If_PackageID_Is_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageID = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageID"); + } + + [Fact] + public void Should_Throw_If_PackageID_Is_Empty() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageID = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageID"); + } + + [Fact] + public void Should_Throw_If_PackageID_Is_WhiteSpace() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageID = " "; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageID"); + } + + [Fact] + public void Should_Throw_If_PackageVersion_Is_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageVersion = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageVersion"); + } + + [Fact] + public void Should_Throw_If_PackageVersion_Is_Empty() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageVersion = string.Empty; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageVersion"); + } + + [Fact] + public void Should_Throw_If_PackageVersion_Is_WhiteSpace() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.PackageVersion = " "; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageVersion"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_NuGet_Executable_Was_Not_Found() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] + [InlineData("./tools/nuget/nuget.exe", "/Working/tools/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/nuget/nuget.exe", "C:/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_NuGet_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new NuGetDeleterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/NuGet.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Add_NuGet_Package_To_Arguments() + { + // Given + var fixture = new NuGetDeleterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("delete existing 0.1.0 -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Api_Key_To_Arguments_If_Not_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.ApiKey = "1234"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("delete existing 0.1.0 1234 -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Configuration_File_To_Arguments_If_Not_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.ConfigFile = "./NuGet.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("delete existing 0.1.0 -NonInteractive " + + "-ConfigFile \"/Working/NuGet.config\"", result.Args); + } + + [Fact] + public void Should_Add_Source_To_Arguments_If_Not_Null() + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.Source = "http://customsource/"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("delete existing 0.1.0 -NonInteractive " + + "-Source \"http://customsource/\"", result.Args); + } + + [Theory] + [InlineData(NuGetVerbosity.Detailed, "delete existing 0.1.0 -NonInteractive -Verbosity detailed")] + [InlineData(NuGetVerbosity.Normal, "delete existing 0.1.0 -NonInteractive -Verbosity normal")] + [InlineData(NuGetVerbosity.Quiet, "delete existing 0.1.0 -NonInteractive -Verbosity quiet")] + public void Should_Add_Verbosity_To_Arguments_If_Not_Null(NuGetVerbosity verbosity, string expected) + { + // Given + var fixture = new NuGetDeleterFixture(); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetInitSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetInitSettingsTests.cs new file mode 100644 index 0000000000..b6a0360f90 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetInitSettingsTests.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.NuGet.Init; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.Init +{ + public sealed class NuGetInitSettingsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Set_Expand_To_False_By_Default() + { + // Given, When + var settings = new NuGetInitSettings(); + + // Then + Assert.False(settings.Expand); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetIniterTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetIniterTests.cs new file mode 100644 index 0000000000..a298046882 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Init/NuGetIniterTests.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.NuGet.Init; +using Cake.Common.Tools.NuGet; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.Init +{ + public sealed class NuGetIniterTests + { + public sealed class TheInitMethod + { + [Fact] + public void Should_Throw_If_Source_Is_Null() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Source = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "sourcePackageSourcePath"); + } + + [Fact] + public void Should_Throw_If_Destination_Is_Null() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Destination = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "destinationPackageSourcePath"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_NuGet_Executable_Was_Not_Found() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] + [InlineData("./tools/nuget/nuget.exe", "/Working/tools/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/nuget/nuget.exe", "C:/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_NuGet_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new NuGetIniterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/NuGet.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new NuGetIniterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Expand_To_Arguments_If_True() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings.Expand = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -Expand -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -ConfigFile \"/Working/nuget.config\" " + + "-NonInteractive", result.Args); + } + + [Theory] + [InlineData(NuGetVerbosity.Detailed, "init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -Verbosity detailed -NonInteractive")] + [InlineData(NuGetVerbosity.Normal, "init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -Verbosity normal -NonInteractive")] + [InlineData(NuGetVerbosity.Quiet, "init \"/Working/NuGet/localfeed\" \"/Working/NuGet/localfeed-destination\" -Verbosity quiet -NonInteractive")] + public void Should_Add_Verbosity_To_Arguments_If_Set(NuGetVerbosity verbosity, string expected) + { + // Given + var fixture = new NuGetIniterFixture(); + fixture.Settings.Verbosity = verbosity; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerSettingsTests.cs index 2d32f928f1..c9df1fffc3 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Install; using Xunit; @@ -17,7 +18,19 @@ public void Should_Set_NoCache_To_False_By_Default() var settings = new NuGetInstallSettings(); // Then + #pragma warning disable CS0618 Assert.False(settings.NoCache); + #pragma warning restore CS0618 + } + + [Fact] + public void Should_Set_NoHttpCache_To_False_By_Default() + { + // Given, When + var settings = new NuGetInstallSettings(); + + // Then + Assert.False(settings.NoHttpCache); } [Fact] @@ -51,4 +64,4 @@ public void Should_Set_ExcludeVersion_To_False_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerTests.cs index c5ac0f93a0..6d4331c83e 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Install/NuGetInstallerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.NuGet.Installer; using Cake.Common.Tools.NuGet; using Cake.Testing; @@ -24,7 +25,7 @@ public void Should_Throw_If_Target_Package_Id_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packageId"); + AssertEx.IsArgumentNullException(result, "packageId"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -52,7 +53,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -99,7 +100,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -113,7 +114,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -190,7 +191,9 @@ public void Should_Add_NoCache_To_Arguments_If_True() { // Given var fixture = new NuGetInstallerFixture(); + #pragma warning disable CS0618 fixture.Settings.NoCache = true; + #pragma warning restore CS0618 // When var result = fixture.Run(); @@ -199,6 +202,20 @@ public void Should_Add_NoCache_To_Arguments_If_True() Assert.Equal("install \"Cake\" -NoCache -NonInteractive", result.Args); } + [Fact] + public void Should_Add_NoHttpCache_To_Arguments_If_True() + { + // Given + var fixture = new NuGetInstallerFixture(); + fixture.Settings.NoHttpCache = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"Cake\" -NoHttpCache -NonInteractive", result.Args); + } + [Fact] public void Should_Add_DisableParallelProcessing_To_Arguments_If_Set() { @@ -214,6 +231,20 @@ public void Should_Add_DisableParallelProcessing_To_Arguments_If_Set() "-NonInteractive", result.Args); } + [Fact] + public void Should_Remove_NonInteractive_From_Arguments_If_False() + { + // Given + var fixture = new NuGetInstallerFixture(); + fixture.Settings.NonInteractive = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"Cake\"", result.Args); + } + [Theory] [InlineData(NuGetVerbosity.Detailed, "install \"Cake\" -Verbosity detailed -NonInteractive")] [InlineData(NuGetVerbosity.Normal, "install \"Cake\" -Verbosity normal -NonInteractive")] @@ -275,7 +306,7 @@ public void Should_Throw_If_Target_Package_Config_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packageConfigPath"); + AssertEx.IsArgumentNullException(result, "packageConfigPath"); } [Fact] @@ -289,7 +320,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -303,7 +334,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -350,7 +381,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -364,7 +395,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -445,7 +476,9 @@ public void Should_Add_NoCache_To_Arguments_If_True() { // Given var fixture = new NuGetInstallerFromConfigFixture(); + #pragma warning disable CS0618 fixture.Settings.NoCache = true; + #pragma warning restore CS0618 // When var result = fixture.Run(); @@ -455,6 +488,21 @@ public void Should_Add_NoCache_To_Arguments_If_True() "-NonInteractive", result.Args); } + [Fact] + public void Should_Add_NoHttpCache_To_Arguments_If_True() + { + // Given + var fixture = new NuGetInstallerFromConfigFixture(); + fixture.Settings.NoHttpCache = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("install \"/Working/packages.config\" -NoHttpCache " + + "-NonInteractive", result.Args); + } + [Fact] public void Should_Add_DisableParallelProcessing_To_Arguments_If_Set() { @@ -471,10 +519,10 @@ public void Should_Add_DisableParallelProcessing_To_Arguments_If_Set() } [Theory] - [InlineData(NuGetVerbosity.Detailed, "detailed", "install \"/Working/packages.config\" -Verbosity detailed -NonInteractive")] - [InlineData(NuGetVerbosity.Normal, "normal", "install \"/Working/packages.config\" -Verbosity normal -NonInteractive")] - [InlineData(NuGetVerbosity.Quiet, "quiet", "install \"/Working/packages.config\" -Verbosity quiet -NonInteractive")] - public void Should_Add_Verbosity_To_Arguments_If_Set(NuGetVerbosity verbosity, string name, string expected) + [InlineData(NuGetVerbosity.Detailed, "install \"/Working/packages.config\" -Verbosity detailed -NonInteractive")] + [InlineData(NuGetVerbosity.Normal, "install \"/Working/packages.config\" -Verbosity normal -NonInteractive")] + [InlineData(NuGetVerbosity.Quiet, "install \"/Working/packages.config\" -Verbosity quiet -NonInteractive")] + public void Should_Add_Verbosity_To_Arguments_If_Set(NuGetVerbosity verbosity, string expected) { // Given var fixture = new NuGetInstallerFromConfigFixture(); @@ -520,4 +568,4 @@ public void Should_Add_FallbackSources_To_Arguments_If_Set() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListSettingsTests.cs new file mode 100644 index 0000000000..ea67dd3b60 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListSettingsTests.cs @@ -0,0 +1,41 @@ +using Cake.Common.Tools.NuGet.List; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.List +{ + public sealed class NuGetListSettingsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Set_AllVersions_To_False_By_Default() + { + // Given, When + var settings = new NuGetListSettings(); + + // Then + Assert.False(settings.AllVersions); + } + + [Fact] + public void Should_Set_IncludeDelisted_To_False_By_Default() + { + // Given, When + var settings = new NuGetListSettings(); + + // Then + Assert.False(settings.IncludeDelisted); + } + + [Fact] + public void Should_Set_Prerelease_To_False_By_Default() + { + // Given, When + var settings = new NuGetListSettings(); + + // Then + Assert.False(settings.Prerelease); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListTests.cs new file mode 100644 index 0000000000..7fab5e8111 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/List/NuGetListTests.cs @@ -0,0 +1,250 @@ +using Cake.Common.Tests.Fixtures.Tools.NuGet.List; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.NuGet.List +{ + public sealed class NuGetListTests + { + public sealed class TheListMethod + { + [Fact] + public void Should_Throw_If_Target_Package_Id_Is_Null() + { + // Given + var fixture = new NuGetListFixture + { + PackageId = null + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "packageId"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_NuGet_Executable_Was_Not_Found() + { + // Given + var fixture = new NuGetListFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] + [InlineData("./tools/nuget/nuget.exe", "/Working/tools/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/nuget/nuget.exe", "C:/nuget/nuget.exe")] + public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new NuGetListFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new NuGetListFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_NuGet_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new NuGetListFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/NuGet.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new NuGetListFixture + { + PackageId = "Cake" + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_AllVersions_To_Arguments_If_True() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.AllVersions = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -AllVersions -Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_IncludeDelisted_To_Arguments_If_True() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.IncludeDelisted = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -IncludeDelisted -Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_True() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.Prerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -Prerelease -Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Sources_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.Source = new[] { "A;B;C" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -Source \"A;B;C\" -Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetListFixture(); + fixture.Settings.ConfigFile = "./nuget.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list \"Cake\" -ConfigFile \"/Working/nuget.config\" " + + "-Verbosity Normal -NonInteractive", result.Args); + } + + [Fact] + public void Should_Return_Correct_List_Of_NuGetListItems() + { + // Given + var fixture = new NuGetListFixture + { + PackageId = "Cake" + }; + fixture.GivenNormalPackageResult(); + + // When + var result = fixture.Run(); + + // Then + Assert.Collection(fixture.Result, + item => + { + Assert.Equal(item.Name, "Cake"); + Assert.Equal(item.Version, "0.22.2"); + }, + item => + { + Assert.Equal(item.Name, "Cake.Core"); + Assert.Equal(item.Version, "0.22.2"); + }, + item => + { + Assert.Equal(item.Name, "Cake.CoreCLR"); + Assert.Equal(item.Version, "0.22.2"); + }); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackSettingsTests.cs index 34db62af62..95d215f944 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Pack; using Xunit; @@ -39,6 +40,16 @@ public void Should_Set_Symbols_To_False_By_Default() // Then Assert.False(settings.Symbols); } + + [Fact] + public void Should_Set_OutputToToolFolder_To_False_By_Default() + { + // Given, When + var settings = new NuGetPackSettings(); + + // Then + Assert.False(settings.OutputToToolFolder); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackerTests.cs index dfa56c2833..7be064b374 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Pack/NuGetPackerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools.NuGet.Packer; @@ -32,7 +33,7 @@ public void Should_Throw_If_Nuspec_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -46,7 +47,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -60,7 +61,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -120,7 +121,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -134,7 +135,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -147,7 +148,7 @@ public void Should_Delete_Transformed_Nuspec() fixture.Run(); // Then - Assert.False(fixture.FileSystem.Exist((FilePath) "/Working/existing.temp.nuspec")); + Assert.False(fixture.FileSystem.Exist((FilePath)"/Working/existing.temp.nuspec")); } [Fact] @@ -161,7 +162,7 @@ public void Should_Throw_If_Nuspec_Do_Not_Exist() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Could not find nuspec file '/Working/nonexisting.nuspec'."); + AssertEx.IsCakeException(result, "Could not find nuspec file '/Working/nonexisting.nuspec'."); } [Fact] @@ -175,7 +176,7 @@ public void Should_Throw_If_Temporary_Nuspec_Already_Exist() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, + AssertEx.IsCakeException(result, "Could not create the nuspec file '/Working/existing.temp.nuspec' since it already exist."); } @@ -193,6 +194,20 @@ public void Should_Add_Version_To_Arguments_If_Not_Null() Assert.Equal("pack -Version \"1.0.0\" \"/Working/existing.temp.nuspec\"", result.Args); } + [Fact] + public void Should_Add_Suffix_To_Arguments_If_Not_Null() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.Settings.Suffix = "beta1"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack -Suffix \"beta1\" \"/Working/existing.temp.nuspec\"", result.Args); + } + [Fact] public void Should_Add_Base_Path_To_Arguments_If_Not_Null() { @@ -265,6 +280,21 @@ public void Should_Add_Symbols_Flag_To_Arguments_If_Set() Assert.Equal("pack \"/Working/existing.temp.nuspec\" -Symbols", result.Args); } + [Fact] + public void Should_Add_SymbolPackageFormat_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.Settings.Symbols = true; + fixture.Settings.SymbolPackageFormat = "snupkg"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack \"/Working/existing.temp.nuspec\" -Symbols -SymbolPackageFormat snupkg", result.Args); + } + [Theory] [InlineData(NuGetVerbosity.Detailed, "pack \"/Working/existing.temp.nuspec\" -Verbosity detailed")] [InlineData(NuGetVerbosity.Normal, "pack \"/Working/existing.temp.nuspec\" -Verbosity normal")] @@ -298,12 +328,15 @@ public void Should_Add_Metadata_Element_To_Nuspec_If_Missing() fixture.Settings.Summary = "The summary"; fixture.Settings.LicenseUrl = new Uri("https://license.com"); fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.DevelopmentDependency = true; fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.Copyright = "The copyright"; fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Serviceable = true; // When var result = fixture.Run(); @@ -314,6 +347,272 @@ public void Should_Add_Metadata_Element_To_Nuspec_If_Missing() result.NuspecContent.NormalizeLineEndings()); } + [Fact] + public void Should_Add_Repository_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Repository = new NuGetRepository { Url = "https://test", Branch = "master", Commit = "0000000000000000000000000000000000000000", Type = "git" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_Repository.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_License_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.License = new NuSpecLicense { Type = "expression", Version = "V1", Value = "MIT" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_License.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_PackageTypes_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.PackageTypes = new[] + { + new NuSpecPackageType { Name = "package1", Version = "V1" }, + new NuSpecPackageType { Name = "package2", Version = "V2" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_PackageTypes.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_FrameworkAssemblies_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.FrameworkAssemblies = new[] + { + new NuSpecFrameworkAssembly { AssemblyName = "System.Net", TargetFramework = "net40" }, + new NuSpecFrameworkAssembly { AssemblyName = "System.Net.Cookie", TargetFramework = "net47" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_FrameworkAssemblies.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_References_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.References = new[] + { + new NuSpecReference { File = "Cake.Core.dll" }, + new NuSpecReference { File = "Cake.Core.xml" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_References.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_References_With_TargetFramework_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.References = new[] + { + new NuSpecReference { File = "Cake.Core.dll", TargetFramework = "net452" }, + new NuSpecReference { File = "Cake.Core.xml", TargetFramework = "net46" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_References_WithTargetFramework.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_ContentFiles_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.ContentFiles = new[] + { + new NuSpecContentFile { Include = "**/images/*.*", BuildAction = "EmbeddedResource" }, + new NuSpecContentFile { Include = "cs/**/*.*", BuildAction = "Compile", CopyToOutput = true } + }; + fixture.Settings.MinClientVersion = "3.3"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_ContentFiles.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + [Fact] public void Should_Replace_Template_Tokens_In_Nuspec() { @@ -329,12 +628,15 @@ public void Should_Replace_Template_Tokens_In_Nuspec() fixture.Settings.Summary = "The summary"; fixture.Settings.LicenseUrl = new Uri("https://license.com"); fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.DevelopmentDependency = true; fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.Copyright = "The copyright"; fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Serviceable = true; // When var result = fixture.Run(); @@ -361,12 +663,15 @@ public void Should_Replace_Template_Tokens_In_Nuspec_Without_Namespaces() fixture.Settings.Summary = "The summary"; fixture.Settings.LicenseUrl = new Uri("https://license.com"); fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.DevelopmentDependency = true; fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.Copyright = "The copyright"; fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Serviceable = true; // When var result = fixture.Run(); @@ -392,12 +697,15 @@ public void Should_Replace_Template_Tokens_In_Nuspec_With_Files() fixture.Settings.Summary = "The summary"; fixture.Settings.LicenseUrl = new Uri("https://license.com"); fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.DevelopmentDependency = true; fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.Copyright = "The copyright"; fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Serviceable = true; fixture.Settings.Files = new[] { new NuSpecContent { Source = "Cake.Core.dll", Target = "lib/net45" }, @@ -436,12 +744,14 @@ public void Should_Replace_Template_Tokens_In_Nuspec_With_Files_Without_Namespac fixture.Settings.Summary = "The summary"; fixture.Settings.LicenseUrl = new Uri("https://license.com"); fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; fixture.Settings.IconUrl = new Uri("https://icon.com"); fixture.Settings.DevelopmentDependency = true; fixture.Settings.RequireLicenseAcceptance = true; fixture.Settings.Copyright = "The copyright"; fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; fixture.Settings.Files = new[] { new NuSpecContent { Source = "Cake.Core.dll", Target = "lib/net45" }, @@ -468,6 +778,8 @@ public void Should_Replace_Template_Tokens_In_Nuspec_With_Files_Without_Namespac [InlineData(NuGetMSBuildVersion.MSBuild4, "pack \"/Working/existing.temp.nuspec\" -MSBuildVersion 4")] [InlineData(NuGetMSBuildVersion.MSBuild12, "pack \"/Working/existing.temp.nuspec\" -MSBuildVersion 12")] [InlineData(NuGetMSBuildVersion.MSBuild14, "pack \"/Working/existing.temp.nuspec\" -MSBuildVersion 14")] + [InlineData(NuGetMSBuildVersion.MSBuild15_9, "pack \"/Working/existing.temp.nuspec\" -MSBuildVersion 15.9")] + [InlineData(NuGetMSBuildVersion.MSBuild16_0, "pack \"/Working/existing.temp.nuspec\" -MSBuildVersion 16.0")] public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion msBuildVersion, string expected) { // Given @@ -480,6 +792,149 @@ public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion ms // Then Assert.Equal(expected, result.Args); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Keep_NuSpec_File_According_To_Flag(bool keepTemporaryNuSpecFile) + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.Settings.KeepTemporaryNuSpecFile = keepTemporaryNuSpecFile; + + // When + fixture.Run(); + + // Then + Assert.Equal(keepTemporaryNuSpecFile, fixture.FileSystem.Exist((FilePath)"/Working/existing.temp.nuspec")); + } + + [Fact] + public void Should_Replace_Template_Tokens_In_Nuspec_With_Files_And_DependencyTargetFramework() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Files = new[] + { + new NuSpecContent { Source = "Cake.Core.dll", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.xml", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.pdb", Target = "lib/net45" }, + new NuSpecContent { Source = "LICENSE" } + }; + fixture.Settings.Dependencies = new[] + { + new NuSpecDependency { Id = "Test1", Version = "1.0.0", TargetFramework = "net452" }, + new NuSpecDependency { Id = "Test2", Version = "[1.0.0]", TargetFramework = "net46" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_Metadata_WithTargetFrameworkDependencies.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Replace_Template_Tokens_In_Nuspec_With_Files_And_DependencyTargetFramework_Without_Namespaces() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataValues_WithoutNamespaces); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Files = new[] + { + new NuSpecContent { Source = "Cake.Core.dll", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.xml", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.pdb", Target = "lib/net45" }, + new NuSpecContent { Source = "LICENSE" } + }; + fixture.Settings.Dependencies = new[] + { + new NuSpecDependency { Id = "Test1", Version = "1.0.0", TargetFramework = "net452" }, + new NuSpecDependency { Id = "Test2", Version = "[1.0.0]", TargetFramework = "net46" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_Metadata_WithoutNamespaces_WithTargetFramworkDependencies.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_ReadMe_Element_To_Nuspec_If_Missing() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_NoMetadataElement); + + fixture.Settings.Id = "The ID"; + fixture.Settings.Version = "The version"; + fixture.Settings.Title = "The title"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Owners = new[] { "Owner #1", "Owner #2" }; + fixture.Settings.Description = "The description"; + fixture.Settings.Summary = "The summary"; + fixture.Settings.LicenseUrl = new Uri("https://license.com"); + fixture.Settings.ProjectUrl = new Uri("https://project.com"); + fixture.Settings.Icon = @"images\icon.png"; + fixture.Settings.IconUrl = new Uri("https://icon.com"); + fixture.Settings.DevelopmentDependency = true; + fixture.Settings.RequireLicenseAcceptance = true; + fixture.Settings.Copyright = "The copyright"; + fixture.Settings.ReleaseNotes = new[] { "Line #1", "Line #2", "Line #3" }; + fixture.Settings.Tags = new[] { "Tag1", "Tag2", "Tag3" }; + fixture.Settings.Language = "en-us"; + fixture.Settings.Serviceable = true; + fixture.Settings.ReadMe = "NuGet.org.md"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_ReadMe.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } } public sealed class WithProjectFile @@ -495,7 +950,7 @@ public void Should_Throw_If_Nuspec_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -509,7 +964,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -523,7 +978,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -583,7 +1038,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -597,7 +1052,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -699,6 +1154,21 @@ public void Should_Add_Symbols_Flag_To_Arguments_If_Set() Assert.Equal("pack \"/Working/existing.csproj\" -Symbols", result.Args); } + [Fact] + public void Should_Add_SymbolPackageFormat_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetPackerWithProjectFileFixture(); + fixture.Settings.Symbols = true; + fixture.Settings.SymbolPackageFormat = "snupkg"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack \"/Working/existing.csproj\" -Symbols -SymbolPackageFormat snupkg", result.Args); + } + [Theory] [InlineData(NuGetVerbosity.Detailed, "pack \"/Working/existing.csproj\" -Verbosity detailed")] [InlineData(NuGetVerbosity.Normal, "pack \"/Working/existing.csproj\" -Verbosity normal")] @@ -730,7 +1200,7 @@ public void Should_Add_Properties_To_Arguments_If_Set() var result = fixture.Run(); // Then - Assert.Equal("pack \"/Working/existing.csproj\" -Properties Configuration=Release", result.Args); + Assert.Equal("pack \"/Working/existing.csproj\" -Properties \"Configuration=Release\"", result.Args); } [Fact] @@ -748,7 +1218,7 @@ public void Should_Separate_Properties_With_Semicolon() var result = fixture.Run(); // Then - Assert.Equal("pack \"/Working/existing.csproj\" -Properties Configuration=Release;Foo=Bar", result.Args); + Assert.Equal("pack \"/Working/existing.csproj\" -Properties \"Configuration=Release;Foo=Bar\"", result.Args); } [Theory] @@ -769,7 +1239,7 @@ public void Should_Throw_For_Invalid_Properties_Keys(string key) var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Properties keys can not be null or empty."); + AssertEx.IsCakeException(result, "Properties keys can not be null or empty."); } [Theory] @@ -790,16 +1260,17 @@ public void Should_Throw_For_Invalid_Properties_Values(string value) var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Properties values can not be null or empty."); + AssertEx.IsCakeException(result, "Properties values can not be null or empty."); } [Theory] [InlineData(NuGetMSBuildVersion.MSBuild4, "pack \"/Working/existing.csproj\" -MSBuildVersion 4")] [InlineData(NuGetMSBuildVersion.MSBuild12, "pack \"/Working/existing.csproj\" -MSBuildVersion 12")] [InlineData(NuGetMSBuildVersion.MSBuild14, "pack \"/Working/existing.csproj\" -MSBuildVersion 14")] + [InlineData(NuGetMSBuildVersion.MSBuild15_9, "pack \"/Working/existing.csproj\" -MSBuildVersion 15.9")] + [InlineData(NuGetMSBuildVersion.MSBuild16_0, "pack \"/Working/existing.csproj\" -MSBuildVersion 16.0")] public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion msBuildVersion, string expected) { - // Given var fixture = new NuGetPackerWithProjectFileFixture(); fixture.Settings.MSBuildVersion = msBuildVersion; @@ -810,6 +1281,30 @@ public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion ms // Then Assert.Equal(expected, result.Args); } + + [Fact] + public void Should_Not_Modify_ContentFiles_Element_If_Files_Specified() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.WithNuSpecXml(Resources.Nuspec_ContentFiles); + + fixture.Settings.Files = new[] + { + new NuSpecContent { Source = "Cake.Core.dll", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.xml", Target = "lib/net45" }, + new NuSpecContent { Source = "Cake.Core.pdb", Target = "lib/net45" }, + new NuSpecContent { Source = "LICENSE" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_ContentFiles.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } } public sealed class WithoutNuSpec @@ -837,6 +1332,30 @@ public void Should_Pack_If_Sufficient_Settings_Specified() "\"/Working/nonexisting.temp.nuspec\"", result.Args); } + [Fact] + public void Should_Pack_If_Sufficient_Settings_For_MetaPackage_Specified() + { + // Given + var fixture = new NuGetPackerWithoutNuSpecFixture(); + fixture.Settings.OutputDirectory = "/Working/"; + fixture.Settings.Id = "nonexisting"; + fixture.Settings.Version = "1.0.0"; + fixture.Settings.Description = "The description"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Files = null; + fixture.Settings.Dependencies = new List + { + new NuSpecDependency { Id = "Test1", Version = "1.0.0" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack -Version \"1.0.0\" -OutputDirectory \"/Working\" " + + "\"/Working/nonexisting.temp.nuspec\"", result.Args); + } + [Fact] public void Should_Throw_If_OutputDirectory_Setting_Not_Specified() { @@ -847,7 +1366,7 @@ public void Should_Throw_If_OutputDirectory_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting OutputDirectory not specified or doesn't exists."); + AssertEx.IsCakeException(result, "Required setting OutputDirectory not specified or doesn't exists."); } [Fact] @@ -861,7 +1380,7 @@ public void Should_Throw_If_Id_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Id not specified."); + AssertEx.IsCakeException(result, "Required setting Id not specified."); } [Fact] @@ -876,7 +1395,7 @@ public void Should_Throw_If_Version_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Version not specified."); + AssertEx.IsCakeException(result, "Required setting Version not specified."); } [Fact] @@ -892,7 +1411,7 @@ public void Should_Throw_If_Authors_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Authors not specified."); + AssertEx.IsCakeException(result, "Required setting Authors not specified."); } [Fact] @@ -909,11 +1428,11 @@ public void Should_Throw_If_Description_Setting_Not_Specified() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Description not specified."); + AssertEx.IsCakeException(result, "Required setting Description not specified."); } [Fact] - public void Should_Throw_If_Files_Setting_Not_Specified() + public void Should_Throw_If_Files_Setting_And_Dependencies_Not_Specified() { // Given var fixture = new NuGetPackerWithoutNuSpecFixture(); @@ -922,14 +1441,55 @@ public void Should_Throw_If_Files_Setting_Not_Specified() fixture.Settings.Version = "1.0.0"; fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; fixture.Settings.Description = "The description"; - + fixture.Settings.Dependencies = null; + fixture.Settings.Files = null; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Required setting Files not specified."); + AssertEx.IsCakeException(result, "Required setting Files not specified."); + } + + [Fact] + public void Should_Pack_If_Sufficient_Settings_For_MetaPackage_With_TargetFrameWork_Specified() + { + // Given + var fixture = new NuGetPackerWithoutNuSpecFixture(); + fixture.Settings.OutputDirectory = "/Working/"; + fixture.Settings.Id = "nonexisting"; + fixture.Settings.Version = "1.0.0"; + fixture.Settings.Description = "The description"; + fixture.Settings.Authors = new[] { "Author #1", "Author #2" }; + fixture.Settings.Files = null; + fixture.Settings.Dependencies = new List + { + new NuSpecDependency { Id = "Test1", Version = "1.0.0", TargetFramework = "net452" }, + new NuSpecDependency { Id = "Test1", Version = "1.0.0", TargetFramework = "net46" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal( + Resources.Nuspec_Metadata_PackWithTargetFrameworkDependencies.NormalizeLineEndings(), + result.NuspecContent.NormalizeLineEndings()); + } + + [Fact] + public void Should_Add_Tool_Flag_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetPackerWithNuSpecFixture(); + fixture.Settings.OutputToToolFolder = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack \"/Working/existing.temp.nuspec\" -Tool", result.Args); } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Push/NuGetPusherTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Push/NuGetPusherTests.cs index bc11115cab..350b19502c 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Push/NuGetPusherTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Push/NuGetPusherTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tests.Fixtures.Tools.NuGet.Pusher; using Cake.Common.Tools.NuGet; @@ -25,7 +26,7 @@ public void Should_Throw_If_Nuspec_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packageFilePath"); + AssertEx.IsArgumentNullException(result, "packageFilePath"); } [Fact] @@ -39,7 +40,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -53,7 +54,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -113,7 +114,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -127,7 +128,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -154,7 +155,21 @@ public void Should_Add_Api_Key_To_Arguments_If_Not_Null() var result = fixture.Run(); // Then - Assert.Equal("push \"/Working/existing.nupkg\" 1234 -NonInteractive", result.Args); + Assert.Equal("push \"/Working/existing.nupkg\" \"1234\" -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Skip_Duplicate_If_True() + { + // Given + var fixture = new NuGetPusherFixture(); + fixture.Settings.SkipDuplicate = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("push \"/Working/existing.nupkg\" -NonInteractive -SkipDuplicate", result.Args); } [Fact] @@ -220,4 +235,4 @@ public void Should_Add_Verbosity_To_Arguments_If_Not_Null(NuGetVerbosity verbosi } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestoreSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestoreSettingsTests.cs index a2d542fe94..a5b817cc56 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestoreSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestoreSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Restore; using Xunit; @@ -29,6 +30,16 @@ public void Should_Set_RequireConsent_To_False_By_Default() // Then Assert.False(settings.RequireConsent); } + + [Fact] + public void Should_Set_NonInteractive_To_True_By_Default() + { + // Given, When + var settings = new NuGetRestoreSettings(); + + // Then + Assert.True(settings.NonInteractive); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestorerTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestorerTests.cs index 8f1091db4f..7cf3af8428 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestorerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Restore/NuGetRestorerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.NuGet.Restorer; using Cake.Common.Tools.NuGet; using Cake.Testing; @@ -24,7 +25,7 @@ public void Should_Throw_If_Target_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "targetFilePath"); + AssertEx.IsArgumentNullException(result, "targetFilePath"); } [Fact] @@ -39,7 +40,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -53,11 +54,11 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] - [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] + [InlineData("/bin/nuget/nuget.exe", "/bin/nuget/nuget.exe")] [InlineData("./tools/nuget/nuget.exe", "/Working/tools/nuget/nuget.exe")] public void Should_Use_NuGet_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) { @@ -100,7 +101,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -114,7 +115,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -218,6 +219,20 @@ public void Should_Add_DisableParallelProcessing_To_Arguments_If_Set() "-NonInteractive", result.Args); } + [Fact] + public void Should_Remove_NonInteractive_From_Arguments_If_False() + { + // Given + var fixture = new NuGetRestorerFixture(); + fixture.Settings.NonInteractive = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore \"/Working/project.sln\"", result.Args); + } + [Theory] [InlineData(NuGetVerbosity.Detailed, "restore \"/Working/project.sln\" -Verbosity detailed -NonInteractive")] [InlineData(NuGetVerbosity.Normal, "restore \"/Working/project.sln\" -Verbosity normal -NonInteractive")] @@ -249,8 +264,6 @@ public void Should_Add_ConfigFile_To_Arguments_If_Set() Assert.Equal("restore \"/Working/project.sln\" -ConfigFile \"/Working/nuget.config\" -NonInteractive", result.Args); } - - [Fact] public void Should_Add_FallbackSources_To_Arguments_If_Set() { @@ -270,9 +283,10 @@ public void Should_Add_FallbackSources_To_Arguments_If_Set() [InlineData(NuGetMSBuildVersion.MSBuild4, "restore \"/Working/project.sln\" -MSBuildVersion 4 -NonInteractive")] [InlineData(NuGetMSBuildVersion.MSBuild12, "restore \"/Working/project.sln\" -MSBuildVersion 12 -NonInteractive")] [InlineData(NuGetMSBuildVersion.MSBuild14, "restore \"/Working/project.sln\" -MSBuildVersion 14 -NonInteractive")] + [InlineData(NuGetMSBuildVersion.MSBuild15_9, "restore \"/Working/project.sln\" -MSBuildVersion 15.9 -NonInteractive")] + [InlineData(NuGetMSBuildVersion.MSBuild16_0, "restore \"/Working/project.sln\" -MSBuildVersion 16.0 -NonInteractive")] public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion msBuildVersion, string expected) { - // Given var fixture = new NuGetRestorerFixture(); fixture.Settings.MSBuildVersion = msBuildVersion; @@ -283,6 +297,20 @@ public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion ms // Then Assert.Equal(expected, result.Args); } + + [Fact] + public void Should_Add_MSBuildPath_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetRestorerFixture(); + fixture.Settings.MSBuildPath = "MSBuild/15.0/Bin"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("restore \"/Working/project.sln\" -MSBuildPath \"/Working/MSBuild/15.0/Bin\" -NonInteractive", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/SetApiKey/NuGetSetApiKeyTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/SetApiKey/NuGetSetApiKeyTests.cs index 9d2fb9b1b2..efa01a8990 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/SetApiKey/NuGetSetApiKeyTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/SetApiKey/NuGetSetApiKeyTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.NuGet.SetApiKey; using Cake.Common.Tools.NuGet; using Cake.Testing; @@ -24,7 +25,7 @@ public void Should_Throw_If_Target_Api_Key_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "apiKey"); + AssertEx.IsArgumentNullException(result, "apiKey"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Target_Source_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "source"); + AssertEx.IsArgumentNullException(result, "source"); } [Fact] @@ -52,21 +53,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Encounter_Unexpected_Output() - { - // Given - var fixture = new NuGetSetApiKeyFixture(); - fixture.GivenUnexpectedOutput(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsCakeException(result, "SetApiKey returned unexpected response."); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -80,7 +67,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -127,7 +114,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -141,7 +128,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -203,4 +190,4 @@ public void Should_Add_ConfigFile_To_Arguments_If_Set() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/SetProxy/NuGetSetProxyTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/SetProxy/NuGetSetProxyTests.cs index 412233d855..77e5488b14 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/SetProxy/NuGetSetProxyTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/SetProxy/NuGetSetProxyTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.NuGet.SetProxy; using Cake.Common.Tools.NuGet; using Cake.Testing; @@ -24,7 +25,7 @@ public void Should_Throw_If_Url_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "url"); + AssertEx.IsArgumentNullException(result, "url"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -52,7 +53,7 @@ public void Should_Throw_If_Encounter_Unexpected_Output() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Set command returned unexpected response."); + AssertEx.IsCakeException(result, "Set command returned unexpected response."); } [Fact] @@ -66,7 +67,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Could not locate executable."); + AssertEx.IsCakeException(result, "NuGet: Could not locate executable."); } [Theory] @@ -113,7 +114,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process was not started."); + AssertEx.IsCakeException(result, "NuGet: Process was not started."); } [Fact] @@ -127,7 +128,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "NuGet: Process returned an error (exit code 1)."); } [Fact] @@ -208,4 +209,4 @@ public void Should_Add_Mandatory_Arguments() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesSettingsTests.cs index d29aa4eb26..dfefe6f075 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Sources; using Xunit; @@ -22,4 +23,4 @@ public void Should_Set_IsSensitiveSource_To_False_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesTests.cs index b5c7fed9a6..4d3db16b92 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Sources/NuGetSourcesTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tests.Fixtures.Tools.NuGet.Sources; using Cake.Common.Tools.NuGet; @@ -26,7 +27,7 @@ public void Should_Throw_If_Name_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "name"); + AssertEx.IsArgumentNullException(result, "name"); } [Theory] @@ -36,13 +37,13 @@ public void Should_Throw_If_Name_Is_Empty(string name) { // Given var fixture = new NuGetAddSourceFixture(); - fixture.Name = string.Empty; + fixture.Name = name; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "name", "Source name cannot be empty."); + AssertEx.IsArgumentException(result, "name", "Source name cannot be empty."); } [Fact] @@ -56,7 +57,7 @@ public void Should_Throw_If_Source_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "source"); + AssertEx.IsArgumentNullException(result, "source"); } [Theory] @@ -66,13 +67,13 @@ public void Should_Throw_If_Source_Is_Empty(string source) { // Given var fixture = new NuGetAddSourceFixture(); - fixture.Source = string.Empty; + fixture.Source = source; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "source", "Source cannot be empty."); + AssertEx.IsArgumentException(result, "source", "Source cannot be empty."); } [Fact] @@ -86,7 +87,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -102,7 +103,7 @@ public void Should_Throw_If_Adding_Source_That_Has_Already_Been_Added() // Then Assert.IsType(result); - Assert.Equal("The source 'existingsource' already exist.", result.Message); + Assert.Equal("The source 'existingsource' already exist.", result?.Message); } [Fact] @@ -117,7 +118,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("NuGet: Could not locate executable.", result.Message); + Assert.Equal("NuGet: Could not locate executable.", result?.Message); } [Theory] @@ -165,7 +166,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process was not started.", result.Message); + Assert.Equal("NuGet: Process was not started.", result?.Message); } [Fact] @@ -180,7 +181,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process returned an error (exit code 1).", result.Message); + Assert.Equal("NuGet: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -270,6 +271,21 @@ public void Should_Add_StorePasswordInClearText_To_Arguments_If_Set() Assert.Equal("sources Add -Name \"name\" -Source \"source\" " + "-NonInteractive -StorePasswordInClearText", result.Args); } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetAddSourceFixture(); + fixture.Settings.ConfigFile = "./src/NuGet.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sources Add -Name \"name\" -Source \"source\" " + + "-ConfigFile \"src/NuGet.config\" -NonInteractive", result.Args); + } } public sealed class TheRemoveSourceMethod @@ -285,7 +301,7 @@ public void Should_Throw_If_Name_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "name"); + AssertEx.IsArgumentNullException(result, "name"); } [Theory] @@ -295,13 +311,13 @@ public void Should_Throw_If_Name_Is_Empty(string name) { // Given var fixture = new NuGetRemoveSourceFixture(); - fixture.Name = string.Empty; + fixture.Name = name; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "name", "Source name cannot be empty."); + AssertEx.IsArgumentException(result, "name", "Source name cannot be empty."); } [Fact] @@ -315,7 +331,7 @@ public void Should_Throw_If_Source_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "source"); + AssertEx.IsArgumentNullException(result, "source"); } [Theory] @@ -325,13 +341,13 @@ public void Should_Throw_If_Source_Is_Empty(string source) { // Given var fixture = new NuGetRemoveSourceFixture(); - fixture.Source = string.Empty; + fixture.Source = source; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "source", "Source cannot be empty."); + AssertEx.IsArgumentException(result, "source", "Source cannot be empty."); } [Fact] @@ -345,7 +361,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -359,7 +375,7 @@ public void Should_Throw_If_Removing_Source_That_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("The source 'source' does not exist.", result.Message); + Assert.Equal("The source 'source' does not exist.", result?.Message); } [Fact] @@ -375,7 +391,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("NuGet: Could not locate executable.", result.Message); + Assert.Equal("NuGet: Could not locate executable.", result?.Message); } [Theory] @@ -426,7 +442,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process was not started.", result.Message); + Assert.Equal("NuGet: Process was not started.", result?.Message); } [Fact] @@ -442,7 +458,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process returned an error (exit code 1).", result.Message); + Assert.Equal("NuGet: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -505,6 +521,22 @@ public void Should_Redact_Source_If_IsSensitiveSource_Set() Assert.Equal("sources Remove -Name \"name\" " + "-Source \"source\" -NonInteractive", result.Args); } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetRemoveSourceFixture(); + fixture.GivenExistingSource(); + fixture.Settings.ConfigFile = "./src/NuGet.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sources Remove -Name \"name\" -Source \"source\" " + + "-ConfigFile \"src/NuGet.config\" -NonInteractive", result.Args); + } } public sealed class TheHasSourceMethod @@ -520,7 +552,7 @@ public void Should_Throw_If_Source_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "source"); + AssertEx.IsArgumentNullException(result, "source"); } [Theory] @@ -530,13 +562,13 @@ public void Should_Throw_If_Source_Is_Empty(string source) { // Given var fixture = new NuGetHasSourceFixture(); - fixture.Source = string.Empty; + fixture.Source = source; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "source", "Source cannot be empty."); + AssertEx.IsArgumentException(result, "source", "Source cannot be empty."); } [Fact] @@ -550,8 +582,49 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new NuGetHasSourceFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sources List -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_ConfigFile_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetHasSourceFixture(); + fixture.Settings.ConfigFile = "./src/NuGet.config"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sources List -ConfigFile \"src/NuGet.config\" -NonInteractive", result.Args); + } + + [Fact] + public void Should_Add_Argument_Customization() + { + // Given + var fixture = new NuGetHasSourceFixture(); + fixture.Settings.ArgumentCustomization = arg => arg.Append("-Foo"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("sources List -NonInteractive -Foo", result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdateSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdateSettingsTests.cs index 4b6f530bdb..56fa24495b 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdateSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdateSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.NuGet.Update; using Xunit; @@ -31,4 +32,4 @@ public void Should_Set_Prerelease_To_False_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdaterTests.cs b/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdaterTests.cs index 54deeb3b10..bf8af17716 100644 --- a/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdaterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/NuGet/Update/NuGetUpdaterTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools.NuGet.Update; using Cake.Common.Tools.NuGet; @@ -26,7 +28,7 @@ public void Should_Throw_If_Target_File_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "targetFile"); + AssertEx.IsArgumentNullException(result, "targetFile"); } [Fact] @@ -40,7 +42,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -55,7 +57,7 @@ public void Should_Throw_If_NuGet_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("NuGet: Could not locate executable.", result.Message); + Assert.Equal("NuGet: Could not locate executable.", result?.Message); } [Theory] @@ -103,7 +105,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process was not started.", result.Message); + Assert.Equal("NuGet: Process was not started.", result?.Message); } [Fact] @@ -118,7 +120,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("NuGet: Process returned an error (exit code 1).", result.Message); + Assert.Equal("NuGet: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -228,6 +230,8 @@ public void Should_Add_Sources_To_Arguments_If_Set() [InlineData(NuGetMSBuildVersion.MSBuild4, "update \"/Working/packages.config\" -MSBuildVersion 4 -NonInteractive")] [InlineData(NuGetMSBuildVersion.MSBuild12, "update \"/Working/packages.config\" -MSBuildVersion 12 -NonInteractive")] [InlineData(NuGetMSBuildVersion.MSBuild14, "update \"/Working/packages.config\" -MSBuildVersion 14 -NonInteractive")] + [InlineData(NuGetMSBuildVersion.MSBuild15_9, "update \"/Working/packages.config\" -MSBuildVersion 15.9 -NonInteractive")] + [InlineData(NuGetMSBuildVersion.MSBuild16_0, "update \"/Working/packages.config\" -MSBuildVersion 16.0 -NonInteractive")] public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion msBuildVersion, string expected) { // Given @@ -240,6 +244,20 @@ public void Should_Add_MSBuildVersion_To_Arguments_If_Set(NuGetMSBuildVersion ms // Then Assert.Equal(expected, result.Args); } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Set() + { + // Given + var fixture = new NuGetUpdateFixture(); + fixture.Settings.Version = "1.0.0.0"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("update \"/Working/packages.config\" -Version 1.0.0.0 -NonInteractive", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoCreateReleaseTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoCreateReleaseTests.cs index 316c8280ee..f912050c12 100644 --- a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoCreateReleaseTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoCreateReleaseTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools; using Cake.Testing; @@ -24,7 +26,7 @@ public void Should_Throw_If_Project_Name_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "projectName"); + AssertEx.IsArgumentNullException(result, "projectName"); } [Fact] @@ -38,7 +40,7 @@ public void Should_Throw_If_Server_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "settings", "No server specified."); + AssertEx.IsArgumentException(result, "settings", "No server specified."); } [Fact] @@ -52,7 +54,7 @@ public void Should_Throw_If_Api_Key_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "settings", "No API key specified."); + AssertEx.IsArgumentException(result, "settings", "No API key specified."); } [Fact] @@ -66,7 +68,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -80,12 +82,12 @@ public void Should_Throw_If_Octo_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Could not locate executable."); + AssertEx.IsCakeException(result, "Octo: Could not locate executable."); } [Theory] - [InlineData("/bin/tools/octopus/octo.exe", "/bin/tools/octopus/octo.exe")] - [InlineData("./tools/octopus/octo.exe", "/Working/tools/octopus/octo.exe")] + [InlineData("/bin/tools/octopus/Octo.exe", "/bin/tools/octopus/Octo.exe")] + [InlineData("./tools/octopus/Octo.exe", "/Working/tools/octopus/Octo.exe")] public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) { // Given @@ -101,7 +103,7 @@ public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided(string toolPat } [WindowsTheory] - [InlineData("C:/octopusDeploy/octo.exe", "C:/octopusDeploy/octo.exe")] + [InlineData("C:/octopusDeploy/Octo.exe", "C:/octopusDeploy/Octo.exe")] public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given @@ -140,7 +142,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Process was not started."); + AssertEx.IsCakeException(result, "Octo: Process was not started."); } [Fact] @@ -154,7 +156,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Octo: Process returned an error (exit code 1)."); } [Fact] @@ -188,7 +190,7 @@ public void Should_Add_Username_To_Arguments_If_Not_Null() // Then Assert.Equal("create-release --project \"testProject\" " + "--server http://octopus --apiKey API-12345 " + - "--username \"mike123\"", result.Args); + "--user \"mike123\"", result.Args); } [Fact] @@ -204,7 +206,7 @@ public void Should_Add_Password_To_Arguments_If_Not_Null() // Then Assert.Equal("create-release --project \"testProject\" " + "--server http://octopus --apiKey API-12345 " + - "--password \"secret\"", result.Args); + "--pass \"secret\"", result.Args); } [Fact] @@ -392,7 +394,7 @@ public void Should_Add_Release_Notes_File_To_Arguments_If_Not_Null() } [Fact] - public void Should_Add_Ignore_Existing_Flag_To_Arguments_If_Not_Null() + public void Should_Add_Ignore_Existing_Flag_To_Arguments_If_True() { // Given var fixture = new OctopusDeployReleaseCreatorFixture(); @@ -407,5 +409,520 @@ public void Should_Add_Ignore_Existing_Flag_To_Arguments_If_Not_Null() "--ignoreexisting", result.Args); } } + + public sealed class DeploymentAgrumentsBuilder + { + [Fact] + public void Should_Add_DeployTo_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\"", result.Args); + } + + [Fact] + public void Should_Add_Progress_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.ShowProgress = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--progress " + + "--deployto \"SomeEnvironment\"", result.Args); + } + + [Fact] + public void Should_Add_ForcePackageDownload_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.ForcePackageDownload = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--forcepackagedownload " + + "--deployto \"SomeEnvironment\"", result.Args); + } + + [Fact] + public void Should_Add_WaitForDeployment_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.WaitForDeployment = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--waitfordeployment " + + "--deployto \"SomeEnvironment\"", result.Args); + } + + [Fact] + public void Should_Add_DeploymentTimeout_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.DeploymentTimeout = TimeSpan.FromMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--deploymenttimeout=\"00:01:00\"", result.Args); + } + + [Fact] + public void Should_Add_CancelTimeout_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.CancelOnTimeout = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--cancelontimeout", result.Args); + } + + [Fact] + public void Should_Add_DeploymentChecksLeepCycle_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.DeploymentChecksLeepCycle = TimeSpan.FromMinutes(77); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--deploymentchecksleepcycle=\"01:17:00\"", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_False() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_SpecificMachines_To_Arguments_If_NotNull() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.SpecificMachines = new string[] { "Machine1", "Machine2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--specificmachines=\"Machine1,Machine2\"", result.Args); + } + + [Fact] + public void Should_Add_Force_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.Force = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--force", result.Args); + } + + [Fact] + public void Should_Add_SkipSteps_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.SkipSteps = new[] { "Step1", "Step2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--skip=\"Step1\" " + + "--skip=\"Step2\"", result.Args); + } + + [Fact] + public void Should_Add_NoRawLog_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.NoRawLog = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--norawlog", result.Args); + } + + [Fact] + public void Should_Add_RawLogFile_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.RawLogFile = "someFile.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--rawlogfile \"/Working/someFile.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Variables_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.Variables.Add(new KeyValuePair("var1", "value1")); + fixture.Settings.Variables.Add(new KeyValuePair("var2", "value2")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--variable=\"var1:value1\" " + + "--variable=\"var2:value2\"", result.Args); + } + + [Fact] + public void Should_Add_DeployAt_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.DeployAt = new DateTime(2010, 6, 15).AddMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--deployat=\"2010-06-15 00:01\"", result.Args); + } + + [Fact] + public void Should_Add_Tenants_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.Tenant = new[] { "Tenant1", "Tenant2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--tenant=\"Tenant1\" " + + "--tenant=\"Tenant2\"", result.Args); + } + + [Fact] + public void Should_Add_TenantTags_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.TenantTags = new[] { "Tag1", "Tag2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"SomeEnvironment\" " + + "--tenanttag=\"Tag1\" " + + "--tenanttag=\"Tag2\"", result.Args); + } + + [Fact] + public void Should_Add_All_Deploymnet_Arguments() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = "SomeEnvironment"; + fixture.Settings.ShowProgress = true; + fixture.Settings.ForcePackageDownload = true; + fixture.Settings.WaitForDeployment = true; + fixture.Settings.DeploymentTimeout = TimeSpan.FromMinutes(1); + fixture.Settings.CancelOnTimeout = true; + fixture.Settings.DeploymentChecksLeepCycle = TimeSpan.FromMinutes(77); + fixture.Settings.GuidedFailure = true; + fixture.Settings.SpecificMachines = new string[] { "Machine1", "Machine2" }; + fixture.Settings.Force = true; + fixture.Settings.SkipSteps = new[] { "Step1", "Step2" }; + fixture.Settings.NoRawLog = true; + fixture.Settings.RawLogFile = "someFile.txt"; + fixture.Settings.Variables.Add(new KeyValuePair("var1", "value1")); + fixture.Settings.Variables.Add(new KeyValuePair("var2", "value2")); + fixture.Settings.DeployAt = new DateTime(2010, 6, 15).AddMinutes(1); + fixture.Settings.Tenant = new[] { "Tenant1", "Tenant2" }; + fixture.Settings.TenantTags = new[] { "Tag1", "Tag2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--progress " + + "--forcepackagedownload " + + "--waitfordeployment " + + "--deployto \"SomeEnvironment\" " + + "--deploymenttimeout=\"00:01:00\" " + + "--cancelontimeout " + + "--deploymentchecksleepcycle=\"01:17:00\" " + + "--guidedfailure=True " + + "--specificmachines=\"Machine1,Machine2\" " + + "--force " + + "--skip=\"Step1\" " + + "--skip=\"Step2\" " + + "--norawlog " + + "--rawlogfile \"/Working/someFile.txt\" " + + "--variable=\"var1:value1\" " + + "--variable=\"var2:value2\" " + + "--deployat=\"2010-06-15 00:01\" " + + "--tenant=\"Tenant1\" " + + "--tenant=\"Tenant2\" " + + "--tenanttag=\"Tag1\" " + + "--tenanttag=\"Tag2\"", result.Args); + } + + [Fact] + public void Should_Add_Channel_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.Channel = @"somechannel"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--channel \"somechannel\"", result.Args); + } + + [Fact] + public void Should_Add_Ignore_Channel_Rules_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.IgnoreChannelRules = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--ignorechannelrules", result.Args); + } + + [Fact] + public void Should_Add_Deployment_Environment_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = @"someenvironment"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"someenvironment\"", result.Args); + } + + [Fact] + public void Should_Add_Deployment_Progress_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeploymentProgress = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--progress", result.Args); + } + + [Fact] + public void Should_Add_Space_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.Space = "spacename"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--space \"spacename\"", result.Args); + } + + [Fact] + public void Should_Add_Multiple_Deployment_Environments_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployToMultiple = new string[] { @"someenvironment", @"someotherenvironment" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"someenvironment\" " + + "--deployto \"someotherenvironment\"", result.Args); + } + + [Fact] + public void Should_Add_Deployment_Environments_From_Both_Properties_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.DeployTo = @"someenvironment"; + fixture.Settings.DeployToMultiple = new string[] { @"someotherenvironment", @"someadditionalenvironment" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--deployto \"someenvironment\" " + + "--deployto \"someotherenvironment\" " + + "--deployto \"someadditionalenvironment\"", result.Args); + } + + [Fact] + public void Should_Add_Exclude_Machines_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseCreatorFixture(); + fixture.Settings.ExcludeMachines = @"somemachine"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("create-release --project \"testProject\" " + + "--server http://octopus --apiKey API-12345 " + + "--excludemachines \"somemachine\"", result.Args); + } + } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployPromoteTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployPromoteTests.cs new file mode 100644 index 0000000000..07509a1c44 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployPromoteTests.cs @@ -0,0 +1,462 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.OctopusDeploy +{ + public sealed class OctoDeployPromoteTests + { + private const string MinimalParameters = "promote-release --project=\"MyProject\" --from=\"Testing\" --to=\"Staging\" --server http://octopus --apiKey API-12345"; + + public sealed class TheBaseArgumentBuilder + { + [Fact] + public void Should_Throw_If_Server_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Server = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "server"); + } + + [Fact] + public void Should_Throw_If_Api_Key_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.ApiKey = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "apiKey"); + } + + [Fact] + public void Should_Throw_If_ProjectName_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Project = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectName"); + } + + [Fact] + public void Should_Throw_If_DeployFrom_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.DeployFrom = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployFrom"); + } + + [Fact] + public void Should_Throw_If_DeployTo_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.DeployTo = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployTo"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Give_Default_Minimal_Parameters() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters, result.Args); + } + + [Fact] + public void Should_Add_Username_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Username = "mike123"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --user \"mike123\"", result.Args); + } + + [Fact] + public void Should_Add_Password_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Password = "secret"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --pass \"secret\"", result.Args); + } + + [Fact] + public void Should_Add_Configuration_File_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.ConfigurationFile = "configFile.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --configFile \"/Working/configFile.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.EnableDebugLogging = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --debug", result.Args); + } + + [Fact] + public void Should_Add_Ignore_Ssl_Errors_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.IgnoreSslErrors = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --ignoreSslErrors", result.Args); + } + + [Fact] + public void Should_Add_Enable_Service_Messages_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.EnableServiceMessages = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --enableServiceMessages", result.Args); + } + } + + public sealed class DeploymentArgumentBuilder + { + [Fact] + public void Should_Add_Progress_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.ShowProgress = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --progress", result.Args); + } + + [Fact] + public void Should_Add_FocePackageDownload_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.ForcePackageDownload = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --forcepackagedownload", result.Args); + } + + [Fact] + public void Should_Add_WaitForDeployment_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.WaitForDeployment = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --waitfordeployment", result.Args); + } + + [Fact] + public void Should_Add_DeploymentTimeout_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.DeploymentTimeout = TimeSpan.FromMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deploymenttimeout=\"00:01:00\"", result.Args); + } + + [Fact] + public void Should_Add_CancelTimeout_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.CancelOnTimeout = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --cancelontimeout", result.Args); + } + + [Fact] + public void Should_Add_DeploymentChecksLeepCycle_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.DeploymentChecksLeepCycle = TimeSpan.FromMinutes(77); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deploymentchecksleepcycle=\"01:17:00\"", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_False() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_SpecificMachines_To_Arguments_If_NotNull() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.SpecificMachines = new string[] { "Machine1", "Machine2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --specificmachines=\"Machine1,Machine2\"", result.Args); + } + + [Fact] + public void Should_Add_Force_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Force = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --force", result.Args); + } + + [Fact] + public void Should_Add_SkipSteps_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.SkipSteps = new[] { "Step1", "Step2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --skip=\"Step1\" --skip=\"Step2\"", result.Args); + } + + [Fact] + public void Should_Add_NoRawLog_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.NoRawLog = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --norawlog", result.Args); + } + + [Fact] + public void Should_Add_RawLogFile_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.RawLogFile = "someFile.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --rawlogfile \"/Working/someFile.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Variables_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Variables.Add("var1", "value1"); + fixture.Settings.Variables.Add("var2", "value2"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --variable=\"var1:value1\"" + + " --variable=\"var2:value2\"", result.Args); + } + + [Fact] + public void Should_Add_DeployAt_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.DeployAt = new DateTime(2010, 6, 15).AddMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deployat=\"2010-06-15 00:01\"", result.Args); + } + + [Fact] + public void Should_Add_Tenants_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Tenant = new[] { "Tenant1", "Tenant2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --tenant=\"Tenant1\"" + + " --tenant=\"Tenant2\"", result.Args); + } + + [Fact] + public void Should_Add_TenantTags_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.TenantTags = new[] { "Tag1", "Tag2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --tenanttag=\"Tag1\"" + + " --tenanttag=\"Tag2\"", result.Args); + } + + [Fact] + public void Should_Add_Space_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleasePromoterFixture(); + fixture.Settings.Space = "spacename"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --space \"spacename\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployReleaseTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployReleaseTests.cs new file mode 100644 index 0000000000..5c9def646b --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoDeployReleaseTests.cs @@ -0,0 +1,531 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tests.Fixtures.Tools; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.OctopusDeploy +{ + public sealed class OctoDeployReleaseTests + { + private const string MinimalParameters = "deploy-release --project=\"MyProject\" --releasenumber=\"0.15.1\" --deployto=\"Testing\" --server http://octopus --apiKey API-12345"; + + public sealed class TheBaseArgumentBuilder + { + [Fact] + public void Should_Throw_If_Server_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Server = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "server"); + } + + [Fact] + public void Should_Throw_If_Api_Key_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.ApiKey = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "apiKey"); + } + + [Fact] + public void Should_Throw_If_ProjectName_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Project = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "projectName"); + } + + [Fact] + public void Should_Throw_If_DeployTo_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.DeployTo = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployTo"); + } + + [Fact] + public void Should_Throw_If_DeployTo_Is_Empty() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.DeployTo = new string[] { }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployTo"); + } + + [Fact] + public void Should_Throw_If_DeployTo_Contains_Null_Items() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.DeployTo = new string[] { "Testing", null }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployTo"); + } + + [Fact] + public void Should_Throw_If_DeployTo_Contains_Empty_Items() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.DeployTo = new string[] { "Testing", string.Empty }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "deployTo"); + } + + [Fact] + public void Should_Throw_If_ReleaseNumber_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.ReleaseNumber = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "releaseNumber"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Give_Default_Minimal_Parameters() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters, result.Args); + } + + [Fact] + public void Should_Add_Username_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Username = "mike123"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --user \"mike123\"", result.Args); + } + + [Fact] + public void Should_Add_Password_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Password = "secret"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --pass \"secret\"", result.Args); + } + + [Fact] + public void Should_Add_Configuration_File_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.ConfigurationFile = "configFile.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --configFile \"/Working/configFile.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Debug_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.EnableDebugLogging = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --debug", result.Args); + } + + [Fact] + public void Should_Add_Ignore_Ssl_Errors_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.IgnoreSslErrors = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --ignoreSslErrors", result.Args); + } + + [Fact] + public void Should_Add_Enable_Service_Messages_Flag_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.EnableServiceMessages = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --enableServiceMessages", result.Args); + } + } + + public sealed class DeploymentArgumentBuilder + { + [Fact] + public void Should_Add_Progress_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.ShowProgress = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --progress", result.Args); + } + + [Fact] + public void Should_Add_FocePackageDownload_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.ForcePackageDownload = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --forcepackagedownload", result.Args); + } + + [Fact] + public void Should_Add_WaitForDeployment_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.WaitForDeployment = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --waitfordeployment", result.Args); + } + + [Fact] + public void Should_Add_DeploymentTimeout_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.DeploymentTimeout = TimeSpan.FromMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deploymenttimeout=\"00:01:00\"", result.Args); + } + + [Fact] + public void Should_Add_CancelTimeout_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.CancelOnTimeout = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --cancelontimeout", result.Args); + } + + [Fact] + public void Should_Add_DeploymentChecksLeepCycle_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.DeploymentChecksLeepCycle = TimeSpan.FromMinutes(77); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deploymentchecksleepcycle=\"01:17:00\"", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_GuidedFailure_To_Arguments_If_False() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.GuidedFailure = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --guidedfailure=True", result.Args); + } + + [Fact] + public void Should_Add_SpecificMachines_To_Arguments_If_NotNull() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.SpecificMachines = new string[] { "Machine1", "Machine2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --specificmachines=\"Machine1,Machine2\"", result.Args); + } + + [Fact] + public void Should_Add_Force_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Force = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --force", result.Args); + } + + [Fact] + public void Should_Add_SkipSteps_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.SkipSteps = new[] { "Step1", "Step2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --skip=\"Step1\" --skip=\"Step2\"", result.Args); + } + + [Fact] + public void Should_Add_NoRawLog_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.NoRawLog = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --norawlog", result.Args); + } + + [Fact] + public void Should_Add_RawLogFile_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.RawLogFile = "someFile.txt"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --rawlogfile \"/Working/someFile.txt\"", result.Args); + } + + [Fact] + public void Should_Add_Variables_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Variables.Add("var1", "value1"); + fixture.Settings.Variables.Add("var2", "value2"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --variable=\"var1:value1\"" + + " --variable=\"var2:value2\"", result.Args); + } + + [Fact] + public void Should_Add_DeployAt_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.DeployAt = new DateTime(2010, 6, 15).AddMinutes(1); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --deployat=\"2010-06-15 00:01\"", result.Args); + } + + [Fact] + public void Should_Add_Tenants_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Tenant = new[] { "Tenant1", "Tenant2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --tenant=\"Tenant1\"" + + " --tenant=\"Tenant2\"", result.Args); + } + + [Fact] + public void Should_Add_TenantTags_To_Arguments_If_Specified() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.TenantTags = new[] { "Tag1", "Tag2" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + + " --tenanttag=\"Tag1\"" + + " --tenanttag=\"Tag2\"", result.Args); + } + + [Fact] + public void Should_Add_Channel_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Channel = @"somechannel"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --channel \"somechannel\"", result.Args); + } + + [Fact] + public void Should_Add_Exclude_Machines_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.ExcludeMachines = @"somemachine"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --excludemachines \"somemachine\"", result.Args); + } + + [Fact] + public void Should_Add_Space_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployReleaseDeployerFixture(); + fixture.Settings.Space = @"spacename"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(MinimalParameters + " --space \"spacename\"", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPackTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPackTests.cs new file mode 100644 index 0000000000..eb09252f0a --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPackTests.cs @@ -0,0 +1,212 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tools.OctopusDeploy; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.OctopusDeploy +{ + public sealed class OctoPackTests + { + public sealed class ThePackMethod + { + [Fact] + public void Should_Throw_If_Id_Is_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "id"); + } + + [Fact] + public void Should_Add_Id_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Version = "1.2.3"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --version 1.2.3", result.Args); + } + + [Fact] + public void Should_Add_OutFolder_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.OutFolder = "out"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --outFolder \"/Working/out\"", result.Args); + } + + [Fact] + public void Should_Add_BasePath_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.BasePath = "base"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --basePath \"/Working/base\"", result.Args); + } + + [Fact] + public void Should_Add_Author_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Author = "author"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --author \"author\"", result.Args); + } + + [Fact] + public void Should_Add_Title_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Title = "title"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --title \"title\"", result.Args); + } + + [Fact] + public void Should_Add_Description_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Description = "description"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --description \"description\"", result.Args); + } + + [Fact] + public void Should_Add_ReleaseNotes_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.ReleaseNotes = "releasenotes"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --releaseNotes \"releasenotes\"", result.Args); + } + + [Fact] + public void Should_Add_ReleaseNotesFile_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.ReleaseNotesFile = "releasenotes.md"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --releaseNotesFile \"/Working/releasenotes.md\"", result.Args); + } + + [Fact] + public void Should_Add_Include_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Include = new[] + { + "bin/*.dll", + "bin/*.pdb" + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --include \"bin/*.dll\" --include \"bin/*.pdb\"", result.Args); + } + + [Fact] + public void Should_Add_Overwrite_To_Arguments_If_True() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Overwrite = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --overwrite", result.Args); + } + + [Fact] + public void Should_Add_Format_To_Arguments_If_Zip() + { + // Given + var fixture = new OctopusDeployPackerFixture(); + fixture.Id = "MyPackage"; + fixture.Settings.Format = OctopusPackFormat.Zip; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("pack --id MyPackage --format Zip", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPushTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPushTests.cs index e8b61e8e21..396c675716 100644 --- a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPushTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctoPushTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; -using Cake.Testing; using Cake.Common.Tests.Fixtures.Tools; using Cake.Core.IO; +using Cake.Testing; using Cake.Testing.Xunit; using Xunit; @@ -25,7 +26,7 @@ public void Should_Throw_If_Server_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "settings", "No server specified."); + AssertEx.IsArgumentException(result, "settings", "No server specified."); } [Fact] @@ -39,7 +40,7 @@ public void Should_Throw_If_Api_Key_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "settings", "No API key specified."); + AssertEx.IsArgumentException(result, "settings", "No API key specified."); } [Fact] @@ -53,7 +54,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -165,7 +166,7 @@ public void Should_Throw_If_No_Packages_Provided() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packagePaths"); + AssertEx.IsArgumentNullException(result, "packagePaths"); } [Fact] @@ -179,7 +180,7 @@ public void Should_Throw_If_Package_List_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "packagePaths"); + AssertEx.IsArgumentNullException(result, "packagePaths"); } [Fact] @@ -236,6 +237,22 @@ public void Should_Add_Replace_Existing_Flag_If_Not_Null() "--server http://octopus --apiKey API-12345", result.Args); } + [Fact] + public void Should_Add_Space_To_Arguments_If_Not_Null() + { + // Given + var fixture = new OctopusDeployPusherFixture(); + fixture.Settings.Space = "spacename"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("push --package \"/Working/MyPackage.1.0.0.zip\" " + + "--package \"/Working/MyOtherPackage.1.0.1.nupkg\" " + + "--server http://octopus --apiKey API-12345 --space \"spacename\"", result.Args); + } + [Fact] public void Should_Throw_If_Octo_Executable_Was_Not_Found() { @@ -247,12 +264,12 @@ public void Should_Throw_If_Octo_Executable_Was_Not_Found() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Could not locate executable."); + AssertEx.IsCakeException(result, "Octo: Could not locate executable."); } [Theory] - [InlineData("/bin/tools/octopus/octo.exe", "/bin/tools/octopus/octo.exe")] - [InlineData("./tools/octopus/octo.exe", "/Working/tools/octopus/octo.exe")] + [InlineData("/bin/tools/octopus/Octo.exe", "/bin/tools/octopus/Octo.exe")] + [InlineData("./tools/octopus/Octo.exe", "/Working/tools/octopus/Octo.exe")] public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) { // Given @@ -268,7 +285,7 @@ public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided(string toolPat } [WindowsTheory] - [InlineData("C:/octopusDeploy/octo.exe", "C:/octopusDeploy/octo.exe")] + [InlineData("C:/octopusDeploy/Octo.exe", "C:/octopusDeploy/Octo.exe")] public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) { // Given @@ -307,7 +324,7 @@ public void Should_Throw_If_Process_Was_Not_Started() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Process was not started."); + AssertEx.IsCakeException(result, "Octo: Process was not started."); } [Fact] @@ -321,7 +338,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Octo: Process returned an error (exit code 1)."); + AssertEx.IsCakeException(result, "Octo: Process returned an error (exit code 1)."); } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctopusDeploymentQueryTests.cs b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctopusDeploymentQueryTests.cs new file mode 100644 index 0000000000..a97b9ae84d --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/OctopusDeploy/OctopusDeploymentQueryTests.cs @@ -0,0 +1,316 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tools.OctopusDeploy; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.OctopusDeploy +{ + public sealed class OctopusDeploymentQueryTests + { + public sealed class TheQueryDeploymentsMethod + { + [Fact] + public void Should_Throw_If_Count_Is_Less_Than_1() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.Count = 0; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentOutOfRangeException(result, "Query must return at least one result"); + } + + [Fact] + public void Should_Map_Count_To_Query_Filter() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.Count = 10; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --number 10 " + + "--server http://octopus --apiKey API-12345", result.Args); + } + + [Fact] + public void Should_Add_ProjectName_To_Query_Filter() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.ProjectName = "Project A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --project \"Project A\" --number 1 " + + "--server http://octopus --apiKey API-12345", result.Args); + } + + [Fact] + public void Should_Add_EnvironmentName_To_Query_Filter() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.EnvironmentName = "Env A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --environment \"Env A\" --number 1 " + + "--server http://octopus --apiKey API-12345", result.Args); + } + + [Fact] + public void Should_Add_TenantName_To_Query_Filter() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.TenantName = "Tenant A"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --tenant \"Tenant A\" --number 1 " + + "--server http://octopus --apiKey API-12345", result.Args); + } + + [Fact] + public void Should_Add_Space_To_Query_Filter_If_Not_Null() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.TenantName = "Tenant A"; + fixture.Settings.Space = "spacename"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --tenant \"Tenant A\" --number 1 " + + "--server http://octopus --apiKey API-12345 --space \"spacename\"", result.Args); + } + + [Fact] + public void Should_Map_All_Query_Filter_Options() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.ProjectName = "Project A"; + fixture.Settings.EnvironmentName = "Env A"; + fixture.Settings.TenantName = "Tenant A"; + fixture.Settings.Count = 5; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("list-deployments --environment \"Env A\" " + + "--project \"Project A\" " + + "--tenant \"Tenant A\" --number 5 " + + "--server http://octopus --apiKey API-12345", result.Args); + } + + [Fact] + public void Should_Throw_If_Octo_Executable_Was_Not_Found() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Octo: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/tools/octopus/Octo.exe", "/bin/tools/octopus/Octo.exe")] + [InlineData("./tools/octopus/Octo.exe", "/Working/tools/octopus/Octo.exe")] + public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/octopusDeploy/Octo.exe", "C:/octopusDeploy/Octo.exe")] + public void Should_Use_Octo_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_Octo_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/Octo.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Octo: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new OctopusDeploymentQuerierFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "Octo: Process returned an error (exit code 1)."); + } + } + + public sealed class TheResultParser + { + [Fact] + public void Should_Return_Empty_List_If_No_Valid_Results_Exist() + { + var parser = new DeploymentQueryResultParser(); + var t = Results.Split(new[] { System.Environment.NewLine }, StringSplitOptions.None); + + var results = parser.ParseResults(t.ToList().Take(4)); + + Assert.Empty(results); + } + + [Fact] + public void Should_Handle_Empty_Response_Gracefully() + { + var parser = new DeploymentQueryResultParser(); + + var results = parser.ParseResults(null); + Assert.Empty(results); + } + + [Fact] + public void Should_Parse_Correct_Response_From_Octo_EXE() + { + var parser = new DeploymentQueryResultParser(); + var t = Results.Split(new[] { System.Environment.NewLine }, StringSplitOptions.None); + + var results = parser.ParseResults(t); + Assert.Equal(3, results.Count()); + + var expected = new OctopusDeployment + { + Assembled = DateTimeOffset.Parse("11/2/2017 12:53:34 -04:00"), + Channel = "Default", + Created = DateTimeOffset.Parse("11/2/2017 12:53:35 -04:00"), + Environment = "Staging", + PackageVersions = "Package A 0.9.104; Package B 0.9.104", + ProjectName = "Project A", + ReleaseNotesHtml = "

Project A

", + Version = "0.9.104" + }; + var actual = results.First(); + Assert.Equal(expected.Environment, actual.Environment); + Assert.Equal(expected.Assembled, actual.Assembled); + Assert.Equal(expected.Channel, actual.Channel); + Assert.Equal(expected.Created, actual.Created); + Assert.Equal(expected.PackageVersions, actual.PackageVersions); + Assert.Equal(expected.ProjectName, actual.ProjectName); + Assert.Equal(expected.ReleaseNotesHtml, actual.ReleaseNotesHtml); + Assert.Equal(expected.Version, actual.Version); + + Assert.Equal("Package A 0.5.114; Package B 0.5.114", results.Last().PackageVersions); + } + + private string Results = +@"Octopus Deploy Command Line Tool, version 4.24.4 + +Handshaking with Octopus server: http://octo +Handshake successful. Octopus version: 3.17.2; API version: 3.0.0 +Authenticated as: user (a service account) +Loading projects... +Loading environments... +Loading tenants... +Loading deployments... +Showing 3 results... + - Project: Project A + - Environment: Staging + - Channel: Default + Created: 11/2/2017 12:53:35 -04:00 + Version: 0.9.104 + Assembled: 11/2/2017 12:53:34 -04:00 + Package Versions: Package A 0.9.104; Package B 0.9.104 + Release Notes:

Project A

+ + - Project: Project B + - Environment: Production + - Channel: Default + Created: 11/1/2017 16:43:36 -04:00 + Version: 0.5.115 + Assembled: 11/1/2017 16:43:35 -04:00 + Package Versions: Package C 0.5.115; Package D 0.5.115 + Release Notes:

Project B

+ + - Project: Project A + - Environment: Production + - Channel: Default + Created: 11/1/2017 16:31:09 -04:00 + Version: 0.5.114 + Assembled: 11/1/2017 16:31:09 -04:00 + Package Versions: Package A 0.5.114; Package B 0.5.114 + Release Notes:

Public Website

lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor lorem ipsum dolor

+ +"; + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs b/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs index 687c6d58a2..7b3ac4994d 100644 --- a/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/OpenCover/OpenCoverTests.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.NUnit; +using Cake.Common.Tools.OpenCover; using Cake.Common.Tools.XUnit; using Cake.Core.IO; using Cake.Testing; @@ -25,7 +27,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -39,8 +41,7 @@ public void Should_Throw_If_Action_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "action"); - + AssertEx.IsArgumentNullException(result, "action"); } [Fact] @@ -54,7 +55,7 @@ public void Should_Throw_If_Output_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputPath"); + AssertEx.IsArgumentNullException(result, "outputPath"); } [Fact] @@ -68,7 +69,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -82,7 +83,7 @@ public void Should_Throw_If_No_Tool_Was_Intercepted() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "No tool was started."); + AssertEx.IsCakeException(result, "No tool was started."); } [Fact] @@ -95,7 +96,7 @@ public void Should_Capture_Tool_And_Arguments_From_Action() var result = fixture.Run(); // Then - Assert.Equal("-target:\"/Working/tools/Test.exe\" "+ + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + "-targetargs:\"-argument\" " + "-register:user -output:\"/Working/result.xml\"", result.Args); } @@ -143,6 +144,24 @@ public void Should_Append_Filters() "-register:user -output:\"/Working/result.xml\"", result.Args); } + [Fact] + public void Should_Append_CaseSensitive_Filters() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.Filters.Add("Value"); + fixture.Settings.Filters.Add("value"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-filter:\"Value value\" " + + "-register:user -output:\"/Working/result.xml\"", result.Args); + } + [Fact] public void Should_Append_Attribute_Filters() { @@ -228,7 +247,7 @@ public void Should_Use_Specified_Register() { // Given var fixture = new OpenCoverFixture(); - fixture.Settings.Register = "Path32"; + fixture.Settings.Register = new OpenCoverRegisterOptionDll("Path32"); // When var result = fixture.Run(); @@ -239,6 +258,54 @@ public void Should_Use_Specified_Register() "-register:Path32 -output:\"/Working/result.xml\"", result.Args); } + [Fact] + public void Should_Use_No_Register() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.Register = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-output:\"/Working/result.xml\"", result.Args); + } + + [Fact] + public void Should_Use_Admin_Register() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.Register = new OpenCoverRegisterOptionAdmin(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register -output:\"/Working/result.xml\"", result.Args); + } + + [Fact] + public void Should_Use_User_Register() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.Register = new OpenCoverRegisterOptionUser(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\"", result.Args); + } + [Fact] public void Should_Add_ReturnTargetCode_If_ReturnTargetCodeOffset_Is_Set() { @@ -273,6 +340,182 @@ public void Should_Append_SkipAutoProps() "-skipautoprops " + "-register:user -output:\"/Working/result.xml\"", result.Args); } + + [Fact] + public void Should_Append_OldStyle() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.OldStyle = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-oldStyle " + + "-register:user -output:\"/Working/result.xml\"", result.Args); + } + + [Fact] + public void Should_Append_MergeOutput() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.MergeOutput = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-mergeoutput " + + "-register:user -output:\"/Working/result.xml\"", result.Args); + } + + [Fact] + public void Should_Append_Exclude_Directories() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.ExcludeDirectories.Add("dir1"); + fixture.Settings.ExcludeDirectories.Add("dir2"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-excludedirs:\"/Working/dir1;/Working/dir2\"", result.Args); + } + + [Fact] + public void Should_Append_LogLevel() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.LogLevel = OpenCoverLogLevel.All; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-log:All", result.Args); + } + + [Fact] + public void Should_Append_HideSkipped() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.HideSkippedOption = OpenCoverHideSkippedOption.File + | OpenCoverHideSkippedOption.MissingPdb + | OpenCoverHideSkippedOption.Filter + | OpenCoverHideSkippedOption.All + | OpenCoverHideSkippedOption.Attribute; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-hideskipped:All", result.Args); + } + + [Fact] + public void Should_Append_MergeByHash() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.MergeByHash = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-mergebyhash", result.Args); + } + + [Fact] + public void Should_Append_NoDefaultFilters() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.NoDefaultFilters = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-nodefaultfilters", result.Args); + } + + [Fact] + public void Should_Append_Search_Directories() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.SearchDirectories.Add("dir1"); + fixture.Settings.SearchDirectories.Add("dir2"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-searchdirs:\"/Working/dir1;/Working/dir2\"", result.Args); + } + + [Fact] + public void Should_Append_Service() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.IsService = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-service", result.Args); + } + + [Fact] + public void Should_Append_TargetDir() + { + // Given + var fixture = new OpenCoverFixture(); + fixture.Settings.TargetDirectory = "TarDir"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-target:\"/Working/tools/Test.exe\" " + + "-targetargs:\"-argument\" " + + "-register:user -output:\"/Working/result.xml\" " + + "-targetdir:\"/Working/TarDir\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ReportGenerator/ReportGeneratorRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/ReportGenerator/ReportGeneratorRunnerTests.cs index 120043528a..522794f469 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ReportGenerator/ReportGeneratorRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ReportGenerator/ReportGeneratorRunnerTests.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Linq; using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.ReportGenerator; using Cake.Core; @@ -25,7 +28,7 @@ public void Should_Throw_If_Reports_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "reports"); + AssertEx.IsArgumentNullException(result, "reports"); } [Fact] @@ -39,7 +42,7 @@ public void Should_Throw_If_Reports_Are_Empty() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentException(result, "reports", "reports must not be empty"); + AssertEx.IsArgumentException(result, "reports", "reports must not be empty"); } [Fact] @@ -53,7 +56,7 @@ public void Should_Throw_If_TargetDir_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "targetDir"); + AssertEx.IsArgumentNullException(result, "targetDir"); } [Fact] @@ -67,7 +70,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -110,7 +113,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("ReportGenerator: Process was not started.", result.Message); + Assert.Equal("ReportGenerator: Process was not started.", result?.Message); } [Fact] @@ -125,7 +128,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("ReportGenerator: Process returned an error (exit code 1).", result.Message); + Assert.Equal("ReportGenerator: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -155,18 +158,43 @@ public void Should_Set_Reports_And_Target_Directory() Assert.Equal("\"-reports:/Working/report1.xml;/Working/report2.xml\" \"-targetdir:/Working/output\"", result.Args); } - [Fact] - public void Should_Set_Report_Types() + [Theory] + [InlineData("Badges", 1)] + [InlineData("Html", 2)] + [InlineData("HtmlSummary", 3)] + [InlineData("Latex", 4)] + [InlineData("LatexSummary", 5)] + [InlineData("TextSummary", 6)] + [InlineData("Xml", 7)] + [InlineData("XmlSummary", 8)] + [InlineData("Cobertura", 9)] + [InlineData("CsvSummary", 10)] + [InlineData("HtmlChart", 11)] + [InlineData("HtmlInline", 12)] + [InlineData("HtmlInline_AzurePipelines", 13)] + [InlineData("PngChart", 14)] + [InlineData("MHtml", 15)] + [InlineData("SonarQube", 16)] + [InlineData("HtmlInline_AzurePipelines_Dark", 17)] + [InlineData("Clover", 18)] + [InlineData("JsonSummary", 19)] + [InlineData("lcov", 20)] + [InlineData("TeamCitySummary", 21)] + [InlineData("MarkdownSummary", 22)] + [InlineData("MarkdownAssembliesSummary", 23)] + [InlineData("MarkdownSummaryGithub", 24)] + public void Should_Set_Report_Types(string expected, int enumValue) { // Given var fixture = new ReportGeneratorRunnerFixture(); - fixture.Settings.ReportTypes = new[] { ReportGeneratorReportType.Html, ReportGeneratorReportType.Latex }; + fixture.Settings.ReportTypes = new[] { (ReportGeneratorReportType)enumValue }; // When var result = fixture.Run(); // Then - Assert.Equal("\"-reports:/Working/report.xml\" \"-targetdir:/Working/output\" \"-reporttypes:Html;Latex\"", result.Args); + Assert.True(Enum.IsDefined(fixture.Settings.ReportTypes.First())); + Assert.Equal($"\"-reports:/Working/report.xml\" \"-targetdir:/Working/output\" \"-reporttypes:{expected}\"", result.Args); } [Fact] @@ -240,4 +268,4 @@ public void Should_Set_Verbosity() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/ReportUnit/ReportUnitRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/ReportUnit/ReportUnitRunnerTests.cs index b68f4dacf3..7aa87209dc 100644 --- a/src/Cake.Common.Tests/Unit/Tools/ReportUnit/ReportUnitRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/ReportUnit/ReportUnitRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.ReportUnit; using Cake.Core; using Cake.Testing; @@ -23,7 +24,7 @@ public void Should_Throw_If_InputFolder_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "inputFolder"); + AssertEx.IsArgumentNullException(result, "inputFolder"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -65,7 +66,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("ReportUnit: Process was not started.", result.Message); + Assert.Equal("ReportUnit: Process was not started.", result?.Message); } [Fact] @@ -80,7 +81,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("ReportUnit: Process returned an error (exit code 1).", result.Message); + Assert.Equal("ReportUnit: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -110,7 +111,7 @@ public void Should_Throw_If_InputFile_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "inputFile"); + AssertEx.IsArgumentNullException(result, "inputFile"); } [Fact] @@ -124,7 +125,7 @@ public void Should_Throw_If_OutputFile_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "outputFile"); + AssertEx.IsArgumentNullException(result, "outputFile"); } [Fact] @@ -138,7 +139,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -166,7 +167,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("ReportUnit: Process was not started.", result.Message); + Assert.Equal("ReportUnit: Process was not started.", result?.Message); } [Fact] @@ -181,7 +182,7 @@ public void Should_Throw_If_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("ReportUnit: Process returned an error (exit code 1).", result.Message); + Assert.Equal("ReportUnit: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -198,4 +199,4 @@ public void Should_Use_Provided_Files_In_Process_Arguments() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/Roundhouse/RoundhouseRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/Roundhouse/RoundhouseRunnerTests.cs index a9e4de1e4b..66fcaedd62 100644 --- a/src/Cake.Common.Tests/Unit/Tools/Roundhouse/RoundhouseRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/Roundhouse/RoundhouseRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.Roundhouse; using Cake.Core; @@ -26,7 +27,7 @@ public void Should_Throw_if_Roundhouse_Executable_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("Roundhouse: Could not locate executable.", result.Message); + Assert.Equal("Roundhouse: Could not locate executable.", result?.Message); } [Theory] @@ -100,7 +101,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("Roundhouse: Process was not started.", result.Message); + Assert.Equal("Roundhouse: Process was not started.", result?.Message); } [Fact] @@ -115,7 +116,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("Roundhouse: Process returned an error (exit code 1).", result.Message); + Assert.Equal("Roundhouse: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -157,12 +158,20 @@ public void Should_Execute_Process_With_Flags() fixture.Settings.Silent = true; fixture.Settings.WarnOnOneTimeScriptChanges = true; fixture.Settings.WithTransaction = true; + fixture.Settings.Baseline = true; + fixture.Settings.RunAllAnyTimeScripts = true; + fixture.Settings.SearchAllSubdirectoriesInsteadOfTraverse = true; + fixture.Settings.DisableTokenReplacement = true; + fixture.Settings.RunAllAnyTimeScripts = true; + fixture.Settings.Debug = true; + fixture.Settings.DisableOutput = true; + fixture.Settings.DoNotCreateDatabase = true; // When var result = fixture.Run(); // Then - Assert.Equal("--drop --dryrun --restore --silent --w --t", result.Args); + Assert.Equal("--drop --dryrun --restore --silent --baseline --searchallinsteadoftraverse --disabletokens --runallanytimescripts --debug --disableoutput --donotcreatedatabase --w --t", result.Args); } [Fact] @@ -217,4 +226,4 @@ public void Should_Execute_Process_With_Roundhouse_Settings() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolResolverTests.cs b/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolResolverTests.cs index 3d7693c161..5bbe92b8ae 100644 --- a/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolResolverTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolResolverTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Xunit; @@ -21,7 +22,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -35,7 +36,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -49,7 +50,7 @@ public void Should_Throw_If_Registry_Is_Null() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsArgumentNullException(result, "registry"); + AssertEx.IsArgumentNullException(result, "registry"); } } @@ -71,12 +72,76 @@ public void Should_Return_From_Disc_If_Found(bool is64Bit) Assert.NotNull(result); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Return_From_Disc_If_Found_Windows_10(bool is64Bit) + { + // Given + var fixture = new SignToolResolverFixture(is64Bit); + fixture.GivenThatToolExistInKnownPathWindows10(); + + // When + var result = fixture.Resolve(); + + // Then + Assert.NotNull(result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Return_From_Disc_If_Found_App_Certification_Kit(bool is64Bit) + { + // Given + var fixture = new SignToolResolverFixture(is64Bit); + fixture.GivenThatToolExistInKnownPathAppCertificationKit(); + + // When + var result = fixture.Resolve(); + + // Then + Assert.NotNull(result); + } + [Fact] public void Should_Return_From_Registry_If_Found() { // Given var fixture = new SignToolResolverFixture(); - fixture.GivenThatToolHasRegistryKey(); + fixture.GivenThatToolHasRegistryKeyMicrosoftSdks(); + + // When + var result = fixture.Resolve(); + + // Then + Assert.NotNull(result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Return_From_Registry_If_Windows_Kits_Found(bool is64Bit) + { + // Given + var fixture = new SignToolResolverFixture(is64Bit); + fixture.GivenThatToolHasRegistryKeyWindowsKits(); + + // When + var result = fixture.Resolve(); + + // Then + Assert.NotNull(result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_Return_From_Registry_If_Windows_10_Kits_Found(bool is64Bit) + { + // Given + var fixture = new SignToolResolverFixture(is64Bit); + fixture.GivenThatToolHasRegistryKeyWindows10Kits(); // When var result = fixture.Resolve(); @@ -96,7 +161,7 @@ public void Should_Throw_If_Not_Found_On_Disc_And_SDK_Registry_Path_Cannot_Be_Re var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsCakeException(result, "Failed to find signtool.exe."); + AssertEx.IsCakeException(result, "Failed to find signtool.exe."); } [Fact] @@ -109,8 +174,8 @@ public void Should_Throw_If_SignTool_Cannot_Be_Resolved() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsCakeException(result, "Failed to find signtool.exe."); + AssertEx.IsCakeException(result, "Failed to find signtool.exe."); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolSignRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolSignRunnerTests.cs index aa2a50bb7e..e9e2035768 100644 --- a/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolSignRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/SignTool/SignToolSignRunnerTests.cs @@ -1,9 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tools.SignTool; using Cake.Core; +using Cake.Core.IO; using Cake.Testing; using Xunit; @@ -24,7 +27,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -38,7 +41,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -52,24 +55,24 @@ public void Should_Throw_If_Process_Runner_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "processRunner"); + AssertEx.IsArgumentNullException(result, "processRunner"); } } public sealed class TheRunMethod { [Fact] - public void Should_Throw_If_Assembly_Path_Is_Null() + public void Should_Throw_If_Assembly_Paths_Is_Null() { // Given var fixture = new SignToolSignRunnerFixture(); - fixture.AssemblyPath = null; + fixture.AssemblyPaths = null; // When var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPath"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -83,7 +86,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -98,53 +101,54 @@ public void Should_Throw_If_Assembly_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: The assembly '/Working/a.dll' do not exist.", result.Message); + Assert.Equal("SignTool SIGN: The assembly '/Working/a.dll' does not exist.", result?.Message); } [Fact] - public void Should_Throw_If_No_Timestamp_Server_URL_Has_Been_Specified() + public void Should_Throw_If_Certificate_Path_And_Thumbprint_And_Subject_Name_Are_Null() { // Given var fixture = new SignToolSignRunnerFixture(); - fixture.Settings.TimeStampUri = null; + fixture.Settings.CertPath = null; + fixture.Settings.CertThumbprint = null; + fixture.Settings.CertSubjectName = null; // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: Timestamp server URL is required but not specified.", result.Message); + Assert.Equal("SignTool SIGN: One of Certificate path, Certificate thumbprint or Certificate subject name is required but neither are specified.", result?.Message); } [Fact] - public void Should_Throw_If_Certificate_Path_And_Thumbprint_Are_Null() + public void Should_Throw_If_Certificate_Path_And_Thumbprint_Are_Both_Specified() { // Given var fixture = new SignToolSignRunnerFixture(); - fixture.Settings.CertPath = null; - fixture.Settings.CertThumbprint = null; + fixture.Settings.CertThumbprint = "123"; // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: One of Certificate path or Certificate thumbprint is required but neither are specified.", result.Message); + Assert.Equal("SignTool SIGN: Certificate path and Certificate thumbprint cannot be specified together.", result?.Message); } [Fact] - public void Should_Throw_If_Certificate_Path_And_Thumbprint_Are_Both_Specified() + public void Should_Throw_If_Certificate_Path_And_Subject_Name_Are_Both_Specified() { // Given var fixture = new SignToolSignRunnerFixture(); - fixture.Settings.CertThumbprint = "123"; + fixture.Settings.CertSubjectName = "abc"; // When var result = Record.Exception(() => fixture.Run()); // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: Certificate path and Certificate thumbprint cannot be specified together.", result.Message); + Assert.Equal("SignTool SIGN: Certificate path and Certificate subject name cannot be specified together.", result?.Message); } [Fact] @@ -161,7 +165,24 @@ public void Should_Throw_If_Certificate_Thumbprint_And_Password_Are_Both_Specifi // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: Certificate thumbprint and Password cannot be specified together.", result.Message); + Assert.Equal("SignTool SIGN: Certificate thumbprint and Password cannot be specified together.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Certificate_Subject_Name_And_Password_Are_Both_Specified() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.CertPath = null; + fixture.Settings.Password = "123"; + fixture.Settings.CertSubjectName = "abc"; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("SignTool SIGN: Certificate subject name and Password cannot be specified together.", result?.Message); } [Fact] @@ -176,11 +197,11 @@ public void Should_Throw_If_Certificate_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: The certificate '/Working/cert.pfx' do not exist.", result.Message); + Assert.Equal("SignTool SIGN: The certificate '/Working/cert.pfx' does not exist.", result?.Message); } [Fact] - public void Should_Throw_If_Password_Is_Null() + public void Should_Not_Throw_If_Password_Is_Null() { // Given var fixture = new SignToolSignRunnerFixture(); @@ -189,9 +210,23 @@ public void Should_Throw_If_Password_Is_Null() // When var result = Record.Exception(() => fixture.Run()); + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Throw_If_Additional_Certificate_File_Does_Not_Exist() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.AdditionalCertPath = "/Working/ac.cer"; + + // When + var result = Record.Exception(() => fixture.Run()); + // Then Assert.IsType(result); - Assert.Equal("SignTool SIGN: Password is required with Certificate path but not specified.", result.Message); + Assert.Equal("SignTool SIGN: The additional certificate '/Working/ac.cer' does not exist.", result?.Message); } [Fact] @@ -232,7 +267,21 @@ public void Should_Call_Sign_Tool_With_Correct_Parameters() var result = fixture.Run(); // Then - Assert.Equal("SIGN /t \"https://t.com/\" /f \"/Working/cert.pfx\" /p secret \"/Working/a.dll\"", result.Args); + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_CertPath_And_No_Password() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.Password = null; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" \"/Working/a.dll\"", result.Args); } [Fact] @@ -246,7 +295,7 @@ public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Description() var result = fixture.Run(); // Then - Assert.Equal("SIGN /t \"https://t.com/\" /f \"/Working/cert.pfx\" /p secret /d \"DescriptionTest\" \"/Working/a.dll\"", result.Args); + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /d \"DescriptionTest\" \"/Working/a.dll\"", result.Args); } [Fact] @@ -260,7 +309,7 @@ public void Should_Call_Sign_Tool_With_Correct_Parameters_With_DescriptionUri() var result = fixture.Run(); // Then - Assert.Equal("SIGN /t \"https://t.com/\" /f \"/Working/cert.pfx\" /p secret /du \"https://example.com/\" \"/Working/a.dll\"", result.Args); + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /du \"https://example.com/\" \"/Working/a.dll\"", result.Args); } [Fact] @@ -276,8 +325,189 @@ public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Thumbprint() var result = fixture.Run(); // Then - Assert.Equal("SIGN /t \"https://t.com/\" /sha1 \"ThumbprintTest\" \"/Working/a.dll\"", result.Args); + Assert.Equal("SIGN /sha1 \"ThumbprintTest\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Subject_Name() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.CertPath = null; + fixture.Settings.Password = null; + fixture.Settings.CertSubjectName = "SubjectNameTest"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /n \"SubjectNameTest\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Sha256_Digest_Algorithm() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.DigestAlgorithm = SignToolDigestAlgorithm.Sha256; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /fd sha256 /f \"/Working/cert.pfx\" /p secret \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Timestamp_Uri() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.TimeStampUri = new Uri("https://t.com"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /t \"https://t.com/\" /f \"/Working/cert.pfx\" /p secret \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_RFC6131_Timestamp_Uri_And_Sha256_Timestamp_Algorithm() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.TimeStampUri = new Uri("https://t.com"); + fixture.Settings.TimeStampDigestAlgorithm = SignToolDigestAlgorithm.Sha256; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /tr \"https://t.com/\" /td sha256 /f \"/Working/cert.pfx\" /p secret \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Append_Signature() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.AppendSignature = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /as \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Use_Machine_Store() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.UseMachineStore = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /sm \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Thumbprint_And_Multiple_Assemblies() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.CertPath = null; + fixture.Settings.Password = null; + fixture.Settings.CertThumbprint = "ThumbprintTest"; + fixture.AssemblyPaths = new[] + { + new FilePath("./a.dll"), + new FilePath("./foo/b.dll") + }; + fixture.FileSystem.CreateFile("/Working/foo/b.dll"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /sha1 \"ThumbprintTest\" \"/Working/a.dll\" \"/Working/foo/b.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Additional_Cert() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.FileSystem.CreateFile("/Working/ac.cer"); + fixture.Settings.AdditionalCertPath = "/Working/ac.cer"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /ac \"/Working/ac.cer\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Store_Name() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.StoreName = "Special Test Store"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /s \"Special Test Store\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Use_Machine_Store_And_Store_Name() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.UseMachineStore = true; + fixture.Settings.StoreName = "Special Test Store"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /sm /s \"Special Test Store\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Cryptographic_Service_Provider() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.CspName = "Test Service Provider"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /csp \"Test Service Provider\" \"/Working/a.dll\"", result.Args); + } + + [Fact] + public void Should_Call_Sign_Tool_With_Correct_Parameters_With_Private_Key_Container_Name() + { + // Given + var fixture = new SignToolSignRunnerFixture(); + fixture.Settings.PrivateKeyContainerName = "[{{password}}]=TestContainerName"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("SIGN /f \"/Working/cert.pfx\" /p secret /kc \"[{{password}}]=TestContainerName\" \"/Working/a.dll\"", result.Args); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterTests.cs b/src/Cake.Common.Tests/Unit/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterTests.cs index 5ba2b0e9f2..78e6b8d102 100644 --- a/src/Cake.Common.Tests/Unit/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporterTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.SpecFlow.StepDefinitionReport; using Xunit; @@ -21,7 +22,7 @@ public void Should_Throw_If_Project_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "projectFile"); + AssertEx.IsArgumentNullException(result, "projectFile"); } [Fact] @@ -35,7 +36,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -84,4 +85,4 @@ public void Should_Append_BinFolder() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterTests.cs b/src/Cake.Common.Tests/Unit/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterTests.cs index 63d5ec5f6d..2846e379f3 100644 --- a/src/Cake.Common.Tests/Unit/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporterTests.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.SpecFlow.TestExecutionReport; +using Cake.Common.Tools.MSTest; using Cake.Common.Tools.NUnit; using Cake.Common.Tools.XUnit; -using Cake.Common.Tools.MSTest; using Cake.Core; using Cake.Core.IO; using Cake.Testing; @@ -27,7 +28,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -41,8 +42,7 @@ public void Should_Throw_If_Action_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "action"); - + AssertEx.IsArgumentNullException(result, "action"); } [Fact] @@ -56,7 +56,7 @@ public void Should_Throw_If_Project_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "projectFile"); + AssertEx.IsArgumentNullException(result, "projectFile"); } [Fact] @@ -70,7 +70,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -84,7 +84,7 @@ public void Should_Throw_If_No_Tool_Was_Intercepted() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "No tool was started."); + AssertEx.IsCakeException(result, "No tool was started."); } [Fact] @@ -120,7 +120,7 @@ public void Should_Throw_If_Action_Is_Not_Supported() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "Unsupported tool /Working/tools/Test.exe."); + AssertEx.IsCakeException(result, "Unsupported tool /Working/tools/Test.exe."); } [Theory] @@ -144,7 +144,7 @@ public void Should_Throw_If_Action_Does_Not_Contain_Arguments(string arguments) var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "No arguments were found for tool."); + AssertEx.IsCakeException(result, "No arguments were found for tool."); } [Fact] @@ -179,6 +179,79 @@ public void Should_Append_XsltFile() "/xsltFile:\"/Working/template.xslt\"", result.Args); } + [Fact] + public void Should_Rethrow_Exception_From_Action() + { + // Given + var exception = new CakeException("The exception message"); + var fixture = new SpecFlowTestExecutionReporterFixture(); + fixture.Settings.ThrowOnTestFailure = true; + var intercepting = true; + + fixture.Action = context => + { + context.ProcessRunner.Start( + new FilePath("/Working/tools/MSTest.exe"), + new ProcessSettings() + { + Arguments = "/resultsfile:\"/Working/TestResult.trx\"" + }); + + // Quick fix to avoid throwing exception while intercepting action + if (intercepting) + { + intercepting = false; + } + else + { + throw exception; + } + }; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, exception.Message); + } + + [Fact] + public void Should_Not_Rethrow_Exception_From_Action() + { + // Given + var exception = new CakeException("The exception message"); + var fixture = new SpecFlowTestExecutionReporterFixture(); + fixture.Settings.ThrowOnTestFailure = false; + var intercepting = true; + + fixture.Action = context => + { + var process = context.ProcessRunner.Start( + new FilePath("/Working/tools/MSTest.exe"), + new ProcessSettings() + { + Arguments = "/resultsfile:\"/Working/TestResult.trx\"" + }); + + // Quick fix to avoid throwing exception while intercepting action + if (intercepting) + { + intercepting = false; + } + else + { + throw exception; + } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("mstestexecutionreport \"/Working/Tests.csproj\" " + + "/testResult:\"/Working/TestResult.trx\"", result.Args); + } + [Fact] public void Should_Capture_XUnit2() { @@ -218,7 +291,7 @@ public void Should_Capture_XUnit2_And_Throw_If_NUnit_Is_Missing() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "XUnit2 must contain argument \"-nunit \""); + AssertEx.IsCakeException(result, "XUnit2 must contain argument \"-nunit \""); } [Fact] @@ -231,8 +304,7 @@ public void Should_Capture_NUnit3() var nUnit3Settings = new NUnit3Settings { ShadowCopy = false, - Results ="/Working/TestResult.xml", - ResultFormat = "nunit2", + Results = new[] { new NUnit3Result { FileName = "/Working/TestResult.xml", Format = "nunit2" } }, Labels = NUnit3Labels.All, OutputFile = "/Working/TestResult.txt" }; @@ -274,7 +346,7 @@ public void Should_Capture_NUnit3_And_Throw_If_Result_Is_Missing() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NUnit3 must contain argument \"--result=;format=nunit2\""); + AssertEx.IsCakeException(result, "NUnit3 must contain argument \"--result=;format=nunit2\""); } [Fact] @@ -287,8 +359,7 @@ public void Should_Capture_NUnit3_And_Throw_If_Output_Is_Missing() var nUnit3Settings = new NUnit3Settings { ShadowCopy = false, - Results = "/Working/TestResult.xml", - ResultFormat = "nunit2" + Results = new[] { new NUnit3Result { FileName = "/Working/TestResult.xml", Format = "nunit2" } }, }; fixture.Action = context => @@ -300,7 +371,7 @@ public void Should_Capture_NUnit3_And_Throw_If_Output_Is_Missing() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NUnit3 must contain argument \"--out=\""); + AssertEx.IsCakeException(result, "NUnit3 must contain argument \"--out=\""); } [Fact] @@ -313,8 +384,7 @@ public void Should_Capture_NUnit3_And_Throw_If_Labels_Is_Missing() var nUnit3Settings = new NUnit3Settings { ShadowCopy = false, - Results = "/Working/TestResult.xml", - ResultFormat = "nunit2", + Results = new[] { new NUnit3Result { FileName = "/Working/TestResult.xml", Format = "nunit2" } }, OutputFile = "/Working/TestResult.txt" }; @@ -327,7 +397,7 @@ public void Should_Capture_NUnit3_And_Throw_If_Labels_Is_Missing() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "NUnit3 must contain argument \"--labels=All\""); + AssertEx.IsCakeException(result, "NUnit3 must contain argument \"--labels=All\""); } [Fact] @@ -379,8 +449,8 @@ public void Should_Capture_MSTest_And_Throw_If_ResultsFile_Is_Missing() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsCakeException(result, "MSTest must contain argument \"/resultsfile:\""); + AssertEx.IsCakeException(result, "MSTest must contain argument \"/resultsfile:\""); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTemplateAliasTests.cs b/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTemplateAliasTests.cs index ff1e0e3a35..4028af29d3 100644 --- a/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTemplateAliasTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTemplateAliasTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.TextTransform; using Cake.Common.Tools.TextTransform; using Cake.Core; @@ -23,7 +24,7 @@ public void Should_Throw_If_Context_Is_Null() var result = Record.Exception(() => TextTransformAliases.TransformTemplate(null, fixture.SourceFile)); // Then - Assert.IsArgumentNullException(result, "context"); + AssertEx.IsArgumentNullException(result, "context"); } [Fact] @@ -36,8 +37,8 @@ public void Should_Throw_If_Source_File_Is_Null() var result = Record.Exception(() => TextTransformAliases.TransformTemplate(context, null)); // Then - Assert.IsArgumentNullException(result, "sourceFile"); + AssertEx.IsArgumentNullException(result, "sourceFile"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTransformRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTransformRunnerTests.cs index 8cdfff7e63..113f41e95e 100644 --- a/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTransformRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/TextTransform/TextTransformRunnerTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Common.Tests.Fixtures.Tools.TextTransform; using Cake.Core; using Cake.Testing; @@ -23,7 +25,7 @@ public void Should_Throw_If_Source_File_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "sourceFile"); + AssertEx.IsArgumentNullException(result, "sourceFile"); } [Fact] @@ -37,7 +39,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -52,7 +54,7 @@ public void Should_Throw_If_Text_Transform_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("TextTransform: Could not locate executable.", result.Message); + Assert.Equal("TextTransform: Could not locate executable.", result?.Message); } [Theory] @@ -123,7 +125,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("TextTransform: Process was not started.", result.Message); + Assert.Equal("TextTransform: Process was not started.", result?.Message); } [Fact] @@ -138,7 +140,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("TextTransform: Process returned an error (exit code 1).", result.Message); + Assert.Equal("TextTransform: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -210,6 +212,38 @@ public void Should_Add_Include_Directory_If_Provided() // Then Assert.Equal("-I \"/Working/Transforms\" \"/Working/Test.tt\"", result.Args); } + + [Fact] + public void Should_Add_Class_If_Provided() + { + // Given + var fixture = new TextTransformFixture(); + fixture.Settings.Class = "HelloWorld"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("--class=\"HelloWorld\" \"/Working/Test.tt\"", result.Args); + } + + [Fact] + public void Should_Add_Properties_If_Provided() + { + // Given + var fixture = new TextTransformFixture(); + fixture.Settings.Properties = new Dictionary() + { + { "FirstName", "John" }, + { "LastName", "Doe" } + }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-p:\"FirstName\"=\"John\" -p:\"LastName\"=\"Doe\" \"/Working/Test.tt\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/VSTest/VSTestRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSTest/VSTestRunnerTests.cs index 5acad86b0e..d9b06f3882 100644 --- a/src/Cake.Common.Tests/Unit/Tools/VSTest/VSTestRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/VSTest/VSTestRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.VSTest; using Cake.Core; @@ -24,7 +25,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Settings_Are_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -53,7 +54,7 @@ public void Should_Throw_If_Tool_Path_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("VSTest: Could not locate executable.", result.Message); + Assert.Equal("VSTest: Could not locate executable.", result?.Message); } [Theory] @@ -90,10 +91,21 @@ public void Should_Use_VSTest_From_Tool_Path_If_Provided_On_Windows(string toolP } [Theory] - [InlineData("/ProgramFilesX86/Microsoft Visual Studio 15.0/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] [InlineData("/ProgramFilesX86/Microsoft Visual Studio 14.0/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] [InlineData("/ProgramFilesX86/Microsoft Visual Studio 12.0/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] [InlineData("/ProgramFilesX86/Microsoft Visual Studio 11.0/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Enterprise/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/Community/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2017/BuildTools/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Enterprise/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/Community/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2019/BuildTools/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFiles/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFiles/Microsoft Visual Studio/2022/Professional/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFiles/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] + [InlineData("/ProgramFilesX86/Microsoft Visual Studio/2022/BuildTools/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe")] public void Should_Use_Available_Tool_Path(string existingToolPath) { // Given @@ -108,6 +120,30 @@ public void Should_Use_Available_Tool_Path(string existingToolPath) Assert.Equal(existingToolPath, result.Path.FullPath); } + [Theory] + [InlineData("/ProgramFiles/Microsoft Visual Studio/2022/Preview/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe", true)] + [InlineData("/ProgramFiles/Microsoft Visual Studio/2022/Preview/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe", false)] + public void Should_Use_Available_Preview_Tool_Path_Only_If_Preview_Is_Set(string previewToolPath, bool allowPreview) + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.FileSystem.CreateFile(previewToolPath); + fixture.Settings.AllowPreviewVersion = allowPreview; + + // When + var result = fixture.Run(); + + // Then + if (allowPreview) + { + Assert.Equal(previewToolPath, result.Path.FullPath); + } + else + { + Assert.NotEqual(previewToolPath, result.Path.FullPath); + } + } + [Fact] public void Should_Set_Working_Directory() { @@ -133,7 +169,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("VSTest: Process was not started.", result.Message); + Assert.Equal("VSTest: Process was not started.", result?.Message); } [Fact] @@ -148,11 +184,11 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("VSTest: Process returned an error (exit code 1).", result.Message); + Assert.Equal("VSTest: Process returned an error (exit code 1).", result?.Message); } [Fact] - public void Should_Not_Use_Isolation_By_Default() + public void Should_Have_No_Args_By_Default() { // Given var fixture = new VSTestRunnerFixture(); @@ -178,58 +214,188 @@ public void Should_Use_Isolation_If_Enabled_In_Settings() Assert.Equal("\"/Working/Test1.dll\" /InIsolation", result.Args); } + [Fact] + public void Should_Enable_UseVsixExtensions_If_Enabled_In_Settings() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.UseVsixExtensions = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /UseVsixExtensions:true", result.Args); + } + + [Fact] + public void Should_Disable_UseVsixExtensions_If_Disabled_In_Settings() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.UseVsixExtensions = false; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /UseVsixExtensions:false", result.Args); + } + + [Fact] + public void Should_Use_TestAdapterPath_If_Provided() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.TestAdapterPath = new DirectoryPath("Path to/test adapters"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /TestAdapterPath:\"/Working/Path to/test adapters\"", result.Args); + } + [Fact] public void Should_Use_Logger_If_Provided() { - //Given + // Given var fixture = new VSTestRunnerFixture(); - fixture.Settings.Logger = VSTestLogger.Trx; + fixture.Settings.Logger = "MyCustomLogger"; - //When + // When var result = fixture.Run(); - Assert.Equal("\"/Working/Test1.dll\" /Logger:trx", result.Args); + // Then + Assert.Equal("\"/Working/Test1.dll\" /Logger:MyCustomLogger", result.Args); + } + + [Fact] + public void Should_Use_Diag_If_Provided() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.Diag = new FilePath("Path to/diag log.txt"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /Diag:\"/Working/Path to/diag log.txt\"", result.Args); + } + + [Fact] + public void Should_Use_ResultsDirectory_If_Provided() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.ResultsDirectory = new DirectoryPath("./Path to/"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /ResultsDirectory:\"/Working/Path to\"", result.Args); } [Fact] public void Should_Use_SettingsFile_If_Provided() { - //Given + // Given var fixture = new VSTestRunnerFixture(); fixture.Settings.SettingsFile = new FilePath("Local.RunSettings"); - //When + // When var result = fixture.Run(); - Assert.Equal("\"/Working/Test1.dll\" /Settings:Local.RunSettings", result.Args); + // Then + Assert.Equal("\"/Working/Test1.dll\" /Settings:\"/Working/Local.RunSettings\"", result.Args); + } + + [Fact] + public void Should_Quote_Absolute_SettingsFile_Path() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.SettingsFile = new FilePath("Filename with spaces.RunSettings"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /Settings:\"/Working/Filename with spaces.RunSettings\"", result.Args); + } + + [Fact] + public void Should_Use_Parallel_If_Enabled_In_Settings() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.Parallel = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /Parallel", result.Args); + } + + [Fact] + public void Should_Use_EnableCodeCoverage_If_Enabled_In_Settings() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.EnableCodeCoverage = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /EnableCodeCoverage", result.Args); } [Fact] public void Should_Use_PlatformArchitecture_If_Provided() { - //Given + // Given var fixture = new VSTestRunnerFixture(); fixture.Settings.PlatformArchitecture = VSTestPlatform.x64; - //When + // When var result = fixture.Run(); + // Then Assert.Equal("\"/Working/Test1.dll\" /Platform:x64", result.Args); } [Fact] public void Should_Use_FrameworkVersion_If_Provided() { - //Given + // Given var fixture = new VSTestRunnerFixture(); fixture.Settings.FrameworkVersion = VSTestFrameworkVersion.NET40; - //When + // When var result = fixture.Run(); + // Then Assert.Equal("\"/Working/Test1.dll\" /Framework:Framework40", result.Args); } + [Fact] + public void Should_Use_TestCaseFilter_If_Provided() + { + // Given + var fixture = new VSTestRunnerFixture(); + fixture.Settings.TestCaseFilter = "(FullyQualifiedName~Nightly|Name=MyTestMethod)"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" /TestCaseFilter:\"(FullyQualifiedName~Nightly|Name=MyTestMethod)\"", result.Args); + } + [Fact] public void Should_Add_FilePath_For_Each_Assembly() { @@ -244,4 +410,4 @@ public void Should_Add_FilePath_For_Each_Assembly() Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\"", result.Args); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/All/VSWhereAllTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/All/VSWhereAllTests.cs new file mode 100644 index 0000000000..db55e415d0 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/All/VSWhereAllTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.VSWhere.All; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere.All +{ + public sealed class VSWhereAllTests + { + public sealed class TheAllMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_VSWhere_Executable_Was_Not_Found() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/vswhere/vswhere.exe", "/bin/vswhere/vswhere.exe")] + [InlineData("./tools/vswhere/vswhere.exe", "/Working/tools/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/vswhere/vswhere.exe", "C:/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_VSWhere_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new VSWhereAllFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Program86/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new VSWhereAllFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-all -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.Version = "15"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-all -version \"15\" -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Requires_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.Requires = "Test.Component"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-all -requires Test.Component -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Not_Include_Property_To_Arguments_If_Set_To_Empty() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.ReturnProperty = string.Empty; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-all -nologo", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereAllFixture(); + fixture.Settings.IncludePrerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-all -property installationPath -prerelease -nologo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/Latest/VSWhereLatestTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Latest/VSWhereLatestTests.cs new file mode 100644 index 0000000000..75b7452127 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Latest/VSWhereLatestTests.cs @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.VSWhere.Latest; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere.Latest +{ + public class VSWhereLatestTests + { + public sealed class TheLatestMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_VSWhere_Executable_Was_Not_Found() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/vswhere/vswhere.exe", "/bin/vswhere/vswhere.exe")] + [InlineData("./tools/vswhere/vswhere.exe", "/Working/tools/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/vswhere/vswhere.exe", "C:/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_VSWhere_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new VSWhereLatestFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Program86/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new VSWhereLatestFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.Version = "15"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -version \"15\" -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Requires_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.Requires = "Test.Component"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -requires Test.Component -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Not_Include_Property_To_Arguments_If_Set_To_Empty() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.ReturnProperty = string.Empty; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -nologo", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.IncludePrerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -property installationPath -prerelease -nologo", result.Args); + } + + [Theory] + [InlineData("Microsoft.VisualStudio.Product.BuildTools")] + [InlineData("Microsoft.VisualStudio.Product.BuildTools Microsoft.VisualStudio.Product.Enterprise")] + [InlineData("*")] + public void Should_Add_Products_To_Arguments_If_Set(string productFilter) + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.Products = productFilter; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal($"-products \"{productFilter}\" -latest -property installationPath -nologo", result.Args); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData(" ")] + public void Should_Not_Include_Products_If_Set_To_Empty(string productFilter) + { + // Given + var fixture = new VSWhereLatestFixture(); + fixture.Settings.Products = productFilter; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-latest -property installationPath -nologo", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/Legacy/VSWhereLegacyTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Legacy/VSWhereLegacyTests.cs new file mode 100644 index 0000000000..177ceda643 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Legacy/VSWhereLegacyTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.VSWhere.Legacy; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere.Legacy +{ + public class VSWhereLegacyTests + { + public sealed class TheLegacyMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_VSWhere_Executable_Was_Not_Found() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/vswhere/vswhere.exe", "/bin/vswhere/vswhere.exe")] + [InlineData("./tools/vswhere/vswhere.exe", "/Working/tools/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/vswhere/vswhere.exe", "C:/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_VSWhere_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new VSWhereLegacyFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Program86/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new VSWhereLegacyFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-legacy -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.Version = "15"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-legacy -version \"15\" -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Not_Include_Property_To_Arguments_If_Set_To_Empty() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.ReturnProperty = string.Empty; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-legacy -nologo", result.Args); + } + + [Fact] + public void Should_Include_Latest_To_Arguments_If_Set_To_True() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.Latest = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-legacy -latest -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereLegacyFixture(); + fixture.Settings.IncludePrerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-legacy -property installationPath -prerelease -nologo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/Product/VSWhereProductTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Product/VSWhereProductTests.cs new file mode 100644 index 0000000000..d89c5bdaa1 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/Product/VSWhereProductTests.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.VSWhere.Product; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere.Product +{ + public class VSWhereProductTests + { + public sealed class TheProductsMethod + { + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_VSWhere_Executable_Was_Not_Found() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Could not locate executable."); + } + + [Theory] + [InlineData("/bin/vswhere/vswhere.exe", "/bin/vswhere/vswhere.exe")] + [InlineData("./tools/vswhere/vswhere.exe", "/Working/tools/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/vswhere/vswhere.exe", "C:/vswhere/vswhere.exe")] + public void Should_Use_VSWhere_Executable_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process was not started."); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsCakeException(result, "VSWhere: Process returned an error (exit code 1)."); + } + + [Fact] + public void Should_Find_VSWhere_Executable_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new VSWhereProductFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Program86/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Add_Mandatory_Arguments() + { + // Given + var fixture = new VSWhereProductFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-products Microsoft.VisualStudio.Product.BuildTools -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Version_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.Version = "15"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-products Microsoft.VisualStudio.Product.BuildTools -version \"15\" -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Requires_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.Requires = "Test.Component"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-products Microsoft.VisualStudio.Product.BuildTools -requires Test.Component -property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Not_Include_Property_To_Arguments_If_Set_To_Empty() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.ReturnProperty = string.Empty; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-products Microsoft.VisualStudio.Product.BuildTools -nologo", result.Args); + } + + [Fact] + public void Should_Not_Include_Products_If_Set_To_Empty() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.Products = string.Empty; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-property installationPath -nologo", result.Args); + } + + [Fact] + public void Should_Add_Prerelease_To_Arguments_If_Set() + { + // Given + var fixture = new VSWhereProductFixture(); + fixture.Settings.IncludePrerelease = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-products Microsoft.VisualStudio.Product.BuildTools -property installationPath -prerelease -nologo", result.Args); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereAliasesTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereAliasesTests.cs new file mode 100644 index 0000000000..e48b91b071 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereAliasesTests.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.VSWhere; +using Cake.Core; +using NSubstitute; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere +{ + public sealed class VSWhereAliasesTests + { + public sealed class TheLegacyMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereLegacy(null, true)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereLegacy(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + } + + public sealed class TheLatestMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereLatest(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereLatest(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + } + + public sealed class TheAllMethod + { + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereAll(null)); + + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereAll(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + } + + public sealed class TheProductMethod + { + [Fact] + public void Should_Throw_If_Products_Are_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereProducts(context, null)); + + // Then + AssertEx.IsArgumentNullException(result, "products"); + } + + [Fact] + public void Should_Throw_If_Settings_Are_Null() + { + // Given + var context = Substitute.For(); + + // When + var result = Record.Exception(() => VSWhereAliases.VSWhereProducts(context, "Community", null)); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereToolTests.cs b/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereToolTests.cs new file mode 100644 index 0000000000..3c3d0eb5ad --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Tools/VSWhere/VSWhereToolTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tests.Fixtures.Tools.VSWhere; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.VSWhere +{ + public sealed class VSWhereToolTests + { + public sealed class ToolResolution + { + [Fact] + public void Should_Return_The_Registered_VSWhere_If_VSWhere_Is_Registered() + { + // Given + var fixture = new VSWhereToolFixture(is64BitOperativeSystem: true); + + var registeredVSWhere = fixture.FileSystem.CreateFile("/Registered/vswhere.exe"); + fixture.Tools.RegisterFile(registeredVSWhere.Path); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(registeredVSWhere.Path.FullPath, result.Path.FullPath); + } + + [Fact] + public void Should_Return_The_VSWhere_Default_Path_64Bit() + { + // Given + var fixture = new VSWhereToolFixture(is64BitOperativeSystem: true); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Program86/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + } + +#pragma warning disable SA1005 // Single line comments should begin with single space + //[Fact] + //public void Should_Return_The_VSWhere_Default_Path_32Bit() + //{ + // // Given + // var fixture = new VSWhereToolFixture(is64BitOperativeSystem: false); + + // // When + // var result = fixture.Run(); + + // // Then + // Assert.Equal("/Program/Microsoft Visual Studio/Installer/vswhere.exe", result.Path.FullPath); + //} +#pragma warning restore SA1005 // Single line comments should begin with single space + } + } +} diff --git a/src/Cake.Common.Tests/Unit/Tools/WiX/CandleRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/WiX/CandleRunnerTests.cs index 728a4a11c1..43765ad333 100644 --- a/src/Cake.Common.Tests/Unit/Tools/WiX/CandleRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/WiX/CandleRunnerTests.cs @@ -1,331 +1,332 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using Cake.Common.Tests.Fixtures.Tools; -using Cake.Common.Tools.WiX; -using Cake.Core; -using Cake.Core.IO; -using Cake.Testing; -using Cake.Testing.Xunit; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.WiX -{ - public sealed class CandleRunnerTests - { - public sealed class TheConstructor - { - [Fact] - public void Should_Throw_If_Environment_Is_Null() - { - // Given - var fixture = new CandleFixture(); - fixture.Environment = null; - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "environment"); - } - } - - public sealed class TheRunMethod - { - [Fact] - public void Should_Throw_If_Source_Files_Is_Null() - { - // Given - var fixture = new CandleFixture(); - fixture.SourceFiles = null; - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "sourceFiles"); - } - - [Fact] - public void Should_Throw_If_Source_Files_Is_Empty() - { - // Given - var fixture = new CandleFixture(); - fixture.SourceFiles = new List(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("sourceFiles", ((ArgumentException)result).ParamName); - } - - [Fact] - public void Should_Throw_If_Settings_Is_Null() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings = null; - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Candle_Runner_Was_Not_Found() - { - // Given - var fixture = new CandleFixture(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("Candle: Could not locate executable.", result.Message); - } - - [Theory] - [InlineData("/bin/tools/WiX/candle.exe", "/bin/tools/WiX/candle.exe")] - [InlineData("./tools/WiX/candle.exe", "/Working/tools/WiX/candle.exe")] - public void Should_Use_Candle_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal(expected, result.Path.FullPath); - } - - [WindowsTheory] - [InlineData("C:/WiX/candle.exe", "C:/WiX/candle.exe")] - public void Should_Use_Candle_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal(expected, result.Path.FullPath); - } - - [Fact] - public void Should_Find_Candle_Runner_If_Tool_Path_Not_Provided() - { - // Given - var fixture = new CandleFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("/Working/tools/candle.exe", result.Path.FullPath); - } - - [Fact] - public void Should_Use_Provided_Source_Files_In_Process_Arguments() - { - // Given - var fixture = new CandleFixture(); - fixture.SourceFiles.Clear(); - fixture.SourceFiles.Add("./Test.wxs"); - fixture.SourceFiles.Add("./Test2.wxs"); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("\"/Working/Test.wxs\" \"/Working/Test2.wxs\"", result.Args); - } - - [Fact] - public void Should_Set_Working_Directory() - { - // Given - var fixture = new CandleFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new CandleFixture(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("Candle: Process was not started.", result.Message); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new CandleFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("Candle: Process returned an error (exit code 1).", result.Message); - } - - [Theory] - [InlineData(Architecture.IA64, "-arch ia64 \"/Working/Test.wxs\"")] - [InlineData(Architecture.X64, "-arch x64 \"/Working/Test.wxs\"")] - [InlineData(Architecture.X86, "-arch x86 \"/Working/Test.wxs\"")] - public void Should_Add_Architecture_To_Arguments_If_Provided(Architecture arch, string expected) - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.Architecture = arch; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal(expected, result.Args); - } - - [Fact] - public void Should_Add_Defines_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.Defines = new Dictionary(); - fixture.Settings.Defines.Add("Foo", "Bar"); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-dFoo=Bar \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_Extensions_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.Extensions = new[] { "WixUIExtension" }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-ext WixUIExtension \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_FIPS_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.FIPS = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-fips \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_NoLogo_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.NoLogo = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-nologo \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_Output_Directory_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.OutputDirectory = "obj"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-o \"/Working/obj\\\\\" \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_Pedantic_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.Pedantic = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-pedantic \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_Show_Source_Trace_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.ShowSourceTrace = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-trace \"/Working/Test.wxs\"", result.Args); - } - - [Fact] - public void Should_Add_Verbose_To_Arguments_If_Provided() - { - // Given - var fixture = new CandleFixture(); - fixture.Settings.Verbose = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-v \"/Working/Test.wxs\"", result.Args); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Common.Tools.WiX; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.WiX +{ + public sealed class CandleRunnerTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var fixture = new CandleFixture(); + fixture.Environment = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + } + + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Source_Files_Is_Null() + { + // Given + var fixture = new CandleFixture(); + fixture.SourceFiles = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "sourceFiles"); + } + + [Fact] + public void Should_Throw_If_Source_Files_Is_Empty() + { + // Given + var fixture = new CandleFixture(); + fixture.SourceFiles = new List(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("sourceFiles", ((ArgumentException)result)?.ParamName); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Candle_Runner_Was_Not_Found() + { + // Given + var fixture = new CandleFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Candle: Could not locate executable.", result?.Message); + } + + [Theory] + [InlineData("/bin/tools/WiX/candle.exe", "/bin/tools/WiX/candle.exe")] + [InlineData("./tools/WiX/candle.exe", "/Working/tools/WiX/candle.exe")] + public void Should_Use_Candle_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/WiX/candle.exe", "C:/WiX/candle.exe")] + public void Should_Use_Candle_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_Candle_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new CandleFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/candle.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Use_Provided_Source_Files_In_Process_Arguments() + { + // Given + var fixture = new CandleFixture(); + fixture.SourceFiles.Clear(); + fixture.SourceFiles.Add("./Test.wxs"); + fixture.SourceFiles.Add("./Test2.wxs"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test.wxs\" \"/Working/Test2.wxs\"", result.Args); + } + + [Fact] + public void Should_Set_Working_Directory() + { + // Given + var fixture = new CandleFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new CandleFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Candle: Process was not started.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new CandleFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Candle: Process returned an error (exit code 1).", result?.Message); + } + + [Theory] + [InlineData(Architecture.IA64, "-arch ia64 \"/Working/Test.wxs\"")] + [InlineData(Architecture.X64, "-arch x64 \"/Working/Test.wxs\"")] + [InlineData(Architecture.X86, "-arch x86 \"/Working/Test.wxs\"")] + public void Should_Add_Architecture_To_Arguments_If_Provided(Architecture arch, string expected) + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.Architecture = arch; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Args); + } + + [Fact] + public void Should_Add_Defines_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.Defines = new Dictionary(); + fixture.Settings.Defines.Add("Foo", "Foo Bar"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-dFoo=\"Foo Bar\" \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_Extensions_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.Extensions = new[] { "WixUIExtension" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-ext WixUIExtension \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_FIPS_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.FIPS = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-fips \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-nologo \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_Output_Directory_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.OutputDirectory = "obj"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-o \"/Working/obj\\\\\" \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_Pedantic_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.Pedantic = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-pedantic \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_Show_Source_Trace_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.ShowSourceTrace = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-trace \"/Working/Test.wxs\"", result.Args); + } + + [Fact] + public void Should_Add_Verbose_To_Arguments_If_Provided() + { + // Given + var fixture = new CandleFixture(); + fixture.Settings.Verbose = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-v \"/Working/Test.wxs\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/WiX/HeatRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/WiX/HeatRunnerTests.cs index ea590d91ec..7fe7f2c116 100644 --- a/src/Cake.Common.Tests/Unit/Tools/WiX/HeatRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/WiX/HeatRunnerTests.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - using System; using Cake.Common.Tests.Fixtures.Tools.WiX; using Cake.Common.Tools.WiX.Heat; using Cake.Core; -using Cake.Core.IO; using Cake.Testing; using Cake.Testing.Xunit; using Xunit; @@ -28,7 +26,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -46,7 +44,7 @@ public void Should_Throw_If_Directory_Path_Empty() // Then Assert.IsType(result); - Assert.Equal("directoryPath", ((ArgumentException)result).ParamName); + Assert.Equal("directoryPath", ((ArgumentException)result)?.ParamName); } [Fact] @@ -61,7 +59,7 @@ public void Should_Throw_If_Output_File_Empty() // Then Assert.IsType(result); - Assert.Equal("outputFile", ((ArgumentException)result).ParamName); + Assert.Equal("outputFile", ((ArgumentException)result)?.ParamName); } [Fact] @@ -88,7 +86,7 @@ public void Should_Throw_If_Settings_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -103,7 +101,7 @@ public void Should_Throw_If_Heat_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("Heat: Could not locate executable.", result.Message); + Assert.Equal("Heat: Could not locate executable.", result?.Message); } [Theory] @@ -164,7 +162,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("Heat: Process was not started.", result.Message); + Assert.Equal("Heat: Process was not started.", result?.Message); } [Fact] @@ -179,7 +177,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("Heat: Process returned an error (exit code 1).", result.Message); + Assert.Equal("Heat: Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -187,7 +185,7 @@ public void Should_Add_File_Harvest_Type_If_Provided() { // Given var fixture = new HeatFixture(); - fixture.Settings.HarvestType = WiXHarvestType.File; + fixture.HarvestType = WiXHarvestType.File; // When var result = fixture.Run(); @@ -201,7 +199,7 @@ public void Should_Add_Website_Harvest_Type_If_Provided() { // Given var fixture = new HeatFixture(); - fixture.Settings.HarvestType = WiXHarvestType.Website; + fixture.HarvestType = WiXHarvestType.Website; // When var result = fixture.Run(); @@ -215,8 +213,8 @@ public void Should_Add_Performance_Harvest_Type_If_Provided() { // Given var fixture = new HeatFixture(); - fixture.Settings.HarvestType = WiXHarvestType.Perf; - fixture.HarvestType = "Cake Category"; + fixture.HarvestType = WiXHarvestType.Perf; + fixture.HarvestTarget = "Cake Category"; // When var result = fixture.Run(); @@ -446,7 +444,7 @@ public void Should_Add_Binaries_Output_Group_To_Arguments_If_Provided() var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: binaries -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Binaries -out \"/Working/cake.wxs\"", result.Args); } [Fact] @@ -460,7 +458,7 @@ public void Should_Add_Symbols_Output_Group_To_Arguments_If_Provided() var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: symbols -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Symbols -out \"/Working/cake.wxs\"", result.Args); } [Fact] @@ -474,21 +472,21 @@ public void Should_Add_Documents_Output_Group_To_Arguments_If_Provided() var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: documents -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Documents -out \"/Working/cake.wxs\"", result.Args); } [Fact] - public void Should_Add_Satallites_Output_Group_To_Arguments_If_Provided() + public void Should_Add_Satellites_Output_Group_To_Arguments_If_Provided() { // Given var fixture = new HeatFixture(); - fixture.Settings.OutputGroup = WiXOutputGroupType.Satallites; + fixture.Settings.OutputGroup = WiXOutputGroupType.Satellites; // When var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: satallites -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Satellites -out \"/Working/cake.wxs\"", result.Args); } [Fact] @@ -502,7 +500,7 @@ public void Should_Add_Sources_Output_Group_To_Arguments_If_Provided() var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: sources -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Sources -out \"/Working/cake.wxs\"", result.Args); } [Fact] @@ -516,7 +514,7 @@ public void Should_Add_Content_Output_Group_To_Arguments_If_Provided() var result = fixture.Run(); // Then - Assert.Equal("dir \"/Working/src/Cake\" -pog: content -out \"/Working/cake.wxs\"", result.Args); + Assert.Equal("dir \"/Working/src/Cake\" -pog Content -out \"/Working/cake.wxs\"", result.Args); } [Fact] @@ -627,7 +625,7 @@ public void Should_Add_Template_Type_Fragment_To_Arguments_If_Provided() // When var result = fixture.Run(); - //Then + // Then Assert.Equal("dir \"/Working/src/Cake\" -template fragment -out \"/Working/cake.wxs\"", result.Args); } @@ -641,7 +639,7 @@ public void Should_Add_Template_Type_Module_To_Arguments_If_Provided() // When var result = fixture.Run(); - //Then + // Then Assert.Equal("dir \"/Working/src/Cake\" -template module -out \"/Working/cake.wxs\"", result.Args); } @@ -655,7 +653,7 @@ public void Should_Add_Template_Type_Product_To_Arguments_If_Provided() // When var result = fixture.Run(); - //Then + // Then Assert.Equal("dir \"/Working/src/Cake\" -template product -out \"/Working/cake.wxs\"", result.Args); } @@ -714,6 +712,19 @@ public void Should_Add_Generate_Binder_Variables_To_Arguments_If_Provided() // Then Assert.Equal("dir \"/Working/src/Cake\" -wixvar -out \"/Working/cake.wxs\"", result.Args); } + + [Fact] + public void Should_Default_To_Directory_Harvest_Type() + { + // Given + var fixture = new HeatFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("dir \"/Working/src/Cake\" -out \"/Working/cake.wxs\"", result.Args); + } } } } \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/WiX/LightRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/WiX/LightRunnerTests.cs index eac438738e..edbe5f7bcb 100644 --- a/src/Cake.Common.Tests/Unit/Tools/WiX/LightRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/WiX/LightRunnerTests.cs @@ -1,256 +1,257 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using Cake.Common.Tests.Fixtures.Tools; -using Cake.Core; -using Cake.Core.IO; -using Cake.Testing; -using Cake.Testing.Xunit; -using Xunit; - -namespace Cake.Common.Tests.Unit.Tools.WiX -{ - public sealed class LightRunnerTests - { - public sealed class TheRunMethod - { - [Fact] - public void Should_Throw_If_Object_Files_Is_Null() - { - // Given - var fixture = new LightFixture(); - fixture.ObjectFiles = null; - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "objectFiles"); - } - - [Fact] - public void Should_Throw_If_Object_Files_Is_Empty() - { - // Given - var fixture = new LightFixture(); - fixture.ObjectFiles = new List(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("objectFiles", ((ArgumentException)result).ParamName); - } - - [Fact] - public void Should_Throw_If_Settings_Is_Null() - { - // Given - var fixture = new LightFixture(); - fixture.Settings = null; - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsArgumentNullException(result, "settings"); - } - - [Fact] - public void Should_Throw_If_Light_Runner_Was_Not_Found() - { - // Given - var fixture = new LightFixture(); - fixture.GivenDefaultToolDoNotExist(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - Assert.IsType(result); - Assert.Equal("Light: Could not locate executable.", result.Message); - } - - [Theory] - [InlineData("/bin/tools/WiX/light.exe", "/bin/tools/WiX/light.exe")] - [InlineData("./tools/WiX/light.exe", "/Working/tools/WiX/light.exe")] - public void Should_Use_Light_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) - { - // Given - var fixture = new LightFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal(expected, result.Path.FullPath); - } - - [WindowsTheory] - [InlineData("C:/WiX/light.exe", "C:/WiX/light.exe")] - public void Should_Use_Light_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) - { - // Given - var fixture = new LightFixture(); - fixture.Settings.ToolPath = toolPath; - fixture.GivenSettingsToolPathExist(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal(expected, result.Path.FullPath); - } - - [Fact] - public void Should_Find_Light_Runner_If_Tool_Path_Not_Provided() - { - // Given - var fixture = new LightFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("/Working/tools/light.exe", result.Path.FullPath); - } - - [Fact] - public void Should_Use_Provided_Object_Files_In_Process_Arguments() - { - // Given - var fixture = new LightFixture(); - fixture.ObjectFiles = new List(); - fixture.ObjectFiles.Add(new FilePath("./Test.wixobj")); - fixture.ObjectFiles.Add(new FilePath("./Test2.wixobj")); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("\"/Working/Test.wixobj\" \"/Working/Test2.wixobj\"", result.Args); - } - - [Fact] - public void Should_Set_Working_Directory() - { - // Given - var fixture = new LightFixture(); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); - } - - [Fact] - public void Should_Throw_If_Process_Was_Not_Started() - { - // Given - var fixture = new LightFixture(); - fixture.GivenProcessCannotStart(); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - // Then - Assert.IsType(result); - Assert.Equal("Light: Process was not started.", result.Message); - } - - [Fact] - public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() - { - // Given - var fixture = new LightFixture(); - fixture.GivenProcessExitsWithCode(1); - - // When - var result = Record.Exception(() => fixture.Run()); - - // Then - // Then - Assert.IsType(result); - Assert.Equal("Light: Process returned an error (exit code 1).", result.Message); - } - - [Fact] - public void Should_Add_Defines_To_Arguments_If_Provided() - { - // Given - var fixture = new LightFixture(); - fixture.Settings.Defines = new Dictionary(); - fixture.Settings.Defines.Add("Foo", "Bar"); - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-dFoo=Bar \"/Working/Test.wixobj\"", result.Args); - } - - [Fact] - public void Should_Add_Extensions_To_Arguments_If_Provided() - { - // Given - var fixture = new LightFixture(); - fixture.Settings.Extensions = new[] { "WixUIExtension" }; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-ext WixUIExtension \"/Working/Test.wixobj\"", result.Args); - } - - [Fact] - public void Should_Add_NoLogo_To_Arguments_If_Provided() - { - // Given - var fixture = new LightFixture(); - fixture.Settings.NoLogo = true; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-nologo \"/Working/Test.wixobj\"", result.Args); - } - - [Fact] - public void Should_Add_Output_Directory_To_Arguments_If_Provided() - { - // Given - var fixture = new LightFixture(); - fixture.Settings.OutputFile = "./bin/test.msi"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-o \"/Working/bin/test.msi\" \"/Working/Test.wixobj\"", result.Args); - } - - [Fact] - public void Should_Add_RawArguments_To_Arguments_If_Provided() - { - // Given - var fixture = new LightFixture(); - fixture.Settings.RawArguments = "-dFoo=Bar"; - - // When - var result = fixture.Run(); - - // Then - Assert.Equal("-dFoo=Bar \"/Working/Test.wixobj\"", result.Args); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tests.Fixtures.Tools; +using Cake.Core; +using Cake.Core.IO; +using Cake.Testing; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Common.Tests.Unit.Tools.WiX +{ + public sealed class LightRunnerTests + { + public sealed class TheRunMethod + { + [Fact] + public void Should_Throw_If_Object_Files_Is_Null() + { + // Given + var fixture = new LightFixture(); + fixture.ObjectFiles = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "objectFiles"); + } + + [Fact] + public void Should_Throw_If_Object_Files_Is_Empty() + { + // Given + var fixture = new LightFixture(); + fixture.ObjectFiles = new List(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("objectFiles", ((ArgumentException)result)?.ParamName); + } + + [Fact] + public void Should_Throw_If_Settings_Is_Null() + { + // Given + var fixture = new LightFixture(); + fixture.Settings = null; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + AssertEx.IsArgumentNullException(result, "settings"); + } + + [Fact] + public void Should_Throw_If_Light_Runner_Was_Not_Found() + { + // Given + var fixture = new LightFixture(); + fixture.GivenDefaultToolDoNotExist(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Light: Could not locate executable.", result?.Message); + } + + [Theory] + [InlineData("/bin/tools/WiX/light.exe", "/bin/tools/WiX/light.exe")] + [InlineData("./tools/WiX/light.exe", "/Working/tools/WiX/light.exe")] + public void Should_Use_Light_Runner_From_Tool_Path_If_Provided(string toolPath, string expected) + { + // Given + var fixture = new LightFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [WindowsTheory] + [InlineData("C:/WiX/light.exe", "C:/WiX/light.exe")] + public void Should_Use_Light_Runner_From_Tool_Path_If_Provided_On_Windows(string toolPath, string expected) + { + // Given + var fixture = new LightFixture(); + fixture.Settings.ToolPath = toolPath; + fixture.GivenSettingsToolPathExist(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal(expected, result.Path.FullPath); + } + + [Fact] + public void Should_Find_Light_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new LightFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/light.exe", result.Path.FullPath); + } + + [Fact] + public void Should_Use_Provided_Object_Files_In_Process_Arguments() + { + // Given + var fixture = new LightFixture(); + fixture.ObjectFiles = new List(); + fixture.ObjectFiles.Add(new FilePath("./Test.wixobj")); + fixture.ObjectFiles.Add(new FilePath("./Test2.wixobj")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test.wixobj\" \"/Working/Test2.wixobj\"", result.Args); + } + + [Fact] + public void Should_Set_Working_Directory() + { + // Given + var fixture = new LightFixture(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working", result.Process.WorkingDirectory.FullPath); + } + + [Fact] + public void Should_Throw_If_Process_Was_Not_Started() + { + // Given + var fixture = new LightFixture(); + fixture.GivenProcessCannotStart(); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + // Then + Assert.IsType(result); + Assert.Equal("Light: Process was not started.", result?.Message); + } + + [Fact] + public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() + { + // Given + var fixture = new LightFixture(); + fixture.GivenProcessExitsWithCode(1); + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + // Then + Assert.IsType(result); + Assert.Equal("Light: Process returned an error (exit code 1).", result?.Message); + } + + [Fact] + public void Should_Add_Defines_To_Arguments_If_Provided() + { + // Given + var fixture = new LightFixture(); + fixture.Settings.Defines = new Dictionary(); + fixture.Settings.Defines.Add("Foo", "Bar"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-dFoo=Bar \"/Working/Test.wixobj\"", result.Args); + } + + [Fact] + public void Should_Add_Extensions_To_Arguments_If_Provided() + { + // Given + var fixture = new LightFixture(); + fixture.Settings.Extensions = new[] { "WixUIExtension" }; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-ext WixUIExtension \"/Working/Test.wixobj\"", result.Args); + } + + [Fact] + public void Should_Add_NoLogo_To_Arguments_If_Provided() + { + // Given + var fixture = new LightFixture(); + fixture.Settings.NoLogo = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-nologo \"/Working/Test.wixobj\"", result.Args); + } + + [Fact] + public void Should_Add_Output_Directory_To_Arguments_If_Provided() + { + // Given + var fixture = new LightFixture(); + fixture.Settings.OutputFile = "./bin/test.msi"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-o \"/Working/bin/test.msi\" \"/Working/Test.wixobj\"", result.Args); + } + + [Fact] + public void Should_Add_RawArguments_To_Arguments_If_Provided() + { + // Given + var fixture = new LightFixture(); + fixture.Settings.RawArguments = "-dFoo=Bar"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("-dFoo=Bar \"/Working/Test.wixobj\"", result.Args); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/WiX/WiXAliasTests.cs b/src/Cake.Common.Tests/Unit/Tools/WiX/WiXAliasTests.cs index 67b7c1f2b0..86af0def97 100644 --- a/src/Cake.Common.Tests/Unit/Tools/WiX/WiXAliasTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/WiX/WiXAliasTests.cs @@ -1,10 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools.WiX; using Cake.Common.Tools.WiX; -using Cake.Core; -using NSubstitute; using Xunit; namespace Cake.Common.Tests.Unit.Tools.WiX @@ -20,24 +19,10 @@ public void Should_Throw_If_Context_Is_Null() var fixture = new HeatFixture(); // When - var result = Record.Exception(() => WiXAliases.WiXHeat(null, fixture.DirectoryPath, fixture.OutputFile)); - - // Then - Assert.IsArgumentNullException(result, "context"); - } - - [Fact] - public void Should_Throw_If_Directory_Path_Is_Null() - { - // Given - var fixture = new HeatFixture(); - var context = Substitute.For(); - - // When - var result = Record.Exception(() => WiXAliases.WiXHeat(context, null, fixture.OutputFile)); + var result = Record.Exception(() => WiXAliases.WiXHeat(null, fixture.DirectoryPath, fixture.OutputFile, fixture.HarvestType)); // Then - Assert.IsArgumentNullException(result, "directoryPath"); + AssertEx.IsArgumentNullException(result, "context"); } } } diff --git a/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsExtensionsTests.cs b/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsExtensionsTests.cs index 4ae90a683e..1b75bf5f50 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsExtensionsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.XBuild; using Cake.Core.Diagnostics; using Xunit; @@ -159,4 +160,4 @@ public void Should_Return_The_Same_Configuration() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsTests.cs index 4fdbfacb2c..f6921efc89 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XBuild/XBuildSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.XBuild; using Cake.Core.Diagnostics; using Xunit; @@ -57,7 +58,7 @@ public void Should_Return_A_Dictionary_That_Is_Case_Insensitive() var settings = new XBuildSettings(); // When - settings.Properties.Add("THEKEY", new []{"THEVALUE"}); + settings.Properties.Add("THEKEY", new[] { "THEVALUE" }); // Then Assert.True(settings.Properties.ContainsKey("thekey")); @@ -77,4 +78,4 @@ public void Should_Be_Empty_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2RunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2RunnerTests.cs index 8c888bf482..098b8affc9 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2RunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2RunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Common.Tools.XUnit; using Cake.Core; @@ -26,7 +27,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPaths"); + AssertEx.IsArgumentNullException(result, "assemblyPaths"); } [Fact] @@ -41,7 +42,7 @@ public void Should_Throw_If_XUnit_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v2): Could not locate executable.", result.Message); + Assert.Equal("xUnit.net (v2): Could not locate executable.", result?.Message); } [Theory] @@ -90,6 +91,35 @@ public void Should_Find_XUnit_Runner_If_Tool_Path_Not_Provided() Assert.Equal("/Working/tools/xunit.console.exe", result.Path.FullPath); } + [Fact] + public void Should_Throw_If_XUnit_X86_Flag_Is_Not_Set_To_True() + { + // Given + var fixture = new XUnit2RunnerFixture("xunit.console.x86.exe"); + fixture.Settings.UseX86 = false; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("xUnit.net (v2): Could not locate executable.", result?.Message); + } + + [Fact] + public void Should_Find_XUnit_X86_Runner_If_Tool_Path_Not_Provided() + { + // Given + var fixture = new XUnit2RunnerFixture("xunit.console.x86.exe"); + fixture.Settings.UseX86 = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("/Working/tools/xunit.console.x86.exe", result.Path.FullPath); + } + [Fact] public void Should_Use_Provided_Assembly_Path_In_Process_Arguments() { @@ -142,7 +172,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v2): Process was not started.", result.Message); + Assert.Equal("xUnit.net (v2): Process was not started.", result?.Message); } [Fact] @@ -157,7 +187,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v2): Process returned an error (exit code 1).", result.Message); + Assert.Equal("xUnit.net (v2): Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -172,7 +202,7 @@ public void Should_Throw_If_HtmlReport_Is_Set_But_OutputDirectory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("Cannot generate HTML report when no output directory has been set.", result.Message); + Assert.Equal("Cannot generate HTML report when no output directory has been set.", result?.Message); } [Fact] @@ -190,6 +220,22 @@ public void Should_Generate_Html_Report_With_Correct_Name_For_Single_Assembly() Assert.Equal("\"/Working/Test1.dll\" -html \"/Output/Test1.dll.html\"", result.Args); } + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -html \"/Output/xUnitReport.html\"", result.Args); + } + [Fact] public void Should_Generate_Html_Report_With_Correct_Name_For_Multiple_Assemblies() { @@ -207,6 +253,24 @@ public void Should_Generate_Html_Report_With_Correct_Name_For_Multiple_Assemblie "-html \"/Output/TestResults.html\"", result.Args); } + [Fact] + public void Should_Generate_Html_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.HtmlReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-html \"/Output/xUnitReport.html\"", result.Args); + } + [Fact] public void Should_Throw_If_XmlReport_Is_Set_But_OutputDirectory_Is_Null() { @@ -219,7 +283,7 @@ public void Should_Throw_If_XmlReport_Is_Set_But_OutputDirectory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("Cannot generate XML report when no output directory has been set.", result.Message); + Assert.Equal("Cannot generate XML report when no output directory has been set.", result?.Message); } [Fact] @@ -237,6 +301,21 @@ public void Should_Generate_Xml_Report_With_Correct_Name_For_Single_Assembly() Assert.Equal("\"/Working/Test1.dll\" -xml \"/Output/Test1.dll.xml\"", result.Args); } + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + fixture.Settings.ReportName = "xUnitReport"; + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -xml \"/Output/xUnitReport.xml\"", result.Args); + } + [Fact] public void Should_Generate_Xml_Report_With_Correct_Name_For_Multiple_Assemblies() { @@ -254,6 +333,24 @@ public void Should_Generate_Xml_Report_With_Correct_Name_For_Multiple_Assemblies "-xml \"/Output/TestResults.xml\"", result.Args); } + [Fact] + public void Should_Generate_Xml_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-xml \"/Output/xUnitReport.xml\"", result.Args); + } + [Fact] public void Should_Throw_If_XmlReportV1_Is_Set_But_OutputDirectory_Is_Null() { @@ -266,7 +363,7 @@ public void Should_Throw_If_XmlReportV1_Is_Set_But_OutputDirectory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("Cannot generate XML report when no output directory has been set.", result.Message); + Assert.Equal("Cannot generate XML report when no output directory has been set.", result?.Message); } [Fact] @@ -284,6 +381,22 @@ public void Should_Generate_Legacy_Xml_Report_With_Correct_Name_For_Single_Assem Assert.Equal("\"/Working/Test1.dll\" -xmlv1 \"/Output/Test1.dll.xml\"", result.Args); } + [Fact] + public void Should_Generate_Legacy_Xml_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReportV1 = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -xmlv1 \"/Output/xUnitReport.xml\"", result.Args); + } + [Fact] public void Should_Generate_Legacy_Xml_Report_With_Correct_Name_For_Multiple_Assemblies() { @@ -301,6 +414,186 @@ public void Should_Generate_Legacy_Xml_Report_With_Correct_Name_For_Multiple_Ass "-xmlv1 \"/Output/TestResults.xml\"", result.Args); } + [Fact] + public void Should_Generate_Legacy_Xml_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.XmlReportV1 = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-xmlv1 \"/Output/xUnitReport.xml\"", result.Args); + } + + [Fact] + public void Should_Throw_If_NUnitReport_Is_Set_But_OutputDirectory_Is_Null() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.NUnitReport = true; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot generate NUnit XML report when no output directory has been set.", result?.Message); + } + + [Fact] + public void Should_Generate_NUnit_Xml_Report_With_Correct_Name_For_Single_Assembly() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.NUnitReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -nunit \"/Output/Test1.dll.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_NUnit_Xml_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.NUnitReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -nunit \"/Output/xUnitReport.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_NUnit_Xml_Report_With_Correct_Name_For_Multiple_Assemblies() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.NUnitReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-nunit \"/Output/TestResults.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_NUnit_Xml_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.NUnitReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-nunit \"/Output/xUnitReport.xml\"", result.Args); + } + + [Fact] + public void Should_Throw_If_JUnitReport_Is_Set_But_OutputDirectory_Is_Null() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.JUnitReport = true; + + // When + var result = Record.Exception(() => fixture.Run()); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot generate JUnit XML report when no output directory has been set.", result?.Message); + } + + [Fact] + public void Should_Generate_JUnit_Xml_Report_With_Correct_Name_For_Single_Assembly() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.JUnitReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -junit \"/Output/Test1.dll.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_JUnit_Xml_Report_With_Correct_Name_For_Single_Assembly_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.JUnitReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -junit \"/Output/xUnitReport.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_JUnit_Xml_Report_With_Correct_Name_For_Multiple_Assemblies() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.JUnitReport = true; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-junit \"/Output/TestResults.xml\"", result.Args); + } + + [Fact] + public void Should_Generate_JUnit_Xml_Report_With_Correct_Name_For_Multiple_Assemblies_ReportName() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.AssemblyPaths = new FilePath[] { "./Test1.dll", "./Test2.dll" }; + fixture.Settings.OutputDirectory = "/Output"; + fixture.Settings.JUnitReport = true; + fixture.Settings.ReportName = "xUnitReport"; + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" \"/Working/Test2.dll\" " + + "-junit \"/Output/xUnitReport.xml\"", result.Args); + } + [Fact] public void Should_Not_Use_Shadow_Copying_If_Disabled_In_Settings() { @@ -330,11 +623,12 @@ public void Should_Not_Use_App_Domains_If_Disabled_In_Settings() } [Theory] + [InlineData(null, "\"/Working/Test1.dll\"")] [InlineData(ParallelismOption.All, "\"/Working/Test1.dll\" -parallel all")] [InlineData(ParallelismOption.Assemblies, "\"/Working/Test1.dll\" -parallel assemblies")] [InlineData(ParallelismOption.Collections, "\"/Working/Test1.dll\" -parallel collections")] - [InlineData(ParallelismOption.None, "\"/Working/Test1.dll\"")] - public void Should_Use_Parallel_Switch_If_Settings_Value_Is_Not_None(ParallelismOption option, string expected) + [InlineData(ParallelismOption.None, "\"/Working/Test1.dll\" -parallel none")] + public void Should_Use_Parallel_Switch_If_Settings_Value_Is_Specified(ParallelismOption? option, string expected) { // Given var fixture = new XUnit2RunnerFixture(); @@ -393,6 +687,48 @@ public void Should_Set_Switches_For_TraitsToExclude_Defined_In_Settings() // Then Assert.Equal("\"/Working/Test1.dll\" -notrait \"Trait1=value1A\" -notrait \"Trait1=value1B\" -notrait \"Trait2=value2\"", result.Args); } + + [Fact] + public void Should_Set_Switches_For_NamespacesToInclude_Defined_In_Settings() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.IncludeNamespace("Company.Product.Feature"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -namespace \"Company.Product.Feature\"", result.Args); + } + + [Fact] + public void Should_Set_Switches_For_ClassNameToInclude_Defined_In_Settings() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.IncludeClass("Company.Product.Feature.ClassName"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -class \"Company.Product.Feature.ClassName\"", result.Args); + } + + [Fact] + public void Should_Set_Switches_For_MethodNameToInclude_Defined_In_Settings() + { + // Given + var fixture = new XUnit2RunnerFixture(); + fixture.Settings.IncludeMethod("Company.Product.Feature.ClassName.MethodName"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("\"/Working/Test1.dll\" -method \"Company.Product.Feature.ClassName.MethodName\"", result.Args); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2SettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2SettingsTests.cs index d91e99b5cc..5c85f15cfc 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2SettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnit2SettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.XUnit; using Xunit; @@ -21,6 +22,16 @@ public void Should_Set_Output_Directory_To_Null_By_Default() Assert.Null(settings.OutputDirectory); } + [Fact] + public void Should_Set_ReportName_To_Null_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.Null(settings.ReportName); + } + [Fact] public void Should_Disable_XML_Report_By_Default() { @@ -41,6 +52,26 @@ public void Should_Disable_HTML_Report_By_Default() Assert.False(settings.HtmlReport); } + [Fact] + public void Should_Disable_NUnit_Report_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.False(settings.NUnitReport); + } + + [Fact] + public void Should_Disable_JUnit_Report_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.False(settings.JUnitReport); + } + [Fact] public void Should_Enable_Shadow_Copying_By_Default() { @@ -62,13 +93,13 @@ public void Should_Set_NoAppDomain_To_False_By_Default() } [Fact] - public void Should_Set_Parallelism_Option_To_None_By_Default() + public void Should_Set_Parallelism_Option_To_Null_By_Default() { // Given, When var settings = new XUnit2Settings(); // Then - Assert.Equal(settings.Parallelism, ParallelismOption.None); + Assert.Null(settings.Parallelism); } [Fact] @@ -110,6 +141,46 @@ public void Should_Set_TraitsToExclude_To_Empty_By_Default() // Then Assert.Empty(settings.TraitsToExclude); } + + [Fact] + public void Should_Set_NamespacesToInclude_To_Empty_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.Empty(settings.NamespacesToInclude); + } + + [Fact] + public void Should_Set_ClassesToInclude_To_Empty_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.Empty(settings.ClassesToInclude); + } + + [Fact] + public void Should_Set_MethodsToInclude_To_Empty_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.Empty(settings.MethodsToInclude); + } + + [Fact] + public void Should_Set_UseX86_To_False_By_Default() + { + // Given, When + var settings = new XUnit2Settings(); + + // Then + Assert.False(settings.UseX86); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitRunnerTests.cs b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitRunnerTests.cs index 033469b76b..f2d7b407a5 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitRunnerTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitRunnerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tests.Fixtures.Tools; using Cake.Core; using Cake.Testing; @@ -24,7 +25,7 @@ public void Should_Throw_If_Assembly_Path_Is_Null() var result = Record.Exception(() => fixture.Run()); // Then - Assert.IsArgumentNullException(result, "assemblyPath"); + AssertEx.IsArgumentNullException(result, "assemblyPath"); } [Fact] @@ -39,7 +40,7 @@ public void Should_Throw_If_XUnit_Runner_Was_Not_Found() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v1): Could not locate executable.", result.Message); + Assert.Equal("xUnit.net (v1): Could not locate executable.", result?.Message); } [Theory] @@ -126,7 +127,7 @@ public void Should_Throw_If_Process_Was_Not_Started() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v1): Process was not started.", result.Message); + Assert.Equal("xUnit.net (v1): Process was not started.", result?.Message); } [Fact] @@ -141,7 +142,7 @@ public void Should_Throw_If_Process_Has_A_Non_Zero_Exit_Code() // Then Assert.IsType(result); - Assert.Equal("xUnit.net (v1): Process returned an error (exit code 1).", result.Message); + Assert.Equal("xUnit.net (v1): Process returned an error (exit code 1).", result?.Message); } [Fact] @@ -156,7 +157,7 @@ public void Should_Throw_If_HtmlReport_Is_Set_But_OutputDirectory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("Cannot generate HTML report when no output directory has been set.", result.Message); + Assert.Equal("Cannot generate HTML report when no output directory has been set.", result?.Message); } [Fact] @@ -186,7 +187,7 @@ public void Should_Throw_If_XmlReport_Is_Set_But_OutputDirectory_Is_Null() // Then Assert.IsType(result); - Assert.Equal("Cannot generate XML report when no output directory has been set.", result.Message); + Assert.Equal("Cannot generate XML report when no output directory has been set.", result?.Message); } [Fact] @@ -233,4 +234,4 @@ public void Should_Set_Silent_Mode_If_Enabled_In_Settings() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitSettingsTests.cs b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitSettingsTests.cs index 6af5759ff0..a2f2739aee 100644 --- a/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitSettingsTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/XUnit/XUnitSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Tools.XUnit; using Xunit; @@ -61,4 +62,4 @@ public void Should_Disable_Silent_Mode_By_Default() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/XML/XmlPeekAliasesTests.cs b/src/Cake.Common.Tests/Unit/XML/XmlPeekAliasesTests.cs index 688ab02c29..455bef8c14 100644 --- a/src/Cake.Common.Tests/Unit/XML/XmlPeekAliasesTests.cs +++ b/src/Cake.Common.Tests/Unit/XML/XmlPeekAliasesTests.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; +using System.Linq; using Cake.Common.Tests.Fixtures; using Cake.Common.Xml; +using Cake.Core.Diagnostics; +using Cake.Testing; using Xunit; namespace Cake.Common.Tests.Unit.XML @@ -22,7 +26,7 @@ public void Should_Throw_If_FilePath_Is_Null() var result = Record.Exception(() => fixture.Peek("gibblygook")); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -49,7 +53,7 @@ public void Should_Throw_If_No_Xpath() var result = Record.Exception(() => fixture.Peek(null)); // Then - Assert.IsArgumentNullException(result, "xpath"); + AssertEx.IsArgumentNullException(result, "xpath"); } [Fact] @@ -91,11 +95,39 @@ public void Should_Get_Node_Value() Assert.Equal("test value", result); } + [Fact] + public void Should_Get_Element_Value() + { + // Given + var fixture = new XmlPeekAliasesFixture(); + + // When + var result = fixture.Peek("/configuration/test"); + + // Then + Assert.Equal("test value", result); + } + + [Fact] + public void Should_Get_Element_Value_With_Namespace() + { + // Given + var fixture = new XmlPeekAliasesFixture(); + fixture.SetContent(Properties.Resources.XmlPeek_Xml_With_Namespace); + fixture.Settings.Namespaces.Add("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003"); + + // When + var result = fixture.Peek("/msbuild:Project/msbuild:PropertyGroup/msbuild:publishUrl"); + + // Then + Assert.Equal("C:\\Deployment\\DeploymentTemplate\\WebApi", result); + } + [Fact] public void Should_Get_Node_Value_From_File_With_Dtd() { // Given - var fixture = new XmlPeekAliasesFixture(xmlWithDtd:true); + var fixture = new XmlPeekAliasesFixture(xmlWithDtd: true); fixture.Settings.DtdProcessing = XmlDtdProcessing.Parse; // When @@ -104,6 +136,36 @@ public void Should_Get_Node_Value_From_File_With_Dtd() // Then Assert.Equal("CFBundleDisplayName", result); } + + [Fact] + public void Should_Log_Unknown_Warning_With_Suppress_Warnings_Off() + { + // Given + var fixture = new XmlPeekAliasesFixture(); + + // When + var result = fixture.Peek("/configuration/test2/text()"); + + // Then + Assert.Equal(null, result); + var warning = fixture.FakeLog.Entries.FirstOrDefault(x => x.Level == LogLevel.Warning); + Assert.Equal("Warning: Failed to find node matching the XPath '/configuration/test2/text()'", warning.Message); + } + + [Fact] + public void Should_Not_Log_Unknown_Warning_With_Suppress_Warnings_On() + { + // Given + var fixture = new XmlPeekAliasesFixture(true, false, true); + + // When + var result = fixture.Peek("/configuration/test2/text()"); + + // Then + Assert.Equal(null, result); + var warning = fixture.FakeLog.Entries.FirstOrDefault(x => x.Level == LogLevel.Warning); + Assert.Equal(null, warning); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/XML/XmlPokeTests.cs b/src/Cake.Common.Tests/Unit/XML/XmlPokeTests.cs index e58708a790..c1546604bc 100644 --- a/src/Cake.Common.Tests/Unit/XML/XmlPokeTests.cs +++ b/src/Cake.Common.Tests/Unit/XML/XmlPokeTests.cs @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.IO; +using System.Text; using Cake.Common.Tests.Fixtures; +using Cake.Common.Tests.Properties; using Cake.Common.Xml; using Xunit; -using Cake.Common.Tests.Properties; -using System.Text; namespace Cake.Common.Tests.Unit.XML { @@ -24,7 +26,7 @@ public void Should_Throw_If_FilePath_Is_Null() var result = Record.Exception(() => fixture.Poke("gibblygook", "")); // Then - Assert.IsArgumentNullException(result, "filePath"); + AssertEx.IsArgumentNullException(result, "filePath"); } [Fact] @@ -51,7 +53,7 @@ public void Should_Throw_If_No_Xpath() var result = Record.Exception(() => fixture.Poke(null, "")); // Then - Assert.IsArgumentNullException(result, "xpath"); + AssertEx.IsArgumentNullException(result, "xpath"); } [Fact] @@ -61,7 +63,7 @@ public void Should_Throw_If_Xml_File_Has_Dtd() var fixture = new XmlPokeFixture(xmlWithDtd: true); // When - var result = Record.Exception(() => fixture.Poke("/plist/dict/string/text()","")); + var result = Record.Exception(() => fixture.Poke("/plist/dict/string/text()", "")); // Then Assert.IsType(result); @@ -71,10 +73,10 @@ public void Should_Throw_If_Xml_File_Has_Dtd() public void Should_Throw_If_Xml_String_Has_Dtd() { // Given - var fixture = new XmlPokeFixture(xmlExists:false); + var fixture = new XmlPokeFixture(xmlExists: false); // When - var result = Record.Exception(() => fixture.PokeString(Resources.XmlPoke_Xml_Dtd, "/plist/dict/string/text()","")); + var result = Record.Exception(() => fixture.PokeString(Resources.XmlPoke_Xml_Dtd, "/plist/dict/string/text()", "")); // Then Assert.IsType(result); @@ -112,11 +114,39 @@ public void Should_Remove_Attribute() "/configuration/appSettings/add[@key = 'server']")); } + [Fact] + public void Should_Have_Encoding_UTF8_With_BOM() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.Encoding = new UTF8Encoding(true); + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']", null); + + // Then + Assert.True(fixture.TestIsUTF8WithBOM()); + } + + [Fact] + public void Should_Have_Encoding_UTF8_Without_BOM() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.Encoding = new UTF8Encoding(false); + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']", null); + + // Then + Assert.False(fixture.TestIsUTF8WithBOM()); + } + [Fact] public void Should_Change_Attribute_From_Xml_File_With_Dtd() { // Given - var fixture = new XmlPokeFixture(xmlWithDtd:true); + var fixture = new XmlPokeFixture(xmlWithDtd: true); fixture.Settings.DtdProcessing = XmlDtdProcessing.Parse; // When @@ -127,6 +157,147 @@ public void Should_Change_Attribute_From_Xml_File_With_Dtd() "/plist/dict/string/text()", "Cake Version")); } + + [Fact] + public void Should_Have_Declaration() + { + // Given + var fixture = new XmlPokeFixture(); + + // When + var resultXml = fixture.PokeString(Resources.XmlPoke_Xml, "/configuration/appSettings/add[@key = 'server']", null); + + // Then + Assert.Contains("" + Environment.NewLine + + "" + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + ""; + Assert.Equal(expectedXml, fixture.GetFullXml()); + } + + [Fact] + public void Should_Ignore_Whitespace() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.PreserveWhitespace = false; + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']/@value", "testhost.somecompany.com"); + + // Then + var expectedXml = ""; + Assert.Equal(expectedXml, fixture.GetFullXml()); + } + + [Fact] + public void Should_Indent() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.PreserveWhitespace = false; + fixture.Settings.Indent = true; + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']/@value", "testhost.somecompany.com"); + + // Then + var expectedXml = + "" + Environment.NewLine + + "" + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + ""; + Assert.Equal(expectedXml, fixture.GetFullXml()); + } + + [Fact] + public void Should_Indent_With_Tab() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.PreserveWhitespace = false; + fixture.Settings.Indent = true; + fixture.Settings.IndentChars = "\t"; + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']/@value", "testhost.somecompany.com"); + + // Then + var expectedXml = + "" + Environment.NewLine + + "" + Environment.NewLine + + "\t" + Environment.NewLine + + "\t\t" + Environment.NewLine + + "\t\t" + Environment.NewLine + + "\t" + Environment.NewLine + + ""; + Assert.Equal(expectedXml, fixture.GetFullXml()); + } + + [Fact] + public void Should_Put_Attributes_On_New_Line() + { + // Given + var fixture = new XmlPokeFixture(); + fixture.Settings.PreserveWhitespace = false; + fixture.Settings.Indent = true; + fixture.Settings.NewLineOnAttributes = true; + + // When + fixture.Poke("/configuration/appSettings/add[@key = 'server']/@value", "testhost.somecompany.com"); + + // Then + var expectedXml = + "" + Environment.NewLine + + "" + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + " " + Environment.NewLine + + ""; + Assert.Equal(expectedXml, fixture.GetFullXml()); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/Unit/XML/XmlTransformationTests.cs b/src/Cake.Common.Tests/Unit/XML/XmlTransformationTests.cs index 06dd72ac67..72cf41db80 100644 --- a/src/Cake.Common.Tests/Unit/XML/XmlTransformationTests.cs +++ b/src/Cake.Common.Tests/Unit/XML/XmlTransformationTests.cs @@ -1,14 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; using System.Linq; using System.Text; +using System.Xml.Xsl; using Cake.Common.Tests.Fixtures; using Cake.Common.Tests.Properties; using Cake.Common.Xml; using Cake.Core; using Cake.Core.IO; +using Cake.Testing.Xunit; using Xunit; namespace Cake.Common.Tests.Unit.XML @@ -28,7 +31,7 @@ public void Should_Throw_If_Xml_Path_Was_Null() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsArgumentNullException(result, "xmlPath"); + AssertEx.IsArgumentNullException(result, "xmlPath"); } [Fact] @@ -42,7 +45,7 @@ public void Should_Throw_If_Xsl_Path_Was_Null() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsArgumentNullException(result, "xslPath"); + AssertEx.IsArgumentNullException(result, "xslPath"); } [Fact] @@ -56,7 +59,7 @@ public void Should_Throw_If_Result_Path_Was_Null() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsArgumentNullException(result, "resultPath"); + AssertEx.IsArgumentNullException(result, "resultPath"); } [Fact] @@ -70,7 +73,7 @@ public void Should_Throw_If_Settings_Was_Null() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] @@ -86,7 +89,7 @@ public void Should_Throw_If_Xml_Not_Exists() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsExceptionWithMessage(result, "XML File not found."); + AssertEx.IsExceptionWithMessage(result, "XML file not found."); } [Fact] @@ -102,7 +105,7 @@ public void Should_Throw_If_Xsl_Not_Exists() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsExceptionWithMessage(result, "Xsl File not found."); + AssertEx.IsExceptionWithMessage(result, "XSL file not found."); } [Fact] @@ -116,9 +119,10 @@ public void Should_Throw_If_Result_Exists_And_Overwrite_False() var result = Record.Exception(() => fixture.Transform()); // Then - Assert.IsExceptionWithMessage(result, "Result file found and overwrite set to false."); + AssertEx.IsExceptionWithMessage(result, "Result file found and overwrite set to false."); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] [Fact] public void Should_Transform_Xml_File_And_Xsl_File_To_Result_File() { @@ -180,62 +184,108 @@ public void Should_Transform_Xml_String_And_Xsl_String_To_Result_String_With_Xml } [Fact] - public void Should_Transform_Xml_String_And_Xsl_String_To_Result_String_With_Utf32Xml_Declaration() + public void Should_Throw_If_Xml_Was_Null() + { + // Given + var xsl = Resources.XmlTransformation_Xsl; + + // When + var result = Record.Exception(() => XmlTransformation.Transform(xsl, null)); + + // Then + AssertEx.IsArgumentNullException(result, "xml"); + } + + [Fact] + public void Should_Throw_If_Xsl_Was_Null() + { + // Given + var xml = Resources.XmlTransformation_Xml; + + // When + var result = Record.Exception(() => XmlTransformation.Transform(null, xml)); + + // Then + AssertEx.IsArgumentNullException(result, "xsl"); + } + + [Fact] + public void Should_Throw_If_Xml_Transformation_Settings_Was_Null() { // Given var xml = Resources.XmlTransformation_Xml; var xsl = Resources.XmlTransformation_Xsl; - var settings = new XmlTransformationSettings - { - Encoding = new UTF32Encoding(false, false, true) - }; // When - var result = string.Concat(XmlTransformation.Transform(xsl, xml, settings).Take(39)); + var result = Record.Exception(() => XmlTransformation.Transform(xsl, xml, null)); // Then - Assert.Equal("", result); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] - public void Should_Throw_If_Xml_Was_Null() + public void Should_Not_Throw_If_Xsl_Argument_List_Was_Null() { // Given + var xml = Resources.XmlTransformation_Xml; var xsl = Resources.XmlTransformation_Xsl; + XmlTransformationSettings xmlTransformationSettings = new XmlTransformationSettings + { + XsltArgumentList = null + }; // When - var result = Record.Exception(() => XmlTransformation.Transform(xsl, null)); + var result = Record.Exception(() => XmlTransformation.Transform(xsl, xml, xmlTransformationSettings)); // Then - Assert.IsArgumentNullException(result, "xml"); + Assert.Null(result); } [Fact] - public void Should_Throw_If_Xsl_Was_Null() + public void Should_Transform_Xml_String_And_Xsl_String_WithArguments_To_Result_String() { // Given var xml = Resources.XmlTransformation_Xml; + var xsl = Resources.XmlTransformationWithArguments_Xsl; + var htm = Resources.XmlTransformation_Htm_NoXmlDeclaration; + XmlTransformationSettings xmlTransformationSettings = new XmlTransformationSettings + { + OmitXmlDeclaration = true, + Encoding = new UTF8Encoding(false), + XsltArgumentList = new XsltArgumentList() + }; + xmlTransformationSettings.XsltArgumentList.AddParam("BackgroundColor", string.Empty, "teal"); + xmlTransformationSettings.XsltArgumentList.AddParam("Color", string.Empty, "white"); // When - var result = Record.Exception(() => XmlTransformation.Transform(null, xml)); + var result = XmlTransformation.Transform(xsl, xml, xmlTransformationSettings); // Then - Assert.IsArgumentNullException(result, "xsl"); + Assert.Equal(htm, result, ignoreLineEndingDifferences: true); } [Fact] - public void Should_Throw_If_String_Settings_Was_Null() + public void Should_Transform_Xml_String_And_Xsl_String_WithArgumentsAndNamespace_To_Result_String() { // Given var xml = Resources.XmlTransformation_Xml; - var xsl = Resources.XmlTransformation_Xsl; + var xsl = Resources.XmlTransformationWithArgumentsAndNamespace_Xsl; + var htm = Resources.XmlTransformation_Htm_NoXmlDeclaration; + XmlTransformationSettings xmlTransformationSettings = new XmlTransformationSettings + { + OmitXmlDeclaration = true, + Encoding = new UTF8Encoding(false), + XsltArgumentList = new XsltArgumentList() + }; + xmlTransformationSettings.XsltArgumentList.AddParam("BackgroundColor", "http://example.com", "teal"); + xmlTransformationSettings.XsltArgumentList.AddParam("Color", "http://example.com", "white"); // When - var result = Record.Exception(() => XmlTransformation.Transform(xsl, xml, null)); + var result = XmlTransformation.Transform(xsl, xml, xmlTransformationSettings); // Then - Assert.IsArgumentNullException(result, "settings"); + Assert.Equal(htm, result, ignoreLineEndingDifferences: true); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common.Tests/VerifyConfig.cs b/src/Cake.Common.Tests/VerifyConfig.cs new file mode 100644 index 0000000000..a67b3c9ea6 --- /dev/null +++ b/src/Cake.Common.Tests/VerifyConfig.cs @@ -0,0 +1,25 @@ +using System.Runtime.CompilerServices; +using Argon; +using VerifyTests.DiffPlex; + +namespace Cake.Common.Tests; + +public static class VerifyConfig +{ + [ModuleInitializer] + public static void Init() + { + EmptyFiles.FileExtensions.AddTextExtension("cake"); + + if (!VerifyDiffPlex.Initialized) + { + VerifyDiffPlex.Initialize(OutputType.Compact); + DerivePathInfo(Expectations.Initialize); + } + + VerifierSettings.DontScrubDateTimes(); + VerifierSettings.DontIgnoreEmptyCollections(); + VerifierSettings.AddExtraSettings(settings => settings.DefaultValueHandling = DefaultValueHandling.Include); + VerifierSettings.IgnoreStackTrace(); + } +} diff --git a/src/Cake.Common.Tests/packages.config b/src/Cake.Common.Tests/packages.config deleted file mode 100644 index 98499b0118..0000000000 --- a/src/Cake.Common.Tests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Cake.Common/ArgumentAliases.cs b/src/Cake.Common/ArgumentAliases.cs index 1e61d0bc82..0c05461a01 100644 --- a/src/Cake.Common/ArgumentAliases.cs +++ b/src/Cake.Common/ArgumentAliases.cs @@ -1,9 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Linq; using Cake.Core; using Cake.Core.Annotations; @@ -25,12 +28,12 @@ public static class ArgumentAliases /// This sample shows how to call the method. /// /// var argumentName = "myArgument"; - /// //Cake.exe .\hasargument.cake -myArgument="is specified" + /// // Cake.exe .\hasargument.cake -myArgument="is specified" /// if (HasArgument(argumentName)) /// { /// Information("{0} is specified", argumentName); /// } - /// //Cake.exe .\hasargument.cake + /// // Cake.exe .\hasargument.cake /// else /// { /// Warning("{0} not specified", argumentName); @@ -40,10 +43,7 @@ public static class ArgumentAliases [CakeMethodAlias] public static bool HasArgument(this ICakeContext context, string name) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); return context.Arguments.HasArgument(name); } @@ -56,7 +56,7 @@ public static bool HasArgument(this ICakeContext context, string name) /// The value of the argument. /// /// - /// //Cake.exe .\argument.cake -myArgument="is valid" -loopCount = 5 + /// // Cake.exe .\argument.cake --myArgument="is valid" --loopCount=5 /// Information("Argument {0}", Argument<string>("myArgument")); /// var loopCount = Argument<int>("loopCount"); /// for(var index = 0;index<loopCount; index++) @@ -70,21 +70,139 @@ public static bool HasArgument(this ICakeContext context, string name) [CakeMethodAlias] public static T Argument(this ICakeContext context, string name) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - var value = context.Arguments.GetArgument(name); + var value = context.Arguments.GetArguments(name).LastOrDefault(); if (value == null) { const string format = "Argument '{0}' was not set."; var message = string.Format(CultureInfo.InvariantCulture, format, name); throw new CakeException(message); } + return Convert(value); } + /// + /// Gets all arguments with the specific name and throws if the argument is missing. + /// + /// The argument type. + /// The context. + /// The argument name. + /// The argument values. + /// + /// + /// // Cake.exe .\argument.cake --foo="foo" --foo="bar" + /// var arguments = Arguments<string>("foo"); + /// Information("Arguments: {0}", string.Join(", ", arguments)); + /// + /// + [CakeMethodAlias] + public static ICollection Arguments(this ICakeContext context, string name) + { + ArgumentNullException.ThrowIfNull(context); + + var values = context.Arguments.GetArguments(name); + if (values == null || values.Count == 0) + { + const string format = "Argument '{0}' was not set."; + var message = string.Format(CultureInfo.InvariantCulture, format, name); + throw new CakeException(message); + } + + return values.Select(value => Convert(value)).ToArray(); + } + + /// + /// Gets all arguments with the specific name and returns the + /// provided if the argument is missing. + /// + /// The argument type. + /// The context. + /// The argument name. + /// The value to return if the argument is missing. + /// The argument values. + /// + /// + /// // Cake.exe .\argument.cake --foo="foo" --foo="bar" + /// var arguments = Arguments<string>("foo", "default"); + /// Information("Arguments: {0}", string.Join(", ", arguments)); + /// + /// + [CakeMethodAlias] + public static ICollection Arguments(this ICakeContext context, string name, T defaultValue) + { + ArgumentNullException.ThrowIfNull(context); + + var values = context.Arguments.GetArguments(name); + if (values == null || values.Count == 0) + { + return new T[] { defaultValue }; + } + + return values.Select(value => Convert(value)).ToArray(); + } + + /// + /// Gets all arguments with the specific name and returns the + /// provided if the argument is missing. + /// + /// The argument type. + /// The context. + /// The argument name. + /// The values to return if the argument is missing. + /// The argument values. + /// + /// + /// // Cake.exe .\argument.cake --foo="foo" --foo="bar" + /// var arguments = Arguments<string>("foo", new [] { "default" }); + /// Information("Arguments: {0}", string.Join(", ", arguments)); + /// + /// + [CakeMethodAlias] + public static ICollection Arguments(this ICakeContext context, string name, ICollection defaultValues) + { + ArgumentNullException.ThrowIfNull(context); + + var values = context.Arguments.GetArguments(name); + if (values == null || values.Count == 0) + { + return defaultValues; + } + + return values.Select(value => Convert(value)).ToArray(); + } + + /// + /// Gets all arguments with the specific name, evaluates and returns the + /// provided if the argument is missing. + /// + /// The argument type. + /// The context. + /// The argument name. + /// The values to return if the argument is missing. + /// The argument values. + /// + /// + /// // Cake.exe .\argument.cake --foo="foo" --foo="bar" + /// var arguments = Arguments<string>("foo", ctx => new [] { "default" }); + /// Information("Arguments: {0}", string.Join(", ", arguments)); + /// + /// + [CakeMethodAlias] + public static ICollection Arguments(this ICakeContext context, string name, Func> defaultValues) + { + ArgumentNullException.ThrowIfNull(context); + + var values = context.Arguments.GetArguments(name); + if (values == null || values.Count == 0) + { + return defaultValues?.Invoke(context); + } + + return values.Select(value => Convert(value)).ToArray(); + } + /// /// Gets an argument and returns the provided if the argument is missing. /// @@ -95,7 +213,7 @@ public static T Argument(this ICakeContext context, string name) /// The value of the argument if it exist; otherwise . /// /// - /// //Cake.exe .\argument.cake -myArgument="is valid" -loopCount = 5 + /// // Cake.exe .\argument.cake --myArgument="is valid" --loopCount=5 /// Information("Argument {0}", Argument<string>("myArgument", "is NOT valid")); /// var loopCount = Argument<int>("loopCount", 10); /// for(var index = 0;index<loopCount; index++) @@ -107,21 +225,49 @@ public static T Argument(this ICakeContext context, string name) [CakeMethodAlias] public static T Argument(this ICakeContext context, string name, T defaultValue) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - var value = context.Arguments.GetArgument(name); + var value = context.Arguments.GetArguments(name)?.LastOrDefault(); return value == null ? defaultValue : Convert(value); } + /// + /// Retrieves all command line arguments. + /// + /// + /// + /// var args = context.Arguments(); + /// + /// if (args.ContainsKey("verbose")) + /// { + /// Information("Verbose output enabled"); + /// } + /// + /// foreach (var arg in args) + /// { + /// Information( + /// "Key: {0}\tValue: \"{1}\"", + /// arg.Key, + /// string.Join(";", arg.Value) + /// ); + /// } + /// + /// + /// The context. + /// The command line arguments. + [CakeMethodAlias] + public static IDictionary> Arguments(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Arguments.GetArguments(); + } + private static T Convert(string value) { var converter = TypeDescriptor.GetConverter(typeof(T)); return (T)converter.ConvertFromInvariantString(value); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorInfo.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorInfo.cs index e5bd7f2d27..e8164c1e43 100644 --- a/src/Cake.Common/Build/AppVeyor/AppVeyorInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; @@ -66,4 +67,4 @@ protected bool GetEnvironmentBoolean(string variable) return false; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorMessageCategoryType.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorMessageCategoryType.cs new file mode 100644 index 0000000000..33c64ff1be --- /dev/null +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorMessageCategoryType.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AppVeyor +{ + /// + /// AppVeyor AddMessage categories. + /// + public enum AppVeyorMessageCategoryType + { + /// + /// Informational message + /// + Information, + + /// + /// Warning message + /// + Warning, + + /// + /// Error message + /// + Error + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorProvider.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorProvider.cs index 81939f5286..1547fd354d 100644 --- a/src/Cake.Common/Build/AppVeyor/AppVeyorProvider.cs +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorProvider.cs @@ -1,12 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; -using System.IO; using System.Net.Http; +using System.Threading.Tasks; using Cake.Common.Build.AppVeyor.Data; +using Cake.Common.Net; using Cake.Core; +using Cake.Core.Diagnostics; using Cake.Core.IO; namespace Cake.Common.Build.AppVeyor @@ -18,60 +21,39 @@ public sealed class AppVeyorProvider : IAppVeyorProvider { private readonly ICakeEnvironment _environment; private readonly IProcessRunner _processRunner; - private readonly AppVeyorEnvironmentInfo _environmentInfo; + private readonly ICakeLog _log; - /// - /// Gets a value indicating whether the current build is running on AppVeyor. - /// - /// - /// true if the current build is running on AppVeyor.; otherwise, false. - /// - public bool IsRunningOnAppVeyor - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("APPVEYOR")); } - } + /// + public bool IsRunningOnAppVeyor => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("APPVEYOR")); - /// - /// Gets the AppVeyor environment. - /// - /// - /// The AppVeyor environment. - /// - public AppVeyorEnvironmentInfo Environment - { - get { return _environmentInfo; } - } + /// + public AppVeyorEnvironmentInfo Environment { get; } /// /// Initializes a new instance of the class. /// /// The environment. /// The process runner. - public AppVeyorProvider(ICakeEnvironment environment, IProcessRunner processRunner) + /// The cake log. + public AppVeyorProvider(ICakeEnvironment environment, IProcessRunner processRunner, ICakeLog log) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - if (processRunner == null) - { - throw new ArgumentNullException("processRunner"); - } - _environment = environment; - _processRunner = processRunner; - _environmentInfo = new AppVeyorEnvironmentInfo(environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _processRunner = processRunner ?? throw new ArgumentNullException(nameof(processRunner)); + _log = log ?? throw new ArgumentNullException(nameof(log)); + Environment = new AppVeyorEnvironmentInfo(environment); } - /// - /// Uploads an AppVeyor artifact. - /// - /// The file path of the artifact to upload. + /// public void UploadArtifact(FilePath path) { - if (path == null) - { - throw new ArgumentNullException("path"); - } + UploadArtifact(path, settings => settings.SetArtifactType(AppVeyorUploadArtifactType.Auto)); + } + + /// + public void UploadArtifact(FilePath path, AppVeyorUploadArtifactsSettings settings) + { + ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(settings); if (!IsRunningOnAppVeyor) { @@ -84,26 +66,36 @@ public void UploadArtifact(FilePath path) // Build the arguments. var arguments = new ProcessArgumentBuilder(); arguments.Append("PushArtifact"); - arguments.Append("-Path"); arguments.AppendQuoted(path.FullPath); - arguments.Append("-FileName"); - arguments.AppendQuoted(path.GetFilename().FullPath); + arguments.Append("-Type"); + arguments.Append(settings.ArtifactType.ToString()); + if (!string.IsNullOrEmpty(settings.DeploymentName)) + { + if (settings.DeploymentName.Contains(" ")) + { + throw new CakeException("The deployment name can not contain spaces"); + } + arguments.Append("-DeploymentName"); + arguments.AppendQuoted(settings.DeploymentName); + } - // Start the process. - _processRunner.Start("appveyor", new ProcessSettings { Arguments = arguments }); + StartAppVeyor(arguments); } - /// - /// Uploads test results XML file to AppVeyor. Results type can be one of the following: mstest, xunit, nunit, nunit3, junit. - /// - /// The file path of the test results XML to upload. - /// The results type. Can be mstest, xunit, nunit, nunit3 or junit. + /// + public void UploadArtifact(FilePath path, Action settingsAction) + { + ArgumentNullException.ThrowIfNull(settingsAction); + + var settings = new AppVeyorUploadArtifactsSettings(); + settingsAction(settings); + UploadArtifact(path, settings); + } + + /// public void UploadTestResults(FilePath path, AppVeyorTestResultsType resultsType) { - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(path); if (!IsRunningOnAppVeyor) { @@ -117,25 +109,24 @@ public void UploadTestResults(FilePath path, AppVeyorTestResultsType resultsType throw new CakeException("Failed to get AppVeyor API url."); } - var url = string.Format(CultureInfo.InvariantCulture, "{0}/api/testresults/{1}/{2}", baseUri, resultsType, Environment.JobId); + var url = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}/api/testresults/{1}/{2}", baseUri, resultsType, Environment.JobId).ToLowerInvariant()); - using (var stream = File.OpenRead(path.FullPath)) - using (var client = new HttpClient()) + _log.Write(Verbosity.Diagnostic, LogLevel.Verbose, "Uploading [{0}] to [{1}]", path.FullPath, url); + Task.Run(async () => { - client.PostAsync(url, new StreamContent(stream)).Wait(); - } + using (var client = new HttpClient()) + { + var response = await client.UploadFileAsync(url, path.FullPath, "text/xml"); + var content = await response.Content.ReadAsStringAsync(); + _log.Write(Verbosity.Diagnostic, LogLevel.Verbose, "Server response [{0}:{1}]:\n\r{2}", response.StatusCode, response.ReasonPhrase, content); + } + }).Wait(); } - /// - /// Updates the build version. - /// - /// The new build version. + /// public void UpdateBuildVersion(string version) { - if (version == null) - { - throw new ArgumentNullException("version"); - } + ArgumentNullException.ThrowIfNull(version); if (string.IsNullOrWhiteSpace(version)) { throw new CakeException("The build version cannot be empty."); @@ -152,8 +143,48 @@ public void UpdateBuildVersion(string version) arguments.Append("-Version"); arguments.AppendQuoted(version); - // Start the process. - _processRunner.Start("appveyor", new ProcessSettings { Arguments = arguments }); + StartAppVeyor(arguments); + } + + /// + public void AddMessage(string message, AppVeyorMessageCategoryType category = AppVeyorMessageCategoryType.Information, string details = null) + { + ArgumentNullException.ThrowIfNull(message); + if (string.IsNullOrWhiteSpace(message)) + { + throw new CakeException("The message cannot be empty."); + } + + if (!IsRunningOnAppVeyor) + { + throw new CakeException("The current build is not running on AppVeyor."); + } + + // Build the arguments. + var arguments = new ProcessArgumentBuilder(); + arguments.Append("AddMessage"); + arguments.AppendQuoted(message); + arguments.Append("-Category"); + arguments.AppendQuoted(category.ToString()); + + if (!string.IsNullOrWhiteSpace(details)) + { + arguments.Append("-Details"); + arguments.AppendQuoted(details); + } + + StartAppVeyor(arguments); + } + + private void StartAppVeyor(ProcessArgumentBuilder arguments, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "") + { + var process = _processRunner.Start("appveyor", new ProcessSettings { Arguments = arguments }); + process.WaitForExit(); + var exitCode = process.GetExitCode(); + if (exitCode != 0) + { + throw new CakeException($"{memberName} failed ({exitCode})."); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorProviderAddMessageExtensions.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorProviderAddMessageExtensions.cs new file mode 100644 index 0000000000..a074a17dec --- /dev/null +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorProviderAddMessageExtensions.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; + +namespace Cake.Common.Build.AppVeyor +{ + /// + /// AddMessage extension methods for the IAppVeyorProvider. + /// + public static class AppVeyorProviderAddMessageExtensions + { + /// + /// Adds an informational message to the AppVeyor build log. + /// + /// The AppVeyor provider. + /// The message. + /// The args. + public static void AddInformationalMessage(this IAppVeyorProvider provider, string format, params object[] args) + { + ArgumentNullException.ThrowIfNull(provider); + provider.AddMessage(string.Format(CultureInfo.InvariantCulture, format, args)); + } + + /// + /// Adds a warning message to the AppVeyor build log. + /// + /// The AppVeyor provider. + /// The message. + /// The args. + public static void AddWarningMessage(this IAppVeyorProvider provider, string format, params object[] args) + { + ArgumentNullException.ThrowIfNull(provider); + provider.AddMessage(string.Format(CultureInfo.InvariantCulture, format, args), AppVeyorMessageCategoryType.Warning); + } + + /// + /// Adds a warning message to the AppVeyor build log. + /// + /// The AppVeyor provider. + /// The message. + /// The args. + public static void AddErrorMessage(this IAppVeyorProvider provider, string format, params object[] args) + { + ArgumentNullException.ThrowIfNull(provider); + provider.AddMessage(string.Format(CultureInfo.InvariantCulture, format, args), AppVeyorMessageCategoryType.Error); + } + + /// + /// Adds a warning message to the AppVeyor build log. + /// + /// The AppVeyor provider. + /// The message. + /// The exception. + public static void AddErrorMessage(this IAppVeyorProvider provider, string message, Exception exception) + { + ArgumentNullException.ThrowIfNull(provider); + provider.AddMessage(message, AppVeyorMessageCategoryType.Error, + exception?.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorTestResultsType.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorTestResultsType.cs index 0ec5d7f5d7..935d4f1809 100644 --- a/src/Cake.Common/Build/AppVeyor/AppVeyorTestResultsType.cs +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorTestResultsType.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Build.AppVeyor { /// @@ -33,4 +34,4 @@ public enum AppVeyorTestResultsType /// JUnit } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactType.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactType.cs new file mode 100644 index 0000000000..ad96aa5efc --- /dev/null +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactType.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AppVeyor +{ + /// + /// Provides the known artifact upload types for the AppVeyor. + /// + public enum AppVeyorUploadArtifactType + { + /// + /// Automatically deploy artifact type + /// + Auto, + + /// + /// The artifact is a web deploy package (.zip) + /// + WebDeployPackage, + + /// + /// The artifact is a NuGet package (.nupkg) + /// + NuGetPackage + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactsSettings.cs b/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactsSettings.cs new file mode 100644 index 0000000000..5a3875d118 --- /dev/null +++ b/src/Cake.Common/Build/AppVeyor/AppVeyorUploadArtifactsSettings.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AppVeyor +{ + /// + /// AppVeyor upload artifacts settings. + /// + public class AppVeyorUploadArtifactsSettings + { + /// + /// Gets or sets a value indicating the type of artifact being uploaded to AppVeyor. + /// + public AppVeyorUploadArtifactType ArtifactType { get; set; } + + /// + /// Gets or sets a value indicating a deployment name to set for the uploaded artifact. + /// + public string DeploymentName { get; set; } + + /// + /// Sets the type of artifact being uploaded to AppVeyor. + /// + /// The type of artifact being uploaded. + /// The settings. + public AppVeyorUploadArtifactsSettings SetArtifactType(AppVeyorUploadArtifactType type) + { + ArtifactType = type; + return this; + } + + /// + /// Sets the deployment name. + /// + /// The deployment name to attach to the artifact, required when using the AppVeyor deployment agent. should not have any spaces. + /// The settings. + public AppVeyorUploadArtifactsSettings SetDeploymentName(string deploymentName) + { + DeploymentName = deploymentName; + return this; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorBuildInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorBuildInfo.cs index 66e83626e3..2ec72d92c2 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorBuildInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorBuildInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -16,10 +17,7 @@ public sealed class AppVeyorBuildInfo : AppVeyorInfo /// /// The path to the clone directory. /// - public string Folder - { - get { return GetEnvironmentString("APPVEYOR_BUILD_FOLDER"); } - } + public string Folder => GetEnvironmentString("APPVEYOR_BUILD_FOLDER"); /// /// Gets the AppVeyor unique build ID. @@ -27,10 +25,7 @@ public string Folder /// /// The AppVeyor unique build ID. /// - public string Id - { - get { return GetEnvironmentString("APPVEYOR_BUILD_ID"); } - } + public string Id => GetEnvironmentString("APPVEYOR_BUILD_ID"); /// /// Gets the build number. @@ -38,10 +33,7 @@ public string Id /// /// The build number. /// - public int Number - { - get { return GetEnvironmentInteger("APPVEYOR_BUILD_NUMBER"); } - } + public int Number => GetEnvironmentInteger("APPVEYOR_BUILD_NUMBER"); /// /// Gets the build version. @@ -49,10 +41,7 @@ public int Number /// /// The build version. /// - public string Version - { - get { return GetEnvironmentString("APPVEYOR_BUILD_VERSION"); } - } + public string Version => GetEnvironmentString("APPVEYOR_BUILD_VERSION"); /// /// Initializes a new instance of the class. @@ -63,4 +52,4 @@ public AppVeyorBuildInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorCommitInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorCommitInfo.cs index 50ddbd16fe..616db1800a 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorCommitInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorCommitInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -16,10 +17,7 @@ public sealed class AppVeyorCommitInfo : AppVeyorInfo /// /// The commit ID (SHA). /// - public string Id - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT"); } - } + public string Id => GetEnvironmentString("APPVEYOR_REPO_COMMIT"); /// /// Gets the commit author's name. @@ -27,10 +25,7 @@ public string Id /// /// The commit author's name. /// - public string Author - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT_AUTHOR"); } - } + public string Author => GetEnvironmentString("APPVEYOR_REPO_COMMIT_AUTHOR"); /// /// Gets the commit author's email address. @@ -38,10 +33,7 @@ public string Author /// /// The commit author's email address. /// - public string Email - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"); } - } + public string Email => GetEnvironmentString("APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"); /// /// Gets the commit date/time. @@ -49,10 +41,7 @@ public string Email /// /// The commit date/time. /// - public string Timestamp - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT_TIMESTAMP"); } - } + public string Timestamp => GetEnvironmentString("APPVEYOR_REPO_COMMIT_TIMESTAMP"); /// /// Gets the commit message. @@ -60,10 +49,7 @@ public string Timestamp /// /// The commit message. /// - public string Message - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT_MESSAGE"); } - } + public string Message => GetEnvironmentString("APPVEYOR_REPO_COMMIT_MESSAGE"); /// /// Gets the rest of commit message after line break (if exists). @@ -71,10 +57,7 @@ public string Message /// /// The rest of commit message after line break (if exists). /// - public string ExtendedMessage - { - get { return GetEnvironmentString("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED"); } - } + public string ExtendedMessage => GetEnvironmentString("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED"); /// /// Initializes a new instance of the class. @@ -85,4 +68,4 @@ public AppVeyorCommitInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorEnvironmentInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorEnvironmentInfo.cs index 525b965b7a..e1863932db 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorEnvironmentInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -10,32 +11,85 @@ namespace Cake.Common.Build.AppVeyor.Data /// public sealed class AppVeyorEnvironmentInfo : AppVeyorInfo { - private readonly AppVeyorProjectInfo _projectProvider; - private readonly AppVeyorBuildInfo _buildProvider; - private readonly AppVeyorPullRequestInfo _pullRequestProvider; - private readonly AppVeyorRepositoryInfo _repositoryProvider; - /// /// Gets the AppVeyor build agent API URL. /// /// /// The AppVeyor build agent API URL. /// - public string ApiUrl - { - get { return GetEnvironmentString("APPVEYOR_API_URL"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"API URL:{0}, + /// BuildSystem.AppVeyor.Environment.ApiUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"API URL:{0}, + /// AppVeyor.Environment.ApiUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public string ApiUrl => GetEnvironmentString("APPVEYOR_API_URL"); /// /// Gets the AppVeyor unique job ID. /// /// - /// The AppVeyor unique job ID. + /// The AppVeyor unique job ID. /// - public string JobId - { - get { return GetEnvironmentString("APPVEYOR_JOB_ID"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Job Id:{0}, + /// BuildSystem.AppVeyor.Environment.JobId + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Job Id:{0}, + /// AppVeyor.Environment.JobId + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public string JobId => GetEnvironmentString("APPVEYOR_JOB_ID"); /// /// Gets the AppVeyor Job Name. @@ -43,10 +97,39 @@ public string JobId /// /// The AppVeyor Job Name. /// - public string JobName - { - get { return GetEnvironmentString("APPVEYOR_JOB_NAME"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Job Name:{0}, + /// BuildSystem.AppVeyor.Environment.JobName + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Job Name:{0}, + /// AppVeyor.Environment.JobName + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public string JobName => GetEnvironmentString("APPVEYOR_JOB_NAME"); /// /// Gets a value indicating whether the build runs by scheduler. @@ -54,10 +137,39 @@ public string JobName /// /// true if the build runs by scheduler; otherwise, false. /// - public bool ScheduledBuild - { - get { return GetEnvironmentBoolean("APPVEYOR_SCHEDULED_BUILD"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Scheduled Build:{0}, + /// BuildSystem.AppVeyor.Environment.ScheduledBuild + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Scheduled Build:{0}, + /// AppVeyor.Environment.ScheduledBuild + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public bool ScheduledBuild => GetEnvironmentBoolean("APPVEYOR_SCHEDULED_BUILD"); /// /// Gets the platform name set on build tab of project settings (or through platform parameter in appveyor.yml). @@ -65,10 +177,39 @@ public bool ScheduledBuild /// /// The platform name set on build tab of project settings (or through platform parameter in appveyor.yml). /// - public string Platform - { - get { return GetEnvironmentString("PLATFORM"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Platform:{0}, + /// BuildSystem.AppVeyor.Environment.Platform + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Platform:{0}, + /// AppVeyor.Environment.Platform + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public string Platform => GetEnvironmentString("PLATFORM"); /// /// Gets the configuration name set on build tab of project settings (or through configuration parameter in appveyor.yml). @@ -76,10 +217,39 @@ public string Platform /// /// The configuration name set on build tab of project settings (or through configuration parameter in appveyor.yml). /// - public string Configuration - { - get { return GetEnvironmentString("CONFIGURATION"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Configuration:{0}, + /// BuildSystem.AppVeyor.Environment.Configuration + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Configuration:{0}, + /// AppVeyor.Environment.Configuration + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public string Configuration => GetEnvironmentString("CONFIGURATION"); /// /// Gets AppVeyor project information. @@ -87,10 +257,50 @@ public string Configuration /// /// The AppVeyor project information. /// - public AppVeyorProjectInfo Project - { - get { return _projectProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Project: + /// Id: {0} + /// Name: {1} + /// Slug: {2}", + /// BuildSystem.AppVeyor.Environment.Project.Id, + /// BuildSystem.AppVeyor.Environment.Project.Name, + /// BuildSystem.AppVeyor.Environment.Project.Slug + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// // via AppVeyor + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Project: + /// Id: {0} + /// Name: {1} + /// Slug: {2}", + /// AppVeyor.Environment.Project.Id, + /// AppVeyor.Environment.Project.Name, + /// AppVeyor.Environment.Project.Slug + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorProjectInfo Project { get; } /// /// Gets AppVeyor build information. @@ -98,10 +308,53 @@ public AppVeyorProjectInfo Project /// /// The AppVeyor build information. /// - public AppVeyorBuildInfo Build - { - get { return _buildProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Build: + /// Folder: {0} + /// Id: {1} + /// Number: {2} + /// Version: {3}", + /// BuildSystem.AppVeyor.Environment.Build.Folder, + /// BuildSystem.AppVeyor.Environment.Build.Id, + /// BuildSystem.AppVeyor.Environment.Build.Number, + /// BuildSystem.AppVeyor.Environment.Build.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Build: + /// Folder: {0} + /// Id: {1} + /// Number: {2} + /// Version: {3}", + /// AppVeyor.Environment.Build.Folder, + /// AppVeyor.Environment.Build.Id, + /// AppVeyor.Environment.Build.Number, + /// AppVeyor.Environment.Build.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorBuildInfo Build { get; } /// /// Gets AppVeyor pull request information. @@ -109,10 +362,49 @@ public AppVeyorBuildInfo Build /// /// The AppVeyor pull request information. /// - public AppVeyorPullRequestInfo PullRequest - { - get { return _pullRequestProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Number: {1} + /// Title: {2}", + /// BuildSystem.AppVeyor.Environment.PullRequest.IsPullRequest, + /// BuildSystem.AppVeyor.Environment.PullRequest.Number, + /// BuildSystem.AppVeyor.Environment.PullRequest.Title + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Number: {1} + /// Title: {2}", + /// AppVeyor.Environment.PullRequest.IsPullRequest, + /// AppVeyor.Environment.PullRequest.Number, + /// AppVeyor.Environment.PullRequest.Title + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorPullRequestInfo PullRequest { get; } /// /// Gets AppVeyor repository information. @@ -120,10 +412,53 @@ public AppVeyorPullRequestInfo PullRequest /// /// The AppVeyor repository information. /// - public AppVeyorRepositoryInfo Repository - { - get { return _repositoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Name: {1} + /// Provider: {2} + /// Scm: {3}", + /// BuildSystem.AppVeyor.Environment.Repository.Branch, + /// BuildSystem.AppVeyor.Environment.Repository.Name, + /// BuildSystem.AppVeyor.Environment.Repository.Provider, + /// BuildSystem.AppVeyor.Environment.Repository.Scm + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Name: {1} + /// Provider: {2} + /// Scm: {3}", + /// AppVeyor.Environment.Repository.Branch, + /// AppVeyor.Environment.Repository.Name, + /// AppVeyor.Environment.Repository.Provider, + /// AppVeyor.Environment.Repository.Scm + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorRepositoryInfo Repository { get; } /// /// Initializes a new instance of the class. @@ -132,10 +467,10 @@ public AppVeyorRepositoryInfo Repository public AppVeyorEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _projectProvider = new AppVeyorProjectInfo(environment); - _buildProvider = new AppVeyorBuildInfo(environment); - _pullRequestProvider = new AppVeyorPullRequestInfo(environment); - _repositoryProvider = new AppVeyorRepositoryInfo(environment); + Project = new AppVeyorProjectInfo(environment); + Build = new AppVeyorBuildInfo(environment); + PullRequest = new AppVeyorPullRequestInfo(environment); + Repository = new AppVeyorRepositoryInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorProjectInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorProjectInfo.cs index a03e734dd6..18f6a74707 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorProjectInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorProjectInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -16,10 +17,7 @@ public sealed class AppVeyorProjectInfo : AppVeyorInfo /// /// The AppVeyor unique project ID. /// - public string Id - { - get { return GetEnvironmentString("APPVEYOR_PROJECT_ID"); } - } + public string Id => GetEnvironmentString("APPVEYOR_PROJECT_ID"); /// /// Gets the project name. @@ -27,10 +25,7 @@ public string Id /// /// The project name. /// - public string Name - { - get { return GetEnvironmentString("APPVEYOR_PROJECT_NAME"); } - } + public string Name => GetEnvironmentString("APPVEYOR_PROJECT_NAME"); /// /// Gets the project slug (as seen in project details URL). @@ -38,10 +33,7 @@ public string Name /// /// The project slug (as seen in project details URL). /// - public string Slug - { - get { return GetEnvironmentString("APPVEYOR_PROJECT_SLUG"); } - } + public string Slug => GetEnvironmentString("APPVEYOR_PROJECT_SLUG"); /// /// Initializes a new instance of the class. @@ -52,4 +44,4 @@ public AppVeyorProjectInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorPullRequestInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorPullRequestInfo.cs index cd54af644f..cf9dd0dfd3 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorPullRequestInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorPullRequestInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -16,10 +17,7 @@ public sealed class AppVeyorPullRequestInfo : AppVeyorInfo /// /// true if the current build was started by a pull request; otherwise, false. /// - public bool IsPullRequest - { - get { return Number > 0; } - } + public bool IsPullRequest => Number > 0; /// /// Gets the GitHub pull request number. @@ -27,10 +25,7 @@ public bool IsPullRequest /// /// The GitHub pull request number. /// - public int Number - { - get { return GetEnvironmentInteger("APPVEYOR_PULL_REQUEST_NUMBER"); } - } + public int Number => GetEnvironmentInteger("APPVEYOR_PULL_REQUEST_NUMBER"); /// /// Gets the GitHub pull request title. @@ -38,10 +33,7 @@ public int Number /// /// The GitHub pull request title. /// - public string Title - { - get { return GetEnvironmentString("APPVEYOR_PULL_REQUEST_TITLE"); } - } + public string Title => GetEnvironmentString("APPVEYOR_PULL_REQUEST_TITLE"); /// /// Initializes a new instance of the class. @@ -52,4 +44,4 @@ public AppVeyorPullRequestInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorRepositoryInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorRepositoryInfo.cs index d2810a12fa..eb1dcf43af 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorRepositoryInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorRepositoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -10,9 +11,6 @@ namespace Cake.Common.Build.AppVeyor.Data /// public sealed class AppVeyorRepositoryInfo : AppVeyorInfo { - private readonly AppVeyorTagInfo _tagProvider; - private readonly AppVeyorCommitInfo _commitProvider; - /// /// Gets the repository provider. /// @@ -25,15 +23,18 @@ public sealed class AppVeyorRepositoryInfo : AppVeyorInfo /// /// kiln /// + /// + /// vso + /// + /// + /// gitlab + /// /// /// /// /// The repository provider. /// - public string Provider - { - get { return GetEnvironmentString("APPVEYOR_REPO_PROVIDER"); } - } + public string Provider => GetEnvironmentString("APPVEYOR_REPO_PROVIDER"); /// /// Gets the revision control system. @@ -49,10 +50,7 @@ public string Provider /// /// The revision control system. /// - public string Scm - { - get { return GetEnvironmentString("APPVEYOR_REPO_SCM"); } - } + public string Scm => GetEnvironmentString("APPVEYOR_REPO_SCM"); /// /// Gets the repository name in format owner-name/repo-name. @@ -60,10 +58,7 @@ public string Scm /// /// The repository name. /// - public string Name - { - get { return GetEnvironmentString("APPVEYOR_REPO_NAME"); } - } + public string Name => GetEnvironmentString("APPVEYOR_REPO_NAME"); /// /// Gets the build branch. For pull request commits it is base branch PR is merging into. @@ -71,10 +66,7 @@ public string Name /// /// The build branch. /// - public string Branch - { - get { return GetEnvironmentString("APPVEYOR_REPO_BRANCH"); } - } + public string Branch => GetEnvironmentString("APPVEYOR_REPO_BRANCH"); /// /// Gets the tag information for the build. @@ -82,10 +74,45 @@ public string Branch /// /// The tag information for the build. /// - public AppVeyorTagInfo Tag - { - get { return _tagProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// IsTag: {0} + /// Name: {1}", + /// BuildSystem.AppVeyor.Environment.Repository.Tag.IsTag, + /// BuildSystem.AppVeyor.Environment.Repository.Tag.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// IsTag: {0} + /// Name: {1}", + /// AppVeyor.Environment.Repository.Tag.IsTag, + /// AppVeyor.Environment.Repository.Tag.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorTagInfo Tag { get; } /// /// Gets the commit information for the build. @@ -93,10 +120,61 @@ public AppVeyorTagInfo Tag /// /// The commit information for the build. /// - public AppVeyorCommitInfo Commit - { - get { return _commitProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// Author: {0} + /// Email: {1} + /// ExtendedMessage: {2} + /// Id: {3} + /// Message: {4} + /// Timestamp: {5}", + /// BuildSystem.AppVeyor.Environment.Repository.Commit.Author, + /// BuildSystem.AppVeyor.Environment.Repository.Commit.Email, + /// BuildSystem.AppVeyor.Environment.Repository.Commit.ExtendedMessage, + /// BuildSystem.AppVeyor.Environment.Repository.Commit.Id, + /// BuildSystem.AppVeyor.Environment.Repository.Commit.Message, + /// BuildSystem.AppVeyor.Environment.Repository.Commit.Timestamp + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Repository: + /// Author: {0} + /// Email: {1} + /// ExtendedMessage: {2} + /// Id: {3} + /// Message: {4} + /// Timestamp: {5}", + /// AppVeyor.Environment.Repository.Commit.Author, + /// AppVeyor.Environment.Repository.Commit.Email, + /// AppVeyor.Environment.Repository.Commit.ExtendedMessage, + /// AppVeyor.Environment.Repository.Commit.Id, + /// AppVeyor.Environment.Repository.Commit.Message, + /// AppVeyor.Environment.Repository.Commit.Timestamp + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + public AppVeyorCommitInfo Commit { get; } /// /// Initializes a new instance of the class. @@ -105,8 +183,8 @@ public AppVeyorCommitInfo Commit public AppVeyorRepositoryInfo(ICakeEnvironment environment) : base(environment) { - _tagProvider = new AppVeyorTagInfo(environment); - _commitProvider = new AppVeyorCommitInfo(environment); + Tag = new AppVeyorTagInfo(environment); + Commit = new AppVeyorCommitInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorTagInfo.cs b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorTagInfo.cs index 303bdab371..0c807f0875 100644 --- a/src/Cake.Common/Build/AppVeyor/Data/AppVeyorTagInfo.cs +++ b/src/Cake.Common/Build/AppVeyor/Data/AppVeyorTagInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.AppVeyor.Data @@ -16,10 +17,7 @@ public sealed class AppVeyorTagInfo : AppVeyorInfo /// /// true if build was started by pushed tag; otherwise, false. /// - public bool IsTag - { - get { return GetEnvironmentBoolean("APPVEYOR_REPO_TAG"); } - } + public bool IsTag => GetEnvironmentBoolean("APPVEYOR_REPO_TAG"); /// /// Gets the name for builds started by tag; otherwise this variable is undefined. @@ -27,10 +25,7 @@ public bool IsTag /// /// The name of the tag. /// - public string Name - { - get { return GetEnvironmentString("APPVEYOR_REPO_TAG_NAME"); } - } + public string Name => GetEnvironmentString("APPVEYOR_REPO_TAG_NAME"); /// /// Initializes a new instance of the class. @@ -41,4 +36,4 @@ public AppVeyorTagInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AppVeyor/IAppVeyorProvider.cs b/src/Cake.Common/Build/AppVeyor/IAppVeyorProvider.cs index d77df9227a..3e46f2d01b 100644 --- a/src/Cake.Common/Build/AppVeyor/IAppVeyorProvider.cs +++ b/src/Cake.Common/Build/AppVeyor/IAppVeyorProvider.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Common.Build.AppVeyor.Data; using Cake.Core.IO; @@ -17,6 +19,32 @@ public interface IAppVeyorProvider /// /// true if the current build is running on AppVeyor.; otherwise, false. /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information("Running on AppVeyor"); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information("Running on AppVeyor"); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// bool IsRunningOnAppVeyor { get; } /// @@ -25,6 +53,60 @@ public interface IAppVeyorProvider /// /// The AppVeyor environment. /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Environment: + /// ApiUrl: {0} + /// Configuration: {1} + /// JobId: {2} + /// JobName: {3} + /// Platform: {4} + /// ScheduledBuild: {5}", + /// BuildSystem.AppVeyor.Environment.ApiUrl, + /// BuildSystem.AppVeyor.Environment.Configuration, + /// BuildSystem.AppVeyor.Environment.JobId, + /// BuildSystem.AppVeyor.Environment.JobName, + /// BuildSystem.AppVeyor.Environment.Platform, + /// BuildSystem.AppVeyor.Environment.ScheduledBuild + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// Information( + /// @"Environment: + /// ApiUrl: {0} + /// Configuration: {1} + /// JobId: {2} + /// JobName: {3} + /// Platform: {4} + /// ScheduledBuild: {5}", + /// AppVeyor.Environment.ApiUrl, + /// AppVeyor.Environment.Configuration, + /// AppVeyor.Environment.JobId, + /// AppVeyor.Environment.JobName, + /// AppVeyor.Environment.Platform, + /// AppVeyor.Environment.ScheduledBuild + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// AppVeyorEnvironmentInfo Environment { get; } /// @@ -33,6 +115,20 @@ public interface IAppVeyorProvider /// The file path of the artifact to upload. void UploadArtifact(FilePath path); + /// + /// Uploads an AppVeyor artifact. + /// + /// The file path of the artifact to upload. + /// The settings to apply when uploading an artifact. + void UploadArtifact(FilePath path, AppVeyorUploadArtifactsSettings settings); + + /// + /// Uploads an AppVeyor artifact. + /// + /// The file path of the artifact to upload. + /// The settings to apply when uploading an artifact. + void UploadArtifact(FilePath path, Action settingsAction); + /// /// Uploads test results XML file to AppVeyor. Results type can be one of the following: mstest, xunit, nunit, nunit3, junit. /// @@ -44,6 +140,98 @@ public interface IAppVeyorProvider /// Updates the build version. /// /// The new build version. + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// BuildSystem.AppVeyor.UpdateBuildVersion("2.0.0.0"); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// AppVeyor.UpdateBuildVersion("2.0.0.0"); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// void UpdateBuildVersion(string version); + + /// + /// Adds a message to the AppVeyor build log. Messages can be categorised as: Information, Warning or Error. + /// + /// A short message to display. + /// The category of the message. + /// Additional message details. + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AppVeyor.IsRunningOnAppVeyor) + /// { + /// BuildSystem.AppVeyor.AddMessage( + /// "This is a error message.", + /// AppVeyorMessageCategoryType.Error, + /// "Error details." + /// ); + /// + /// BuildSystem.AppVeyor.AddMessage( + /// "This is a information message.", + /// AppVeyorMessageCategoryType.Information, + /// "Information details." + /// ); + /// + /// BuildSystem.AppVeyor.AddMessage( + /// "This is a warning message.", + /// AppVeyorMessageCategoryType.Warning, + /// "Warning details." + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + /// Via AppVeyor. + /// + /// + /// if (AppVeyor.IsRunningOnAppVeyor) + /// { + /// AppVeyor.AddMessage( + /// "This is a error message.", + /// AppVeyorMessageCategoryType.Error, + /// "Error details." + /// ); + /// + /// AppVeyor.AddMessage( + /// "This is a information message.", + /// AppVeyorMessageCategoryType.Information, + /// "Information details." + /// ); + /// + /// AppVeyor.AddMessage( + /// "This is a warning message.", + /// AppVeyorMessageCategoryType.Warning, + /// "Warning details." + /// ); + /// } + /// else + /// { + /// Information("Not running on AppVeyor"); + /// } + /// + /// + void AddMessage(string message, AppVeyorMessageCategoryType category = AppVeyorMessageCategoryType.Information, string details = null); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AzurePipelines/AzurePipelinesCommands.cs b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesCommands.cs new file mode 100644 index 0000000000..df1f554cc7 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesCommands.cs @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// Responsible for issuing Azure Pipelines agent commands (see ). + /// + public sealed class AzurePipelinesCommands : IAzurePipelinesCommands + { + private const string FormatPrefix = "##["; + private const string MessagePrefix = "##vso["; + private const string MessagePostfix = "]"; + + private readonly ICakeEnvironment _environment; + private readonly IBuildSystemServiceMessageWriter _writer; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The build system service message writer. + public AzurePipelinesCommands(ICakeEnvironment environment, IBuildSystemServiceMessageWriter writer) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + } + + /// + public void WriteWarning(string message) + { + WriteLoggingCommand("task.logissue", new Dictionary + { + ["type"] = "warning" + }, message); + } + + /// + public void WriteWarning(string message, AzurePipelinesMessageData data) + { + var properties = data.GetProperties(); + properties.Add("type", "warning"); + WriteLoggingCommand("task.logissue", properties, message); + } + + /// + public void WriteError(string message) + { + WriteLoggingCommand("task.logissue", new Dictionary + { + ["type"] = "error" + }, message); + } + + /// + public void WriteError(string message, AzurePipelinesMessageData data) + { + var properties = data.GetProperties(); + properties.Add("type", "error"); + WriteLoggingCommand("task.logissue", properties, message); + } + + /// + public void BeginGroup(string name) + { + WriteFormatCommand("group", name); + } + + /// + public void EndGroup() + { + WriteFormatCommand("endgroup", string.Empty); + } + + /// + public void Section(string name) + { + WriteFormatCommand("section", name); + } + + /// + public void SetProgress(int progress, string currentOperation) + { + WriteLoggingCommand("task.setprogress", new Dictionary + { + ["value"] = progress.ToString() + }, currentOperation); + } + + /// + public void CompleteCurrentTask() + { + WriteLoggingCommand("task.complete", "DONE"); + } + + /// + public void CompleteCurrentTask(AzurePipelinesTaskResult result) + { + WriteLoggingCommand("task.complete", new Dictionary + { + ["result"] = result.ToString() + }, "DONE"); + } + + /// + public Guid CreateNewRecord(string name, string type, int order) + { + var guid = Guid.NewGuid(); + WriteLoggingCommand("task.logdetail", new Dictionary + { + ["id"] = guid.ToString(), + ["name"] = name, + ["type"] = type, + ["order"] = order.ToString() + }, "create new timeline record"); + return guid; + } + + /// + public Guid CreateNewRecord(string name, string type, int order, AzurePipelinesRecordData data) + { + var guid = Guid.NewGuid(); + var properties = data.GetProperties(); + properties.Add("id", guid.ToString()); + properties.Add("name", name); + properties.Add("type", type); + properties.Add("order", order.ToString()); + WriteLoggingCommand("task.logdetail", properties, "create new timeline record"); + return guid; + } + + /// + public void UpdateRecord(Guid id, AzurePipelinesRecordData data) + { + var properties = data.GetProperties(); + properties.Add("id", id.ToString()); + WriteLoggingCommand("task.logdetail", properties, "update"); + } + + /// + public void SetVariable(string name, string value) + { + WriteLoggingCommand("task.setvariable", new Dictionary + { + ["variable"] = name + }, value); + } + + /// + public void SetOutputVariable(string name, string value) + { + WriteLoggingCommand("task.setvariable", new Dictionary + { + ["variable"] = name, + ["isOutput"] = "true" + }, value); + } + + /// + public void SetSecretVariable(string name, string value) + { + WriteLoggingCommand("task.setvariable", new Dictionary + { + ["variable"] = name, + ["issecret"] = "true" + }, value); + } + + /// + public void UploadTaskSummary(FilePath markdownPath) + { + WriteLoggingCommand("task.uploadsummary", markdownPath.MakeAbsolute(_environment).FullPath); + } + + /// + public void UploadTaskLogFile(FilePath logFile) + { + WriteLoggingCommand("task.uploadfile", logFile.MakeAbsolute(_environment).FullPath); + } + + /// + public void LinkArtifact(string name, AzurePipelinesArtifactType type, string location) + { + WriteLoggingCommand("artifact.associate", new Dictionary + { + ["artifactname"] = name, + ["type"] = type.ToString() + }, location); + } + + /// + public void UploadArtifact(string folderName, FilePath file) + { + WriteLoggingCommand("artifact.upload", new Dictionary + { + ["containerfolder"] = folderName + }, file.MakeAbsolute(_environment).FullPath); + } + + /// + public void UploadArtifact(string folderName, FilePath file, string artifactName) + { + WriteLoggingCommand("artifact.upload", new Dictionary + { + ["containerfolder"] = folderName, + ["artifactname"] = artifactName + }, file.MakeAbsolute(_environment).FullPath); + } + + /// + public void UploadArtifactDirectory(DirectoryPath directory) + { + ArgumentNullException.ThrowIfNull(directory); + + UploadArtifactDirectory(directory, directory.GetDirectoryName()); + } + + /// + public void UploadArtifactDirectory(DirectoryPath directory, string artifactName) + { + ArgumentNullException.ThrowIfNull(directory); + + ArgumentNullException.ThrowIfNull(artifactName); + + WriteLoggingCommand("artifact.upload", new Dictionary + { + ["containerfolder"] = artifactName, + ["artifactname"] = artifactName + }, directory.MakeAbsolute(_environment).FullPath); + } + + /// + public void UploadBuildLogFile(FilePath logFile) + { + WriteLoggingCommand("build.uploadlog", logFile.MakeAbsolute(_environment).FullPath); + } + + /// + public void UpdateBuildNumber(string buildNumber) + { + WriteLoggingCommand("build.updatebuildnumber", buildNumber); + } + + /// + public void AddBuildTag(string tag) + { + WriteLoggingCommand("build.addbuildtag", tag); + } + + /// + public void PublishTestResults(AzurePipelinesPublishTestResultsData data) + { + var properties = data.GetProperties(_environment); + WriteLoggingCommand("results.publish", properties, string.Empty); + } + + /// + public void PublishCodeCoverage(AzurePipelinesPublishCodeCoverageData data) + { + var properties = data.GetProperties(_environment); + WriteLoggingCommand("codecoverage.publish", properties, string.Empty); + } + + /// + public void PublishCodeCoverage(FilePath summaryFilePath, AzurePipelinesPublishCodeCoverageData data) + { + ArgumentNullException.ThrowIfNull(summaryFilePath); + + var properties = data.GetProperties(_environment, summaryFilePath); + WriteLoggingCommand("codecoverage.publish", properties, string.Empty); + } + + /// + public void PublishCodeCoverage(FilePath summaryFilePath, Action action) + { + ArgumentNullException.ThrowIfNull(action); + + var data = new AzurePipelinesPublishCodeCoverageData(); + action(data); + + PublishCodeCoverage(summaryFilePath, data); + } + + private void WriteFormatCommand(string actionName, string value) + { + _writer.Write("{0}{1}{2}{3}", FormatPrefix, actionName, MessagePostfix, value); + } + + private void WriteLoggingCommand(string actionName, string value) + { + _writer.Write("{0}{1}{2}{3}", MessagePrefix, actionName, MessagePostfix, value); + } + + private void WriteLoggingCommand(string actionName, Dictionary properties, string value) + { + var props = string.Concat(properties.Select(pair => + { + return string.Format(CultureInfo.InvariantCulture, "{0}={1};", pair.Key, pair.Value); + })); + + _writer.Write("{0}{1} {2}{3}{4}", MessagePrefix, actionName, props, MessagePostfix, value); + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/AzurePipelinesDisposableExtensions.cs b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesDisposableExtensions.cs new file mode 100644 index 0000000000..80d90c2b5e --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesDisposableExtensions.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// A set of extensions for allowing "using" with Azure Pipelines "blocks". + /// + public static class AzurePipelinesDisposableExtensions + { + /// + /// Groups Azure Pipelines output. + /// + /// The Azure Pipelines Commands. + /// The name. + /// An . + public static IDisposable Group(this IAzurePipelinesCommands azurePipelinesCommands, string name) + { + ArgumentNullException.ThrowIfNull(name); + + azurePipelinesCommands.BeginGroup(name); + return new AzurePipelinesActionDisposable(azurePipelinesCommands, apc => apc.EndGroup()); + } + + /// + /// Disposable helper for writing Azure Pipelines message blocks. + /// + internal sealed class AzurePipelinesActionDisposable : IDisposable + { + private readonly Action _disposeAction; + private readonly T _instance; + + /// + /// Initializes a new instance of the class. + /// + /// The instance. + /// The dispose action. + public AzurePipelinesActionDisposable(T instance, Action disposeAction) + { + _instance = instance; + _disposeAction = disposeAction; + } + + /// + /// Calls dispose action. + /// + public void Dispose() + { + _disposeAction(_instance); + } + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/AzurePipelinesInfo.cs b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesInfo.cs new file mode 100644 index 0000000000..bcd7d5db33 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesInfo.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// Base class used to provide information about the Azure Pipelines environment. + /// + public abstract class AzurePipelinesInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected AzurePipelinesInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected long GetEnvironmentLongInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + long result; + if (long.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected Uri GetEnvironmentUri(string variable) + { + var value = GetEnvironmentString(variable); + Uri uri; + if (Uri.TryCreate(value, UriKind.Absolute, out uri)) + { + return uri; + } + return null; + } + + /// + /// Gets the current repository type as a from an environment variable. + /// + /// The environment variable name. + /// The current repository type. + protected AzurePipelinesRepositoryType? GetRepositoryType(string variable) + { + var value = GetEnvironmentString(variable); + AzurePipelinesRepositoryType type; + if (Enum.TryParse(value, true, out type)) + { + return type; + } + return null; + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/AzurePipelinesProvider.cs b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesProvider.cs new file mode 100644 index 0000000000..a5ff92fd05 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/AzurePipelinesProvider.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// Responsible for communicating with Azure Pipelines. + /// + public sealed class AzurePipelinesProvider : IAzurePipelinesProvider + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The build system service message writer. + public AzurePipelinesProvider(ICakeEnvironment environment, IBuildSystemServiceMessageWriter writer) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new AzurePipelinesEnvironmentInfo(environment); + Commands = new AzurePipelinesCommands(environment, writer); + } + + /// + public bool IsRunningOnAzurePipelines + => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("TF_BUILD")); + + /// + public AzurePipelinesEnvironmentInfo Environment { get; } + + /// + public IAzurePipelinesCommands Commands { get; } + + /// + /// Gets a value indicating whether the current build is running on a hosted build agent. + /// + /// + /// true if the current build is running on a hosted agent; otherwise, false. + /// + private bool IsHostedAgent => Environment.Agent.IsHosted; + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesAgentInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesAgentInfo.cs new file mode 100644 index 0000000000..0a5ae1a204 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesAgentInfo.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines agent info for the current build and build agent. + /// + public sealed class AzurePipelinesAgentInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesAgentInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the local path on the agent where all folders for a given build definition are created. + /// + /// + /// The local path on the agent where all folders for a given build definition are created. + /// + /// c:\agent\_work\1. + public DirectoryPath BuildDirectory => GetEnvironmentString("AGENT_BUILDDIRECTORY"); + + /// + /// Gets the directory the agent is installed into. This contains the agent software. + /// + /// If you are using a self-hosted agent, this directory is specified by you. + /// + /// The directory the agent is installed into. + /// + /// c:\agent\. + public DirectoryPath HomeDirectory => GetEnvironmentString("AGENT_HOMEDIRECTORY"); + + /// + /// Gets the working directory for this agent. + /// + /// + /// The working directory for this agent. + /// + public DirectoryPath WorkingDirectory => GetEnvironmentString("AGENT_WORKFOLDER"); + + /// + /// Gets the ID of the agent. + /// + /// + /// The ID of the agent. + /// + public int Id => GetEnvironmentInteger("AGENT_ID"); + + /// + /// Gets the display name of the running job. + /// + /// + /// The display name of the running job. + /// + public string JobName => GetEnvironmentString("AGENT_JOBNAME"); + + /// + /// Gets the status of the build. + /// + /// + /// The status of the build. + /// + public string JobStatus => GetEnvironmentString("AGENT_JOBSTATUS"); + + /// + /// Gets the name of the agent that is registered with the pool. + /// + /// If you are using a self-hosted agent, this is specified by you. + /// + /// The name of the agent that is registered with the pool. + /// + public string Name => GetEnvironmentString("AGENT_NAME"); + + /// + /// Gets the name of the machine on which the agent is installed. + /// + /// + /// The name of the machine on which the agent is installed. + /// + public string MachineName => GetEnvironmentString("AGENT_MACHINENAME"); + + /// + /// Gets the directory used by tasks such as Node Tool Installer and Use Python Version to switch between multiple versions of a tool. + /// + /// + /// These tasks will add tools from this directory to PATH so that subsequent build steps can use them. + /// + /// + /// The task directory. + /// + public DirectoryPath ToolsDirectory => GetEnvironmentString("AGENT_TOOLSDIRECTORY"); + + /// + /// Gets a value indicating whether the current agent is a Microsoft hosted agent. + /// + /// + /// true if the current agent is a Microsoft hosted agent. false if the current agent is a self hosted agent. + /// + public bool IsHosted => Name != null && (Name.StartsWith("Hosted") || Name.StartsWith("Azure Pipelines")); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesArtifactType.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesArtifactType.cs new file mode 100644 index 0000000000..0d1cb2282d --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesArtifactType.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides the type of an Azure Pipelines artifact. + /// + public enum AzurePipelinesArtifactType + { + /// + /// The container type. + /// + Container, + + /// + /// The file path type. + /// + FilePath, + + /// + /// The version control path type. + /// + VersionControl, + + /// + /// The Git reference type. + /// + GitRef, + + /// + /// The TFVC label type. + /// + TFVCLabel + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesBuildInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesBuildInfo.cs new file mode 100644 index 0000000000..c28c3454b6 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesBuildInfo.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines Build info for the current build. + /// + public sealed class AzurePipelinesBuildInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesBuildInfo(ICakeEnvironment environment) + : base(environment) + { + TriggeredBy = new AzurePipelinesTriggeredBy(environment); + } + + /// + /// Gets the a special variable that carries the security token used by the running build. + /// + /// + /// The security token. + /// + public string AccessToken => GetEnvironmentString("SYSTEM_ACCESSTOKEN"); + + /// + /// Gets a value indicating whether more detailed logs to debug pipeline problems is enabled. + /// + /// + /// True if more detailed logs are enabled. + /// + public bool Debug => GetEnvironmentBoolean("SYSTEM_DEBUG"); + + /// + /// Gets the local path on the agent where any artifacts are copied to before being pushed to their destination. + /// + /// + /// The path of the staging directory. + /// + public DirectoryPath ArtifactStagingDirectory => GetEnvironmentString("BUILD_ARTIFACTSTAGINGDIRECTORY"); + + /// + /// Gets the local path on the agent you can use as an output folder for compiled binaries. + /// + /// + /// The path to the binaries directory. + /// + public DirectoryPath BinariesDirectory => GetEnvironmentString("BUILD_BINARIESDIRECTORY"); + + /// + /// Gets the ID of the record for the completed build. + /// + /// + /// The ID of the record for the completed build. + /// + public int Id => GetEnvironmentInteger("BUILD_BUILDID"); + + /// + /// Gets the name of the completed build. + /// + /// You can specify the build number format that generates this value in the build definition. + /// + /// The name of the completed build. + /// + public string Number => GetEnvironmentString("BUILD_BUILDNUMBER"); + + /// + /// Gets the URI for the build. + /// + /// vstfs:///Build/Build/1430. + /// + /// The URI for the build. + /// + public Uri Uri => new Uri(GetEnvironmentString("BUILD_BUILDURI")); + + /// + /// Gets the user who queued the build. + /// + /// + /// The user who queued the build. + /// + public string QueuedBy => GetEnvironmentString("BUILD_QUEUEDBY"); + + /// + /// Gets the event that caused the build to run. + /// + /// + /// The event name. + /// + public string Reason => GetEnvironmentString("BUILD_REASON"); + + /// + /// Gets the user the build was requested for. + /// + /// + /// The user the build was requested for. + /// + public string RequestedFor => GetEnvironmentString("BUILD_REQUESTEDFOR"); + + /// + /// Gets the email of the user the build was requested for. + /// + /// + /// The email of the user the build was requested for. + /// + public string RequestedForEmail => GetEnvironmentString("BUILD_REQUESTEDFOREMAIL"); + + /// + /// Gets the local path on the agent where your source code files are downloaded. + /// + /// + /// The source code directory. + /// + public DirectoryPath SourcesDirectory => GetEnvironmentString("BUILD_SOURCESDIRECTORY"); + + /// + /// Gets the local path on the agent where any artifacts are copied to before being pushed to their destination. + /// + /// + /// The staging directory. + /// + public DirectoryPath StagingDirectory => GetEnvironmentString("BUILD_STAGINGDIRECTORY"); + + /// + /// Gets local path on the agent where the test results are created. + /// + /// + /// The test result directory. + /// + public DirectoryPath TestResultsDirectory => GetEnvironmentString("COMMON_TESTRESULTSDIRECTORY"); + + /// + /// Gets Azure Pipelines Build TriggeredBy information. + /// + /// + /// This is only populated if the build was triggered by another build. + /// + /// + /// The Azure Pipelines Build Trigger information. + /// + public AzurePipelinesTriggeredBy TriggeredBy { get; } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesCodeCoverageToolType.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesCodeCoverageToolType.cs new file mode 100644 index 0000000000..5d7f329e6f --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesCodeCoverageToolType.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides the known values for the Code Coverage tool formats. + /// + public enum AzurePipelinesCodeCoverageToolType + { + /// + /// JaCoCo code coverage format + /// + JaCoCo, + + /// + /// Cobertura code coverage format + /// + Cobertura + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfo.cs new file mode 100644 index 0000000000..0f94b5e90d --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesDefinitionInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines definition information for the current build. + /// + public sealed class AzurePipelinesDefinitionInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesDefinitionInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the build definition ID. + /// + /// + /// The build definition ID. + /// + public int Id => GetEnvironmentInteger("SYSTEM_DEFINITIONID"); + + /// + /// Gets the build definition name. + /// + /// + /// The build definition name. + /// + public string Name => GetEnvironmentString("BUILD_DEFINITIONNAME"); + + /// + /// Gets the build definition version. + /// + /// + /// The build definition version. + /// + public int Version => GetEnvironmentInteger("BUILD_DEFINITIONVERSION"); + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesEnvironmentInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesEnvironmentInfo.cs new file mode 100644 index 0000000000..aecab7624d --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesEnvironmentInfo.cs @@ -0,0 +1,321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines environment information for the current build. + /// + public sealed class AzurePipelinesEnvironmentInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Repository = new AzurePipelinesRepositoryInfo(environment); + BuildDefinition = new AzurePipelinesDefinitionInfo(environment); + Build = new AzurePipelinesBuildInfo(environment); + PullRequest = new AzurePipelinesPullRequestInfo(environment); + Agent = new AzurePipelinesAgentInfo(environment); + TeamProject = new AzurePipelinesTeamProjectInfo(environment); + } + + /// + /// Gets Azure Pipelines repository information. + /// + /// + /// The Azure Pipelines repository information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// SourceVersion: {1} + /// Shelveset: {2}", + /// BuildSystem.AzurePipelines.Environment.Repository.Branch, + /// BuildSystem.AzurePipelines.Environment.Repository.SourceVersion, + /// BuildSystem.AzurePipelines.Environment.Repository.Shelveset + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// SourceVersion: {1} + /// Shelveset: {2}", + /// AzurePipelines.Environment.Repository.Branch, + /// AzurePipelines.Environment.Repository.SourceVersion, + /// AzurePipelines.Environment.Repository.Shelveset + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + public AzurePipelinesRepositoryInfo Repository { get; } + + /// + /// Gets Azure Pipelines Build Definition information. + /// + /// + /// The Azure Pipelines Build Definition. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"BuildDefinition: + /// Id: {0} + /// Name: {1} + /// Version: {2}", + /// BuildSystem.AzurePipelines.Environment.BuildDefinition.Id, + /// BuildSystem.AzurePipelines.Environment.BuildDefinition.Name, + /// BuildSystem.AzurePipelines.Environment.BuildDefinition.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on AzurePipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"BuildDefinition: + /// Id: {0} + /// Name: {1} + /// Version: {2}", + /// AzurePipelines.Environment.BuildDefinition.Id, + /// AzurePipelines.Environment.BuildDefinition.Name, + /// AzurePipelines.Environment.BuildDefinition.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on AzurePipelines"); + /// } + /// + /// + public AzurePipelinesDefinitionInfo BuildDefinition { get; } + + /// + /// Gets Azure Pipelines Build information. + /// + /// + /// The Azure Pipelines Build. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Build: + /// Id: {0} + /// Number: {1} + /// QueuedBy: {2}", + /// BuildSystem.AzurePipelines.Environment.Build.Id, + /// BuildSystem.AzurePipelines.Environment.Build.Number, + /// BuildSystem.AzurePipelines.Environment.Build.QueuedBy + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Build: + /// Id: {0} + /// Number: {1} + /// QueuedBy: {2}", + /// AzurePipelines.Environment.Build.Id, + /// AzurePipelines.Environment.Build.Number, + /// AzurePipelines.Environment.Build.QueuedBy + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + public AzurePipelinesBuildInfo Build { get; } + + /// + /// Gets Azure Pipelines pull request information. + /// + /// + /// The Azure Pipelines pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1} + /// Number: {2}", + /// BuildSystem.AzurePipelines.Environment.PullRequest.IsPullRequest, + /// BuildSystem.AzurePipelines.Environment.PullRequest.Id, + /// BuildSystem.AzurePipelines.Environment.PullRequest.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1} + /// Number: {2}", + /// AzurePipelines.Environment.PullRequest.IsPullRequest, + /// AzurePipelines.Environment.PullRequest.Id, + /// AzurePipelines.Environment.PullRequest.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + public AzurePipelinesPullRequestInfo PullRequest { get; } + + /// + /// Gets Azure Pipeline Team Project information. + /// + /// + /// The Azure Pipelines Team Project. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"TeamProject: + /// Id: {0} + /// Name: {1}", + /// BuildSystem.AzurePipelines.Environment.TeamProject.Id, + /// BuildSystem.AzurePipelines.Environment.TeamProject.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"TeamProject: + /// Id: {0} + /// Name: {1}", + /// AzurePipelines.Environment.TeamProject.Id, + /// AzurePipelines.Environment.TeamProject.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + public AzurePipelinesTeamProjectInfo TeamProject { get; } + + /// + /// Gets Azure Pipelines agent information. + /// + /// + /// The Azure Pipelines agent. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Agent: + /// Id: {0} + /// Name: {1}", + /// BuildSystem.AzurePipelines.Environment.Agent.Id, + /// BuildSystem.AzurePipelines.Environment.Agent.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + /// Via AzurePipelines. + /// + /// + /// if (AzurePipelines.IsRunningOnAzurePipelines) + /// { + /// Information( + /// @"Agent: + /// Id: {0} + /// Name: {1}", + /// AzurePipelines.Environment.Agent.Id, + /// AzurePipelines.Environment.Agent.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on Azure Pipelines"); + /// } + /// + /// + public AzurePipelinesAgentInfo Agent { get; } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesMessageData.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesMessageData.cs new file mode 100644 index 0000000000..830412419f --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesMessageData.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides optional data associated with a Azure Pipelines logging message. + /// + public sealed class AzurePipelinesMessageData + { + /// + /// Gets or sets the source file path the message should originate from. + /// + /// + /// The path of the originating file. + /// + public string SourcePath { get; set; } + + /// + /// Gets or sets the line number the message relates to. + /// + /// + /// The line number. + /// + public int? LineNumber { get; set; } + + /// + /// Gets or sets the column number the message relates to. + /// + /// + /// The column number. + /// + public int? ColumnNumber { get; set; } + + /// + /// Gets or sets the error code of the warning or error message. + /// + /// + /// The error code of the warning or error. + /// + public int? ErrorCode { get; set; } + + internal Dictionary GetProperties() + { + var properties = new Dictionary(); + if (!string.IsNullOrWhiteSpace(SourcePath)) + { + properties.Add("sourcepath", SourcePath); + } + if (LineNumber.HasValue) + { + properties.Add("linenumber", LineNumber.ToString()); + } + if (ColumnNumber.HasValue) + { + properties.Add("columnnumber", ColumnNumber.ToString()); + } + if (ErrorCode.HasValue) + { + properties.Add("code", ErrorCode.ToString()); + } + return properties; + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishCodeCoverageData.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishCodeCoverageData.cs new file mode 100644 index 0000000000..776d9995b1 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishCodeCoverageData.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Description of code coverage information to publish to Azure Pipelines. + /// + public class AzurePipelinesPublishCodeCoverageData + { + /// + /// Gets or sets the tool from which code coverage results are generated. + /// + public AzurePipelinesCodeCoverageToolType? CodeCoverageTool { get; set; } + + /// + /// Gets or sets the path of the summary file containing code coverage statistics, such as line, method, and class coverage. + /// + public FilePath SummaryFileLocation { get; set; } + + /// + /// Gets or sets the path of the code coverage HTML report directory. The report directory is published for later viewing as an artifact of the build. + /// + public DirectoryPath ReportDirectory { get; set; } + + /// + /// Gets or sets the file paths for any additional code coverage files to be published as artifacts of the build. + /// + public FilePath[] AdditionalCodeCoverageFiles { get; set; } + + internal Dictionary GetProperties(ICakeEnvironment environment, FilePath summaryFilePath = null) + { + ArgumentNullException.ThrowIfNull(environment); + + var properties = new Dictionary(); + + if (CodeCoverageTool.HasValue) + { + properties.Add("codecoveragetool", CodeCoverageTool.Value.ToString()); + } + + if (summaryFilePath != null) + { + properties.Add("summaryfile", + summaryFilePath + .MakeAbsolute(environment) + .FullPath + .Replace(summaryFilePath.Separator, System.IO.Path.DirectorySeparatorChar)); + } + + if (SummaryFileLocation != null && summaryFilePath == null) + { + properties.Add("summaryfile", + SummaryFileLocation + .MakeAbsolute(environment) + .FullPath + .Replace(SummaryFileLocation.Separator, System.IO.Path.DirectorySeparatorChar)); + } + + if (ReportDirectory != null) + { + properties.Add("reportdirectory", + ReportDirectory + .MakeAbsolute(environment) + .FullPath + .Replace(ReportDirectory.Separator, System.IO.Path.DirectorySeparatorChar)); + } + + if (AdditionalCodeCoverageFiles != null && AdditionalCodeCoverageFiles.Any()) + { + properties.Add("additionalcodecoveragefiles", + string.Join(',', + AdditionalCodeCoverageFiles + .Select(filePath => + filePath + .MakeAbsolute(environment) + .FullPath + .Replace(filePath.Separator, System.IO.Path.DirectorySeparatorChar)))); + } + + return properties; + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishTestResultsData.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishTestResultsData.cs new file mode 100644 index 0000000000..984880b61a --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPublishTestResultsData.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Description of test result information to publish to Azure Pipelines. + /// + public class AzurePipelinesPublishTestResultsData + { + /// + /// Gets or sets the type test runner the results are formatted in. + /// + public AzurePipelinesTestRunnerType? TestRunner { get; set; } + + /// + /// Gets or sets the list of test result files to publish. + /// + public ICollection TestResultsFiles { get; set; } = new List(); + + /// + /// Gets or sets whether to merge all test result files into one run. + /// + public bool? MergeTestResults { get; set; } + + /// + /// Gets or sets the platform for which the tests were run on. + /// + public string Platform { get; set; } + + /// + /// Gets or sets the configuration for which the tests were run on. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets a name for the test run. + /// + public string TestRunTitle { get; set; } + + /// + /// Gets or sets whether to opt in/out of publishing test run level attachments. + /// + public bool? PublishRunAttachments { get; set; } + + internal Dictionary GetProperties(ICakeEnvironment environment) + { + ArgumentNullException.ThrowIfNull(environment); + + var properties = new Dictionary(); + + if (TestRunner.HasValue) + { + properties.Add("type", TestRunner.Value.ToString()); + } + if (MergeTestResults.HasValue) + { + properties.Add("mergeResults", MergeTestResults.ToString().ToLowerInvariant()); + } + if (!string.IsNullOrWhiteSpace(Platform)) + { + properties.Add("platform", Platform); + } + if (!string.IsNullOrWhiteSpace(Configuration)) + { + properties.Add("config", Configuration); + } + if (!string.IsNullOrWhiteSpace(TestRunTitle)) + { + properties.Add("runTitle", $"'{TestRunTitle}'"); + } + if (PublishRunAttachments.HasValue) + { + properties.Add("publishRunAttachments", PublishRunAttachments.ToString().ToLowerInvariant()); + } + if (TestResultsFiles != null && TestResultsFiles.Any()) + { + properties.Add("resultFiles", + string.Join(',', + TestResultsFiles.Select(filePath => + filePath + .MakeAbsolute(environment) + .FullPath + .Replace(filePath.Separator, System.IO.Path.DirectorySeparatorChar)))); + } + + return properties; + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfo.cs new file mode 100644 index 0000000000..dea6c332fc --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesPullRequestInfo.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines pull request information for the current build. + /// + public sealed class AzurePipelinesPullRequestInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesPullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the pull request is from a fork of the repository. + /// + public bool IsFork => GetEnvironmentBoolean("SYSTEM_PULLREQUEST_ISFORK"); + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => LongId > 0; + + /// + /// Gets the ID of the pull request that caused this build. + /// This value is set only if the build ran because of a Git PR affected by a branch policy. + /// + /// + /// The ID of the pull request that caused this build. + /// + [Obsolete("Type will change in next major version to long, meanwhile use LongId to ensure proper value returned.")] + public int Id => GetEnvironmentInteger("SYSTEM_PULLREQUEST_PULLREQUESTID"); + + /// + /// Gets the ID of the pull request that caused this build. + /// This value is set only if the build ran because of a Git PR affected by a branch policy. + /// + /// + /// The ID of the pull request that caused this build. + /// + public long LongId => GetEnvironmentLongInteger("SYSTEM_PULLREQUEST_PULLREQUESTID"); + + /// + /// Gets the number of the pull request that caused this build. + /// This value is set for pull requests from GitHub which have a different pull request ID and pull request number. + /// + /// + /// The number of the pull request that caused this build. + /// + public int Number => GetEnvironmentInteger("SYSTEM_PULLREQUEST_PULLREQUESTNUMBER"); + + /// + /// Gets the branch that is being reviewed in a pull request. + /// + /// + /// This property is populated only if the build ran because of a Git PR affected by a branch policy. + /// + public string SourceBranch => GetEnvironmentString("SYSTEM_PULLREQUEST_SOURCEBRANCH"); + + /// + /// Gets the URL to the repo that contains the pull requests. + /// + /// + /// This property is populated only if the build ran because of a Git PR affected by a branch policy. It is not initialized for GitHub PRs. + /// + public Uri SourceRepositoryUri => GetEnvironmentUri("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI"); + + /// + /// Gets the branch that is the target of a pull request. + /// + /// + /// This property is populated only if the build ran because of a Git PR affected by a branch policy. + /// + public string TargetBranch => GetEnvironmentString("SYSTEM_PULLREQUEST_TARGETBRANCH"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRecordData.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRecordData.cs new file mode 100644 index 0000000000..68b5cd7f39 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRecordData.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides optional data associated with an Azure Pipelines timeline record. + /// + public sealed class AzurePipelinesRecordData + { + /// + /// Gets or sets the parent record of a new or existing timeline record. + /// + /// + /// The ID of the parent record. + /// + public Guid ParentRecord { get; set; } + + /// + /// Gets or sets the start time of this record. + /// + /// + /// The start time of this record. + /// + public DateTime? StartTime { get; set; } + + /// + /// Gets or sets the finish time of this record. + /// + /// + /// The finish time of this record. + /// + public DateTime? FinishTime { get; set; } + + /// + /// Gets or sets the current progress of this record. + /// + /// + /// The current progress of this record. + /// + public int? Progress { get; set; } + + /// + /// Gets or sets the current status of this record. + /// + /// + /// The current status of this record. + /// + public AzurePipelinesTaskStatus? Status { get; set; } + + /// + /// Gets or sets the result of this record. + /// + /// + /// The result of this record. + /// + public AzurePipelinesTaskResult? Result { get; set; } + + internal Dictionary GetProperties() + { + var properties = new Dictionary(); + if (ParentRecord != default(Guid)) + { + properties.Add("parentid", ParentRecord.ToString()); + } + if (StartTime.HasValue) + { + properties.Add("starttime", StartTime.ToString()); + } + if (FinishTime.HasValue) + { + properties.Add("finishtime", FinishTime.ToString()); + } + if (Progress.HasValue) + { + properties.Add("progress", Progress.ToString()); + } + if (Status.HasValue) + { + properties.Add("state", Status.ToString()); + } + if (Result.HasValue) + { + properties.Add("result", Result.ToString()); + } + return properties; + } + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfo.cs new file mode 100644 index 0000000000..436a406950 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryInfo.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines Repository information for the current build. + /// + public sealed class AzurePipelinesRepositoryInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesRepositoryInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the branch the build was queued for. + /// + /// + /// The full SCM branch. + /// + public string SourceBranch => GetEnvironmentString("BUILD_SOURCEBRANCH"); + + /// + /// Gets name of the branch the build was queued for. + /// + /// + /// The SCM branch name. + /// + public string SourceBranchName => GetEnvironmentString("BUILD_SOURCEBRANCHNAME"); + + /// + /// Gets the latest version control change that is included in this build. + /// + /// Note: for Git this is the commit ID. For TFVC this is the changeset. + /// + /// The SCM source version. + /// + public string SourceVersion => GetEnvironmentString("BUILD_SOURCEVERSION"); + + /// + /// Gets the comment of the commit or changeset. + /// + /// Note: This variable is available in TFS 2015.4. + /// + /// The comment. + /// + public string SourceVersionMessage => GetEnvironmentString("BUILD_SOURCEVERSIONMESSAGE"); + + /// + /// Gets the name of the shelveset you are building, if you are running a gated build or a shelveset build. + /// + /// Defined only if your repository is Team Foundation Version Control. + /// + /// The shelveset name. + /// + public string Shelveset => GetEnvironmentString("BUILD_SOURCETFVCSHELVESET"); + + /// + /// Gets the name of the repository. + /// + /// + /// The name of the repository. + /// + public string RepoName => GetEnvironmentString("BUILD_REPOSITORY_NAME"); + + /// + /// Gets the type of the current repository. + /// + /// + /// The type of the current repository. + /// + public AzurePipelinesRepositoryType? Provider => GetRepositoryType("BUILD_REPOSITORY_PROVIDER"); + + /// + /// Gets the value you've selected for Checkout submodules on the repository tab. + /// + /// + /// The checkout submodule value. + /// + public string GitSubmoduleCheckout => GetEnvironmentString("BUILD_REPOSITORY_GIT_SUBMODULECHECKOUT"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryType.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryType.cs new file mode 100644 index 0000000000..004e2382b9 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesRepositoryType.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides the known values for the Azure Pipelines Repository types. + /// + public enum AzurePipelinesRepositoryType + { + /// + /// TFS Git repository. + /// + TfsGit, + + /// + /// Team Foundation Version Control repository. + /// + TfsVersionControl, + + /// + /// Git repository hosted on an external server. + /// + Git, + + /// + /// GitHub repository. + /// + GitHub, + + /// + /// Subversion repository. + /// + Svn + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskResult.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskResult.cs new file mode 100644 index 0000000000..df3e597a30 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskResult.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides the result of an Azure Pipelines task record. + /// + public enum AzurePipelinesTaskResult + { + /// + /// Succeeded status. + /// + Succeeded, + + /// + /// Succeeded with issues status. + /// + SucceededWithIssues, + + /// + /// Failed status. + /// + Failed, + + /// + /// Cancelled status. + /// + Cancelled, + + /// + /// Skipped status. + /// + Skipped + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskStatus.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskStatus.cs new file mode 100644 index 0000000000..88d0270a53 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTaskStatus.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides the status of an Azure Pipelines task record. + /// + public enum AzurePipelinesTaskStatus + { + /// + /// Unknown status. + /// + Unknown, + + /// + /// Initialized status. + /// + Initialized, + + /// + /// In progress status. + /// + InProgress, + + /// + /// Completed status. + /// + Completed + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfo.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfo.cs new file mode 100644 index 0000000000..56d3b032ed --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTeamProjectInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines Team Project information for the current build. + /// + public sealed class AzurePipelinesTeamProjectInfo : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesTeamProjectInfo(ICakeEnvironment environment) : base(environment) + { + } + + /// + /// Gets the name of the team project that contains this build. + /// + /// + /// The name of the team project that contains this build. + /// + public string Name => GetEnvironmentString("SYSTEM_TEAMPROJECT"); + + /// + /// Gets the ID of the team project that contains this build. + /// + /// + /// The ID of the team project that contains this build. + /// + public string Id => GetEnvironmentString("SYSTEM_TEAMPROJECTID"); + + /// + /// Gets the URI of the team foundation collection. + /// + /// + /// The URI of the team foundation collection. + /// + public Uri CollectionUri => GetEnvironmentUri("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"); + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTestRunnerType.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTestRunnerType.cs new file mode 100644 index 0000000000..efef5e4641 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTestRunnerType.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Test runner file formats supported on Azure Pipelines. + /// + public enum AzurePipelinesTestRunnerType + { + /// + /// JUnit Test Result Format + /// + JUnit, + + /// + /// NUnit (v2) Test Result Format + /// + NUnit, + + /// + /// Visual Studio (MSTest) Test Result Format + /// + VSTest, + + /// + /// XUnit Test Result Format + /// + XUnit + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTriggeredBy.cs b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTriggeredBy.cs new file mode 100644 index 0000000000..b67051b7a0 --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/Data/AzurePipelinesTriggeredBy.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.AzurePipelines.Data +{ + /// + /// Provides Azure Pipelines Trigger information for the current build. + /// + /// + /// Only populated if the build was triggered by another build. + /// + public class AzurePipelinesTriggeredBy : AzurePipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public AzurePipelinesTriggeredBy(ICakeEnvironment environment) : base(environment) + { + } + + /// + /// Gets the BuildID of the triggering build. + /// + public int BuildId => GetEnvironmentInteger("BUILD_TRIGGEREDBY_BUILDID"); + + /// + /// Gets the DefinitionID of the triggering build. + /// + public int DefinitionId => GetEnvironmentInteger("BUILD_TRIGGEREDBY_DEFINITIONID"); + + /// + /// Gets the name of the triggering build pipeline. + /// + public string DefinitionName => GetEnvironmentString("BUILD_TRIGGEREDBY_DEFINITIONNAME"); + + /// + /// Gets the number of the triggering build. + /// + public string BuildNumber => GetEnvironmentString("BUILD_TRIGGEREDBY_BUILDNUMBER"); + + /// + /// Gets the ID of the project that contains the triggering build. + /// + public string ProjectId => GetEnvironmentString("BUILD_TRIGGEREDBY_PROJECTID"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesCommands.cs b/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesCommands.cs new file mode 100644 index 0000000000..0ca0beda1a --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesCommands.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.AzurePipelines.Data; +using Cake.Core.IO; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// Represents an Azure Pipelines command provider. + /// + public interface IAzurePipelinesCommands + { + /// + /// Log a warning issue to timeline record of current task. + /// + /// The warning message. + void WriteWarning(string message); + + /// + /// Log a warning issue with detailed data to timeline record of current task. + /// + /// The warning message. + /// The message data. + void WriteWarning(string message, AzurePipelinesMessageData data); + + /// + /// Log an error to timeline record of current task. + /// + /// The error message. + void WriteError(string message); + + /// + /// Log an error with detailed data to timeline record of current task. + /// + /// The error message. + /// The message data. + void WriteError(string message, AzurePipelinesMessageData data); + + /// + /// Begin a collapsible group. + /// + /// The name of the group. + public void BeginGroup(string name); + + /// + /// End a collapsible group. + /// + public void EndGroup(); + + /// + /// Log section. + /// + /// The name of the section. + public void Section(string name); + + /// + /// Set progress and current operation for current task. + /// + /// Current progress as percentage. + /// The current operation. + void SetProgress(int progress, string currentOperation); + + /// + /// Finish timeline record for current task and set task result to succeeded. + /// + void CompleteCurrentTask(); + + /// + /// Finish timeline record for current task and set task result. + /// + /// The task result status. + void CompleteCurrentTask(AzurePipelinesTaskResult result); + + /// + /// Create detail timeline record. + /// + /// Name of the new timeline record. + /// Type of the new timeline record. + /// Order of the timeline record. + /// The timeline record ID. + Guid CreateNewRecord(string name, string type, int order); + + /// + /// Create detail timeline record. + /// + /// Name of the new timeline record. + /// Type of the new timeline record. + /// Order of the timeline record. + /// Additional data for the new timeline record. + /// The timeline record ID. + Guid CreateNewRecord(string name, string type, int order, AzurePipelinesRecordData data); + + /// + /// Update an existing detail timeline record. + /// + /// The ID of the existing timeline record. + /// Additional data for the timeline record. + void UpdateRecord(Guid id, AzurePipelinesRecordData data); + + /// + /// Sets a variable in the variable service of the task context. + /// + /// + /// The variable is exposed to following tasks as an environment variable. + /// + /// The variable name. + /// The variable value. + void SetVariable(string name, string value); + + /// + /// Sets a output variable in the variable service of the task context. + /// + /// + /// The variable is exposed to following tasks as an environment variable. + /// + /// The variable name. + /// The variable value. + void SetOutputVariable(string name, string value); + + /// + /// Sets a secret variable in the variable service of the task context. + /// + /// + /// The variable is not exposed to following tasks as an environment variable, and must be passed as inputs. + /// + /// The variable name. + /// The variable value. + void SetSecretVariable(string name, string value); + + /// + /// Upload and attach summary markdown to current timeline record. + /// + /// + /// This summary is added to the build/release summary and is not available for download with logs. + /// + /// Path to the summary markdown file. + void UploadTaskSummary(FilePath markdownPath); + + /// + /// Upload file as additional log information to the current timeline record. + /// + /// + /// + /// The file shall be available for download along with task logs. + /// + /// + /// Requires agent version 1.101. + /// + /// + /// Path to the additional log file. + void UploadTaskLogFile(FilePath logFile); + + /// + /// Create an artifact link, such as a file or folder path or a version control path. + /// + /// The artifact name. + /// The artifact type. + /// The link path or value. + void LinkArtifact(string name, AzurePipelinesArtifactType type, string location); + + /// + /// Upload local file into a file container folder. + /// + /// Folder that the file will upload to. + /// Path to the local file. + void UploadArtifact(string folderName, FilePath file); + + /// + /// Upload local file into a file container folder, and create an artifact. + /// + /// Folder that the file will upload to. + /// Path to the local file. + /// The artifact name. + void UploadArtifact(string folderName, FilePath file, string artifactName); + + /// + /// Upload local directory as a container folder, and create an artifact. + /// + /// Path to the local directory. + void UploadArtifactDirectory(DirectoryPath directory); + + /// + /// Upload local directory as a container folder, and create an artifact with the specified name. + /// + /// Path to the local directory. + /// The artifact name. + void UploadArtifactDirectory(DirectoryPath directory, string artifactName); + + /// + /// Upload additional log to build container's logs/tool folder. + /// + /// The log file. + void UploadBuildLogFile(FilePath logFile); + + /// + /// Update build number for current build. + /// + /// + /// Requires agent version 1.88. + /// + /// The build number. + void UpdateBuildNumber(string buildNumber); + + /// + /// Add a tag for current build. + /// + /// + /// Requires agent version 1.95. + /// + /// The tag. + void AddBuildTag(string tag); + + /// + /// Publishes and uploads tests results. + /// + /// The publish test results data. + void PublishTestResults(AzurePipelinesPublishTestResultsData data); + + /// + /// Publishes and uploads code coverage results. + /// + /// The code coverage data. + void PublishCodeCoverage(AzurePipelinesPublishCodeCoverageData data); + + /// + /// Publishes and uploads code coverage results. + /// + /// The code coverage summary file path. + /// The code coverage data. + void PublishCodeCoverage(FilePath summaryFilePath, AzurePipelinesPublishCodeCoverageData data); + + /// + /// Publishes and uploads code coverage results. + /// + /// The code coverage summary file path. + /// The configuration action for the code coverage data. + void PublishCodeCoverage(FilePath summaryFilePath, Action action); + } +} diff --git a/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesProvider.cs b/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesProvider.cs new file mode 100644 index 0000000000..05cc97546e --- /dev/null +++ b/src/Cake.Common/Build/AzurePipelines/IAzurePipelinesProvider.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.AzurePipelines.Data; + +namespace Cake.Common.Build.AzurePipelines +{ + /// + /// Represents a Azure Pipelines provider. + /// + public interface IAzurePipelinesProvider + { + /// + /// Gets a value indicating whether the current build is running on Azure Pipelines. + /// + /// + /// true if the current build is running on Azure Pipelines; otherwise, false. + /// + bool IsRunningOnAzurePipelines { get; } + + /// + /// Gets the Azure Pipelines environment. + /// + /// + /// The Azure Pipelines environment. + /// + AzurePipelinesEnvironmentInfo Environment { get; } + + /// + /// Gets the Azure Pipelines Commands provider. + /// + /// + /// The Azure Pipelines commands provider. + /// + IAzurePipelinesCommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/Bamboo/BambooInfo.cs b/src/Cake.Common/Build/Bamboo/BambooInfo.cs index 77382c844f..bc1ea9eb43 100644 --- a/src/Cake.Common/Build/Bamboo/BambooInfo.cs +++ b/src/Cake.Common/Build/Bamboo/BambooInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; @@ -66,4 +67,4 @@ protected bool GetEnvironmentBoolean(string variable) return false; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/BambooProvider.cs b/src/Cake.Common/Build/Bamboo/BambooProvider.cs index 1bc136b11c..84c58700e8 100644 --- a/src/Cake.Common/Build/Bamboo/BambooProvider.cs +++ b/src/Cake.Common/Build/Bamboo/BambooProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Build.Bamboo.Data; using Cake.Core; @@ -13,7 +14,6 @@ namespace Cake.Common.Build.Bamboo public sealed class BambooProvider : IBambooProvider { private readonly ICakeEnvironment _environment; - private readonly BambooEnvironmentInfo _environmentInfo; /// /// Initializes a new instance of the class. @@ -21,34 +21,14 @@ public sealed class BambooProvider : IBambooProvider /// The environment. public BambooProvider(ICakeEnvironment environment) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - _environment = environment; - _environmentInfo = new BambooEnvironmentInfo(environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new BambooEnvironmentInfo(environment); } - /// - /// Gets a value indicating whether the current build is running on Bamboo. - /// - /// - /// true if the current build is running on Bamboo.; otherwise, false. - /// - public bool IsRunningOnBamboo - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("bamboo_buildNumber")); } - } + /// + public bool IsRunningOnBamboo => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("bamboo_buildNumber")); - /// - /// Gets the Bamboo environment. - /// - /// - /// The Bamboo environment. - /// - public BambooEnvironmentInfo Environment - { - get { return _environmentInfo; } - } + /// + public BambooEnvironmentInfo Environment { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooBuildInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooBuildInfo.cs index 061cac01bd..6bb8a454f9 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooBuildInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooBuildInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -10,18 +11,13 @@ namespace Cake.Common.Build.Bamboo.Data /// public sealed class BambooBuildInfo : BambooInfo { - private readonly BambooCustomBuildInfo _customBuildProvider; - /// /// Gets the path to the clone directory. /// /// /// The path to the clone directory. /// - public string Folder - { - get { return GetEnvironmentString("bamboo_build_working_directory"); } - } + public string Folder => GetEnvironmentString("bamboo_build_working_directory"); /// /// Gets the build number. @@ -29,21 +25,15 @@ public string Folder /// /// The build number. /// - public int Number - { - get { return GetEnvironmentInteger("bamboo_buildNumber"); } - } + public int Number => GetEnvironmentInteger("bamboo_buildNumber"); /// - /// Gets the job key for the current job, in the form PROJECT-PLAN-JOB, e.g. BAM-MAIN-JOBX + /// Gets the job key for the current job, in the form PROJECT-PLAN-JOB, e.g. BAM-MAIN-JOBX. /// /// /// The Bamboo Build Key. /// - public string BuildKey - { - get { return GetEnvironmentString("bamboo_buildKey"); } - } + public string BuildKey => GetEnvironmentString("bamboo_buildKey"); /// /// Gets the Bamboo Build Result Key. @@ -53,10 +43,7 @@ public string BuildKey /// /// The Build Result Key. /// - public string ResultKey - { - get { return GetEnvironmentString("bamboo_buildResultKey"); } - } + public string ResultKey => GetEnvironmentString("bamboo_buildResultKey"); /// /// Gets the URL of the result in Bamboo once the job has finished executing. @@ -64,10 +51,7 @@ public string ResultKey /// /// The Bamboo build result url. /// - public string ResultsUrl - { - get { return GetEnvironmentString("bamboo_buildResultsUrl"); } - } + public string ResultsUrl => GetEnvironmentString("bamboo_buildResultsUrl"); /// /// Gets the time when build was started in ISO 8601 format e.g. 2010-01-01T01:00:00.000+01:00. @@ -75,10 +59,7 @@ public string ResultsUrl /// /// The Bamboo build timestamp. /// - public string BuildTimestamp - { - get { return GetEnvironmentString("bamboo_buildTimeStamp"); } - } + public string BuildTimestamp => GetEnvironmentString("bamboo_buildTimeStamp"); /// /// Gets Bamboo custom build information. @@ -86,10 +67,7 @@ public string BuildTimestamp /// /// The Bamboo custom build information. /// - public BambooCustomBuildInfo CustomBuild - { - get { return _customBuildProvider; } - } + public BambooCustomBuildInfo CustomBuild { get; } /// /// Initializes a new instance of the class. @@ -98,7 +76,7 @@ public BambooCustomBuildInfo CustomBuild public BambooBuildInfo(ICakeEnvironment environment) : base(environment) { - _customBuildProvider = new BambooCustomBuildInfo(environment); + CustomBuild = new BambooCustomBuildInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooCommitInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooCommitInfo.cs index 11e7e56eaa..1a88a95886 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooCommitInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooCommitInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -16,10 +17,7 @@ public sealed class BambooCommitInfo : BambooInfo /// /// The commit ID (SHA). /// - public string RepositoryRevision - { - get { return GetEnvironmentString("bamboo_planRepository_revision"); } - } + public string RepositoryRevision => GetEnvironmentString("bamboo_planRepository_revision"); /// /// Initializes a new instance of the class. @@ -30,4 +28,4 @@ public BambooCommitInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooCustomBuildInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooCustomBuildInfo.cs index 221f9c421c..0f2afd0ad6 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooCustomBuildInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooCustomBuildInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -16,10 +17,7 @@ public sealed class BambooCustomBuildInfo : BambooInfo /// /// true if build was started by pushed tag; otherwise, false. /// - public bool IsCustomBuild - { - get { return !string.IsNullOrWhiteSpace(GetEnvironmentString("bamboo_customRevision")); } - } + public bool IsCustomBuild => !string.IsNullOrWhiteSpace(GetEnvironmentString("bamboo_customRevision")); /// /// Gets the name for builds started by tag; otherwise this variable is undefined. @@ -27,10 +25,7 @@ public bool IsCustomBuild /// /// The name of the tag. /// - public string RevisonName - { - get { return GetEnvironmentString("bamboo_customRevision"); } - } + public string RevisonName => GetEnvironmentString("bamboo_customRevision"); /// /// Initializes a new instance of the class. @@ -41,4 +36,4 @@ public BambooCustomBuildInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooEnvironmentInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooEnvironmentInfo.cs index de44cd526a..259f6c7247 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooEnvironmentInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -10,20 +11,67 @@ namespace Cake.Common.Build.Bamboo.Data /// public sealed class BambooEnvironmentInfo : BambooInfo { - private readonly BambooPlanInfo _planProvider; - private readonly BambooBuildInfo _buildProvider; - private readonly BambooRepositoryInfo _repositoryProvider; - /// /// Gets Bamboo plan information. /// /// /// The Bamboo plan information. /// - public BambooPlanInfo Plan - { - get { return _planProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Build: + /// Plan Name: {0} + /// Short Plan Name: {1} + /// Plan Key: {2} + /// Short Plan Key: {3} + /// Short Job Key: {4} + /// Short Job Name: {5}", + /// BuildSystem.Bamboo.Environment.Plan.PlanName, + /// BuildSystem.Bamboo.Environment.Plan.ShortPlanName, + /// BuildSystem.Bamboo.Environment.Plan.PlanKey, + /// BuildSystem.Bamboo.Environment.Plan.ShortPlanKey, + /// BuildSystem.Bamboo.Environment.Plan.ShortJobKey, + /// BuildSystem.Bamboo.Environment.Plan.ShortJobName + /// ); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + /// Via Bamboo. + /// + /// + /// if (Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Build: + /// Plan Name: {0} + /// Short Plan Name: {1} + /// Plan Key: {2} + /// Short Plan Key: {3} + /// Short Job Key: {4} + /// Short Job Name: {5}", + /// Bamboo.Environment.Plan.PlanName, + /// Bamboo.Environment.Plan.ShortPlanName, + /// Bamboo.Environment.Plan.PlanKey, + /// Bamboo.Environment.Plan.ShortPlanKey, + /// Bamboo.Environment.Plan.ShortJobKey, + /// Bamboo.Environment.Plan.ShortJobName + /// ); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + public BambooPlanInfo Plan { get; } /// /// Gets Bamboo build information. @@ -31,10 +79,67 @@ public BambooPlanInfo Plan /// /// The Bamboo build information. /// - public BambooBuildInfo Build - { - get { return _buildProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Build: + /// Folder: {0} + /// Number: {1} + /// Build Key: {2} + /// Result Key: {3} + /// Results Url: {4} + /// Build Timestamp: {5} + /// Is Custom: {6} + /// Revision Name: {7}", + /// BuildSystem.Bamboo.Environment.Build.Folder, + /// BuildSystem.Bamboo.Environment.Build.Number, + /// BuildSystem.Bamboo.Environment.Build.BuildKey, + /// BuildSystem.Bamboo.Environment.Build.ResultKey, + /// BuildSystem.Bamboo.Environment.Build.ResultsUrl, + /// BuildSystem.Bamboo.Environment.Build.BuildTimestamp, + /// BuildSystem.Bamboo.Environment.Build.CustomBuild.IsCustomBuild, + /// BuildSystem.Bamboo.Environment.Build.CustomBuild.RevisionName); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + /// Via Bamboo. + /// + /// + /// if (Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Build: + /// Folder: {0} + /// Number: {1} + /// Build Key: {2} + /// Result Key: {3} + /// Results Url: {4} + /// Build Timestamp: {5} + /// Is Custom: {6} + /// Revision Name: {7}", + /// Bamboo.Environment.Build.Folder, + /// Bamboo.Environment.Build.Number, + /// Bamboo.Environment.Build.BuildKey, + /// Bamboo.Environment.Build.ResultKey, + /// Bamboo.Environment.Build.ResultsUrl, + /// Bamboo.Environment.Build.BuildTimestamp, + /// Bamboo.Environment.Build.CustomBuild.IsCustomBuild, + /// Bamboo.Environment.Build.CustomBuild.RevisionName); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + public BambooBuildInfo Build { get; } /// /// Gets Bamboo repository information. @@ -42,10 +147,53 @@ public BambooBuildInfo Build /// /// The Bamboo repository information. /// - public BambooRepositoryInfo Repository - { - get { return _repositoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Name: {1} + /// Repository Revision: {2} + /// Scm: {3}", + /// BuildSystem.Bamboo.Environment.Repository.Branch, + /// BuildSystem.Bamboo.Environment.Repository.Name, + /// BuildSystem.Bamboo.Environment.Repository.Commit.RepositoryRevision, + /// BuildSystem.Bamboo.Environment.Repository.Scm + /// ); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + /// Via Bamboo. + /// + /// + /// if (Bamboo.IsRunningOnBamboo) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Name: {1} + /// Repository Revision: {2} + /// Scm: {3}", + /// Bamboo.Environment.Repository.Branch, + /// Bamboo.Environment.Repository.Name, + /// Bamboo.Environment.Repository.Commit.RepositoryRevision, + /// Bamboo.Environment.Repository.Scm + /// ); + /// } + /// else + /// { + /// Information("Not running on Bamboo"); + /// } + /// + /// + public BambooRepositoryInfo Repository { get; } /// /// Initializes a new instance of the class. @@ -54,9 +202,9 @@ public BambooRepositoryInfo Repository public BambooEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _planProvider = new BambooPlanInfo(environment); - _buildProvider = new BambooBuildInfo(environment); - _repositoryProvider = new BambooRepositoryInfo(environment); + Plan = new BambooPlanInfo(environment); + Build = new BambooBuildInfo(environment); + Repository = new BambooRepositoryInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooPlanInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooPlanInfo.cs index 4a2a69bd85..b97ef31c64 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooPlanInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooPlanInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -11,70 +12,52 @@ namespace Cake.Common.Build.Bamboo.Data public sealed class BambooPlanInfo : BambooInfo { /// - /// Gets the Bamboo Plan Name + /// Gets the Bamboo Plan Name. /// /// /// The Bamboo Plan Name. /// - public string PlanName - { - get { return GetEnvironmentString("bamboo_planName"); } - } + public string PlanName => GetEnvironmentString("bamboo_planName"); /// - /// Gets the Bamboo short Plan Name + /// Gets the Bamboo short Plan Name. /// /// - /// The Bamboo Plan Name in it's short form. + /// The Bamboo Plan Name in its short form. /// - public string ShortPlanName - { - get { return GetEnvironmentString("bamboo_shortPlanName"); } - } + public string ShortPlanName => GetEnvironmentString("bamboo_shortPlanName"); /// - /// Gets the key of the current plan, in the form PROJECT-PLAN, e.g. BAM-MAIN + /// Gets the key of the current plan, in the form PROJECT-PLAN, e.g. BAM-MAIN. /// /// /// The project name. /// - public string PlanKey - { - get { return GetEnvironmentString("bamboo_planKey"); } - } + public string PlanKey => GetEnvironmentString("bamboo_planKey"); /// /// Gets the Bamboo short Plan Key. /// /// - /// The Bamboo Plan Key in it's hort form. + /// The Bamboo Plan Key in its short form. /// - public string ShortPlanKey - { - get { return GetEnvironmentString("bamboo_shortPlanKey"); } - } + public string ShortPlanKey => GetEnvironmentString("bamboo_shortPlanKey"); /// /// Gets the Bamboo short job key. /// /// - /// The Bamboo job key in it's short form. + /// The Bamboo job key in its short form. /// - public string ShortJobKey - { - get { return GetEnvironmentString("bamboo_shortJobKey"); } - } + public string ShortJobKey => GetEnvironmentString("bamboo_shortJobKey"); /// /// Gets the Bamboo short Job Name. /// /// - /// The Bamboo Job Name in it's short form. + /// The Bamboo Job Name in its short form. /// - public string ShortJobName - { - get { return GetEnvironmentString("bamboo_shortJobName"); } - } + public string ShortJobName => GetEnvironmentString("bamboo_shortJobName"); /// /// Initializes a new instance of the class. @@ -85,4 +68,4 @@ public BambooPlanInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/Data/BambooRepositoryInfo.cs b/src/Cake.Common/Build/Bamboo/Data/BambooRepositoryInfo.cs index a158d40bcd..c08699b428 100644 --- a/src/Cake.Common/Build/Bamboo/Data/BambooRepositoryInfo.cs +++ b/src/Cake.Common/Build/Bamboo/Data/BambooRepositoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bamboo.Data @@ -10,8 +11,6 @@ namespace Cake.Common.Build.Bamboo.Data /// public sealed class BambooRepositoryInfo : BambooInfo { - private readonly BambooCommitInfo _commitProvider; - /// /// Gets the revision control system. /// @@ -35,21 +34,15 @@ public sealed class BambooRepositoryInfo : BambooInfo /// /// The revision control system. /// - public string Scm - { - get { return GetEnvironmentString("bamboo_planRepository_type"); } - } + public string Scm => GetEnvironmentString("bamboo_planRepository_type"); /// - /// Gets the repository name as named in Bamboo + /// Gets the repository name as named in Bamboo. /// /// /// The bamboo repository name. /// - public string Name - { - get { return GetEnvironmentString("bamboo_repository_name"); } - } + public string Name => GetEnvironmentString("bamboo_repository_name"); /// /// Gets the build branch. @@ -57,10 +50,7 @@ public string Name /// /// The build branch. /// - public string Branch - { - get { return GetEnvironmentString("bamboo_planRepository_branch"); } - } + public string Branch => GetEnvironmentString("bamboo_planRepository_branch"); /// /// Gets the commit information for the build. @@ -68,10 +58,7 @@ public string Branch /// /// The commit information for the build. /// - public BambooCommitInfo Commit - { - get { return _commitProvider; } - } + public BambooCommitInfo Commit { get; } /// /// Initializes a new instance of the class. @@ -80,7 +67,7 @@ public BambooCommitInfo Commit public BambooRepositoryInfo(ICakeEnvironment environment) : base(environment) { - _commitProvider = new BambooCommitInfo(environment); + Commit = new BambooCommitInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bamboo/IBambooProvider.cs b/src/Cake.Common/Build/Bamboo/IBambooProvider.cs index 9c72f32092..4848eb3255 100644 --- a/src/Cake.Common/Build/Bamboo/IBambooProvider.cs +++ b/src/Cake.Common/Build/Bamboo/IBambooProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bamboo.Data; namespace Cake.Common.Build.Bamboo @@ -26,4 +27,4 @@ public interface IBambooProvider /// BambooEnvironmentInfo Environment { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesInfo.cs b/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesInfo.cs new file mode 100644 index 0000000000..67472d4248 --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesInfo.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.BitbucketPipelines +{ + /// + /// Base class used to provide information about the Bitbucket Pipelines environment. + /// + public abstract class BitbucketPipelinesInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected BitbucketPipelinesInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) && int.TryParse(value, out var result) ? result : 0; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesProvider.cs b/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesProvider.cs new file mode 100644 index 0000000000..4ff799f03d --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/BitbucketPipelinesProvider.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.BitbucketPipelines.Data; +using Cake.Core; + +namespace Cake.Common.Build.BitbucketPipelines +{ + /// + /// Responsible for communicating with Pipelines. + /// + public sealed class BitbucketPipelinesProvider : IBitbucketPipelinesProvider + { + /// + public bool IsRunningOnBitbucketPipelines => !string.IsNullOrWhiteSpace(Environment.Repository.RepoOwner) && + !string.IsNullOrWhiteSpace(Environment.Repository.RepoSlug) && + !string.IsNullOrWhiteSpace(Environment.Repository.Commit); + + /// + public BitbucketPipelinesEnvironmentInfo Environment { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public BitbucketPipelinesProvider(ICakeEnvironment environment) + { + Environment = new BitbucketPipelinesEnvironmentInfo(environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesEnvironmentInfo.cs b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesEnvironmentInfo.cs new file mode 100644 index 0000000000..85f4646218 --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesEnvironmentInfo.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.BitbucketPipelines.Data +{ + /// + /// Provides Bitbucket Pipelines environment information for the current build. + /// + public class BitbucketPipelinesEnvironmentInfo : BitbucketPipelinesInfo + { + /// + /// Gets Bitbucket Pipelines repository information. + /// + /// + /// The repository. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.BitbucketPipelines.IsRunningOnBitbucketPipelines) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Tag: {1} + /// Commit: {2} + /// Repo Owner: {3} + /// Repo Slug: {4}", + /// BuildSystem.BitbucketPipelines.Environment.Repository.Branch, + /// BuildSystem.BitbucketPipelines.Environment.Repository.Tag, + /// BuildSystem.BitbucketPipelines.Environment.Repository.Commit, + /// BuildSystem.BitbucketPipelines.Environment.Repository.RepoOwner, + /// BuildSystem.BitbucketPipelines.Environment.Repository.RepoSlug + /// ); + /// } + /// else + /// { + /// Information("Not running on BitbucketPipelines"); + /// } + /// + /// + /// Via BitbucketPipelines. + /// + /// + /// if (BitbucketPipelines.IsRunningOnBitbucketPipelines) + /// { + /// Information( + /// @"Repository: + /// Branch: {0} + /// Tag: {1} + /// Commit: {2} + /// Repo Owner: {3} + /// Repo Slug: {4}", + /// BitbucketPipelines.Environment.Repository.Branch, + /// BitbucketPipelines.Environment.Repository.Tag, + /// BitbucketPipelines.Environment.Repository.Commit, + /// BitbucketPipelines.Environment.Repository.RepoOwner, + /// BitbucketPipelines.Environment.Repository.RepoSlug + /// ); + /// } + /// else + /// { + /// Information("Not running on BitbucketPipelines"); + /// } + /// + /// + public BitbucketPipelinesRepositoryInfo Repository { get; } + + /// + /// Gets Bitbucket Pipelines pull request information. + /// + /// + /// The Bitbucket Pipelines pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.BitbucketPipelines.IsRunningOnBitbucketPipelines) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// BuildSystem.BitbucketPipelines.Environment.PullRequest.IsPullRequest, + /// BuildSystem.BitbucketPipelines.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on BitbucketPipelines"); + /// } + /// + /// + /// Via BitbucketPipelines. + /// + /// + /// if (BitbucketPipelines.IsRunningOnBitbucketPipelines) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// BitbucketPipelines.Environment.PullRequest.IsPullRequest, + /// BitbucketPipelines.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on BitbucketPipelines"); + /// } + /// + /// + public BitbucketPipelinesPullRequestInfo PullRequest { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public BitbucketPipelinesEnvironmentInfo(ICakeEnvironment environment) : base(environment) + { + Repository = new BitbucketPipelinesRepositoryInfo(environment); + PullRequest = new BitbucketPipelinesPullRequestInfo(environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfo.cs b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfo.cs new file mode 100644 index 0000000000..7321ae3eea --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesPullRequestInfo.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.BitbucketPipelines.Data +{ + /// + /// Provides BitbucketPipelines pull request information for a current build. + /// + public sealed class BitbucketPipelinesPullRequestInfo : BitbucketPipelinesInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public BitbucketPipelinesPullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => Id > 0; + + /// + /// Gets the pull request id. + /// + /// + /// The pull request id. + /// + public int Id => GetEnvironmentInteger("BITBUCKET_PR_ID"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfo.cs b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfo.cs new file mode 100644 index 0000000000..93cef3d35b --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/Data/BitbucketPipelinesRepositoryInfo.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.BitbucketPipelines.Data +{ + /// + /// Provides Bitbucket Pipelines repository information for the current build. + /// + public class BitbucketPipelinesRepositoryInfo : BitbucketPipelinesInfo + { + /// + /// Gets the branch on which the build was kicked off. This value is only available on branches. + /// + /// Note: and are mutually exclusive. If you use both, only one will have a value. + /// + /// The SCM branch. + /// + public string Branch => GetEnvironmentString("BITBUCKET_BRANCH"); + + /// + /// Gets the tag on which the build was kicked off. This value is only available when tagged. + /// + /// Note: and are mutually exclusive. If you use both, only one will have a value. + /// + /// The SCM tag. + /// + public string Tag => GetEnvironmentString("BITBUCKET_TAG"); + + /// + /// Gets the commit hash of a commit that kicked off the build. + /// + /// + /// The SCM commit. + /// + public string Commit => GetEnvironmentString("BITBUCKET_COMMIT"); + + /// + /// Gets the name of the account in which the repository lives. + /// + /// + /// The repository owner account. + /// + public string RepoOwner => GetEnvironmentString("BITBUCKET_REPO_OWNER"); + + /// + /// Gets the URL-friendly version of a repository name. + /// + /// + /// The URL-friendly repository name. + /// + public string RepoSlug => GetEnvironmentString("BITBUCKET_REPO_SLUG"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public BitbucketPipelinesRepositoryInfo(ICakeEnvironment environment) : base(environment) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BitbucketPipelines/IBitbucketPipelinesProvider.cs b/src/Cake.Common/Build/BitbucketPipelines/IBitbucketPipelinesProvider.cs new file mode 100644 index 0000000000..342ab6583c --- /dev/null +++ b/src/Cake.Common/Build/BitbucketPipelines/IBitbucketPipelinesProvider.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.BitbucketPipelines.Data; + +namespace Cake.Common.Build.BitbucketPipelines +{ + /// + /// Represents a Bitrise provider. + /// + public interface IBitbucketPipelinesProvider + { + /// + /// Gets a value indicating whether the current build is running on Bitbucket Pipelines. + /// + /// + /// true if the current build is running on Bitbucket Pipelines; otherwise, false. + /// + bool IsRunningOnBitbucketPipelines { get; } + + /// + /// Gets the Bitbucket Pipelines environment. + /// + /// + /// The Bitbucket Pipelines environment. + /// + BitbucketPipelinesEnvironmentInfo Environment { get; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/BitriseInfo.cs b/src/Cake.Common/Build/Bitrise/BitriseInfo.cs index 6545404cdc..947039248e 100644 --- a/src/Cake.Common/Build/Bitrise/BitriseInfo.cs +++ b/src/Cake.Common/Build/Bitrise/BitriseInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; @@ -40,15 +41,7 @@ protected string GetEnvironmentString(string variable) protected int GetEnvironmentInteger(string variable) { var value = GetEnvironmentString(variable); - if (!string.IsNullOrWhiteSpace(value)) - { - int result; - if (int.TryParse(value, out result)) - { - return result; - } - } - return 0; + return !string.IsNullOrWhiteSpace(value) && int.TryParse(value, out var result) ? result : 0; } /// @@ -59,11 +52,7 @@ protected int GetEnvironmentInteger(string variable) protected bool GetEnvironmentBoolean(string variable) { var value = GetEnvironmentString(variable); - if (!string.IsNullOrWhiteSpace(value)) - { - return value.Equals("true", StringComparison.OrdinalIgnoreCase); - } - return false; + return !string.IsNullOrWhiteSpace(value) && value.Equals("true", StringComparison.OrdinalIgnoreCase); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/BitriseProvider.cs b/src/Cake.Common/Build/Bitrise/BitriseProvider.cs index 66a5a7437a..054b73792a 100644 --- a/src/Cake.Common/Build/Bitrise/BitriseProvider.cs +++ b/src/Cake.Common/Build/Bitrise/BitriseProvider.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Common.Build.Bitrise.Data; using Cake.Core; +using Cake.Core.IO; namespace Cake.Common.Build.Bitrise { @@ -12,38 +15,47 @@ namespace Cake.Common.Build.Bitrise public sealed class BitriseProvider : IBitriseProvider { private readonly ICakeEnvironment _environment; - private readonly BitriseEnvironmentInfo _bitriseEnvironment; + private readonly IProcessRunner _processRunner; - /// - /// Gets a value indicating whether the current build is running on Bamboo. - /// - /// - /// true if the current build is running on Bamboo; otherwise, false. - /// - public bool IsRunningOnBitrise - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("BITRISE_BUILD_URL")); } - } + /// + public bool IsRunningOnBitrise => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("BITRISE_BUILD_URL")); - /// - /// Gets the Bamboo environment. - /// - /// - /// The Bamboo environment. - /// - public BitriseEnvironmentInfo Environment - { - get { return _bitriseEnvironment; } - } + /// + public BitriseEnvironmentInfo Environment { get; } /// /// Initializes a new instance of the class. /// /// The environment. - public BitriseProvider(ICakeEnvironment environment) + /// The process runner. + public BitriseProvider(ICakeEnvironment environment, IProcessRunner processRunner) { - _environment = environment; - _bitriseEnvironment = new BitriseEnvironmentInfo(_environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _processRunner = processRunner ?? throw new ArgumentNullException(nameof(processRunner)); + Environment = new BitriseEnvironmentInfo(_environment); + } + + /// + public void SetEnvironmentString(string variable, string value) + { + var arguments = new ProcessArgumentBuilder() + .Append("add") + .Append("--key") + .Append(variable) + .Append("--value") + .AppendQuoted(value); + + var process = _processRunner.Start("envman", new ProcessSettings + { + Arguments = arguments + }); + + process.WaitForExit(); + var exitCode = process.GetExitCode(); + if (exitCode != 0) + { + throw new CakeException($"BitriseProvider SetEnvironmentString failed ({exitCode})."); + } } } } diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseApplicationInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseApplicationInfo.cs index e2598d4141..6a36dad300 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseApplicationInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseApplicationInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -16,10 +17,7 @@ public class BitriseApplicationInfo : BitriseInfo /// /// The application title. /// - public string ApplicationTitle - { - get { return GetEnvironmentString("BITRISE_APP_TITLE"); } - } + public string ApplicationTitle => GetEnvironmentString("BITRISE_APP_TITLE"); /// /// Gets the application URL. @@ -27,10 +25,7 @@ public string ApplicationTitle /// /// The application URL. /// - public string ApplicationUrl - { - get { return GetEnvironmentString("BITRISE_APP_URL"); } - } + public string ApplicationUrl => GetEnvironmentString("BITRISE_APP_URL"); /// /// Gets the application slug. @@ -38,10 +33,7 @@ public string ApplicationUrl /// /// The application slug. /// - public string AppSlug - { - get { return GetEnvironmentString("BITRISE_APP_SLUG"); } - } + public string AppSlug => GetEnvironmentString("BITRISE_APP_SLUG"); /// /// Initializes a new instance of the class. @@ -51,4 +43,4 @@ public BitriseApplicationInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseBuildInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseBuildInfo.cs index 8677d28394..9f4aef943b 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseBuildInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseBuildInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -16,10 +17,7 @@ public class BitriseBuildInfo : BitriseInfo /// /// The build number. /// - public string BuildNumber - { - get { return GetEnvironmentString("BITRISE_BUILD_NUMBER"); } - } + public string BuildNumber => GetEnvironmentString("BITRISE_BUILD_NUMBER"); /// /// Gets the build URL. @@ -27,10 +25,7 @@ public string BuildNumber /// /// The build URL. /// - public string BuildUrl - { - get { return GetEnvironmentString("BITRISE_BUILD_URL"); } - } + public string BuildUrl => GetEnvironmentString("BITRISE_BUILD_URL"); /// /// Gets the build slug. @@ -38,10 +33,7 @@ public string BuildUrl /// /// The build slug. /// - public string BuildSlug - { - get { return GetEnvironmentString("BITRISE_BUILD_SLUG"); } - } + public string BuildSlug => GetEnvironmentString("BITRISE_BUILD_SLUG"); /// /// Gets the build trigger timestamp. @@ -49,10 +41,7 @@ public string BuildSlug /// /// The build trigger timestamp. /// - public string BuildTriggerTimestamp - { - get { return GetEnvironmentString("BITRISE_BUILD_TRIGGER_TIMESTAMP"); } - } + public string BuildTriggerTimestamp => GetEnvironmentString("BITRISE_BUILD_TRIGGER_TIMESTAMP"); /// /// Gets a value indicating whether the build is passing. @@ -60,10 +49,7 @@ public string BuildTriggerTimestamp /// /// true if [build status]; otherwise, false. /// - public bool BuildStatus - { - get { return GetEnvironmentBoolean("BITRISE_BUILD_STATUS"); } - } + public bool BuildStatus => GetEnvironmentBoolean("BITRISE_BUILD_STATUS"); /// /// Initializes a new instance of the class. @@ -73,4 +59,4 @@ public BitriseBuildInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseDirectoryInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseDirectoryInfo.cs index 98ddc7c8d8..84e2365e36 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseDirectoryInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseDirectoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -16,10 +17,7 @@ public class BitriseDirectoryInfo : BitriseInfo /// /// The source directory. /// - public string SourceDirectory - { - get { return GetEnvironmentString("BITRISE_SOURCE_DIR"); } - } + public string SourceDirectory => GetEnvironmentString("BITRISE_SOURCE_DIR"); /// /// Gets the deploy directory. @@ -27,10 +25,7 @@ public string SourceDirectory /// /// The deploy directory. /// - public string DeployDirectory - { - get { return GetEnvironmentString("BITRISE_DEPLOY_DIR"); } - } + public string DeployDirectory => GetEnvironmentString("BITRISE_DEPLOY_DIR"); /// /// Initializes a new instance of the class. @@ -40,4 +35,4 @@ public BitriseDirectoryInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseEnvironmentInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseEnvironmentInfo.cs index 4dae96fcc2..e0a709bab7 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseEnvironmentInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -10,23 +11,55 @@ namespace Cake.Common.Build.Bitrise.Data /// public class BitriseEnvironmentInfo : BitriseInfo { - private readonly BitriseApplicationInfo _applicationProvider; - private readonly BitriseBuildInfo _buildProvider; - private readonly BitriseProvisioningInfo _provisioningProvider; - private readonly BitriseRepositoryInfo _repositoryProvider; - private readonly BitriseWorkflowInfo _workflowProvider; - private readonly BitriseDirectoryInfo _directoryProvider; - /// /// Gets Bitrise application information. /// /// /// The application. /// - public BitriseApplicationInfo Application - { - get { return _applicationProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Application: + /// Title: {0} + /// Url: {1} + /// Slug: {2}", + /// BuildSystem.Bitrise.Environment.Application.ApplicationTitle, + /// BuildSystem.Bitrise.Environment.Application.ApplicationUrl, + /// BuildSystem.Bitrise.Environment.Application.AppSlug + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Application: + /// Title: {0} + /// Url: {1} + /// Slug: {2}", + /// Bitrise.Environment.Application.ApplicationTitle, + /// Bitrise.Environment.Application.ApplicationUrl, + /// Bitrise.Environment.Application.AppSlug + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseApplicationInfo Application { get; } /// /// Gets Bitrise build information. @@ -34,10 +67,103 @@ public BitriseApplicationInfo Application /// /// The build. /// - public BitriseBuildInfo Build - { - get { return _buildProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Build: + /// Build Number: {0} + /// Build Url: {1} + /// Build Slug: {2} + /// Build Trigger Timestamp: {3} + /// Build Status: {4}", + /// BuildSystem.Bitrise.Environment.Build.BuildNumber, + /// BuildSystem.Bitrise.Environment.Build.BuildUrl, + /// BuildSystem.Bitrise.Environment.Build.BuildSlug, + /// BuildSystem.Bitrise.Environment.Build.BuildTriggerTimestamp, + /// BuildSystem.Bitrise.Environment.Build.BuildStatus + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Build: + /// Build Number: {0} + /// Build Url: {1} + /// Build Slug: {2} + /// Build Trigger Timestamp: {3} + /// Build Status: {4}", + /// Bitrise.Environment.Build.BuildNumber, + /// Bitrise.Environment.Build.BuildUrl, + /// Bitrise.Environment.Build.BuildSlug, + /// Bitrise.Environment.Build.BuildTriggerTimestamp, + /// Bitrise.Environment.Build.BuildStatus + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseBuildInfo Build { get; } + + /// + /// Gets Bitrise pull request information. + /// + /// + /// The Bitrise pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// BuildSystem.Bitrise.Environment.PullRequest.IsPullRequest, + /// BuildSystem.Bitrise.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// Bitrise.Environment.PullRequest.IsPullRequest, + /// Bitrise.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitrisePullRequestInfo PullRequest { get; } /// /// Gets Bitrise directory information. @@ -45,10 +171,45 @@ public BitriseBuildInfo Build /// /// The directory. /// - public BitriseDirectoryInfo Directory - { - get { return _directoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Directory: + /// Source Directory: {0} + /// Deploy Directory: {1}", + /// BuildSystem.Bitrise.Environment.Directory.SourceDirectory, + /// BuildSystem.Bitrise.Environment.Directory.DeployDirectory + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Directory: + /// Source Directory: {0} + /// Deploy Directory: {1}", + /// Bitrise.Environment.Directory.SourceDirectory, + /// Bitrise.Environment.Directory.DeployDirectory + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseDirectoryInfo Directory { get; } /// /// Gets Bitrise provisioning information. @@ -56,10 +217,49 @@ public BitriseDirectoryInfo Directory /// /// The provisioning. /// - public BitriseProvisioningInfo Provisioning - { - get { return _provisioningProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Provisioning: + /// Provision Url: {0} + /// Certificate Url: {1} + /// Certificate Passphrase: {2}", + /// BuildSystem.Bitrise.Environment.Provisioning.ProvisionUrl, + /// BuildSystem.Bitrise.Environment.Provisioning.CertificateUrl, + /// BuildSystem.Bitrise.Environment.Provisioning.CertificatePassphrase + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Provisioning: + /// Provision Url: {0} + /// Certificate Url: {1} + /// Certificate Passphrase: {2}", + /// Bitrise.Environment.Provisioning.ProvisionUrl, + /// Bitrise.Environment.Provisioning.CertificateUrl, + /// Bitrise.Environment.Provisioning.CertificatePassphrase + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseProvisioningInfo Provisioning { get; } /// /// Gets Bitrise repository information. @@ -67,10 +267,57 @@ public BitriseProvisioningInfo Provisioning /// /// The repository. /// - public BitriseRepositoryInfo Repository - { - get { return _repositoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Repository: + /// Git Repository Url: {0} + /// Git Branch: {1} + /// Git Tag: {2} + /// Git Commit: {3} + /// Pull Request: {4}", + /// BuildSystem.Bitrise.Environment.Repository.GitRepositoryUrl, + /// BuildSystem.Bitrise.Environment.Repository.GitBranch, + /// BuildSystem.Bitrise.Environment.Repository.GitTag, + /// BuildSystem.Bitrise.Environment.Repository.GitCommit, + /// BuildSystem.Bitrise.Environment.Repository.PullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Repository: + /// Git Repository Url: {0} + /// Git Branch: {1} + /// Git Tag: {2} + /// Git Commit: {3} + /// Pull Request: {4}", + /// Bitrise.Environment.Repository.GitRepositoryUrl, + /// Bitrise.Environment.Repository.GitBranch, + /// Bitrise.Environment.Repository.GitTag, + /// Bitrise.Environment.Repository.GitCommit, + /// Bitrise.Environment.Repository.PullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseRepositoryInfo Repository { get; } /// /// Gets Bitrise workflow information. @@ -78,10 +325,45 @@ public BitriseRepositoryInfo Repository /// /// The workflow. /// - public BitriseWorkflowInfo Workflow - { - get { return _workflowProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Workflow: + /// Workflow Id: {0} + /// Workflow Title: {1}", + /// BuildSystem.Bitrise.Environment.Workflow.WorkflowId, + /// BuildSystem.Bitrise.Environment.Workflow.WorkflowTitle + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + /// Via Bitrise. + /// + /// + /// if (Bitrise.IsRunningOnBitrise) + /// { + /// Information( + /// @"Workflow: + /// Workflow Id: {0} + /// Workflow Title: {1}", + /// Bitrise.Environment.Workflow.WorkflowId, + /// Bitrise.Environment.Workflow.WorkflowTitle + /// ); + /// } + /// else + /// { + /// Information("Not running on Bitrise"); + /// } + /// + /// + public BitriseWorkflowInfo Workflow { get; } /// /// Initializes a new instance of the class. @@ -89,12 +371,13 @@ public BitriseWorkflowInfo Workflow /// The environment. public BitriseEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _applicationProvider = new BitriseApplicationInfo(environment); - _buildProvider = new BitriseBuildInfo(environment); - _provisioningProvider = new BitriseProvisioningInfo(environment); - _repositoryProvider = new BitriseRepositoryInfo(environment); - _workflowProvider = new BitriseWorkflowInfo(environment); - _directoryProvider = new BitriseDirectoryInfo(environment); + Application = new BitriseApplicationInfo(environment); + Build = new BitriseBuildInfo(environment); + PullRequest = new BitrisePullRequestInfo(environment); + Provisioning = new BitriseProvisioningInfo(environment); + Repository = new BitriseRepositoryInfo(environment); + Workflow = new BitriseWorkflowInfo(environment); + Directory = new BitriseDirectoryInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseProvisioningInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseProvisioningInfo.cs index d89eb9b25d..c973aac796 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseProvisioningInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseProvisioningInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -16,10 +17,7 @@ public class BitriseProvisioningInfo : BitriseInfo /// /// The provision URL. /// - public string ProvisionUrl - { - get { return GetEnvironmentString("BITRISE_PROVISION_URL"); } - } + public string ProvisionUrl => GetEnvironmentString("BITRISE_PROVISION_URL"); /// /// Gets the certificate URL. @@ -27,10 +25,7 @@ public string ProvisionUrl /// /// The certificate URL. /// - public string CertificateUrl - { - get { return GetEnvironmentString("BITRISE_CERTIFICATE_URL"); } - } + public string CertificateUrl => GetEnvironmentString("BITRISE_CERTIFICATE_URL"); /// /// Gets the certificate passphrase. @@ -38,10 +33,7 @@ public string CertificateUrl /// /// The certificate passphrase. /// - public string CertificatePassphrase - { - get { return GetEnvironmentString("BITRISE_CERTIFICATE_PASSPHRASE"); } - } + public string CertificatePassphrase => GetEnvironmentString("BITRISE_CERTIFICATE_PASSPHRASE"); /// /// Initializes a new instance of the class. @@ -51,4 +43,4 @@ public BitriseProvisioningInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitrisePullRequestInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitrisePullRequestInfo.cs new file mode 100644 index 0000000000..3ce2292138 --- /dev/null +++ b/src/Cake.Common/Build/Bitrise/Data/BitrisePullRequestInfo.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Bitrise.Data +{ + /// + /// Provides Bitrise pull request information for a current build. + /// + public sealed class BitrisePullRequestInfo : BitriseInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public BitrisePullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => Id > 0; + + /// + /// Gets the pull request id. + /// + /// + /// The pull request id. + /// + public int Id => GetEnvironmentInteger("BITRISE_PULL_REQUEST"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseRepositoryInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseRepositoryInfo.cs index 3ff838007f..b29ea75b36 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseRepositoryInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseRepositoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -11,48 +12,36 @@ namespace Cake.Common.Build.Bitrise.Data public class BitriseRepositoryInfo : BitriseInfo { /// - /// Gets the git repository URL. + /// Gets the Git repository URL. /// /// - /// The git repository URL. + /// The Git repository URL. /// - public string GitRepositoryUrl - { - get { return GetEnvironmentString("GIT_REPOSITORY_URL"); } - } + public string GitRepositoryUrl => GetEnvironmentString("GIT_REPOSITORY_URL"); /// - /// Gets the git branch. + /// Gets the Git branch. /// /// - /// The git branch. + /// The Git branch. /// - public string GitBranch - { - get { return GetEnvironmentString("BITRISE_GIT_BRANCH"); } - } + public string GitBranch => GetEnvironmentString("BITRISE_GIT_BRANCH"); /// - /// Gets the git tag. + /// Gets the Git tag. /// /// - /// The git tag. + /// The Git tag. /// - public string GitTag - { - get { return GetEnvironmentString("BITRISE_GIT_TAG"); } - } + public string GitTag => GetEnvironmentString("BITRISE_GIT_TAG"); /// - /// Gets the git commit. + /// Gets the Git commit. /// /// - /// The git commit. + /// The Git commit. /// - public string GitCommit - { - get { return GetEnvironmentString("BITRISE_GIT_COMMIT"); } - } + public string GitCommit => GetEnvironmentString("BITRISE_GIT_COMMIT"); /// /// Gets the pull request. @@ -60,10 +49,7 @@ public string GitCommit /// /// The pull request. /// - public string PullRequest - { - get { return GetEnvironmentString("BITRISE_PULL_REQUEST"); } - } + public string PullRequest => GetEnvironmentString("BITRISE_PULL_REQUEST"); /// /// Initializes a new instance of the class. @@ -73,4 +59,4 @@ public BitriseRepositoryInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/Data/BitriseWorkflowInfo.cs b/src/Cake.Common/Build/Bitrise/Data/BitriseWorkflowInfo.cs index b7f8bb3770..bfcbcea4e6 100644 --- a/src/Cake.Common/Build/Bitrise/Data/BitriseWorkflowInfo.cs +++ b/src/Cake.Common/Build/Bitrise/Data/BitriseWorkflowInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Bitrise.Data @@ -16,10 +17,7 @@ public class BitriseWorkflowInfo : BitriseInfo /// /// The workflow identifier. /// - public string WorkflowId - { - get { return GetEnvironmentString("BITRISE_TRIGGERED_WORKFLOW_ID"); } - } + public string WorkflowId => GetEnvironmentString("BITRISE_TRIGGERED_WORKFLOW_ID"); /// /// Gets the workflow title. @@ -27,10 +25,7 @@ public string WorkflowId /// /// The workflow title. /// - public string WorkflowTitle - { - get { return GetEnvironmentString("BITRISE_TRIGGERED_WORKFLOW_TITLE"); } - } + public string WorkflowTitle => GetEnvironmentString("BITRISE_TRIGGERED_WORKFLOW_TITLE"); /// /// Initializes a new instance of the class. @@ -40,4 +35,4 @@ public BitriseWorkflowInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Bitrise/IBitriseProvider.cs b/src/Cake.Common/Build/Bitrise/IBitriseProvider.cs index 4e84c2ad86..9d6946ffea 100644 --- a/src/Cake.Common/Build/Bitrise/IBitriseProvider.cs +++ b/src/Cake.Common/Build/Bitrise/IBitriseProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Bitrise.Data; namespace Cake.Common.Build.Bitrise @@ -11,19 +12,26 @@ namespace Cake.Common.Build.Bitrise public interface IBitriseProvider { /// - /// Gets a value indicating whether the current build is running on Bamboo. + /// Gets a value indicating whether the current build is running on Bitrise. /// /// - /// true if the current build is running on Bamboo; otherwise, false. + /// true if the current build is running on Bitrise; otherwise, false. /// bool IsRunningOnBitrise { get; } /// - /// Gets the Bamboo environment. + /// Gets the Bitrise environment. /// /// - /// The Bamboo environment. + /// The Bitrise environment. /// BitriseEnvironmentInfo Environment { get; } + + /// + /// Sets and environment variable that can be used in next steps on Bitrise. + /// + /// The variable. + /// The value. + void SetEnvironmentString(string variable, string value); } } diff --git a/src/Cake.Common/Build/BuildProvider.cs b/src/Cake.Common/Build/BuildProvider.cs new file mode 100644 index 0000000000..1b8a69bedc --- /dev/null +++ b/src/Cake.Common/Build/BuildProvider.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Build +{ + /// + /// Represents a build provider. + /// + [Flags] + public enum BuildProvider + { + /// + /// Local build provider. + /// + Local = 0, + + /// + /// AppVeyor build provider. + /// + AppVeyor = 1, + + /// + /// TeamCity build provider. + /// + TeamCity = 2, + + /// + /// MyGet build provider. + /// + MyGet = 4, + + /// + /// Bamboo build provider. + /// + Bamboo = 8, + + /// + /// ContinuaCI build provider. + /// + ContinuaCI = 16, + + /// + /// Jenkins build provider. + /// + Jenkins = 32, + + /// + /// Bitrise build provider. + /// + Bitrise = 64, + + /// + /// TravisCI build provider. + /// + TravisCI = 128, + + /// + /// BitbucketPipelines build provider. + /// + BitbucketPipelines = 256, + + /// + /// GoCD build provider. + /// + GoCD = 512, + + /// + /// GitLabCI build provider. + /// + GitLabCI = 1024, + + /// + /// AzurePipelines build provider. + /// + AzurePipelines = 2048, + + /// + /// GitHubActions build provider. + /// + GitHubActions = 8192, + + /// + /// WoodpeckerCI build provider. + /// + WoodpeckerCI = 16384, + + /// + /// RWX build provider. + /// + Rwx = 32768 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/BuildSystem.cs b/src/Cake.Common/Build/BuildSystem.cs index 00a504cca5..b8b17d3f93 100644 --- a/src/Cake.Common/Build/BuildSystem.cs +++ b/src/Cake.Common/Build/BuildSystem.cs @@ -1,15 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Build.AppVeyor; +using Cake.Common.Build.AzurePipelines; using Cake.Common.Build.Bamboo; +using Cake.Common.Build.BitbucketPipelines; using Cake.Common.Build.Bitrise; using Cake.Common.Build.ContinuaCI; +using Cake.Common.Build.GitHubActions; +using Cake.Common.Build.GitLabCI; +using Cake.Common.Build.GoCD; using Cake.Common.Build.Jenkins; using Cake.Common.Build.MyGet; +using Cake.Common.Build.Rwx; using Cake.Common.Build.TeamCity; using Cake.Common.Build.TravisCI; +using Cake.Common.Build.WoodpeckerCI; namespace Cake.Common.Build { @@ -19,15 +27,6 @@ namespace Cake.Common.Build /// public sealed class BuildSystem { - private readonly IAppVeyorProvider _appVeyorProvider; - private readonly ITeamCityProvider _teamCityProvider; - private readonly IMyGetProvider _myGetProvider; - private readonly IBambooProvider _bambooProvider; - private readonly IContinuaCIProvider _continuaCIProvider; - private readonly IJenkinsProvider _jenkinsProvider; - private readonly IBitriseProvider _bitriseProvider; - private readonly ITravisCIProvider _travisCIProvider; - /// /// Initializes a new instance of the class. /// @@ -39,49 +38,90 @@ public sealed class BuildSystem /// The Jenkins Provider. /// The Bitrise Provider. /// The Travis CI provider. - public BuildSystem(IAppVeyorProvider appVeyorProvider, ITeamCityProvider teamCityProvider, IMyGetProvider myGetProvider, IBambooProvider bambooProvider, IContinuaCIProvider continuaCIProvider, IJenkinsProvider jenkinsProvider, IBitriseProvider bitriseProvider, ITravisCIProvider travisCIProvider) + /// The Bitbucket Pipelines provider. + /// The Go.CD provider. + /// The GitLab CI provider. + /// The GitHub Actions provider. + /// The Azure Pipelines provider. + /// The WoodpeckerCI provider. + /// The RWX provider. + public BuildSystem( + IAppVeyorProvider appVeyorProvider, + ITeamCityProvider teamCityProvider, + IMyGetProvider myGetProvider, + IBambooProvider bambooProvider, + IContinuaCIProvider continuaCIProvider, + IJenkinsProvider jenkinsProvider, + IBitriseProvider bitriseProvider, + ITravisCIProvider travisCIProvider, + IBitbucketPipelinesProvider bitbucketPipelinesProvider, + IGoCDProvider goCDProvider, + IGitLabCIProvider gitLabCIProvider, + IGitHubActionsProvider gitHubActionsProvider, + IAzurePipelinesProvider azurePipelinesProvider, + IWoodpeckerCIProvider woodpeckerCIProvider, + IRwxProvider rwxProvider) { - if (appVeyorProvider == null) - { - throw new ArgumentNullException("appVeyorProvider"); - } - if (teamCityProvider == null) - { - throw new ArgumentNullException("teamCityProvider"); - } - if (myGetProvider == null) - { - throw new ArgumentNullException("myGetProvider"); - } - if (bambooProvider == null) - { - throw new ArgumentNullException("bambooProvider"); - } - if (continuaCIProvider == null) - { - throw new ArgumentNullException("continuaCIProvider"); - } - if (jenkinsProvider == null) - { - throw new ArgumentNullException("jenkinsProvider"); - } - if (bitriseProvider == null) - { - throw new ArgumentNullException("bitriseProvider"); - } - if (travisCIProvider == null) - { - throw new ArgumentNullException("travisCIProvider"); - } - - _appVeyorProvider = appVeyorProvider; - _teamCityProvider = teamCityProvider; - _myGetProvider = myGetProvider; - _bambooProvider = bambooProvider; - _continuaCIProvider = continuaCIProvider; - _jenkinsProvider = jenkinsProvider; - _bitriseProvider = bitriseProvider; - _travisCIProvider = travisCIProvider; + ArgumentNullException.ThrowIfNull(appVeyorProvider); + ArgumentNullException.ThrowIfNull(teamCityProvider); + ArgumentNullException.ThrowIfNull(myGetProvider); + ArgumentNullException.ThrowIfNull(bambooProvider); + ArgumentNullException.ThrowIfNull(continuaCIProvider); + ArgumentNullException.ThrowIfNull(jenkinsProvider); + ArgumentNullException.ThrowIfNull(bitriseProvider); + ArgumentNullException.ThrowIfNull(travisCIProvider); + ArgumentNullException.ThrowIfNull(bitbucketPipelinesProvider); + ArgumentNullException.ThrowIfNull(goCDProvider); + ArgumentNullException.ThrowIfNull(gitLabCIProvider); + ArgumentNullException.ThrowIfNull(gitHubActionsProvider); + ArgumentNullException.ThrowIfNull(azurePipelinesProvider); + ArgumentNullException.ThrowIfNull(woodpeckerCIProvider); + ArgumentNullException.ThrowIfNull(rwxProvider); + + AppVeyor = appVeyorProvider; + TeamCity = teamCityProvider; + MyGet = myGetProvider; + Bamboo = bambooProvider; + ContinuaCI = continuaCIProvider; + Jenkins = jenkinsProvider; + Bitrise = bitriseProvider; + TravisCI = travisCIProvider; + BitbucketPipelines = bitbucketPipelinesProvider; + GoCD = goCDProvider; + GitLabCI = gitLabCIProvider; + GitHubActions = gitHubActionsProvider; + AzurePipelines = azurePipelinesProvider; + WoodpeckerCI = woodpeckerCIProvider; + Rwx = rwxProvider; + + Provider = (AppVeyor.IsRunningOnAppVeyor ? BuildProvider.AppVeyor : BuildProvider.Local) + | (TeamCity.IsRunningOnTeamCity ? BuildProvider.TeamCity : BuildProvider.Local) + | (MyGet.IsRunningOnMyGet ? BuildProvider.MyGet : BuildProvider.Local) + | (Bamboo.IsRunningOnBamboo ? BuildProvider.Bamboo : BuildProvider.Local) + | (ContinuaCI.IsRunningOnContinuaCI ? BuildProvider.ContinuaCI : BuildProvider.Local) + | (Jenkins.IsRunningOnJenkins ? BuildProvider.Jenkins : BuildProvider.Local) + | (Bitrise.IsRunningOnBitrise ? BuildProvider.Bitrise : BuildProvider.Local) + | (TravisCI.IsRunningOnTravisCI ? BuildProvider.TravisCI : BuildProvider.Local) + | (BitbucketPipelines.IsRunningOnBitbucketPipelines ? BuildProvider.BitbucketPipelines : BuildProvider.Local) + | (GoCD.IsRunningOnGoCD ? BuildProvider.GoCD : BuildProvider.Local) + | (GitLabCI.IsRunningOnGitLabCI ? BuildProvider.GitLabCI : BuildProvider.Local) + | (GitHubActions.IsRunningOnGitHubActions ? BuildProvider.GitHubActions : BuildProvider.Local) + | (AzurePipelines.IsRunningOnAzurePipelines ? BuildProvider.AzurePipelines : BuildProvider.Local) + | (WoodpeckerCI.IsRunningOnWoodpeckerCI ? BuildProvider.WoodpeckerCI : BuildProvider.Local) + | (Rwx.IsRunningOnRwx ? BuildProvider.Rwx : BuildProvider.Local); + + IsLocalBuild = Provider == BuildProvider.Local; + + IsPullRequest = ((Provider & BuildProvider.AppVeyor) != 0 && AppVeyor.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.TeamCity) != 0 && TeamCity.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.Bitrise) != 0 && Bitrise.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.TravisCI) != 0 && TravisCI.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.BitbucketPipelines) != 0 && BitbucketPipelines.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.GitLabCI) != 0 && GitLabCI.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.AzurePipelines) != 0 && AzurePipelines.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.GitHubActions) != 0 && GitHubActions.Environment.PullRequest.IsPullRequest) + || ((Provider & BuildProvider.WoodpeckerCI) != 0 && !string.IsNullOrWhiteSpace(WoodpeckerCI.Environment.Commit.PullRequest)) + || ((Provider & BuildProvider.Jenkins) != 0 && Jenkins.Environment.Change.IsPullRequest); } /// @@ -89,7 +129,7 @@ public BuildSystem(IAppVeyorProvider appVeyorProvider, ITeamCityProvider teamCit /// /// /// - /// if(BuildSystem.IsRunningOnAppVeyor) + /// if (BuildSystem.IsRunningOnAppVeyor) /// { /// // Upload artifact to AppVeyor. /// AppVeyor.UploadArtifact("./build/release_x86.zip"); @@ -99,34 +139,28 @@ public BuildSystem(IAppVeyorProvider appVeyorProvider, ITeamCityProvider teamCit /// /// true if the build currently is running on AppVeyor; otherwise, false. /// - public bool IsRunningOnAppVeyor - { - get { return _appVeyorProvider.IsRunningOnAppVeyor; } - } + public bool IsRunningOnAppVeyor => AppVeyor.IsRunningOnAppVeyor; /// /// Gets the AppVeyor Provider. /// /// /// - /// if(BuildSystem.IsRunningOnAppVeyor) + /// if (BuildSystem.IsRunningOnAppVeyor) /// { /// // Upload artifact to AppVeyor. /// BuildSystem.AppVeyor.UploadArtifact("./build/release_x86.zip"); /// } /// /// - public IAppVeyorProvider AppVeyor - { - get { return _appVeyorProvider; } - } + public IAppVeyorProvider AppVeyor { get; } /// /// Gets a value indicating whether the current build is running on TeamCity. /// /// /// - /// if(BuildSystem.IsRunningOnTeamCity) + /// if (BuildSystem.IsRunningOnTeamCity) /// { /// TeamCity.ProgressMessage("Doing an action..."); /// // Do action... @@ -136,34 +170,28 @@ public IAppVeyorProvider AppVeyor /// /// true if the build currently is running on TeamCity; otherwise, false. /// - public bool IsRunningOnTeamCity - { - get { return _teamCityProvider.IsRunningOnTeamCity; } - } + public bool IsRunningOnTeamCity => TeamCity.IsRunningOnTeamCity; /// /// Gets the TeamCity Provider. /// /// /// - /// if(BuildSystem.IsRunningOnTeamCiy) + /// if (BuildSystem.IsRunningOnTeamCity) /// { /// // Set the build number. /// BuildSystem.TeamCity.SetBuildNumber("1.2.3.4"); /// } /// /// - public ITeamCityProvider TeamCity - { - get { return _teamCityProvider; } - } + public ITeamCityProvider TeamCity { get; } /// /// Gets a value indicating whether the current build is running on MyGet. /// /// /// - /// if(BuildSystem.IsRunningOnMyGet) + /// if (BuildSystem.IsRunningOnMyGet) /// { /// MyGet.BuildProblem("Something went wrong..."); /// // Do action... @@ -173,34 +201,28 @@ public ITeamCityProvider TeamCity /// /// true if the build currently is running on MyGet; otherwise, false. /// - public bool IsRunningOnMyGet - { - get { return _myGetProvider.IsRunningOnMyGet; } - } + public bool IsRunningOnMyGet => MyGet.IsRunningOnMyGet; /// /// Gets the MyGet Provider. /// /// /// - /// if(BuildSystem.IsRunningOnMyGet) + /// if (BuildSystem.IsRunningOnMyGet) /// { /// // Set the build number. /// BuildSystem.MyGet.SetBuildNumber("1.2.3.4"); /// } /// /// - public IMyGetProvider MyGet - { - get { return _myGetProvider; } - } + public IMyGetProvider MyGet { get; } /// /// Gets a value indicating whether the current build is running on Bamboo. /// /// /// - /// if(BuildSystem.IsRunningOnBamboo) + /// if (BuildSystem.IsRunningOnBamboo) /// { /// // Get the build number. /// var buildNumber = BuildSystem.Bamboo.Number; @@ -210,34 +232,28 @@ public IMyGetProvider MyGet /// /// true if the build currently is running on Bamboo; otherwise, false. /// - public bool IsRunningOnBamboo - { - get { return _bambooProvider.IsRunningOnBamboo; } - } + public bool IsRunningOnBamboo => Bamboo.IsRunningOnBamboo; /// /// Gets the Bamboo Provider. /// /// /// - /// if(BuildSystem.IsRunningOnBamboo) + /// if (BuildSystem.IsRunningOnBamboo) /// { /// //Get the Bamboo Plan Name /// var planName = BuildSystem.Bamboo.Project.PlanName /// } /// /// - public IBambooProvider Bamboo - { - get { return _bambooProvider; } - } + public IBambooProvider Bamboo { get; } /// /// Gets a value indicating whether the current build is running on Continua CI. /// /// /// - /// if(BuildSystem.IsRunningOnContinuaCI) + /// if (BuildSystem.IsRunningOnContinuaCI) /// { /// // Get the build version. /// var buildVersion = BuildSystem.ContinuaCI.Environment.Build.Version; @@ -247,34 +263,28 @@ public IBambooProvider Bamboo /// /// true if the build currently is running on Continua CI; otherwise, false. /// - public bool IsRunningOnContinuaCI - { - get { return _continuaCIProvider.IsRunningOnContinuaCI; } - } + public bool IsRunningOnContinuaCI => ContinuaCI.IsRunningOnContinuaCI; /// /// Gets the Continua CI Provider. /// /// /// - /// if(BuildSystem.IsRunningOnContinuaCI) + /// if (BuildSystem.IsRunningOnContinuaCI) /// { /// //Get the Continua CI Project Name /// var projectName = BuildSystem.ContinuaCI.Environment.Project.Name; /// } /// /// - public IContinuaCIProvider ContinuaCI - { - get { return _continuaCIProvider; } - } + public IContinuaCIProvider ContinuaCI { get; } /// /// Gets a value indicating whether this instance is running on Jenkins. /// /// /// - /// if(BuildSystem.IsRunningOnJenkins) + /// if (BuildSystem.IsRunningOnJenkins) /// { /// // Get the build number. /// var buildNumber = BuildSystem.Jenkins.Environment.Build.BuildNumber; @@ -284,10 +294,7 @@ public IContinuaCIProvider ContinuaCI /// /// true if this instance is running on jenkins; otherwise, false. /// - public bool IsRunningOnJenkins - { - get { return _jenkinsProvider.IsRunningOnJenkins; } - } + public bool IsRunningOnJenkins => Jenkins.IsRunningOnJenkins; /// /// Gets the Jenkins Provider. @@ -297,24 +304,21 @@ public bool IsRunningOnJenkins /// /// /// - /// if(BuildSystem.IsRunningOnJenkins) + /// if (BuildSystem.IsRunningOnJenkins) /// { /// // Get the job name. /// var jobName = BuildSystem.Jenkins.Environment.Build.JobName; /// } /// /// - public IJenkinsProvider Jenkins - { - get { return _jenkinsProvider; } - } + public IJenkinsProvider Jenkins { get; } /// /// Gets a value indicating whether this instance is running on Bitrise. /// /// /// - /// if(BuildSystem.IsRunningOnBitrise) + /// if (BuildSystem.IsRunningOnBitrise) /// { /// // Get the build number. /// var buildNumber = BuildSystem.Bitrise.Environment.Build.BuildNumber; @@ -324,34 +328,28 @@ public IJenkinsProvider Jenkins /// /// true if this instance is running on bitrise; otherwise, false. /// - public bool IsRunningOnBitrise - { - get { return _bitriseProvider.IsRunningOnBitrise; } - } + public bool IsRunningOnBitrise => Bitrise.IsRunningOnBitrise; /// /// Gets the Bitrise Provider. /// /// /// - /// if(BuildSystem.IsRunningOnBitrise) + /// if (BuildSystem.IsRunningOnBitrise) /// { /// // Get the provision profile url. /// var buildNumber = BuildSystem.Bitrise.Environment.Provisioning.ProvisionUrl; /// } /// /// - public IBitriseProvider Bitrise - { - get { return _bitriseProvider; } - } + public IBitriseProvider Bitrise { get; } /// /// Gets a value indicating whether this instance is running on Travis CI. /// /// /// - /// if(BuildSystem.IsRunningOnTravisCI) + /// if (BuildSystem.IsRunningOnTravisCI) /// { /// // Get the build directory. /// var buildDirectory = BuildSystem.TravisCI.Environment.Build.BuildDirectory; @@ -361,17 +359,14 @@ public IBitriseProvider Bitrise /// /// true if this instance is running on Travis CI; otherwise, false. /// - public bool IsRunningOnTravisCI - { - get { return _travisCIProvider.IsRunningOnTravisCI; } - } + public bool IsRunningOnTravisCI => TravisCI.IsRunningOnTravisCI; /// /// Gets the Travis CI provider. /// /// /// - /// if(BuildSystem.IsRunningOnTravisCI) + /// if (BuildSystem.IsRunningOnTravisCI) /// { /// // Get the operating system name. /// var osName = BuildSystem.TravisCI.Environment.Job.OSName; @@ -381,17 +376,237 @@ public bool IsRunningOnTravisCI /// /// The Travis CI. /// - public ITravisCIProvider TravisCI - { - get { return _travisCIProvider; } - } + public ITravisCIProvider TravisCI { get; } + + /// + /// Gets a value indicating whether this instance is running on Bitbucket Pipelines. + /// + /// + /// + /// if (BuildSystem.IsRunningOnBitbucketPipelines) + /// { + /// // Get the build commit hash. + /// var commitHash = BuildSystem.BitbucketPipelines.Environment.Repository.Commit; + /// } + /// + /// + /// + /// true if this instance is running on Bitbucket Pipelines; otherwise, false. + /// + public bool IsRunningOnBitbucketPipelines => BitbucketPipelines.IsRunningOnBitbucketPipelines; + + /// + /// Gets the Bitbucket Pipelines Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnBitbucketPipelines) + /// { + /// // Get the URL friendly repo name. + /// var repoSlug = BuildSystem.BitbucketPipelines.Environment.Repository.RepoSlug; + /// } + /// + /// + public IBitbucketPipelinesProvider BitbucketPipelines { get; } + + /// + /// Gets a value indicating whether the current build is running on Go.CD. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGoCD) + /// { + /// // Get the build counter. + /// var counter = BuildSystem.GoCD.Environment.Pipeline.Counter; + /// } + /// + /// + /// + /// true if the build currently is running on Go.CD; otherwise, false. + /// + public bool IsRunningOnGoCD => GoCD.IsRunningOnGoCD; + + /// + /// Gets the Go.CD Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGoCD) + /// { + /// // Get the pipeline counter. + /// var counter = BuildSystem.GoCD.Environment.Environment.Pipeline.Counter; + /// } + /// + /// + public IGoCDProvider GoCD { get; } + + /// + /// Gets the GitLab CI Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGitLabCI) + /// { + /// // Get the build commit hash. + /// var commitHash = BuildSystem.GitLabCI.Environment.Build.Reference; + /// } + /// + /// + public IGitLabCIProvider GitLabCI { get; } + + /// + /// Gets a value indicating whether this instance is running on GitLab CI. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGitLabCI) + /// { + /// // Get the build commit hash. + /// var commitHash = BuildSystem.GitLabCI.Environment.Build.Reference; + /// } + /// + /// + /// + /// true if this instance is running on GitLab CI; otherwise, false. + /// + public bool IsRunningOnGitLabCI => GitLabCI.IsRunningOnGitLabCI; + + /// + /// Gets a value indicating whether this instance is running on Azure Pipelines. + /// + /// + /// + /// if (BuildSystem.IsRunningOnAzurePipelines) + /// { + /// // Get the build commit hash. + /// var commitHash = BuildSystem.AzurePipelines.Environment.Repository.SourceVersion; + /// } + /// + /// + /// + /// true if this instance is running on Azure Pipelines; otherwise, false. + /// + public bool IsRunningOnAzurePipelines => AzurePipelines.IsRunningOnAzurePipelines; + + /// + /// Gets the Azure Pipelines Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnAzurePipelines) + /// { + /// // Get the build definition name. + /// var definitionName = BuildSystem.AzurePipelines.Environment.BuildDefinition.Name; + /// } + /// + /// + public IAzurePipelinesProvider AzurePipelines { get; } + + /// + /// Gets a value indicating whether this instance is running on GitHub Actions. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGitHubActions) + /// { + /// // Get the workflow name. + /// var workflow = BuildSystem.GitHubActions.Environment.Workflow.Workflow; + /// } + /// + /// + /// + /// true if this instance is running on GitHub Actions; otherwise, false. + /// + public bool IsRunningOnGitHubActions => GitHubActions.IsRunningOnGitHubActions; + + /// + /// Gets the GitHub Actions Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnGitHubActions) + /// { + /// // Get the workflow name. + /// var workflow = BuildSystem.GitHubActions.Environment.Workflow.Workflow; + /// } + /// + /// + public IGitHubActionsProvider GitHubActions { get; } + + /// + /// Gets a value indicating whether this instance is running on WoodpeckerCI. + /// + /// + /// + /// if (BuildSystem.IsRunningOnWoodpeckerCI) + /// { + /// // Get the commit SHA. + /// var commitSha = BuildSystem.WoodpeckerCI.Environment.Commit.Sha; + /// } + /// + /// + /// + /// true if this instance is running on WoodpeckerCI; otherwise, false. + /// + public bool IsRunningOnWoodpeckerCI => WoodpeckerCI.IsRunningOnWoodpeckerCI; + + /// + /// Gets the WoodpeckerCI Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnWoodpeckerCI) + /// { + /// // Get the commit SHA. + /// var commitSha = BuildSystem.WoodpeckerCI.Environment.Commit.Sha; + /// } + /// + /// + public IWoodpeckerCIProvider WoodpeckerCI { get; } + + /// + /// Gets a value indicating whether this instance is running on RWX. + /// + /// + /// + /// if (BuildSystem.IsRunningOnRwx) + /// { + /// // Get the run identifier. + /// var runId = BuildSystem.Rwx.Environment.Run.Id; + /// } + /// + /// + /// + /// true if this instance is running on RWX; otherwise, false. + /// + public bool IsRunningOnRwx => Rwx.IsRunningOnRwx; + + /// + /// Gets the RWX Provider. + /// + /// + /// + /// if (BuildSystem.IsRunningOnRwx) + /// { + /// // Get the run URL. + /// var runUrl = BuildSystem.Rwx.Environment.Run.Url; + /// } + /// + /// + public IRwxProvider Rwx { get; } + + /// + /// Gets the current build provider. + /// + /// The current build provider. + public BuildProvider Provider { get; } /// /// Gets a value indicating whether the current build is local build. /// /// /// - /// // Get a flag telling us if this is a local build or not. + /// // Gets a flag telling us if this is a local build or not. /// var isLocal = BuildSystem.IsLocalBuild; /// /// // Define a task that only runs locally. @@ -405,9 +620,14 @@ public ITravisCIProvider TravisCI /// /// true if the current build is local build; otherwise, false. /// - public bool IsLocalBuild - { - get { return !(IsRunningOnAppVeyor || IsRunningOnTeamCity || IsRunningOnMyGet || IsRunningOnBamboo || IsRunningOnContinuaCI || IsRunningOnJenkins || IsRunningOnBitrise || IsRunningOnTravisCI); } - } + public bool IsLocalBuild { get; } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest { get; } } } diff --git a/src/Cake.Common/Build/BuildSystemAliases.cs b/src/Cake.Common/Build/BuildSystemAliases.cs index fdc4fa3f32..1a0b0e33ef 100644 --- a/src/Cake.Common/Build/BuildSystemAliases.cs +++ b/src/Cake.Common/Build/BuildSystemAliases.cs @@ -1,15 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Build.AppVeyor; +using Cake.Common.Build.AzurePipelines; using Cake.Common.Build.Bamboo; +using Cake.Common.Build.BitbucketPipelines; using Cake.Common.Build.Bitrise; using Cake.Common.Build.ContinuaCI; +using Cake.Common.Build.GitHubActions; +using Cake.Common.Build.GitLabCI; +using Cake.Common.Build.GoCD; using Cake.Common.Build.Jenkins; using Cake.Common.Build.MyGet; +using Cake.Common.Build.Rwx; using Cake.Common.Build.TeamCity; using Cake.Common.Build.TravisCI; +using Cake.Common.Build.WoodpeckerCI; using Cake.Core; using Cake.Core.Annotations; @@ -22,7 +30,7 @@ namespace Cake.Common.Build public static class BuildSystemAliases { /// - /// Gets a instance that can + /// Gets a instance that can /// be used to query for information about the current build system. /// /// @@ -31,28 +39,33 @@ public static class BuildSystemAliases /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] public static BuildSystem BuildSystem(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - var appVeyorProvider = new AppVeyorProvider(context.Environment, context.ProcessRunner); - var teamCityProvider = new TeamCityProvider(context.Environment, context.Log); - var myGetProvider = new MyGetProvider(context.Environment); + ArgumentNullException.ThrowIfNull(context); + + var appVeyorProvider = new AppVeyorProvider(context.Environment, context.ProcessRunner, context.Log); + var teamCityProvider = new TeamCityProvider(context.Environment, context.FileSystem, new BuildSystemServiceMessageWriter()); + var myGetProvider = new MyGetProvider(context.Environment, new BuildSystemServiceMessageWriter()); var bambooProvider = new BambooProvider(context.Environment); - var continuaCIProvider = new ContinuaCIProvider(context.Environment); + var continuaCIProvider = new ContinuaCIProvider(context.Environment, new BuildSystemServiceMessageWriter()); var jenkinsProvider = new JenkinsProvider(context.Environment); - var bitriseProvider = new BitriseProvider(context.Environment); - var travisCIProvider = new TravisCIProvider(context.Environment, context.Log); + var bitriseProvider = new BitriseProvider(context.Environment, context.ProcessRunner); + var travisCIProvider = new TravisCIProvider(context.Environment, new BuildSystemServiceMessageWriter()); + var bitbucketPipelinesProvider = new BitbucketPipelinesProvider(context.Environment); + var goCDProvider = new GoCDProvider(context.Environment, context.Log); + var gitLabCIProvider = new GitLabCIProvider(context.Environment, context.FileSystem); + var gitHubActionsProvider = new GitHubActionsProvider(context.Environment, context.FileSystem, new BuildSystemServiceMessageWriter()); + var azurePipelinesProvider = new AzurePipelinesProvider(context.Environment, new BuildSystemServiceMessageWriter()); + var woodpeckerCIProvider = new WoodpeckerCIProvider(context.Environment, context.FileSystem); + var rwxProvider = new RwxProvider(context.Environment, context.FileSystem); - return new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider); + return new BuildSystem(appVeyorProvider, teamCityProvider, myGetProvider, bambooProvider, continuaCIProvider, jenkinsProvider, bitriseProvider, travisCIProvider, bitbucketPipelinesProvider, goCDProvider, gitLabCIProvider, gitHubActionsProvider, azurePipelinesProvider, woodpeckerCIProvider, rwxProvider); } /// - /// Gets a instance that can + /// Gets a instance that can /// be used to manipulate the AppVeyor environment. /// /// @@ -61,22 +74,20 @@ public static BuildSystem BuildSystem(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] [CakeNamespaceImport("Cake.Common.Build.AppVeyor")] [CakeNamespaceImport("Cake.Common.Build.AppVeyor.Data")] public static IAppVeyorProvider AppVeyor(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.AppVeyor; } /// - /// Gets a instance that can + /// Gets a instance that can /// be used to manipulate the TeamCity environment. /// /// @@ -85,20 +96,19 @@ public static IAppVeyorProvider AppVeyor(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.TeamCity")] public static ITeamCityProvider TeamCity(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.TeamCity; } /// - /// Gets a instance that can + /// Gets a instance that can /// be used to manipulate the MyGet environment. /// /// @@ -107,42 +117,41 @@ public static ITeamCityProvider TeamCity(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.MyGet")] public static IMyGetProvider MyGet(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.MyGet; } /// - /// Gets a instance that can + /// Gets a instance that can /// be used to manipulate the Bamboo environment. /// /// /// - /// var isBambooBuild = Bamboo.IsRunningBamboo; + /// var isBambooBuild = Bamboo.IsRunningOnBamboo; /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.Bamboo")] + [CakeNamespaceImport("Cake.Common.Build.Bamboo.Data")] public static IBambooProvider Bamboo(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.Bamboo; } /// - /// Gets a instance that can + /// Gets a instance that can /// be used to manipulate the Continua CI environment. /// /// @@ -151,22 +160,20 @@ public static IBambooProvider Bamboo(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] [CakeNamespaceImport("Cake.Common.Build.ContinuaCI")] [CakeNamespaceImport("Cake.Common.Build.ContinuaCI.Data")] public static IContinuaCIProvider ContinuaCI(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.ContinuaCI; } /// - /// Get a instance that can be user to + /// Gets a instance that can be used to /// obtain information from the Jenkins environment. /// /// @@ -175,22 +182,20 @@ public static IContinuaCIProvider ContinuaCI(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] [CakeNamespaceImport("Cake.Common.Build.Jenkins")] [CakeNamespaceImport("Cake.Common.Build.Jenkins.Data")] public static IJenkinsProvider Jenkins(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.Jenkins; } /// - /// Get a instance that can be user to + /// Gets a instance that can be used to /// obtain information from the Bitrise environment. /// /// @@ -199,22 +204,20 @@ public static IJenkinsProvider Jenkins(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] [CakeNamespaceImport("Cake.Common.Build.Bitrise")] [CakeNamespaceImport("Cake.Common.Build.Bitrise.Data")] public static IBitriseProvider Bitrise(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.Bitrise; } /// - /// Get a instance that can be user to + /// Gets a instance that can be used to /// obtain information from the Travis CI environment. /// /// @@ -223,18 +226,170 @@ public static IBitriseProvider Bitrise(this ICakeContext context) /// /// /// The context. - /// A instance. + /// A instance. [CakePropertyAlias(Cache = true)] [CakeNamespaceImport("Cake.Common.Build.TravisCI")] [CakeNamespaceImport("Cake.Common.Build.TravisCI.Data")] public static ITravisCIProvider TravisCI(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var buildSystem = context.BuildSystem(); return buildSystem.TravisCI; } + + /// + /// Gets a instance that can be used to + /// obtain information from the Bitbucket Pipelines environment. + /// + /// + /// + /// var isBitbucketPipelinesBuild = BitbucketPipelines.IsRunningOnBitbucketPipelines; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.BitbucketPipelines")] + [CakeNamespaceImport("Cake.Common.Build.BitbucketPipelines.Data")] + public static IBitbucketPipelinesProvider BitbucketPipelines(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.BitbucketPipelines; + } + + /// + /// Gets a instance that can be used to + /// obtain information from the Go.CD environment. + /// + /// + /// + /// var isGoCDBuild = GoCD.IsRunningOnGoCD; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.GoCD")] + [CakeNamespaceImport("Cake.Common.Build.GoCD.Data")] + public static IGoCDProvider GoCD(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.GoCD; + } + + /// + /// Gets a instance that can be used to + /// obtain information from the GitLab CI environment. + /// + /// + /// + /// var isGitLabCIBuild = GitLabCI.IsRunningOnGitLabCI; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.GitLabCI")] + [CakeNamespaceImport("Cake.Common.Build.GitLabCI.Data")] + public static IGitLabCIProvider GitLabCI(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.GitLabCI; + } + + /// + /// Gets a instance that can be used to + /// obtain information from the GitHub Actions environment. + /// + /// + /// + /// var isGitHubActionsBuild = GitHubActions.IsRunningOnGitHubActions; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.GitHubActions")] + [CakeNamespaceImport("Cake.Common.Build.GitHubActions.Data")] + public static IGitHubActionsProvider GitHubActions(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.GitHubActions; + } + + /// + /// Gets a instance that can be used to + /// obtain information from the Azure Pipelines environment. + /// + /// + /// + /// var isAzurePipelines = AzurePipelines.IsRunningOnAzurePipelines; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.AzurePipelines")] + [CakeNamespaceImport("Cake.Common.Build.AzurePipelines.Data")] + public static IAzurePipelinesProvider AzurePipelines(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.AzurePipelines; + } + + /// + /// Gets a instance that can be used to + /// obtain information from the WoodpeckerCI environment. + /// + /// + /// + /// var isWoodpeckerCIBuild = WoodpeckerCI.IsRunningOnWoodpeckerCI; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.WoodpeckerCI")] + [CakeNamespaceImport("Cake.Common.Build.WoodpeckerCI.Data")] + public static IWoodpeckerCIProvider WoodpeckerCI(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.WoodpeckerCI; + } + + /// + /// Gets an instance that can be used to + /// obtain information from the RWX environment. + /// + /// + /// + /// var isRwxBuild = Rwx.IsRunningOnRwx; + /// + /// + /// The context. + /// A instance. + [CakePropertyAlias(Cache = true)] + [CakeNamespaceImport("Cake.Common.Build.Rwx")] + [CakeNamespaceImport("Cake.Common.Build.Rwx.Data")] + public static IRwxProvider Rwx(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var buildSystem = context.BuildSystem(); + return buildSystem.Rwx; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/ContinuaCIInfo.cs b/src/Cake.Common/Build/ContinuaCI/ContinuaCIInfo.cs index 59d65e5d2c..be92e934f9 100644 --- a/src/Cake.Common/Build/ContinuaCI/ContinuaCIInfo.cs +++ b/src/Cake.Common/Build/ContinuaCI/ContinuaCIInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -157,7 +158,7 @@ protected IEnumerable GetEnvironmentStringList(string variable) /// Gets matching list of environment variables as an dictionary of . /// /// The prefix for the environment variables name. - /// A dictionary of environment variables starting with variablePrefix + /// A dictionary of environment variables starting with variablePrefix. protected IDictionary GetEnvironmentStringDictionary(string variablePrefix) { if (_allEnvironmentVariables == null) @@ -171,4 +172,4 @@ protected IDictionary GetEnvironmentStringDictionary(string vari return matchingVariables; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/ContinuaCIMessageType.cs b/src/Cake.Common/Build/ContinuaCI/ContinuaCIMessageType.cs index e38fd114f3..4604969e0a 100644 --- a/src/Cake.Common/Build/ContinuaCI/ContinuaCIMessageType.cs +++ b/src/Cake.Common/Build/ContinuaCI/ContinuaCIMessageType.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Build.ContinuaCI { /// - /// Provides the known values for Continua CI Message Types + /// Provides the known values for Continua CI Message Types. /// public enum ContinuaCIMessageType { @@ -38,4 +39,4 @@ public enum ContinuaCIMessageType /// Fatal, } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/ContinuaCIProvider.cs b/src/Cake.Common/Build/ContinuaCI/ContinuaCIProvider.cs index e8bb91044d..c42892467f 100644 --- a/src/Cake.Common/Build/ContinuaCI/ContinuaCIProvider.cs +++ b/src/Cake.Common/Build/ContinuaCI/ContinuaCIProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -17,9 +18,11 @@ public sealed class ContinuaCIProvider : IContinuaCIProvider { private const string MessagePrefix = "@@continua["; private const string MessagePostfix = "]"; + private static readonly Dictionary _sanitizationTokens; + private readonly ICakeEnvironment _environment; - private readonly ContinuaCIEnvironmentInfo _environmentInfo; + private readonly IBuildSystemServiceMessageWriter _writer; static ContinuaCIProvider() { @@ -38,23 +41,15 @@ static ContinuaCIProvider() /// Initializes a new instance of the class. /// /// The cake environment. - public ContinuaCIProvider(ICakeEnvironment environment) + /// The build system service message writer. + public ContinuaCIProvider(ICakeEnvironment environment, IBuildSystemServiceMessageWriter writer) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - - _environment = environment; - _environmentInfo = new ContinuaCIEnvironmentInfo(environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + Environment = new ContinuaCIEnvironmentInfo(environment); } - /// - /// Gets a value indicating whether the current build is running on Continua CI. - /// - /// - /// true if the current build is running on Continua CI; otherwise, false. - /// + /// public bool IsRunningOnContinuaCI { get @@ -64,25 +59,13 @@ public bool IsRunningOnContinuaCI } } - /// - /// Gets the Continua CI environment. - /// - /// - /// The Continua CI environment. - /// - public ContinuaCIEnvironmentInfo Environment - { - get { return _environmentInfo; } - } + /// + public ContinuaCIEnvironmentInfo Environment { get; } - /// - /// Write a status message to the Continua CI build log. - /// - /// Message contents. - /// Build status. + /// public void WriteMessage(string message, ContinuaCIMessageType status) { - var name = Enum.GetName(typeof(ContinuaCIMessageType), status); + var name = Enum.GetName(status); if (name != null) { var statusToWrite = name.ToLower(); @@ -95,30 +78,19 @@ public void WriteMessage(string message, ContinuaCIMessageType status) } } - /// - /// Write the start of a message group to the Continua CI build log. - /// - /// Group name. + /// public void WriteStartGroup(string groupName) { WriteServiceMessage("startGroup ", "name", groupName); } - /// - /// Write the end of a message block to the Continua CI build log. - /// - /// Group name. + /// public void WriteEndBlock(string groupName) { WriteServiceMessage("endGroup", "name", groupName); } - /// - /// Set a Continua CI build variable. - /// - /// Name of the variable to set. - /// Value to assign to the variable. - /// Set to 'true' to prevent the build failing if the variable has not been defined for the configuration.. + /// public void SetVariable(string name, string value, bool skipIfNotDefined = true) { WriteServiceMessage("setParameter", new Dictionary @@ -129,30 +101,24 @@ public void SetVariable(string name, string value, bool skipIfNotDefined = true) }); } - /// - /// Set a Continua CI build version. - /// - /// The new build version. + /// public void SetBuildVersion(string version) { WriteServiceMessage("setBuildVersion", "value", version); } - /// - /// Set a Continua CI build status message, which is shown on the build details page when a build is running. - /// - /// The new build status text. + /// public void SetBuildStatus(string text) { WriteServiceMessage("setBuildStatus", "value", text); } - private static void WriteServiceMessage(string messageName, string attributeName, string attributeValue) + private void WriteServiceMessage(string messageName, string attributeName, string attributeValue) { WriteServiceMessage(messageName, new Dictionary { { attributeName, attributeValue } }); } - private static void WriteServiceMessage(string messageName, Dictionary parameters) + private void WriteServiceMessage(string messageName, Dictionary parameters) { var arrayOfParameters = parameters.Select(keypair => { @@ -165,7 +131,7 @@ private static void WriteServiceMessage(string messageName, Dictionary /// Initializes a new instance of the class. /// /// The environment. - /// The prefix for environment variables in this clas + /// The prefix for environment variables in this class. public ContinuaCIBuildInfo(ICakeEnvironment environment, string prefix) : base(environment) { - _latestChangesetInfo = new ContinuaCIChangesetInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.LatestChangeset", prefix)); + LatestChangeset = new ContinuaCIChangesetInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.LatestChangeset", prefix)); _prefix = prefix; } @@ -126,7 +125,7 @@ public bool HasNewChanges } /// - /// Gets build the number of changesets associated with this build + /// Gets build the number of changesets associated with this build. /// public int ChangesetCount { @@ -138,7 +137,7 @@ public int ChangesetCount } /// - /// Gets build the number of issues associated with this build + /// Gets build the number of issues associated with this build. /// public int IssueCount { @@ -174,7 +173,7 @@ public long TimeOnQueue } /// - /// Gets list of repository names + /// Gets list of repository names. /// public IEnumerable Repositories { @@ -186,7 +185,7 @@ public IEnumerable Repositories } /// - /// Gets list of repository branch names + /// Gets list of repository branch names. /// public IEnumerable RepositoryBranches { @@ -198,7 +197,7 @@ public IEnumerable RepositoryBranches } /// - /// Gets triggering branch name + /// Gets triggering branch name. /// public string TriggeringBranch { @@ -210,7 +209,7 @@ public string TriggeringBranch } /// - /// Gets list of changeset revisions + /// Gets list of changeset revisions. /// public IEnumerable ChangesetRevisions { @@ -222,7 +221,7 @@ public IEnumerable ChangesetRevisions } /// - /// Gets list of changeset user names + /// Gets list of changeset user names. /// public IEnumerable ChangesetUserNames { @@ -234,7 +233,7 @@ public IEnumerable ChangesetUserNames } /// - /// Gets list of changeset tag names + /// Gets list of changeset tag names. /// public IEnumerable ChangesetTagNames { @@ -246,11 +245,8 @@ public IEnumerable ChangesetTagNames } /// - /// Gets the latest build changeset + /// Gets the latest build changeset. /// - public ContinuaCIChangesetInfo LatestChangeset - { - get { return _latestChangesetInfo; } - } + public ContinuaCIChangesetInfo LatestChangeset { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIChangesetInfo.cs b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIChangesetInfo.cs index 660a2fb775..bbe83abfb2 100644 --- a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIChangesetInfo.cs +++ b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIChangesetInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -39,7 +40,7 @@ public string Revision } /// - /// Gets the changeset branch name + /// Gets the changeset branch name. /// public string Branch { @@ -75,7 +76,7 @@ public int FileCount } /// - /// Gets the changeset author user/committer name + /// Gets the changeset author user/committer name. /// public string UserName { @@ -111,7 +112,7 @@ public int IssueCount } /// - /// Gets list of changeset tag names + /// Gets list of changeset tag names. /// public IEnumerable TagNames { @@ -123,7 +124,7 @@ public IEnumerable TagNames } /// - /// Gets list of changeset issue names + /// Gets list of changeset issue names. /// public IEnumerable IssueNames { @@ -134,4 +135,4 @@ public IEnumerable IssueNames } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIConfigurationInfo.cs b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIConfigurationInfo.cs index 2cbf6fd3b9..c813b1cf9b 100644 --- a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIConfigurationInfo.cs +++ b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIConfigurationInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Globalization; using Cake.Core; @@ -25,7 +26,7 @@ public ContinuaCIConfigurationInfo(ICakeEnvironment environment, string prefix) } /// - /// Gets the Continua CI Configuration Name + /// Gets the Continua CI Configuration Name. /// /// /// The Continua CI Configuration Name. @@ -39,4 +40,4 @@ public string Name } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfo.cs b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfo.cs index fdcdb15fc8..3dc6d516a3 100644 --- a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfo.cs +++ b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Globalization; using Cake.Core; @@ -14,10 +15,6 @@ public sealed class ContinuaCIEnvironmentInfo : ContinuaCIInfo { private const string Prefix = "ContinuaCI"; - private readonly ContinuaCIProjectInfo _projectInfo; - private readonly ContinuaCIConfigurationInfo _configurationInfo; - private readonly ContinuaCIBuildInfo _buildInfo; - /// /// Initializes a new instance of the class. /// @@ -25,9 +22,9 @@ public sealed class ContinuaCIEnvironmentInfo : ContinuaCIInfo public ContinuaCIEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _projectInfo = new ContinuaCIProjectInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Project", Prefix)); - _buildInfo = new ContinuaCIBuildInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Build", Prefix)); - _configurationInfo = new ContinuaCIConfigurationInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Configuration", Prefix)); + Project = new ContinuaCIProjectInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Project", Prefix)); + Build = new ContinuaCIBuildInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Build", Prefix)); + Configuration = new ContinuaCIConfigurationInfo(environment, string.Format(CultureInfo.InvariantCulture, "{0}.Configuration", Prefix)); } /// @@ -36,10 +33,41 @@ public ContinuaCIEnvironmentInfo(ICakeEnvironment environment) /// /// The Continua CI configuration information. /// - public ContinuaCIConfigurationInfo Configuration - { - get { return _configurationInfo; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Configuration: + /// Name: {0}", + /// BuildSystem.ContinuaCI.Environment.Configuration.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Configuration: + /// Name: {0}", + /// ContinuaCI.Environment.Configuration.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + public ContinuaCIConfigurationInfo Configuration { get; } /// /// Gets Continua CI project information. @@ -47,10 +75,41 @@ public ContinuaCIConfigurationInfo Configuration /// /// The Continua CI project information. /// - public ContinuaCIProjectInfo Project - { - get { return _projectInfo; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Project: + /// Name: {0}", + /// BuildSystem.ContinuaCI.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Project: + /// Name: {0}", + /// ContinuaCI.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + public ContinuaCIProjectInfo Project { get; } /// /// Gets Continua CI build information. @@ -58,10 +117,61 @@ public ContinuaCIProjectInfo Project /// /// The Continua CI build information. /// - public ContinuaCIBuildInfo Build - { - get { return _buildInfo; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Build: + /// Id: {0} + /// Version: {1} + /// Started By: {2} + /// Is Feature Branch Build: {3} + /// Build Number: {4} + /// Started: {5}", + /// BuildSystem.ContinuaCI.Environment.Build.Id, + /// BuildSystem.ContinuaCI.Environment.Build.Version, + /// BuildSystem.ContinuaCI.Environment.Build.StartedBy, + /// BuildSystem.ContinuaCI.Environment.Build.IsFeatureBranchBuild, + /// BuildSystem.ContinuaCI.Environment.Build.BuildNumber, + /// BuildSystem.ContinuaCI.Environment.Build.Started + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Build: + /// Id: {0} + /// Version: {1} + /// Started By: {2} + /// Is Feature Branch Build: {3} + /// Build Number: {4} + /// Started: {5}", + /// ContinuaCI.Environment.Build.Id, + /// ContinuaCI.Environment.Build.Version, + /// ContinuaCI.Environment.Build.StartedBy, + /// ContinuaCI.Environment.Build.IsFeatureBranchBuild, + /// ContinuaCI.Environment.Build.BuildNumber, + /// ContinuaCI.Environment.Build.Started + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + public ContinuaCIBuildInfo Build { get; } /// /// Gets Continua CI build variables. @@ -69,6 +179,46 @@ public ContinuaCIBuildInfo Build /// /// The Continua CI build variables. /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Variables: + /// {0}", + /// BuildSystem.ContinuaCI.Environment.Variable.Aggregate( + /// new StringBuilder(),(builder, pair) => builder.AppendLine( + /// string.Format(":", pair.Key, pair.Value)), + /// builder => builder.ToString()) + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Variables: + /// {0}", + /// ContinuaCI.Environment.Variable.Aggregate( + /// new StringBuilder(),(builder, pair) => builder.AppendLine( + /// string.Format(":", pair.Key, pair.Value)), + /// builder => builder.ToString()) + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// public IDictionary Variable { get @@ -79,11 +229,51 @@ public IDictionary Variable } /// - /// Gets Continua CI build agent properties + /// Gets Continua CI build agent properties. /// /// /// The Continua CI build agent properties. /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Agent Property: + /// {0}", + /// BuildSystem.ContinuaCI.Environment.AgentProperty.Aggregate( + /// new StringBuilder(),(builder, pair) => builder.AppendLine( + /// string.Format(":", pair.Key, pair.Value)), + /// builder => builder.ToString()) + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Variables: + /// {0}", + /// ContinuaCI.Environment.AgentProperty.Aggregate( + /// new StringBuilder(),(builder, pair) => builder.AppendLine( + /// string.Format(":", pair.Key, pair.Value)), + /// builder => builder.ToString()) + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// public IDictionary AgentProperty { get @@ -99,6 +289,38 @@ public IDictionary AgentProperty /// /// The Continua CI product version. /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Version: {0}", + /// BuildSystem.ContinuaCI.Environment.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// + /// Via ContinuaCI. + /// + /// + /// if (ContinuaCI.IsRunningOnContinuaCI) + /// { + /// Information( + /// @"Version: {0}", + /// ContinuaCI.Environment.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on ContinuaCI"); + /// } + /// + /// public string Version { get @@ -108,4 +330,4 @@ public string Version } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIProjectInfo.cs b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIProjectInfo.cs index faa4c2eba6..26a92e91e0 100644 --- a/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIProjectInfo.cs +++ b/src/Cake.Common/Build/ContinuaCI/Data/ContinuaCIProjectInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Globalization; using Cake.Core; @@ -25,7 +26,7 @@ public ContinuaCIProjectInfo(ICakeEnvironment environment, string prefix) } /// - /// Gets the Continua CI Project Name + /// Gets the Continua CI Project Name. /// /// /// The Continua CI Project Name. @@ -39,4 +40,4 @@ public string Name } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/ContinuaCI/IContinuaCIProvider.cs b/src/Cake.Common/Build/ContinuaCI/IContinuaCIProvider.cs index d766c42212..5ea9aa98ee 100644 --- a/src/Cake.Common/Build/ContinuaCI/IContinuaCIProvider.cs +++ b/src/Cake.Common/Build/ContinuaCI/IContinuaCIProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.ContinuaCI.Data; namespace Cake.Common.Build.ContinuaCI @@ -34,7 +35,7 @@ public interface IContinuaCIProvider /// /// Name of the variable to set. /// Value to assign to the variable. - /// Set to 'true' to prevent the build failing if the variable has not been defined for the configuration.. + /// Set to 'true' to prevent the build failing if the variable has not been defined for the configuration. void SetVariable(string name, string value, bool skipIfNotDefined = true); /// @@ -65,4 +66,4 @@ public interface IContinuaCIProvider /// ContinuaCIEnvironmentInfo Environment { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactRequest.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactRequest.cs new file mode 100644 index 0000000000..200c292518 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactRequest.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record CreateArtifactRequest( + [property: JsonPropertyName("version")] + int Version, + [property: JsonPropertyName("name")] + string Name, + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactResponse.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactResponse.cs new file mode 100644 index 0000000000..aef73d7654 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/CreateArtifactResponse.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record CreateArtifactResponse( + [property: JsonPropertyName("ok")] + bool Ok, + [property: JsonPropertyName("signed_upload_url")] + string SignedUploadUrl); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactRequest.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactRequest.cs new file mode 100644 index 0000000000..83552ad2d0 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactRequest.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record FinalizeArtifactRequest( + [property: JsonPropertyName("name")] + string Name, + [property: JsonPropertyName("hash")] + string Hash, + [property: JsonPropertyName("size")] + long Size, + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactResponse.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactResponse.cs new file mode 100644 index 0000000000..a42815b25d --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/FinalizeArtifactResponse.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record FinalizeArtifactResponse( + [property: JsonPropertyName("ok")] + bool Ok, + [property: JsonPropertyName("artifact_id")] + string ArtifactId); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLRequest.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLRequest.cs new file mode 100644 index 0000000000..deea3b1c10 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLRequest.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record GetSignedArtifactURLRequest( + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId, + [property: JsonPropertyName("name")] + string Name); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLResponse.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLResponse.cs new file mode 100644 index 0000000000..6bb1719dcc --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GetSignedArtifactURLResponse.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record GetSignedArtifactURLResponse( + [property: JsonPropertyName("name")] + string Name, + [property: JsonPropertyName("signed_url")] + string SignedUrl); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs new file mode 100644 index 0000000000..86d8bc99e2 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/GitHubActionsArtifactService.cs @@ -0,0 +1,339 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Text.Json; +using System.Threading.Tasks; +using Cake.Common.Build.GitHubActions.Data; +using Cake.Core; +using Cake.Core.IO; +using Microsoft.IdentityModel.JsonWebTokens; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ + internal record GitHubActionsArtifactService( +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + ICakeEnvironment Environment, + IFileSystem FileSystem, + GitHubActionsEnvironmentInfo ActionsEnvironment, + Func CreateHttpClient) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + private const string JsonContentType = "application/json"; + private const string ZipContentType = "application/zip"; + private static readonly Uri CreateArtifactUrl = new Uri("CreateArtifact", UriKind.Relative); + private static readonly Uri FinalizeArtifactUrl = new Uri("FinalizeArtifact", UriKind.Relative); + private static readonly Uri GetSignedArtifactURLUrl = new Uri("GetSignedArtifactURL", UriKind.Relative); + private static readonly Uri ListArtifactsUrl = new Uri("ListArtifacts", UriKind.Relative); + + internal async Task DownloadArtifactFiles( + string artifactName, + DirectoryPath directoryPath) + { + var listArtifactsResponse = await ListArtifacts( + artifactName); + + if (listArtifactsResponse.Artifacts.FirstOrDefault(artifact => artifact.Name == artifactName) + is { WorkflowRunBackendId.Length: > 0 } and { WorkflowJobRunBackendId.Length: > 0 } artifact) + { + var signedArtifactURLResponse = await GetSignedArtifactURL(artifact.WorkflowRunBackendId, artifact.WorkflowJobRunBackendId, artifactName); + + await DownloadArtifact(signedArtifactURLResponse.SignedUrl, directoryPath); + } + else + { + throw new CakeException($"Artifact {artifactName} not found."); + } + } + + private async Task ListArtifacts( + string nameFilter = null, + long? idFilter = null) + { + GetWorkflowBackendIds(out var workflowRunBackendId, out var workflowJobRunBackendId); + + var listArtifactsRequest = new ListArtifactsRequest( + workflowRunBackendId, + workflowJobRunBackendId, + nameFilter, + idFilter); + + return await PostArtifactService( + ListArtifactsUrl, + listArtifactsRequest); + } + + private async Task DownloadArtifact(string signedUrl, DirectoryPath directoryPath) + { + if (string.IsNullOrWhiteSpace(signedUrl)) + { + throw new ArgumentNullException(nameof(signedUrl)); + } + + using var downloadClient = GetStorageHttpClient(); + using var downloadResponse = await downloadClient.GetAsync(signedUrl); + + if (!downloadResponse.IsSuccessStatusCode) + { + throw new CakeException($"Artifact download failed {downloadResponse.StatusCode:F} ({downloadResponse.StatusCode:D})."); + } + + await using var downloadStream = await downloadResponse.Content.ReadAsStreamAsync(); + using var archive = new ZipArchive(downloadStream, ZipArchiveMode.Read); + foreach (var entry in archive.Entries) + { + var entryPath = directoryPath.CombineWithFilePath(entry.FullName); + if (FileSystem.GetFile(entryPath).Exists) + { + FileSystem.GetFile(entryPath).Delete(); + } + else if (FileSystem.GetDirectory(entryPath.GetDirectory()) is { Exists: false } entryDirectory) + { + entryDirectory.Create(); + } + + await using var entryStream = entry.Open(); + await using var fileStream = FileSystem.GetFile(entryPath).OpenWrite(); + await entryStream.CopyToAsync(fileStream); + } + } + + private async Task GetSignedArtifactURL( + string workflowRunBackendId, + string workflowJobRunBackendId, + string artifactName) + { + var getSignedArtifactURLRequest = new GetSignedArtifactURLRequest( + workflowRunBackendId, + workflowJobRunBackendId, + artifactName); + + return await PostArtifactService( + GetSignedArtifactURLUrl, + getSignedArtifactURLRequest); + } + + internal async Task CreateAndUploadArtifactFiles( + string artifactName, + DirectoryPath rootPath, + params IFile[] files) + { + var tempArchivePath = Environment + .GetSpecialPath(SpecialPath.LocalTemp) + .CombineWithFilePath($"{Guid.NewGuid():n}.zip"); + + try + { + GetWorkflowBackendIds(out var workflowRunBackendId, out var workflowJobRunBackendId); + + await CreateArtifactArchive(rootPath, files, tempArchivePath); + + (long size, string hash) = GetArtifactArchiveSizeAndHash(tempArchivePath); + + var signedUploadUrl = await CreateArtifact(artifactName, workflowRunBackendId, workflowJobRunBackendId); + + await UploadArtifact(tempArchivePath, size, signedUploadUrl); + + var artifactId = await FinalizeArtifact(artifactName, hash, size, workflowRunBackendId, workflowJobRunBackendId); + + return artifactId; + } + finally + { + if (FileSystem.GetFile(tempArchivePath).Exists) + { + FileSystem.GetFile(tempArchivePath).Delete(); + } + } + } + + private async Task PostArtifactService( + Uri uri, + TParam param, + [System.Runtime.CompilerServices.CallerMemberName] string memberName = null) + { + using var httpClient = GetArtifactsHttpClient(); + + var jsonData = JsonSerializer.SerializeToUtf8Bytes(param); + + using var response = await httpClient.PostAsync( + uri, + new ByteArrayContent(jsonData) + { + Headers = { ContentType = MediaTypeHeaderValue.Parse(JsonContentType) } + }); + + if (!response.IsSuccessStatusCode) + { + throw new CakeException($"Artifact service call {memberName} failed {response.StatusCode:F} ({response.StatusCode:D})."); + } + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + + return await JsonSerializer.DeserializeAsync(responseStream); + } + + private async Task CreateArtifact(string artifactName, string workflowRunBackendId, string workflowJobRunBackendId) + { + var createArtifactRequest = new CreateArtifactRequest( + 4, + artifactName, + workflowRunBackendId, + workflowJobRunBackendId); + + var (ok, signedUploadUrl) = await PostArtifactService( + CreateArtifactUrl, + createArtifactRequest); + + if (!ok) + { + throw new CakeException("Artifact creation failed."); + } + + if (string.IsNullOrWhiteSpace(signedUploadUrl)) + { + throw new CakeException("Artifact upload url missing."); + } + + return signedUploadUrl; + } + + private async Task UploadArtifact(FilePath contentPath, long contentLength, string signedUploadUrl) + { + using var uploadClient = GetStorageHttpClient(); + await using var uploadStream = FileSystem.GetFile(contentPath).OpenRead(); + using var uploadContent = new StreamContent(uploadStream) + { + Headers = + { + ContentType = MediaTypeHeaderValue.Parse(ZipContentType), + ContentLength = contentLength + } + }; + uploadContent.Headers.TryAddWithoutValidation("x-ms-blob-content-type", ZipContentType); + uploadContent.Headers.TryAddWithoutValidation("x-ms-blob-type", "BlockBlob"); + + using var response = await uploadClient.PutAsync( + signedUploadUrl, + uploadContent); + + if (!response.IsSuccessStatusCode) + { + throw new CakeException($"Artifact upload failed {response.StatusCode:F} ({response.StatusCode:D})."); + } + } + + private async Task FinalizeArtifact( + string artifactName, + string hash, + long contentLength, + string workflowRunBackendId, + string workflowJobRunBackendId) + { + var finalizeArtifactRequest = new FinalizeArtifactRequest( + artifactName, + hash, + contentLength, + workflowRunBackendId, + workflowJobRunBackendId); + + var (ok, artifactId) = await PostArtifactService( + FinalizeArtifactUrl, + finalizeArtifactRequest); + + if (!ok) + { + throw new CakeException("Artifact finalization failed."); + } + + if (string.IsNullOrWhiteSpace(artifactId)) + { + throw new CakeException("Artifact id missing."); + } + + return artifactId; + } + + private (long size, string hash) GetArtifactArchiveSizeAndHash(FilePath tempArchivePath) + { + var size = FileSystem.GetFile(tempArchivePath).Length; + using var stream = FileSystem.GetFile(tempArchivePath).OpenRead(); + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(stream); + var hash = Convert.ToHexString(hashBytes).ToLowerInvariant(); + return (size, hash); + } + + private void GetWorkflowBackendIds(out string workflowRunBackendId, out string workflowJobRunBackendId) + { + try + { + var jwt = new JsonWebToken(ActionsEnvironment.Runtime.Token); + (workflowRunBackendId, workflowJobRunBackendId) = jwt.TryGetClaim("scp", out var scope) + ? scope.Value.Split(' ').FirstOrDefault(s => s.StartsWith("Actions.Results:"))?.Split(':') is { Length: 3 } workflowRunBackendParts + ? (workflowRunBackendParts[1], + workflowRunBackendParts[2]) + : default + : default; + + if (string.IsNullOrWhiteSpace(workflowRunBackendId)) + { + throw new CakeException("GitHub Actions Workflow Token workflowRunBackendId missing."); + } + + if (string.IsNullOrWhiteSpace(workflowJobRunBackendId)) + { + throw new CakeException("GitHub Actions Workflow Token workflowJobRunBackendId missing."); + } + } + catch (Exception ex) + { + throw new CakeException("GitHub Actions Workflow Token invalid.", ex); + } + } + + private async Task CreateArtifactArchive(DirectoryPath rootPath, IFile[] files, FilePath tempArchivePath) + { + if (FileSystem.GetDirectory(tempArchivePath.GetDirectory()) is { Exists: false } tempArchiveDirectory) + { + tempArchiveDirectory.Create(); + } + + await using var archiveStream = FileSystem.GetFile(tempArchivePath).OpenWrite(); + using var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create); + foreach (var file in files) + { + var relativePath = rootPath.GetRelativePath(file.Path.GetDirectory()); + var entry = archive.CreateEntry(relativePath.CombineWithFilePath(file.Path.GetFilename()).FullPath, CompressionLevel.SmallestSize); + await using var entryStream = entry.Open(); + await using var fileStream = file.OpenRead(); + await fileStream.CopyToAsync(entryStream); + } + } + + private HttpClient GetArtifactsHttpClient([System.Runtime.CompilerServices.CallerMemberName] string memberName = null) + { + var client = CreateHttpClient(memberName); + client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(JsonContentType)); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ActionsEnvironment.Runtime.Token); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Cake", Environment.Runtime.CakeVersion.ToString())); + client.BaseAddress = new Uri(string.Concat( + ActionsEnvironment.Runtime.ResultsUrl, + "twirp/github.actions.results.api.v1.ArtifactService/")); + return client; + } + + private HttpClient GetStorageHttpClient([System.Runtime.CompilerServices.CallerMemberName] string memberName = null) + { + var client = CreateHttpClient(memberName); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Cake", Environment.Runtime.CakeVersion.ToString())); + return client; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs new file mode 100644 index 0000000000..fe06c1fa52 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsRequest.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record ListArtifactsRequest( + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId, + [property: JsonPropertyName("name_filter")] + string NameFilter = null, + [property: JsonPropertyName("id_filter")] + long? IdFilter = null); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter +} diff --git a/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs new file mode 100644 index 0000000000..28559b6f51 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/Artifact/ListArtifactsResponse.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text.Json.Serialization; + +namespace Cake.Common.Build.GitHubActions.Commands.Artifact +{ +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + internal record ListArtifactsResponse( + [property: JsonPropertyName("artifacts")] + ListArtifactsResponse.MonolithArtifact[] Artifacts) + { + internal record MonolithArtifact( + [property: JsonPropertyName("workflow_run_backend_id")] + string WorkflowRunBackendId, + [property: JsonPropertyName("workflow_job_run_backend_id")] + string WorkflowJobRunBackendId, + [property: JsonPropertyName("database_id")] + string DatabaseId, + [property: JsonPropertyName("name")] + string Name, + [property: JsonPropertyName("created_at")] + DateTimeOffset CreatedAt); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Commands/GitHubActionsCommands.cs b/src/Cake.Common/Build/GitHubActions/Commands/GitHubActionsCommands.cs new file mode 100644 index 0000000000..ee5e55e3f1 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Commands/GitHubActionsCommands.cs @@ -0,0 +1,318 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Cake.Common.Build.GitHubActions.Commands.Artifact; +using Cake.Common.Build.GitHubActions.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions.Commands +{ + /// + /// Provides GitHub Actions commands for a current build. + /// + public sealed class GitHubActionsCommands + { + private readonly ICakeEnvironment _environment; + private readonly IFileSystem _fileSystem; + private readonly IBuildSystemServiceMessageWriter _writer; + private readonly GitHubActionsEnvironmentInfo _actionsEnvironment; + private readonly GitHubActionsArtifactService _artifactsService; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + /// The build system service message writer. + /// The actions environment. + /// The http client factory. + public GitHubActionsCommands( + ICakeEnvironment environment, + IFileSystem fileSystem, + IBuildSystemServiceMessageWriter writer, + GitHubActionsEnvironmentInfo actionsEnvironment, + Func createHttpClient) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + _actionsEnvironment = actionsEnvironment ?? throw new ArgumentNullException(nameof(actionsEnvironment)); + // Internal service class, keeping public API unchanged, + // introduced in pr https://github.com/cake-build/cake/pull/4350 + _artifactsService = new GitHubActionsArtifactService(environment, fileSystem, actionsEnvironment, createHttpClient ?? throw new ArgumentNullException(nameof(createHttpClient))); + } + + /// + /// Write debug message to the build log. + /// + /// The message. + public void Debug(string message) + { + WriteCommand("debug", message); + } + + /// + /// Write notice message to the build log. + /// + /// The message. + /// The annotation. + public void Notice(string message, GitHubActionsAnnotation annotation = null) + { + WriteCommand("notice", annotation?.GetParameters(), message); + } + + /// + /// Write warning message to the build log. + /// + /// The message. + /// The annotation. + public void Warning(string message, GitHubActionsAnnotation annotation = null) + { + WriteCommand("warning", annotation?.GetParameters(), message); + } + + /// + /// Write error message to the build log. + /// + /// The message. + /// The annotation. + public void Error(string message, GitHubActionsAnnotation annotation = null) + { + WriteCommand("error", annotation?.GetParameters(), message); + } + + /// + /// Start a group in the build log. + /// + /// The title. + public void StartGroup(string title) + { + WriteCommand("group", title); + } + + /// + /// End a group in the build log. + /// + public void EndGroup() + { + WriteCommand("endgroup"); + } + + /// + /// Registers a secret which will get masked in the build log. + /// + /// The secret. + public void SetSecret(string secret) + { + WriteCommand("add-mask", secret); + } + + /// + /// Prepends a directory to the system PATH variable and automatically makes it available to all subsequent actions in the current job. + /// + /// The directory path. + public void AddPath(DirectoryPath path) + { + ArgumentNullException.ThrowIfNull(path); + + if (_actionsEnvironment.Runtime.SystemPath == null) + { + throw new CakeException("GitHub Actions Runtime SystemPath missing."); + } + + var file = _fileSystem.GetFile(_actionsEnvironment.Runtime.SystemPath); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.WriteLine(path.MakeAbsolute(_environment).FullPath); + } + + /// + /// Creates or updates an environment variable for any steps running next in a job. + /// + /// The key. + /// The Value. + public void SetEnvironmentVariable(string key, string value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key)); + } + + ArgumentNullException.ThrowIfNull(value); + + if (_actionsEnvironment.Runtime.EnvPath == null) + { + throw new CakeException("GitHub Actions Runtime EnvPath missing."); + } + + var file = _fileSystem.GetFile(_actionsEnvironment.Runtime.EnvPath); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(key); + writer.WriteLine("< + /// Creates or updates an output parameter for any steps running next in a job. + /// + /// The key. + /// The Value. + public void SetOutputParameter(string key, string value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key)); + } + + ArgumentNullException.ThrowIfNull(value); + + if (_actionsEnvironment.Runtime.OutputPath == null) + { + throw new CakeException("GitHub Actions Runtime OutputPath missing."); + } + + var file = _fileSystem.GetFile(_actionsEnvironment.Runtime.OutputPath); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(key); + writer.Write('='); + writer.WriteLine(value); + } + + /// + /// Creates or updates the step summary for a GitHub workflow. + /// + /// The step summary. + public void SetStepSummary(string summary) + { + if (string.IsNullOrEmpty(summary)) + { + throw new ArgumentNullException(nameof(summary)); + } + + if (_actionsEnvironment.Runtime.StepSummary == null) + { + throw new CakeException("GitHub Actions Runtime StepSummary missing."); + } + + var file = _fileSystem.GetFile(_actionsEnvironment.Runtime.StepSummary); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.WriteLine(summary); + } + + /// + /// Upload local file into a file container folder, and create an artifact. + /// + /// Path to the local file. + /// The artifact name. + /// A representing the asynchronous operation. + public async Task UploadArtifact(FilePath path, string artifactName) + { + var file = _fileSystem.GetFile(ValidateArtifactParameters(path, artifactName)); + + if (!file.Exists) + { + throw new FileNotFoundException("Artifact file not found.", file.Path.FullPath); + } + + await _artifactsService.CreateAndUploadArtifactFiles(artifactName, file.Path.GetDirectory(), file); + } + + /// + /// Upload local directory files into a file container folder, and create an artifact. + /// + /// Path to the local directory. + /// The artifact name. + /// A representing the asynchronous operation. + public async Task UploadArtifact(DirectoryPath path, string artifactName) + { + var directory = _fileSystem.GetDirectory(ValidateArtifactParameters(path, artifactName)); + + if (!directory.Exists) + { + throw new DirectoryNotFoundException(FormattableString.Invariant($"Artifact directory {directory.Path.FullPath} not found.")); + } + + var files = directory + .GetFiles("*", SearchScope.Recursive) + .ToArray(); + + await _artifactsService.CreateAndUploadArtifactFiles(artifactName, directory.Path, files); + } + + /// + /// Download remote artifact container into local directory. + /// + /// The artifact name. + /// Path to the local directory. + /// A representing the asynchronous operation. + public async Task DownloadArtifact(string artifactName, DirectoryPath path) + { + var directory = _fileSystem.GetDirectory(ValidateArtifactParameters(path, artifactName)); + + if (!directory.Exists) + { + throw new DirectoryNotFoundException(FormattableString.Invariant($"Local directory {directory.Path.FullPath} not found.")); + } + + await _artifactsService.DownloadArtifactFiles(artifactName, directory.Path); + } + + internal void WriteCommand(string command, string message = null) + { + WriteCommand(command, new Dictionary(), message); + } + + internal void WriteCommand(string command, Dictionary parameters, string message) + { + var parameterString = parameters?.Count > 0 ? string.Concat(" ", string.Join(',', parameters.Select(pair => $"{pair.Key}={EscapeCommandParameter(pair.Value)}"))) : string.Empty; + + _writer.Write("::{0}{1}::{2}", command, parameterString, EscapeCommandMessage(message)); + } + + private static string EscapeCommandMessage(string value) => (value ?? string.Empty).Replace("%", "%25").Replace("\r", "%0D").Replace("\n", "%0A"); + + private static string EscapeCommandParameter(string value) => (value ?? string.Empty).Replace("%", "%25").Replace("\r", "%0D").Replace("\n", "%0A").Replace(":", "%3A").Replace(",", "%2C"); + + private T ValidateArtifactParameters(T path, string artifactName) where T : IPath + { + if (path is null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (string.IsNullOrWhiteSpace(artifactName)) + { + throw new ArgumentNullException(nameof(artifactName)); + } + + if (string.IsNullOrWhiteSpace(_actionsEnvironment.Runtime.Token)) + { + throw new CakeException("GitHub Actions Runtime Token missing."); + } + + if (string.IsNullOrWhiteSpace(_actionsEnvironment.Runtime.Url)) + { + throw new CakeException("GitHub Actions Runtime Url missing."); + } + + if (string.IsNullOrWhiteSpace(_actionsEnvironment.Workflow.RunId)) + { + throw new CakeException("GitHub Actions Workflow RunId missing."); + } + + return path.MakeAbsolute(_environment); + } + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsAnnotation.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsAnnotation.cs new file mode 100644 index 0000000000..72dc20f8ff --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsAnnotation.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provides optional annotation data associated with a GitHub Actions command. + /// + public sealed class GitHubActionsAnnotation + { + /// + /// Gets or sets the custom title. + /// + /// + /// The custom title. + /// + public string Title { get; set; } + + /// + /// Gets or sets the file path. + /// + /// + /// The path of the file. + /// + public string File { get; set; } + + /// + /// Gets or sets the start line number. + /// + /// + /// The start line number. + /// + public int? StartLine { get; set; } + + /// + /// Gets or sets the end line number. + /// + /// + /// The end line number. + /// + public int? EndLine { get; set; } + + /// + /// Gets or sets the start column number. + /// + /// + /// The start column number. + /// + public int? StartColumn { get; set; } + + /// + /// Gets or sets the end column number. + /// + /// + /// The end column number. + /// + public int? EndColumn { get; set; } + + internal Dictionary GetParameters() + { + var parameters = new Dictionary(); + if (!string.IsNullOrWhiteSpace(Title)) + { + parameters.Add("title", Title); + } + if (!string.IsNullOrWhiteSpace(File)) + { + parameters.Add("file", File); + } + if (StartLine.HasValue) + { + parameters.Add("line", StartLine.ToString()); + } + if (EndLine.HasValue) + { + parameters.Add("endLine", EndLine.ToString()); + } + if (StartColumn.HasValue) + { + parameters.Add("col", StartColumn.ToString()); + } + if (EndColumn.HasValue) + { + parameters.Add("endColumn", EndColumn.ToString()); + } + return parameters; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsArchitecture.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsArchitecture.cs new file mode 100644 index 0000000000..11cce1248b --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsArchitecture.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// The GitHub Actions Architecture. + /// + public enum GitHubActionsArchitecture + { + /// + /// Unknown. + /// + Unknown, + + /// + /// X86. + /// + X86, + + /// + /// X64. + /// + X64, + + /// + /// ARM. + /// + ARM, + + /// + /// ARM64. + /// + ARM64 + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsEnvironmentInfo.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsEnvironmentInfo.cs new file mode 100644 index 0000000000..3c363cd254 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsEnvironmentInfo.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provides GitHub Actions environment information for a current build. + /// + public sealed class GitHubActionsEnvironmentInfo : GitHubActionsInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitHubActionsEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Runner = new GitHubActionsRunnerInfo(environment); + Workflow = new GitHubActionsWorkflowInfo(environment); + PullRequest = new GitHubActionsPullRequestInfo(environment); + Runtime = new GitHubActionsRuntimeInfo(environment); + } + + /// + /// Gets the GitHub Actions home directory. + /// + /// + /// The home. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"Home: {0}", + /// BuildSystem.GitHubActions.Environment.Home + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + /// Via GitHubActions. + /// + /// + /// if (GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"Home: {0}", + /// GitHubActions.Environment.Home + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + public DirectoryPath Home => GetEnvironmentDirectoryPath("HOME"); + + /// + /// Gets GitHub Actions runner information. + /// + /// + /// The GitHub Actions runner information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"Runner: + /// OS: {0} + /// Temp: {1} + /// ToolCache: {2} + /// Workspace: {3}", + /// BuildSystem.GitHubActions.Environment.Runner.OS, + /// BuildSystem.GitHubActions.Environment.Runner.Temp, + /// BuildSystem.GitHubActions.Environment.Runner.ToolCache, + /// BuildSystem.GitHubActions.Environment.Runner.Workspace + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + /// Via GitHubActions. + /// + /// + /// if (GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"Runner: + /// OS: {0} + /// Temp: {1} + /// ToolCache: {2} + /// Workspace: {3}", + /// GitHubActions.Environment.Runner.OS, + /// GitHubActions.Environment.Runner.Temp, + /// GitHubActions.Environment.Runner.ToolCache, + /// GitHubActions.Environment.Runner.Workspace + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + public GitHubActionsRunnerInfo Runner { get; } + + /// + /// Gets the GitHub Actions workflow information. + /// + /// + /// The GitHub Actions workflow information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"Workflow: + /// Workflow: {0} + /// Action: {1} + /// Actor: {2}", + /// BuildSystem.GitHubActions.Environment.Workflow.Workflow, + /// BuildSystem.GitHubActions.Environment.Workflow.Action, + /// BuildSystem.GitHubActions.Environment.Workflow.Actor + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + /// Via GitHubActions. + /// + /// + /// Information( + /// @"Workflow: + /// Workflow: {0} + /// Action: {1} + /// Actor: {2}", + /// GitHubActions.Environment.Workflow.Workflow, + /// GitHubActions.Environment.Workflow.Action, + /// GitHubActions.Environment.Workflow.Actor + /// ); + /// + /// + public GitHubActionsWorkflowInfo Workflow { get; } + + /// + /// Gets GitHub Actions pull request information. + /// + /// + /// The GitHub Actions pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0}", + /// BuildSystem.GitHubActions.Environment.PullRequest.IsPullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + /// Via GitHubActions. + /// + /// + /// if (GitHubActions.IsRunningOnGitHubActions) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0}", + /// GitHubActions.Environment.PullRequest.IsPullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on GitHubActions"); + /// } + /// + /// + public GitHubActionsPullRequestInfo PullRequest { get; } + + /// + /// Gets GitHub Actions runtime information. + /// + /// + /// The GitHub Actions runtime information. + /// + /// Via BuildSystem. + /// + /// + /// // TODO + /// + /// + /// Via GitHubActions. + /// + /// + /// // TODO + /// + /// + public GitHubActionsRuntimeInfo Runtime { get; } + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsPullRequestInfo.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsPullRequestInfo.cs new file mode 100644 index 0000000000..dcfd94ad73 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsPullRequestInfo.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provides GitHub Actions pull request information for the current build. + /// + public sealed class GitHubActionsPullRequestInfo : GitHubActionsInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitHubActionsPullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => GetEnvironmentString("GITHUB_EVENT_NAME") == "pull_request"; + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRefType.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRefType.cs new file mode 100644 index 0000000000..a87288777f --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRefType.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// The GitHub Actions Ref Type. + /// + public enum GitHubActionsRefType + { + /// + /// Unknown. + /// + Unknown, + + /// + /// Branch. + /// + Branch, + + /// + /// Tag. + /// + Tag + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRunnerInfo.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRunnerInfo.cs new file mode 100644 index 0000000000..9361d59993 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRunnerInfo.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provides GitHub Actions runner information for the current build. + /// + public sealed class GitHubActionsRunnerInfo : GitHubActionsInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitHubActionsRunnerInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the name of the runner executing the job. + /// + /// + /// The name of the runner executing the job. + /// + public string Name => GetEnvironmentString("RUNNER_NAME"); + + /// + /// Gets the operating system of the runner executing the job. + /// + /// + /// The operating system of the runner executing the job. + /// + // ReSharper disable once InconsistentNaming + public string OS => GetEnvironmentString("RUNNER_OS"); + + /// + /// Gets the path of the temporary directory for the runner. + /// + /// + /// The path of the temporary directory for the runner. + /// This directory is guaranteed to be empty at the start of each job, even on self-hosted runners. + /// + public DirectoryPath Temp => GetEnvironmentDirectoryPath("RUNNER_TEMP"); + + /// + /// Gets the path of the directory containing some of the pre-installed tools for GitHub-hosted runners. + /// + /// + /// The path of the directory containing some of the pre-installed tools for GitHub-hosted runners. + /// + public DirectoryPath ToolCache => GetEnvironmentDirectoryPath("RUNNER_TOOL_CACHE"); + + /// + /// Gets the runner workspace directory path. + /// + /// + /// The runner workspace directory path. + /// + public DirectoryPath Workspace => GetEnvironmentDirectoryPath("RUNNER_WORKSPACE"); + + /// + /// Gets the runner image OS on hosted agents. + /// + /// + /// The runner image OS on hosted agents. + /// + public string ImageOS => GetEnvironmentString("ImageOS"); + + /// + /// Gets the runner image version on hosted agents. + /// + /// + /// The runner image version on hosted agents. + /// + public string ImageVersion => GetEnvironmentString("ImageVersion"); + + /// + /// Gets the runner user name. + /// + /// + /// The runner user name. + /// + public string User => GetEnvironmentString("RUNNER_USER"); + + /// + /// Gets the runner architecture of the runner executing the job. + /// + /// + /// The runner architecture of the runner executing the job. Possible values are X86, X64, ARM, and ARM64. + /// + public GitHubActionsArchitecture Architecture => GetEnvironmentString("RUNNER_ARCH") + ?.ToUpperInvariant() switch + { + "X86" => GitHubActionsArchitecture.X86, + "X64" => GitHubActionsArchitecture.X64, + "ARM" => GitHubActionsArchitecture.ARM, + "ARM64" => GitHubActionsArchitecture.ARM64, + _ => GitHubActionsArchitecture.Unknown + }; + } +} diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRuntimeInfo.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRuntimeInfo.cs new file mode 100644 index 0000000000..7c9ec5e7a0 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsRuntimeInfo.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provides GitHub Actions runtime information for the current build. + /// + public sealed class GitHubActionsRuntimeInfo : GitHubActionsInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitHubActionsRuntimeInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the GitHub Actions Runtime is available for the current build. + /// + /// + /// true if the GitHub Actions Runtime is available for the current build. + /// + public bool IsRuntimeAvailable + => !string.IsNullOrWhiteSpace(Token) && !string.IsNullOrWhiteSpace(Url) && !string.IsNullOrWhiteSpace(ResultsUrl); + + /// + /// Gets the current runtime API authorization token. + /// + /// + /// The current runtime API authorization token. + /// + public string Token => GetEnvironmentString("ACTIONS_RUNTIME_TOKEN"); + + /// + /// Gets the current runtime API endpoint url. + /// + /// + /// The current runtime API endpoint url. + /// + public string Url => GetEnvironmentString("ACTIONS_RUNTIME_URL"); + + /// + /// Gets the current runtime API endpoint url for the job. + /// + public string ResultsUrl => GetEnvironmentString("ACTIONS_RESULTS_URL"); + + /// + /// Gets the path to environment file to set an environment variable that the following steps in a job can use. + /// + /// + /// The path to environment file to set an environment variable that the following steps in a job can use. + /// + public FilePath EnvPath => GetEnvironmentFilePath("GITHUB_ENV"); + + /// + /// Gets the path to output file to set an output parameter that the following steps in a job can use. + /// + /// + /// The path to output file to set an output parameter that the following steps in a job can use. + /// + public FilePath OutputPath => GetEnvironmentFilePath("GITHUB_OUTPUT"); + + /// + /// Gets the path to output file to set the step summary. + /// + /// + /// The path to output file to set the step summary. + /// + public FilePath StepSummary => GetEnvironmentFilePath("GITHUB_STEP_SUMMARY"); + + /// + /// Gets the path to path file to add a path to system path that the following steps in a job can use. + /// + /// + /// The path to path file to add a path to system path that the following steps in a job can use. + /// + public FilePath SystemPath => GetEnvironmentFilePath("GITHUB_PATH"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsWorkflowInfo.cs b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsWorkflowInfo.cs new file mode 100644 index 0000000000..aa89390058 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/Data/GitHubActionsWorkflowInfo.cs @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions.Data +{ + /// + /// Provide GitHub Actions workflow information for a current build. + /// + public sealed class GitHubActionsWorkflowInfo : GitHubActionsInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitHubActionsWorkflowInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the unique identifier of the action. + /// + /// + /// The unique identifier of the action. + /// + public string Action => GetEnvironmentString("GITHUB_ACTION"); + + /// + /// Gets the path where your action is located. You can use this path to access files located in the same repository as your action. This variable is only supported in composite run steps actions. + /// + /// + /// The path where your action is located. You can use this path to access files located in the same repository as your action. This variable is only supported in composite run steps actions. + /// + public DirectoryPath ActionPath => GetEnvironmentDirectoryPath("GITHUB_ACTION_PATH"); + + /// + /// Gets the name of the person or app that initiated the workflow. + /// + /// + /// The name of the person or app that initiated the workflow. + /// + public string Actor => GetEnvironmentString("GITHUB_ACTOR"); + + /// + /// Gets the API URL. + /// + /// + /// The API URL. For example: https://api.github.com. + /// + public string ApiUrl => GetEnvironmentString("GITHUB_API_URL"); + + /// + /// Gets the branch of the base repository. + /// + /// + /// The branch of the base repository. Only set for forked repositories. + /// + public string BaseRef => GetEnvironmentString("GITHUB_BASE_REF"); + + /// + /// Gets the name of the webhook event that triggered the workflow. + /// + /// + /// The name of the webhook event that triggered the workflow. + /// + public string EventName => GetEnvironmentString("GITHUB_EVENT_NAME"); + + /// + /// Gets the path of the file with the complete webhook event payload. + /// + /// + /// The path of the file with the complete webhook event payload. + /// + public FilePath EventPath => GetEnvironmentFilePath("GITHUB_EVENT_PATH"); + + /// + /// Gets the GraphQL API URL. + /// + /// + /// The GraphQL API URL. For example: https://api.github.com/graphql. + /// + public string GraphQLUrl => GetEnvironmentString("GITHUB_GRAPHQL_URL"); + + /// + /// Gets the branch of the head repository. + /// + /// + /// The branch of the head repository. Only set for forked repositories. + /// + public string HeadRef => GetEnvironmentString("GITHUB_HEAD_REF"); + + /// + /// Gets the job name. + /// + /// + /// The job name. + /// + public string Job => GetEnvironmentString("GITHUB_JOB"); + + /// + /// Gets the branch or tag ref that triggered the workflow. + /// + /// + /// The branch or tag ref that triggered the workflow. + /// + public string Ref => GetEnvironmentString("GITHUB_REF"); + + /// + /// Gets the owner and repository name. + /// + /// + /// The owner and repository name. + /// + public string Repository => GetEnvironmentString("GITHUB_REPOSITORY"); + + /// + /// Gets the repository owner. + /// + /// + /// The repository owner. + /// + public string RepositoryOwner => GetEnvironmentString("GITHUB_REPOSITORY_OWNER"); + + /// + /// Gets the unique number for each run within the repository. + /// + /// + /// The unique number for each run within the repository. + /// + public string RunId => GetEnvironmentString("GITHUB_RUN_ID"); + + /// + /// Gets the unique number for each run of a particular workflow in the repository. + /// + /// + /// The unique number for each run of a particular workflow in the repository. + /// + public int RunNumber => GetEnvironmentInteger("GITHUB_RUN_NUMBER"); + + /// + /// Gets the URL of the GitHub server. + /// + /// + /// The URL of the GitHub server. For example: https://github.com. + /// + public string ServerUrl => GetEnvironmentString("GITHUB_SERVER_URL"); + + /// + /// Gets the commit SHA that triggered the workflow. + /// + /// + /// The commit SHA that triggered the workflow. + /// + public string Sha => GetEnvironmentString("GITHUB_SHA"); + + /// + /// Gets the name of the workflow. + /// + /// + /// The name of the workflow. + /// + public string Workflow => GetEnvironmentString("GITHUB_WORKFLOW"); + + /// + /// Gets the GitHub workspace directory path. + /// + /// + /// The GitHub workspace directory path. + /// + public DirectoryPath Workspace => GetEnvironmentDirectoryPath("GITHUB_WORKSPACE"); + + /// + /// Gets the number of attempts for current run. + /// + /// + /// The attempt number for current run. + /// + public int Attempt => GetEnvironmentInteger("GITHUB_RUN_ATTEMPT"); + + /// + /// Gets a value indicating whether if branch protections are configured for the ref that triggered the workflow run. + /// + /// + /// Value whether if branch protections are configured for the ref that triggered the workflow run. + /// + public bool RefProtected => GetEnvironmentBoolean("GITHUB_REF_PROTECTED"); + + /// + /// Gets the branch or tag name that triggered the workflow run. + /// + /// + /// The branch or tag name that triggered the workflow run. + /// + public string RefName => GetEnvironmentString("GITHUB_REF_NAME"); + + /// + /// Gets the type of ref that triggered the workflow run. + /// + /// + /// The type of ref that triggered the workflow run. Valid values are branch or tag. + /// + public GitHubActionsRefType RefType => GetEnvironmentString("GITHUB_REF_TYPE") + ?.ToLowerInvariant() switch + { + "branch" => GitHubActionsRefType.Branch, + "tag" => GitHubActionsRefType.Tag, + _ => GitHubActionsRefType.Unknown + }; + } +} diff --git a/src/Cake.Common/Build/GitHubActions/GitHubActionsInfo.cs b/src/Cake.Common/Build/GitHubActions/GitHubActionsInfo.cs new file mode 100644 index 0000000000..1c0ce6f64a --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/GitHubActionsInfo.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions +{ + /// + /// Base class used to provide information about the GitHub Actions environment. + /// + public abstract class GitHubActionsInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected GitHubActionsInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected DirectoryPath GetEnvironmentDirectoryPath(string variable) + { + return _environment.GetEnvironmentVariable(variable) is string value + ? DirectoryPath.FromString(value) + : null; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected FilePath GetEnvironmentFilePath(string variable) + { + return _environment.GetEnvironmentVariable(variable) is string value + ? FilePath.FromString(value) + : null; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + } +} diff --git a/src/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cs b/src/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cs new file mode 100644 index 0000000000..692fc57b44 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/GitHubActionsProvider.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.GitHubActions.Commands; +using Cake.Common.Build.GitHubActions.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitHubActions +{ + /// + /// Responsible for communicating with GitHub Actions. + /// + public class GitHubActionsProvider : IGitHubActionsProvider + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + /// The build system service message writer. + public GitHubActionsProvider(ICakeEnvironment environment, IFileSystem fileSystem, IBuildSystemServiceMessageWriter writer) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new GitHubActionsEnvironmentInfo(environment); + Commands = new GitHubActionsCommands(environment, fileSystem, writer, Environment, _ => new System.Net.Http.HttpClient()); + } + + /// + public bool IsRunningOnGitHubActions => _environment.GetEnvironmentVariable("GITHUB_ACTIONS")?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false; + + /// + public GitHubActionsEnvironmentInfo Environment { get; } + + /// + public GitHubActionsCommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/GitHubActions/IGitHubActionsProvider.cs b/src/Cake.Common/Build/GitHubActions/IGitHubActionsProvider.cs new file mode 100644 index 0000000000..32ae3fb917 --- /dev/null +++ b/src/Cake.Common/Build/GitHubActions/IGitHubActionsProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitHubActions.Commands; +using Cake.Common.Build.GitHubActions.Data; + +namespace Cake.Common.Build.GitHubActions +{ + /// + /// Represents a GitHub Actions provider. + /// + public interface IGitHubActionsProvider + { + /// + /// Gets a value indicating whether the current build is running on GitHub Actions. + /// + /// + /// true if the current build is running on GitHub Actions; otherwise, false. + /// + bool IsRunningOnGitHubActions { get; } + + /// + /// Gets the GitHub Actions environment. + /// + /// + /// The GitHub Actions environment. + /// + GitHubActionsEnvironmentInfo Environment { get; } + + /// + /// Gets the GitHub Actions commands. + /// + /// + /// The GitHub Actions commands. + /// + public GitHubActionsCommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs new file mode 100644 index 0000000000..693277ddab --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIBuildInfo.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provide GitLab CI build information for a current build. + /// + public sealed class GitLabCIBuildInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIBuildInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the unique id of the current build that GitLab CI uses internally. + /// + /// + /// The build ID. + /// + public long Id => GetEnvironmentLong("CI_JOB_ID", "CI_BUILD_ID"); + + /// + /// Gets the commit revision for which project is built. + /// + /// + /// The commit revision hash. + /// + public string Reference => GetEnvironmentString("CI_COMMIT_SHA", "CI_BUILD_REF"); + + /// + /// Gets the commit tag name. Present only when building tags. + /// + /// + /// The build tag name. + /// + public string Tag => GetEnvironmentString("CI_COMMIT_TAG", "CI_BUILD_TAG"); + + /// + /// Gets the name of the build as defined in .gitlab-ci.yml. + /// + /// + /// The name of the build. + /// + public string Name => GetEnvironmentString("CI_JOB_NAME", "CI_BUILD_NAME"); + + /// + /// Gets the name of the stage as defined in .gitlab-ci.yml. + /// + /// + /// The name of the current stage. + /// + public string Stage => GetEnvironmentString("CI_JOB_STAGE", "CI_BUILD_STAGE"); + + /// + /// Gets the branch or tag name for which project is built. + /// + /// + /// The branch or tag for this build. + /// + public string RefName => GetEnvironmentString("CI_COMMIT_REF_NAME", "CI_BUILD_REF_NAME"); + + /// + /// Gets the URL to clone the Git repository. + /// + /// + /// The repository URL. + /// + public string RepoUrl => GetEnvironmentString("CI_REPOSITORY_URL", "CI_BUILD_REPO"); + + /// + /// Gets a value indicating whether the build was triggered. + /// + /// + /// True if the build was triggered, otherwise false. + /// + public bool Triggered => GetEnvironmentBoolean("CI_PIPELINE_TRIGGERED", "CI_BUILD_TRIGGERED"); + + /// + /// Gets a value indicating whether the build was manually started. + /// + /// + /// True if the build was started manually, otherwise false. + /// + public bool Manual => GetEnvironmentBoolean("CI_JOB_MANUAL", "CI_BUILD_MANUAL"); + + /// + /// Gets the token used for authenticating with the GitLab Container Registry. + /// + /// + /// The build authorisation token. + /// + public string Token => GetEnvironmentString("CI_JOB_TOKEN", "CI_BUILD_TOKEN"); + + /// + /// Gets the unique id of the current pipeline that GitLab CI uses internally. + /// + /// + /// The unique build ID. + /// + public long PipelineId => GetEnvironmentLong("CI_PIPELINE_ID"); + + /// + /// Gets the unique id of the current pipeline scoped to the project. + /// + /// + /// The unique build ID. + /// + public long PipelineIId => GetEnvironmentLong("CI_PIPELINE_IID"); + + /// + /// Gets the id of the user who started the build. + /// + /// + /// The user ID. + /// + public long UserId => GetEnvironmentLong("GITLAB_USER_ID"); + + /// + /// Gets the email of the user who started the build. + /// + /// + /// The email address of the user. + /// + public string UserEmail => GetEnvironmentString("GITLAB_USER_EMAIL"); + + /// + /// Gets how the pipeline was triggered. + /// + /// + /// The trigger of the pipeline as or null if the trigger could not be determined. + /// + public GitLabCIPipelineSource? Source => GetEnvironmentEnum("CI_PIPELINE_SOURCE"); + } +} diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIEnvironmentInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIEnvironmentInfo.cs new file mode 100644 index 0000000000..9213e5fa71 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIEnvironmentInfo.cs @@ -0,0 +1,284 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provides GitLab CI environment information for a current build. + /// + public sealed class GitLabCIEnvironmentInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Server = new GitLabCIServerInfo(environment); + Build = new Data.GitLabCIBuildInfo(environment); + PullRequest = new GitLabCIPullRequestInfo(environment); + Project = new Data.GitLabCIProjectInfo(environment); + Runner = new Data.GitLabCIRunnerInfo(environment); + } + + /// + /// Gets the GitLab CI runner information. + /// + /// + /// The GitLab CI runner information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Runner: + /// Id: {0} + /// Description: {1} + /// Tags: {2}", + /// BuildSystem.GitLabCI.Environment.Runner.Id, + /// BuildSystem.GitLabCI.Environment.Runner.Description, + /// BuildSystem.GitLabCI.Environment.Runner.Tags + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + /// Via GitLabCI. + /// + /// + /// if (GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Runner: + /// Id: {0} + /// Description: {1} + /// Tags: {2}", + /// GitLabCI.Environment.Runner.Id, + /// GitLabCI.Environment.Runner.Description, + /// GitLabCI.Environment.Runner.Tags + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + public GitLabCIRunnerInfo Runner { get; } + + /// + /// Gets the GitLab CI server information. + /// + /// + /// The GitLab CI server information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Server: + /// Name: {0} + /// Version: {1} + /// Revision: {2}", + /// BuildSystem.GitLabCI.Environment.Server.Name, + /// BuildSystem.GitLabCI.Environment.Server.Version, + /// BuildSystem.GitLabCI.Environment.Server.Revision + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + /// Via GitLabCI. + /// + /// + /// if (GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Server: + /// Name: {0} + /// Version: {1} + /// Revision: {2}", + /// GitLabCI.Environment.Server.Name, + /// GitLabCI.Environment.Server.Version, + /// GitLabCI.Environment.Server.Revision + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + public GitLabCIServerInfo Server { get; } + + /// + /// Gets the GitLab CI build information. + /// + /// + /// The GitLab CI build information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Build: + /// Id: {0} + /// Reference: {1} + /// Tag: {2} + /// Name: {3} + /// Stage: {4}", + /// BuildSystem.GitLabCI.Environment.Build.Id, + /// BuildSystem.GitLabCI.Environment.Build.Reference, + /// BuildSystem.GitLabCI.Environment.Build.Tag, + /// BuildSystem.GitLabCI.Environment.Build.Tag, + /// BuildSystem.GitLabCI.Environment.Build.Stage + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + /// Via GitLabCI. + /// + /// + /// Information( + /// @"Build: + /// Id: {0} + /// Reference: {1} + /// Tag: {2} + /// Name: {3} + /// Stage: {4}", + /// GitLabCI.Environment.Build.Id, + /// GitLabCI.Environment.Build.Reference, + /// GitLabCI.Environment.Build.Tag, + /// GitLabCI.Environment.Build.Tag, + /// GitLabCI.Environment.Build.Stage + /// ); + /// + /// + public GitLabCIBuildInfo Build { get; } + + /// + /// Gets GitLab CI pull request information. + /// + /// + /// The GitLab CI pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1} + /// IId: {2}", + /// BuildSystem.GitLabCI.Environment.PullRequest.IsPullRequest, + /// BuildSystem.GitLabCI.Environment.PullRequest.Id, + /// BuildSystem.GitLabCI.Environment.PullRequest.IId + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + /// Via GitLabCI. + /// + /// + /// if (GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1} + /// IId: {2}", + /// GitLabCI.Environment.PullRequest.IsPullRequest, + /// GitLabCI.Environment.PullRequest.Id, + /// GitLabCI.Environment.PullRequest.IId + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + public GitLabCIPullRequestInfo PullRequest { get; } + + /// + /// Gets the GitLab CI project information. + /// + /// + /// The GitLab CI project information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GitLabCI.IsRunningOnGitLabCI) + /// { + /// Information( + /// @"Project: + /// Id: {0} + /// Name: {1} + /// Namespace: {2} + /// Path: {3} + /// Url: {4} + /// Directory: {5}", + /// BuildSystem.GitLabCI.Environment.Project.Id, + /// BuildSystem.GitLabCI.Environment.Project.Name, + /// BuildSystem.GitLabCI.Environment.Project.Namespace, + /// BuildSystem.GitLabCI.Environment.Project.Path, + /// BuildSystem.GitLabCI.Environment.Project.Url, + /// BuildSystem.GitLabCI.Environment.Project.Directory + /// ); + /// } + /// else + /// { + /// Information("Not running on GitLabCI"); + /// } + /// + /// + /// Via GitLabCI. + /// + /// + /// Information( + /// @"Project: + /// Id: {0} + /// Name: {1} + /// Namespace: {2} + /// Path: {3} + /// Url: {4} + /// Directory: {5}", + /// GitLabCI.Environment.Project.Id, + /// GitLabCI.Environment.Project.Name, + /// GitLabCI.Environment.Project.Namespace, + /// GitLabCI.Environment.Project.Path, + /// GitLabCI.Environment.Project.Url, + /// GitLabCI.Environment.Project.Directory + /// ); + /// + /// + public GitLabCIProjectInfo Project { get; } + } +} diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs new file mode 100644 index 0000000000..0d2b3ea8d6 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPipelineSource.cs @@ -0,0 +1,91 @@ +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GitLabCI.Data; + +/// +/// Enumerates possible triggers of a GitLab CI pipeline. +/// +/// CI_PIPELINE_SOURCE predefined variable (GitLab Documentation) +public enum GitLabCIPipelineSource +{ + /// + /// Pipeline var triggered by the pipelines API. + /// + Api, + + /// + /// Pipeline was created using a GitLab ChatOps command. + /// + Chat, + + /// + /// Pipeline was created using a CI service other than GitLab + /// + External, + + /// + /// Pipeline was created because an external pull request on GitHub was created or updated. + /// + [EnumMember(Value = "external_pull_request_event")] + ExternalPullRequestEvent, + + /// + /// Pipeline was created because a merge request was created or updated. + /// + [EnumMember(Value = "merge_request_event")] + MergeRequestEvent, + + /// + /// Pipeline is an on-demand DAST scan pipeline. + /// + [EnumMember(Value = "ondemand_dast_scan")] + OnDemandDastScan, + + /// + /// Pipeline is an on-demand DAST validation pipeline. + /// + [EnumMember(Value = "ondemand_dast_validation")] + OnDemandDastValidation, + + /// + /// Pipeline was created by a parent pipeline. + /// + [EnumMember(Value = "parent_pipeline")] + ParentPipeline, + + /// + /// Pipeline was created by another pipeline + /// + Pipeline, + + /// + /// Pipelune was triggered by a Git push event. + /// + Push, + + /// + /// Pipeline was triggered by a schedule. + /// + Schedule, + + /// + /// Pipeline is a security orchestration policy pipeline. + /// + [EnumMember(Value = "security_orchestration_policy")] + SecurityOrchestrationPolicy, + + /// + /// Pipeline was created using a trigger token. + /// + Trigger, + + /// + /// Pipeline was created by selecting New Pipeline in the GitLab UI. + /// + Web, + + /// + /// Pipeline was created using the Web IDE. + /// + WebIde +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIProjectInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIProjectInfo.cs new file mode 100644 index 0000000000..9f9624e038 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIProjectInfo.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provides GitLab CI project information for a current build. + /// + public sealed class GitLabCIProjectInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIProjectInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the unique id of the current project that GitLab CI uses internally. + /// + /// + /// The project ID. + /// + public long Id => GetEnvironmentLong("CI_PROJECT_ID"); + + /// + /// Gets the project name that is currently being built. + /// + /// + /// The project name. + /// + public string Name => GetEnvironmentString("CI_PROJECT_NAME"); + + /// + /// Gets the project namespace (username or groupname) that is currently being built. + /// + /// + /// The project namespace. + /// + public string Namespace => GetEnvironmentString("CI_PROJECT_NAMESPACE"); + + /// + /// Gets the namespace with project name. + /// + /// + /// The project namespace and project name. + /// + public string Path => GetEnvironmentString("CI_PROJECT_PATH"); + + /// + /// Gets the HTTP address to access the project. + /// + /// + /// The HTTP address to access the project. + /// + public string Url => GetEnvironmentString("CI_PROJECT_URL"); + + /// + /// Gets the full path where the repository is cloned and where the build is run. + /// + /// + /// The full path where the repository is cloned and where the build is run. + /// + public string Directory => GetEnvironmentString("CI_PROJECT_DIR"); + } +} diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPullRequestInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPullRequestInfo.cs new file mode 100644 index 0000000000..ca346dab4a --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIPullRequestInfo.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provides GitLab CI pull request information for the current build. + /// + public sealed class GitLabCIPullRequestInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIPullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => Id > 0; + + /// + /// Gets the pull request id that GitLab CI uses internally. + /// + /// + /// The pull request id. + /// + public long Id => GetEnvironmentLong("CI_MERGE_REQUEST_ID"); + + /// + /// Gets the pull request id scoped to the project. + /// + /// + /// The pull request id. + /// + public long IId => GetEnvironmentLong("CI_MERGE_REQUEST_IID"); + + /// + /// Gets the source branch of the pull request. + /// + /// + /// The source branch of the pull request. + /// + public string SourceBranch => GetEnvironmentString("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"); + + /// + /// Gets the target branch of the pull request. + /// + /// + /// The target branch name of the pull request. + /// + public string TargetBranch => GetEnvironmentString("CI_MERGE_REQUEST_TARGET_BRANCH_NAME"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIRunnerInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIRunnerInfo.cs new file mode 100644 index 0000000000..96ecb676fe --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIRunnerInfo.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provides GitLab CI runner information for a current build. + /// + public sealed class GitLabCIRunnerInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIRunnerInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the unique id of runner being used. + /// + /// + /// The unique id of runner being used. + /// + public long Id => GetEnvironmentLong("CI_RUNNER_ID"); + + /// + /// Gets the description of the runner as saved in GitLab. + /// + /// + /// The description of the runner as saved in GitLab. + /// + public string Description => GetEnvironmentString("CI_RUNNER_DESCRIPTION"); + + /// + /// Gets an array of the defined runner tags. + /// + /// + /// The defined runner tags. + /// + public string[] Tags + { + get + { + var tags = GetEnvironmentString("CI_RUNNER_TAGS").Trim('[', ']').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < tags.Length; i++) + { + tags[i] = tags[i].Trim(' ', '"'); + } + return tags; + } + } + } +} diff --git a/src/Cake.Common/Build/GitLabCI/Data/GitLabCIServerInfo.cs b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIServerInfo.cs new file mode 100644 index 0000000000..f9fdf89f3b --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/Data/GitLabCIServerInfo.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI.Data +{ + /// + /// Provides GitLab CI server information for a current build. + /// + public sealed class GitLabCIServerInfo : GitLabCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GitLabCIServerInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the name of CI server that is used to coordinate builds. + /// + /// + /// The name of CI server that is used to coordinate builds. + /// + public string Name => GetEnvironmentString("CI_SERVER_NAME"); + + /// + /// Gets the GitLab version that is used to schedule builds. + /// + /// + /// The GitLab version that is used to schedule builds. + /// + public string Version => GetEnvironmentString("CI_SERVER_VERSION"); + + /// + /// Gets the GitLab revision that is used to schedule builds. + /// + /// + /// The GitLab revision that is used to schedule builds. + /// + public string Revision => GetEnvironmentString("CI_SERVER_REVISION"); + + /// + /// Gets the base URL of the GitLab instance, including protocol and port. + /// + /// + /// The base URL of the GitLab instance, including protocol and port. + /// + public string Url => GetEnvironmentString("CI_SERVER_URL"); + } +} diff --git a/src/Cake.Common/Build/GitLabCI/GitLabCICommands.cs b/src/Cake.Common/Build/GitLabCI/GitLabCICommands.cs new file mode 100644 index 0000000000..baf938b2db --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/GitLabCICommands.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitLabCI +{ + /// + /// Provides GitLab CI commands for a current build. + /// + public sealed class GitLabCICommands + { + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + public GitLabCICommands(IFileSystem fileSystem) + { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + } + + /// + /// Creates or updates an environment variable for any steps running next in a job. + /// + /// Path to env file. + /// The key. + /// The Value. + public void SetEnvironmentVariable(FilePath envPath, string key, string value) + { + ArgumentNullException.ThrowIfNull(envPath); + + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key)); + } + + ArgumentNullException.ThrowIfNull(value); + + var file = _fileSystem.GetFile(envPath); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(key); + writer.Write('='); + writer.WriteLine(value); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs b/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs new file mode 100644 index 0000000000..86036a6cc3 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/GitLabCIInfo.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Cake.Core; + +namespace Cake.Common.Build.GitLabCI +{ + /// + /// Base class used to provide information about the GitLab CI environment. + /// + public abstract class GitLabCIInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected GitLabCIInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The primary environment variable name. + /// The secondary environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string primaryVariable, string secondaryVariable) + { + return !string.IsNullOrEmpty(GetEnvironmentString(primaryVariable)) ? GetEnvironmentString(primaryVariable) : GetEnvironmentString(secondaryVariable); + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The primary environment variable name. + /// The secondary environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string primaryVariable, string secondaryVariable) + { + return GetEnvironmentInteger(primaryVariable) != 0 ? GetEnvironmentInteger(primaryVariable) : GetEnvironmentInteger(secondaryVariable); + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + + /// + /// Gets an environment variable as a . + /// + /// The primary environment variable name. + /// The secondary environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string primaryVariable, string secondaryVariable) + { + return GetEnvironmentBoolean(primaryVariable) ? GetEnvironmentBoolean(primaryVariable) : GetEnvironmentBoolean(secondaryVariable); + } + + /// + /// Gets an environment variable as an enum. + /// + /// + /// By default, the environment variable value is presumed to be identical to the enum value name. + /// To define a mapping between environment variable value and enum name, apply the attribue in the enum definition. + /// + /// Parsing is case-insensitive. + /// + /// + /// The primary environment variable name. + /// The type of the enum to return. + /// + /// The environment variable value converted to the corresponding value of or + /// null if the variable is not set or the value could not be converted to the the specified enum type. + /// + protected TEnum? GetEnvironmentEnum(string variable) where TEnum : struct, Enum + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + // Instead of using Enum.TryParse(), enumerate the enum fields using reflection. + // This defining enums where the environment variable value differs from the name of the corresponding enum member: + // - If the enum member has a [EnumMember] attribute, use the value defined by the attribute instead of the enum member name + // - Otherwise, use the enum member name (matching the behavior of Enum.TryParse()) + foreach (var field in typeof(TEnum).GetFields().Where(fi => fi.FieldType == typeof(TEnum))) + { + var enumMemberName = field.Name; + if (field.GetCustomAttribute() is { Value: { } customName } && !string.IsNullOrEmpty(customName)) + { + enumMemberName = customName; + } + + if (StringComparer.OrdinalIgnoreCase.Equals(enumMemberName, value)) + { + return (TEnum?)field.GetValue(null); + } + } + } + + return null; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected long GetEnvironmentLong(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + if (long.TryParse(value, out long result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The primary environment variable name. + /// The secondary environment variable name. + /// The environment variable. + protected long GetEnvironmentLong(string primaryVariable, string secondaryVariable) + { + return GetEnvironmentLong(primaryVariable) != 0 ? GetEnvironmentLong(primaryVariable) : GetEnvironmentLong(secondaryVariable); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GitLabCI/GitLabCIProvider.cs b/src/Cake.Common/Build/GitLabCI/GitLabCIProvider.cs new file mode 100644 index 0000000000..0f448b7c72 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/GitLabCIProvider.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.GitLabCI.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.GitLabCI +{ + /// + /// Responsible for communicating with GitLab CI. + /// + public class GitLabCIProvider : IGitLabCIProvider + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + public GitLabCIProvider(ICakeEnvironment environment, IFileSystem fileSystem) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new GitLabCIEnvironmentInfo(environment); + Commands = new GitLabCICommands(fileSystem); + } + + /// + public bool IsRunningOnGitLabCI => _environment.GetEnvironmentVariable("CI_SERVER")?.Equals("yes", StringComparison.OrdinalIgnoreCase) ?? false; + + /// + public GitLabCIEnvironmentInfo Environment { get; } + + /// + public GitLabCICommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/GitLabCI/IGitLabCIProvider.cs b/src/Cake.Common/Build/GitLabCI/IGitLabCIProvider.cs new file mode 100644 index 0000000000..bdabebc0e5 --- /dev/null +++ b/src/Cake.Common/Build/GitLabCI/IGitLabCIProvider.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GitLabCI.Data; + +namespace Cake.Common.Build.GitLabCI +{ + /// + /// Represents a GitLab CI provider. + /// + public interface IGitLabCIProvider + { + /// + /// Gets a value indicating whether the current build is running on GitLab CI. + /// + /// + /// true if the current build is running on GitLab CI; otherwise, false. + /// + bool IsRunningOnGitLabCI { get; } + + /// + /// Gets the GitLab CI environment. + /// + /// + /// The GitLab CI environment. + /// + GitLabCIEnvironmentInfo Environment { get; } + + /// + /// Gets the GitLab CI commands. + /// + /// + /// The GitLab CI commands. + /// + public GitLabCICommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDBuildCauseInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDBuildCauseInfo.cs new file mode 100644 index 0000000000..73b31ca22b --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDBuildCauseInfo.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// The Go.CD build cause. + /// + [DataContract] + public class GoCDBuildCauseInfo + { + /// + /// Gets or sets the approver. + /// + /// + /// The approver. + /// + [DataMember(Name = "approver")] + public string Approver { get; set; } + + /// + /// Gets or sets the material revisions. + /// + /// + /// The material revisions. + /// + [DataMember(Name = "material_revisions")] + public IEnumerable MaterialRevisions { get; set; } + + /// + /// Gets or sets a value indicating whether the trigger was forced. + /// + /// + /// true if the trigger was forced; otherwise, false. + /// + [DataMember(Name = "trigger_forced")] + public bool TriggerForced { get; set; } + + /// + /// Gets or sets the trigger message. + /// + /// + /// The trigger message. + /// + [DataMember(Name = "trigger_message")] + public string TriggerMessage { get; set; } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDEnvironmentInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDEnvironmentInfo.cs new file mode 100644 index 0000000000..53cc57e984 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDEnvironmentInfo.cs @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// Provides Go.CD environment information for a current build. + /// + public sealed class GoCDEnvironmentInfo : GoCDInfo + { + /// + /// Gets GoCD pipeline information. + /// + /// + /// The GoCD pipeline information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Pipeline: + /// Name: {0} + /// Counter: {1} + /// Label: {2}", + /// BuildSystem.GoCD.Environment.Pipeline.Name, + /// BuildSystem.GoCD.Environment.Pipeline.Counter, + /// BuildSystem.GoCD.Environment.Pipeline.Label + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Pipeline: + /// Name: {0} + /// Counter: {1} + /// Label: {2}", + /// GoCD.Environment.Pipeline.Name, + /// GoCD.Environment.Pipeline.Counter, + /// GoCD.Environment.Pipeline.Label + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public GoCDPipelineInfo Pipeline { get; } + + /// + /// Gets GoCD stage information. + /// + /// + /// The GoCD stage information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Stage: + /// Name: {0} + /// Counter: {1}", + /// BuildSystem.GoCD.Environment.Stage.Name, + /// BuildSystem.GoCD.Environment.Stage.Counter + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Stage: + /// Name: {0} + /// Counter: {1}", + /// GoCD.Environment.Stage.Name, + /// GoCD.Environment.Stage.Counter + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public GoCDStageInfo Stage { get; } + + /// + /// Gets GoCD repository information. + /// + /// + /// The GoCD repository information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Repository: + /// Revision: {0} + /// ToRevision: {1} + /// FromRevision: {2}", + /// BuildSystem.GoCD.Environment.Repository.Revision, + /// BuildSystem.GoCD.Environment.Repository.ToRevision, + /// BuildSystem.GoCD.Environment.Repository.FromRevision + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"Repository: + /// Revision: {0} + /// ToRevision: {1} + /// FromRevision: {2}", + /// GoCD.Environment.Repository.Revision, + /// GoCD.Environment.Repository.ToRevision, + /// GoCD.Environment.Repository.FromRevision + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public GoCDRepositoryInfo Repository { get; } + + /// + /// Gets the Go.CD URL. + /// + /// + /// The Go.CD URL. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"GoCDUrl: {0}", + /// BuildSystem.GoCD.Environment.GoCDUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"GoCDUrl: {0}", + /// GoCD.Environment.GoCDUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public string GoCDUrl => GetEnvironmentString("GO_SERVER_URL"); + + /// + /// Gets the environment name. This is only set if the environment is specified. Otherwise the variable is not set. + /// + /// + /// The environment name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"EnvironmentName: {0}", + /// BuildSystem.GoCD.Environment.EnvironmentName + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"EnvironmentName: {0}", + /// GoCD.Environment.EnvironmentName + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public string EnvironmentName => GetEnvironmentString("GO_ENVIRONMENT_NAME"); + + /// + /// Gets the name of the current job being run. + /// + /// + /// The job name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"JobName: {0}", + /// BuildSystem.GoCD.Environment.JobName + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"JobName: {0}", + /// GoCD.Environment.JobName + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public string JobName => GetEnvironmentString("GO_JOB_NAME"); + + /// + /// Gets the username of the user that triggered the build. This will have one of three possible values: + /// anonymous - if there is no security. + /// username of the user, who triggered the build. + /// changes, if SCM changes auto-triggered the build. + /// timer, if the pipeline is triggered at a scheduled time. + /// + /// + /// The username of the user that triggered the build. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"User: {0}", + /// BuildSystem.GoCD.Environment.User + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + /// Via GoCD. + /// + /// + /// if (GoCD.IsRunningOnGoCD) + /// { + /// Information( + /// @"User: {0}", + /// GoCD.Environment.User + /// ); + /// } + /// else + /// { + /// Information("Not running on GoCD"); + /// } + /// + /// + public string User => GetEnvironmentString("GO_TRIGGER_USER"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GoCDEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Pipeline = new GoCDPipelineInfo(environment); + Stage = new GoCDStageInfo(environment); + Repository = new GoCDRepositoryInfo(environment); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDHistoryInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDHistoryInfo.cs new file mode 100644 index 0000000000..73a123c51d --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDHistoryInfo.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// The Go.CD history. + /// + [DataContract] + public class GoCDHistoryInfo + { + /// + /// Gets or sets the pipelines. + /// + /// + /// The pipelines. + /// + [DataMember(Name = "pipelines")] + public IEnumerable Pipelines { get; set; } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDMaterialRevisionsInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDMaterialRevisionsInfo.cs new file mode 100644 index 0000000000..b08817af5a --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDMaterialRevisionsInfo.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// The Go.CD material revision information. + /// + [DataContract] + public class GoCDMaterialRevisionsInfo + { + /// + /// Gets or sets a value indicating whether a change was made. + /// + /// + /// true if changed; otherwise, false. + /// + [DataMember(Name = "changed")] + public bool Changed { get; set; } + + /// + /// Gets or sets the modifications. + /// + /// + /// The modifications. + /// + [DataMember(Name = "modifications")] + public IEnumerable Modifications { get; set; } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDModificationInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDModificationInfo.cs new file mode 100644 index 0000000000..83b97f41bb --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDModificationInfo.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// A change made in the repository since the last time the Go.CD pipeline was run. + /// + [DataContract] + public class GoCDModificationInfo + { + /// + /// Gets or sets the email address. + /// + /// + /// The email address. + /// + [DataMember(Name = "email_address")] + public string EmailAddress { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + [DataMember(Name = "id")] + public int Id { get; set; } + + /// + /// Gets or sets the modified time in milliseconds from the Unix epoch. + /// + /// + /// The modified time in milliseconds from the Unix epoch. + /// + [DataMember(Name = "modified_time")] + public long ModifiedTimeUnixMilliseconds { get; set; } + + /// + /// Gets or sets the modified time. + /// + /// + /// The modified time. + /// + public DateTime ModifiedTime + { + get { return FromUnixTimeMilliseconds(ModifiedTimeUnixMilliseconds); } + set { ModifiedTimeUnixMilliseconds = ToUnixTimeMilliseconds(value); } + } + + /// + /// Gets or sets the username. + /// + /// + /// The username. + /// + [DataMember(Name = "user_name")] + public string Username { get; set; } + + /// + /// Gets or sets the comment. + /// + /// + /// The comment. + /// + [DataMember(Name = "comment")] + public string Comment { get; set; } + + /// + /// Gets or sets the revision. + /// + /// + /// The revision. + /// + [DataMember(Name = "revision")] + public string Revision { get; set; } + + private static DateTime FromUnixTimeMilliseconds(long milliseconds) + { + if ((milliseconds < -62135596800000L) || (milliseconds > 0xe677d21fdbffL)) + { + throw new ArgumentOutOfRangeException(nameof(milliseconds)); + } + + return new DateTime((milliseconds * 0x2710L) + 0x89f7ff5f7b58000L); + } + + private static long ToUnixTimeMilliseconds(DateTime dateTime) + { + long num = dateTime.Ticks / 0x2710L; + return num - 0x3883122cd800L; + } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDPipelineHistoryInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDPipelineHistoryInfo.cs new file mode 100644 index 0000000000..f8837b1c25 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDPipelineHistoryInfo.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// The Go.CD pipeline history. + /// + [DataContract] + public class GoCDPipelineHistoryInfo + { + /// + /// Gets or sets the build cause. + /// + /// + /// The build cause. + /// + [DataMember(Name = "build_cause")] + public GoCDBuildCauseInfo BuildCause { get; set; } + + /// + /// Gets or sets the comment. + /// + /// + /// The comment. + /// + [DataMember(Name = "comment")] + public string Comment { get; set; } + + /// + /// Gets or sets the name. + /// + /// + /// The name. + /// + [DataMember(Name = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the natural order. + /// + /// + /// The natural order. + /// + [DataMember(Name = "natural_order")] + public string NaturalOrder { get; set; } + } +} diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDPipelineInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDPipelineInfo.cs new file mode 100644 index 0000000000..11218db469 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDPipelineInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// Provides GoCD pipeline information for a current build. + /// + public sealed class GoCDPipelineInfo : GoCDInfo + { + /// + /// Gets the name of the pipeline. + /// + /// + /// The name of the pipeline. + /// + public string Name => GetEnvironmentString("GO_PIPELINE_NAME"); + + /// + /// Gets the pipeline counter, showing how many times the current pipeline has been run. + /// + /// + /// The pipeline counter. + /// + public int Counter => GetEnvironmentInteger("GO_PIPELINE_COUNTER"); + + /// + /// Gets the pipeline label. By default, this is set to the pipeline count. + /// + /// + /// The pipeline label. + /// + public string Label => GetEnvironmentString("GO_PIPELINE_LABEL"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GoCDPipelineInfo(ICakeEnvironment environment) + : base(environment) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDRepositoryInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDRepositoryInfo.cs new file mode 100644 index 0000000000..aff82b63b6 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDRepositoryInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// Provides Go.CD repository information for a current build. + /// + public sealed class GoCDRepositoryInfo : GoCDInfo + { + /// + /// Gets the source control revision. + /// + /// + /// The the source control revision. + /// + public string Revision => GetEnvironmentString("GO_REVISION"); + + /// + /// Gets the last revision the build was triggered by if there were multiple revisions. + /// + /// + /// The last revision the build was triggered by if there were multiple revisions. + /// + public string ToRevision => GetEnvironmentString("GO_TO_REVISION"); + + /// + /// Gets the first revision the build was triggered by if there were multiple revisions. + /// + /// + /// The first revision the build was triggered by if there were multiple revisions. + /// + public string FromRevision => GetEnvironmentString("GO_FROM_REVISION"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GoCDRepositoryInfo(ICakeEnvironment environment) + : base(environment) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GoCD/Data/GoCDStageInfo.cs b/src/Cake.Common/Build/GoCD/Data/GoCDStageInfo.cs new file mode 100644 index 0000000000..2be00b2eb1 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/Data/GoCDStageInfo.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.GoCD.Data +{ + /// + /// Provides Go.CD commit information for a current build. + /// + public sealed class GoCDStageInfo : GoCDInfo + { + /// + /// Gets the name of the current stage being run. + /// + /// + /// The stage name. + /// + public string Name => GetEnvironmentString("GO_STAGE_NAME"); + + /// + /// Gets the count of the number of times the current stage has been run. + /// + /// + /// The stage counter. + /// + public int Counter => GetEnvironmentInteger("GO_STAGE_COUNTER"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public GoCDStageInfo(ICakeEnvironment environment) + : base(environment) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GoCD/GoCDInfo.cs b/src/Cake.Common/Build/GoCD/GoCDInfo.cs new file mode 100644 index 0000000000..78239826be --- /dev/null +++ b/src/Cake.Common/Build/GoCD/GoCDInfo.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.GoCD +{ + /// + /// Base class used to provide information about the Go.CD environment. + /// + public abstract class GoCDInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected GoCDInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/GoCD/GoCDProvider.cs b/src/Cake.Common/Build/GoCD/GoCDProvider.cs new file mode 100644 index 0000000000..104669275f --- /dev/null +++ b/src/Cake.Common/Build/GoCD/GoCDProvider.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using Cake.Common.Build.GoCD.Data; +using Cake.Core; +using Cake.Core.Diagnostics; + +namespace Cake.Common.Build.GoCD +{ + /// + /// Responsible for communicating with GoCD. + /// + public sealed class GoCDProvider : IGoCDProvider + { + private readonly ICakeEnvironment _environment; + private readonly ICakeLog _log; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The cake log. + public GoCDProvider(ICakeEnvironment environment, ICakeLog log) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _log = log ?? throw new ArgumentNullException(nameof(log)); + Environment = new GoCDEnvironmentInfo(environment); + } + + /// + public bool IsRunningOnGoCD => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("GO_SERVER_URL")); + + /// + public GoCDEnvironmentInfo Environment { get; } + + /// + public GoCDHistoryInfo GetHistory(string username, string password) + { + return GetHistory(username, password, Environment.GoCDUrl); + } + + /// + public GoCDHistoryInfo GetHistory(string username, string password, string serverUrl) + { + ArgumentNullException.ThrowIfNull(username); + ArgumentNullException.ThrowIfNull(password); + ArgumentNullException.ThrowIfNull(serverUrl); + + if (!IsRunningOnGoCD) + { + throw new CakeException("The current build is not running on Go.CD."); + } + + var url = new Uri(string.Format( + CultureInfo.InvariantCulture, + "{0}/go/api/pipelines/{1}/history/0", + serverUrl, + Environment.Pipeline.Name).ToLowerInvariant()); + + _log.Write(Verbosity.Diagnostic, LogLevel.Verbose, "Getting [{0}]", url); + return Task.Run(async () => + { + var encodedCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes( + string.Format(CultureInfo.InvariantCulture, "{0}:{1}", username, password))); + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Add( + "Authorization", + string.Format(CultureInfo.InvariantCulture, "Basic {0}", encodedCredentials)); + var response = await client.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + _log.Write(Verbosity.Diagnostic, LogLevel.Verbose, "Server response [{0}:{1}]:\n\r{2}", response.StatusCode, response.ReasonPhrase, content); + + var jsonSerializer = new DataContractJsonSerializer(typeof(GoCDHistoryInfo)); + + using (var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + return jsonSerializer.ReadObject(jsonStream) as GoCDHistoryInfo; + } + } + }).GetAwaiter().GetResult(); + } + } +} diff --git a/src/Cake.Common/Build/GoCD/IGoCDProvider.cs b/src/Cake.Common/Build/GoCD/IGoCDProvider.cs new file mode 100644 index 0000000000..e8105f5a41 --- /dev/null +++ b/src/Cake.Common/Build/GoCD/IGoCDProvider.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.GoCD.Data; + +namespace Cake.Common.Build.GoCD +{ + /// + /// Represents a Go.CD provider. + /// + public interface IGoCDProvider + { + /// + /// Gets a value indicating whether the current build is running on Go.CD. + /// + /// + /// true if the current build is running on Go.CD; otherwise, false. + /// + bool IsRunningOnGoCD { get; } + + /// + /// Gets the Go.CD environment. + /// + /// + /// The Go.CD environment. + /// + GoCDEnvironmentInfo Environment { get; } + + /// + /// Gets the Go.CD build history, including the repository modifications that caused the pipeline to start. + /// + /// The Go.CD username. + /// The Go.CD password. + /// The Go.CD build history. + GoCDHistoryInfo GetHistory(string username, string password); + + /// + /// Gets the Go.CD build history, including the repository modifications that caused the pipeline to start. + /// + /// The Go.CD username. + /// The Go.CD password. + /// The Go.CD server URL. + /// The Go.CD build history. + GoCDHistoryInfo GetHistory(string username, string password, string serverUrl); + } +} diff --git a/src/Cake.Common/Build/IBuildSystemServiceMessageWriter.cs b/src/Cake.Common/Build/IBuildSystemServiceMessageWriter.cs new file mode 100644 index 0000000000..f0ebcfaad8 --- /dev/null +++ b/src/Cake.Common/Build/IBuildSystemServiceMessageWriter.cs @@ -0,0 +1,26 @@ +using System; + +namespace Cake.Common.Build +{ + /// + /// Represents build system output. + /// + public interface IBuildSystemServiceMessageWriter + { + /// + /// Writes a message to the build system output. + /// + /// A composite format string. + /// An array of objects to write using format. + void Write(string format, params object[] args); + } + + internal sealed class BuildSystemServiceMessageWriter : IBuildSystemServiceMessageWriter + { + public void Write(string format, params object[] args) + { + Console.Out.WriteLine(format, args); + Console.Out.Flush(); + } + } +} diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsBuildInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsBuildInfo.cs index 32a1f9ee09..16cce3043f 100644 --- a/src/Cake.Common/Build/Jenkins/Data/JenkinsBuildInfo.cs +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsBuildInfo.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Jenkins.Data { /// - /// Provides Jenkins build information for a current build. + /// Provides Jenkins build information for the current build. /// public sealed class JenkinsBuildInfo : JenkinsInfo { @@ -16,21 +17,15 @@ public sealed class JenkinsBuildInfo : JenkinsInfo /// /// The build number. /// - public int BuildNumber - { - get { return GetEnvironmentInteger("BUILD_NUMBER"); } - } + public int BuildNumber => GetEnvironmentInteger("BUILD_NUMBER"); /// - /// Gets the build identifier. + /// Gets the build identifier which is identical to starting from Jenkins 1.597 and a YYYY-MM-DD_hh-mm-ss timestamp for older builds. /// /// /// The build identifier. /// - public string BuildId - { - get { return GetEnvironmentString("BUILD_ID"); } - } + public string BuildId => GetEnvironmentString("BUILD_ID"); /// /// Gets the display name of the build. @@ -38,21 +33,15 @@ public string BuildId /// /// The display name of the build. /// - public string BuildDisplayName - { - get { return GetEnvironmentString("BUILD_DISPLAY_NAME"); } - } + public string BuildDisplayName => GetEnvironmentString("BUILD_DISPLAY_NAME"); /// - /// Gets the build URL. + /// Gets the build tag which is a string of "jenkins-${JOB_NAME}-${BUILD_NUMBER}". All forward slashes (/) in the JOB_NAME are replaced with dashes (-). /// /// - /// The build URL. + /// The build tag. /// - public string BuildTag - { - get { return GetEnvironmentString("BUILD_TAG"); } - } + public string BuildTag => GetEnvironmentString("BUILD_TAG"); /// /// Gets the build URL. @@ -60,10 +49,7 @@ public string BuildTag /// /// The build URL. /// - public string BuildUrl - { - get { return GetEnvironmentString("BUILD_URL"); } - } + public string BuildUrl => GetEnvironmentString("BUILD_URL"); /// /// Gets the executor number. @@ -71,10 +57,15 @@ public string BuildUrl /// /// The executor number. /// - public int ExecutorNumber - { - get { return GetEnvironmentInteger("EXECUTOR_NUMBER"); } - } + public int ExecutorNumber => GetEnvironmentInteger("EXECUTOR_NUMBER"); + + /// + /// Gets the absolute path of the workspace directory assigned to the build. + /// + /// + /// The workspace directory path. + /// + public string Workspace => GetEnvironmentString("WORKSPACE"); /// /// Initializes a new instance of the class. @@ -84,4 +75,4 @@ public JenkinsBuildInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsChangeInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsChangeInfo.cs new file mode 100644 index 0000000000..9d1d7ff298 --- /dev/null +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsChangeInfo.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Jenkins.Data +{ + /// + /// Provides Jenkins change information for the current build in multibranch projects. + /// + public class JenkinsChangeInfo : JenkinsInfo + { + /// + /// Gets the change id. + /// + /// + /// The changed id such as a pull request number. + /// + public string Id => GetEnvironmentString("CHANGE_ID"); + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => !string.IsNullOrWhiteSpace(Id); + + /// + /// Gets the change URL. + /// + /// + /// The change URL. + /// + public string Url => GetEnvironmentString("CHANGE_URL"); + + /// + /// Gets the change title. + /// + /// + /// The change title. + /// + public string Title => GetEnvironmentString("CHANGE_TITLE"); + + /// + /// Gets change author. + /// + /// + /// The change author. + /// + public string Author => GetEnvironmentString("CHANGE_AUTHOR"); + + /// + /// Gets the human name of the change author. + /// + /// + /// The human name of the change author. + /// + public string AuthorDisplayName => GetEnvironmentString("CHANGE_AUTHOR_DISPLAY_NAME"); + + /// + /// Gets the email address of the change author. + /// + /// + /// The email address of the change author. + /// + public string AuthorEmail => GetEnvironmentString("CHANGE_AUTHOR_EMAIL"); + + /// + /// Gets the target/base branch of the change. + /// + /// + /// The target of the change. + /// + public string Target => GetEnvironmentString("CHANGE_TARGET"); + + /// + /// Gets the origin branch of the change. + /// + /// + /// The origin branch of the change. + /// + public string Branch => GetEnvironmentString("CHANGE_BRANCH"); + + /// + /// Gets the fork name of the change. + /// + /// + /// The fork name of the change. + /// + public string Fork => GetEnvironmentString("CHANGE_FORK"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public JenkinsChangeInfo(ICakeEnvironment environment) : base(environment) + { + } + } +} diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsEnvironmentInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsEnvironmentInfo.cs index 7177747d09..971894f317 100644 --- a/src/Cake.Common/Build/Jenkins/Data/JenkinsEnvironmentInfo.cs +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Jenkins.Data @@ -10,21 +11,55 @@ namespace Cake.Common.Build.Jenkins.Data /// public sealed class JenkinsEnvironmentInfo : JenkinsInfo { - private readonly JenkinsBuildInfo _buildProvider; - private readonly JenkinsRepositoryInfo _repositoryProvider; - private readonly JenkinsNodeInfo _nodeProvider; - private readonly JenkinsJobInfo _jobProvider; - /// /// Gets Jenkins build information. /// /// /// The build. /// - public JenkinsBuildInfo Build - { - get { return _buildProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Build: + /// BuildNumber: {0} + /// BuildId: {1} + /// BuildDisplayName: {2}", + /// BuildSystem.Jenkins.Environment.Build.BuildNumber, + /// BuildSystem.Jenkins.Environment.Build.BuildId, + /// BuildSystem.Jenkins.Environment.Build.BuildDisplayName + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Build: + /// BuildNumber: {0} + /// BuildId: {1} + /// BuildDisplayName: {2}", + /// Jenkins.Environment.Build.BuildNumber, + /// Jenkins.Environment.Build.BuildId, + /// Jenkins.Environment.Build.BuildDisplayName + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public JenkinsBuildInfo Build { get; } /// /// Gets Jenkins repository information. @@ -32,10 +67,49 @@ public JenkinsBuildInfo Build /// /// The repository. /// - public JenkinsRepositoryInfo Repository - { - get { return _repositoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Repository: + /// BranchName: {0} + /// GitCommitSha: {1} + /// GitBranch: {2}", + /// BuildSystem.Jenkins.Environment.Repository.BranchName, + /// BuildSystem.Jenkins.Environment.Repository.GitCommitSha, + /// BuildSystem.Jenkins.Environment.Repository.GitBranch + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Repository: + /// BranchName: {0} + /// GitCommitSha: {1} + /// GitBranch: {2}", + /// Jenkins.Environment.Repository.BranchName, + /// Jenkins.Environment.Repository.GitCommitSha, + /// Jenkins.Environment.Repository.GitBranch + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public JenkinsRepositoryInfo Repository { get; } /// /// Gets Jenkins job information. @@ -43,10 +117,49 @@ public JenkinsRepositoryInfo Repository /// /// The job. /// - public JenkinsJobInfo Job - { - get { return _jobProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Job: + /// JobName: {0} + /// JobBaseName: {1} + /// JobUrl: {2}", + /// BuildSystem.Jenkins.Environment.Job.JobName, + /// BuildSystem.Jenkins.Environment.Job.JobBaseName, + /// BuildSystem.Jenkins.Environment.Job.JobUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Job: + /// JobName: {0} + /// JobBaseName: {1} + /// JobUrl: {2}", + /// Jenkins.Environment.Job.JobName, + /// Jenkins.Environment.Job.JobBaseName, + /// Jenkins.Environment.Job.JobUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public JenkinsJobInfo Job { get; } /// /// Gets the node. @@ -54,54 +167,175 @@ public JenkinsJobInfo Job /// /// The node. /// - public JenkinsNodeInfo Node - { - get { return _nodeProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Node: + /// NodeName: {0} + /// NodeLabels: {1}", + /// BuildSystem.Jenkins.Environment.Node.NodeName, + /// BuildSystem.Jenkins.Environment.Node.NodeLabels + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Node: + /// NodeName: {0} + /// NodeLabels: {1}", + /// Jenkins.Environment.Node.NodeName, + /// Jenkins.Environment.Node.NodeLabels + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public JenkinsNodeInfo Node { get; } /// - /// Gets the jenkins home. + /// Gets the change. /// /// - /// The jenkins home. + /// The change. /// - public string JenkinsHome - { - get { return GetEnvironmentString("JENKINS_HOME"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Change: + /// Id: {0} + /// Url: {1} + /// Title: {2}", + /// BuildSystem.Jenkins.Environment.Change.Id, + /// BuildSystem.Jenkins.Environment.Change.Url, + /// BuildSystem.Jenkins.Environment.Change.Title + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"Change: + /// Id: {0} + /// Url: {1} + /// Title: {2}", + /// Jenkins.Environment.Change.Id, + /// Jenkins.Environment.Change.Url, + /// Jenkins.Environment.Change.Title + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public JenkinsChangeInfo Change { get; } /// - /// Gets the jenkins URL. + /// Gets the absolute path of the directory assigned on the master node for Jenkins to store data. /// /// - /// The jenkins URL. + /// The absolute path of the directory assigned on the master node for Jenkins to store data. /// - public string JenkinsUrl - { - get { return GetEnvironmentString("JENKINS_URL"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"JenkinsHome: {0}", + /// BuildSystem.Jenkins.Environment.JenkinsHome + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"JenkinsHome: {0}", + /// Jenkins.Environment.JenkinsHome + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public string JenkinsHome => GetEnvironmentString("JENKINS_HOME"); /// - /// Gets the executor number. + /// Gets the full URL of Jenkins (note: only available if Jenkins URL is set in system configuration). /// /// - /// The executor number. + /// The full URL of Jenkins. /// - public int ExecutorNumber - { - get { return GetEnvironmentInteger("EXECUTOR_NUMBER"); } - } - - /// - /// Gets the workspace. - /// - /// - /// The workspace. - /// - public string Workspace - { - get { return GetEnvironmentString("WORKSPACE"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"JenkinsUrl: {0}", + /// BuildSystem.Jenkins.Environment.JenkinsUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + /// Via Jenkins. + /// + /// + /// if (Jenkins.IsRunningOnJenkins) + /// { + /// Information( + /// @"JenkinsUrl: {0}", + /// Jenkins.Environment.JenkinsUrl + /// ); + /// } + /// else + /// { + /// Information("Not running on Jenkins"); + /// } + /// + /// + public string JenkinsUrl => GetEnvironmentString("JENKINS_URL"); /// /// Initializes a new instance of the class. @@ -109,10 +343,11 @@ public string Workspace /// The environment. public JenkinsEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _buildProvider = new JenkinsBuildInfo(environment); - _repositoryProvider = new JenkinsRepositoryInfo(environment); - _nodeProvider = new JenkinsNodeInfo(environment); - _jobProvider = new JenkinsJobInfo(environment); + Build = new JenkinsBuildInfo(environment); + Repository = new JenkinsRepositoryInfo(environment); + Node = new JenkinsNodeInfo(environment); + Job = new JenkinsJobInfo(environment); + Change = new JenkinsChangeInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsJobInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsJobInfo.cs index ce5df95a28..4a70f56635 100644 --- a/src/Cake.Common/Build/Jenkins/Data/JenkinsJobInfo.cs +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsJobInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Jenkins.Data @@ -16,21 +17,23 @@ public class JenkinsJobInfo : JenkinsInfo /// /// The name of the job. /// - public string JobName - { - get { return GetEnvironmentString("JOB_NAME"); } - } + public string JobName => GetEnvironmentString("JOB_NAME"); /// - /// Gets the name of the job. + /// Gets the short name of the project of this build stripping off folder paths, such as "foo" for "bar/foo". /// /// - /// The name of the job. + /// The base name of the job. /// - public string JobUrl - { - get { return GetEnvironmentString("JOB_URL"); } - } + public string JobBaseName => GetEnvironmentString("JOB_BASE_NAME"); + + /// + /// Gets the URL of the job. + /// + /// + /// The URL of the job. + /// + public string JobUrl => GetEnvironmentString("JOB_URL"); /// /// Initializes a new instance of the class. @@ -40,4 +43,4 @@ public JenkinsJobInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsNodeInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsNodeInfo.cs index 53648560c7..82ae0ddd35 100644 --- a/src/Cake.Common/Build/Jenkins/Data/JenkinsNodeInfo.cs +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsNodeInfo.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Core; namespace Cake.Common.Build.Jenkins.Data @@ -16,10 +18,7 @@ public class JenkinsNodeInfo : JenkinsInfo /// /// The name of the node. /// - public string NodeName - { - get { return GetEnvironmentString("NODE_NAME"); } - } + public string NodeName => GetEnvironmentString("NODE_NAME"); /// /// Gets the node labels. @@ -27,10 +26,7 @@ public string NodeName /// /// The node labels. /// - public string NodeLabels - { - get { return GetEnvironmentString("NODE_LABELS"); } - } + public string[] NodeLabels => GetEnvironmentString("NODE_LABELS").Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); /// /// Initializes a new instance of the class. @@ -40,4 +36,4 @@ public JenkinsNodeInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/Data/JenkinsRepositoryInfo.cs b/src/Cake.Common/Build/Jenkins/Data/JenkinsRepositoryInfo.cs index 7bb01323aa..803aba498c 100644 --- a/src/Cake.Common/Build/Jenkins/Data/JenkinsRepositoryInfo.cs +++ b/src/Cake.Common/Build/Jenkins/Data/JenkinsRepositoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.Jenkins.Data @@ -11,26 +12,68 @@ namespace Cake.Common.Build.Jenkins.Data public sealed class JenkinsRepositoryInfo : JenkinsInfo { /// - /// Gets the git commit sha. + /// Gets the branch name which will be build in a multibranch project. /// /// - /// The git commit sha. + /// The branch name. /// - public string GitCommitSha - { - get { return GetEnvironmentString("GIT_COMMIT"); } - } + public string BranchName => GetEnvironmentString("BRANCH_NAME"); /// - /// Gets the git branch. + /// Gets the Git commit sha. /// /// - /// The git branch. + /// The Git commit sha. /// - public string GitBranch - { - get { return GetEnvironmentString("GIT_BRANCH"); } - } + public string GitCommitSha => GetEnvironmentString("GIT_COMMIT"); + + /// + /// Gets the previous Git commit sha. + /// + /// + /// The previous Git commit sha. + /// + public string GitPreviousCommitSha => GetEnvironmentString("GIT_PREVIOUS_COMMIT"); + + /// + /// Gets the previous successfull Git commit sha. + /// + /// + /// The previous successfull Git commit sha. + /// + public string GitPreviousSuccessfullCommitSha => GetEnvironmentString("GIT_PREVIOUS_SUCCESSFUL_COMMIT"); + + /// + /// Gets the Git branch. + /// + /// + /// The Git branch. + /// + public string GitBranch => GetEnvironmentString("GIT_BRANCH"); + + /// + /// Gets the Git local branch. + /// + /// + /// The Git local branch. + /// + public string GitLocalBranch => GetEnvironmentString("GIT_LOCAL_BRANCH"); + + /// + /// Gets the Git checkout directory. + /// + /// + /// The Git checkout directory. + /// + public string GitCheckoutDirectory => GetEnvironmentString("GIT_CHECKOUT_DIR"); + + /// + /// Gets the Git remote URL. + /// + /// + /// The Git remote URL. + /// + public string GitUrl => GetEnvironmentString("GIT_URL"); /// /// Gets the SVN revision. @@ -38,10 +81,7 @@ public string GitBranch /// /// The SVN revision. /// - public string SvnRevision - { - get { return GetEnvironmentString("SVN_REVISION"); } - } + public string SvnRevision => GetEnvironmentString("SVN_REVISION"); /// /// Gets the CVS branch. @@ -49,10 +89,7 @@ public string SvnRevision /// /// The CVS branch. /// - public string CvsBranch - { - get { return GetEnvironmentString("CVS_BRANCH"); } - } + public string CvsBranch => GetEnvironmentString("CVS_BRANCH"); /// /// Gets the SVN URL. @@ -60,13 +97,47 @@ public string CvsBranch /// /// The SVN URL. /// - public string SvnUrl - { - get - { - return GetEnvironmentString("SVN_URL"); - } - } + public string SvnUrl => GetEnvironmentString("SVN_URL"); + + /// + /// Gets the full id of the Mercurial revision. + /// + /// + /// The full id of the Mercurial revision. + /// + public string MercurialRevision => GetEnvironmentString("MERCURIAL_REVISION"); + + /// + /// Gets the abbreviated id of the Mercurial revision. + /// + /// + /// The abbreviated id of the Mercurial revision. + /// + public string MercurialRevisionShort => GetEnvironmentString("MERCURIAL_REVISION_SHORT"); + + /// + /// Gets the Mercurial revision number. + /// + /// + /// The Mercurial revision number. + /// + public string MercurialRevisionNumber => GetEnvironmentString("MERCURIAL_REVISION_NUMBER"); + + /// + /// Gets the Mercurial revision branch. + /// + /// + /// The Mercurial revision branch. + /// + public string MercurialRevisionBranch => GetEnvironmentString("MERCURIAL_REVISION_BRANCH"); + + /// + /// Gets the Mercurial repository URL. + /// + /// + /// The Mercurial repository URL. + /// + public string MercurialRepositoryUrl => GetEnvironmentString("MERCURIAL_REPOSITORY_URL"); /// /// Initializes a new instance of the class. @@ -76,4 +147,4 @@ public JenkinsRepositoryInfo(ICakeEnvironment environment) : base(environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/IJenkinsProvider.cs b/src/Cake.Common/Build/Jenkins/IJenkinsProvider.cs index a8d83b121d..de32f037ac 100644 --- a/src/Cake.Common/Build/Jenkins/IJenkinsProvider.cs +++ b/src/Cake.Common/Build/Jenkins/IJenkinsProvider.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.Jenkins.Data; namespace Cake.Common.Build.Jenkins { /// - /// Represnts a Jenkins Provider + /// Represents a Jenkins Provider. /// public interface IJenkinsProvider { @@ -19,11 +20,11 @@ public interface IJenkinsProvider bool IsRunningOnJenkins { get; } /// - /// Gets the Bamboo environment. + /// Gets the Jenkins environment. /// /// - /// The Bamboo environment. + /// The Jenkins environment. /// JenkinsEnvironmentInfo Environment { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/JenkinsInfo.cs b/src/Cake.Common/Build/Jenkins/JenkinsInfo.cs index a04e13afa5..ef1e61e2ad 100644 --- a/src/Cake.Common/Build/Jenkins/JenkinsInfo.cs +++ b/src/Cake.Common/Build/Jenkins/JenkinsInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; @@ -66,4 +67,4 @@ protected bool GetEnvironmentBoolean(string variable) return false; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Jenkins/JenkinsProvider.cs b/src/Cake.Common/Build/Jenkins/JenkinsProvider.cs index 5df4cdc28c..b59853e1c1 100644 --- a/src/Cake.Common/Build/Jenkins/JenkinsProvider.cs +++ b/src/Cake.Common/Build/Jenkins/JenkinsProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Build.Jenkins.Data; using Cake.Core; @@ -13,7 +14,6 @@ namespace Cake.Common.Build.Jenkins public sealed class JenkinsProvider : IJenkinsProvider { private readonly ICakeEnvironment _environment; - private readonly JenkinsEnvironmentInfo _jenkinsEnvironment; /// /// Initializes a new instance of the class. @@ -21,35 +21,14 @@ public sealed class JenkinsProvider : IJenkinsProvider /// The environment. public JenkinsProvider(ICakeEnvironment environment) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - - _environment = environment; - _jenkinsEnvironment = new JenkinsEnvironmentInfo(_environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new JenkinsEnvironmentInfo(_environment); } - /// - /// Gets a value indicating whether this instance is running on jenkins. - /// - /// - /// true if this instance is running on jenkins; otherwise, false. - /// - public bool IsRunningOnJenkins - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("JENKINS_URL")); } - } + /// + public bool IsRunningOnJenkins => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("JENKINS_URL")); - /// - /// Gets the Jenkins environment. - /// - /// - /// The Jenkins environment. - /// - public JenkinsEnvironmentInfo Environment - { - get { return _jenkinsEnvironment; } - } + /// + public JenkinsEnvironmentInfo Environment { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/MyGet/IMyGetProvider.cs b/src/Cake.Common/Build/MyGet/IMyGetProvider.cs index 55260e4560..2c69e6a63a 100644 --- a/src/Cake.Common/Build/MyGet/IMyGetProvider.cs +++ b/src/Cake.Common/Build/MyGet/IMyGetProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Build.MyGet { /// @@ -43,4 +44,4 @@ public interface IMyGetProvider /// The required build number. void SetBuildNumber(string buildNumber); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/MyGet/MyGetBuildStatus.cs b/src/Cake.Common/Build/MyGet/MyGetBuildStatus.cs index f267fc77a0..ea51888ec7 100644 --- a/src/Cake.Common/Build/MyGet/MyGetBuildStatus.cs +++ b/src/Cake.Common/Build/MyGet/MyGetBuildStatus.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Build.MyGet { /// - /// Provides the known values for MyGet Build Status + /// Provides the known values for MyGet Build Status. /// public enum MyGetBuildStatus { @@ -28,4 +29,4 @@ public enum MyGetBuildStatus /// Normal } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/MyGet/MyGetProvider.cs b/src/Cake.Common/Build/MyGet/MyGetProvider.cs index b0f81159de..9610b7f92b 100644 --- a/src/Cake.Common/Build/MyGet/MyGetProvider.cs +++ b/src/Cake.Common/Build/MyGet/MyGetProvider.cs @@ -1,11 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Text; using Cake.Core; namespace Cake.Common.Build.MyGet @@ -17,42 +19,40 @@ public sealed class MyGetProvider : IMyGetProvider { private const string MessagePrefix = "##myget["; private const string MessagePostfix = "]"; - private static readonly Dictionary _sanitizationTokens; + + private static readonly Dictionary _sanitizationTokens; + private static readonly char[] _specialCharacters; + private readonly ICakeEnvironment _environment; + private readonly IBuildSystemServiceMessageWriter _writer; static MyGetProvider() { - _sanitizationTokens = new Dictionary + _sanitizationTokens = new Dictionary { - { "|", "||" }, - { "\'", "|\'" }, - { "\n", "|n" }, - { "\r", "|r" }, - { "[", "|['" }, - { "]", "|]" } + { '|', "||" }, + { '\'', "|\'" }, + { '\n', "|n" }, + { '\r', "|r" }, + { '[', "|[" }, + { ']', "|]" } }; + + _specialCharacters = _sanitizationTokens.Keys.ToArray(); } /// /// Initializes a new instance of the class. /// /// The cake environment. - public MyGetProvider(ICakeEnvironment environment) + /// The build system service message writer. + public MyGetProvider(ICakeEnvironment environment, IBuildSystemServiceMessageWriter writer) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - - _environment = environment; + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); } - /// - /// Gets a value indicating whether the current build is running on MyGet. - /// - /// - /// true if the current build is running on MyGet; otherwise, false. - /// + /// public bool IsRunningOnMyGet { get @@ -62,20 +62,13 @@ public bool IsRunningOnMyGet } } - /// - /// Report a build problem to MyGet. - /// - /// Description of build problem. + /// public void BuildProblem(string description) { WriteServiceMessage("buildProblem", "description", description); } - /// - /// Allows setting an environment variable that can be used by a future process. - /// - /// Name of the parameter to set. - /// Value to assign to the parameter. + /// public void SetParameter(string name, string value) { WriteServiceMessage("setParameter", new Dictionary @@ -85,12 +78,7 @@ public void SetParameter(string name, string value) }); } - /// - /// Write a status message to the MyGet build log. - /// - /// Message contents. - /// Build status. - /// Error details if status is error. + /// public void WriteStatus(string message, MyGetBuildStatus status, string errorDetails = null) { var statusToWrite = string.Empty; @@ -125,52 +113,71 @@ public void WriteStatus(string message, MyGetBuildStatus status, string errorDet WriteServiceMessage("message", attrs); } - /// - /// Tells MyGet to change the current build number. - /// - /// The required build number. + /// public void SetBuildNumber(string buildNumber) { WriteServiceMessage("buildNumber", buildNumber); } - private static void WriteServiceMessage(string messageName, string attributeValue) + private void WriteServiceMessage(string messageName, string attributeValue) { - WriteServiceMessage(messageName, new Dictionary { { " ", attributeValue } }); + WriteServiceMessage(messageName, new Dictionary { { string.Empty, attributeValue } }); } - private static void WriteServiceMessage(string messageName, string attributeName, string attributeValue) + private void WriteServiceMessage(string messageName, string attributeName, string attributeValue) { WriteServiceMessage(messageName, new Dictionary { { attributeName, attributeValue } }); } [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "Reviewed.")] - private static void WriteServiceMessage(string messageName, Dictionary values) + private void WriteServiceMessage(string messageName, Dictionary values) { var valueString = string.Join(" ", values .Select(keypair => { - if (string.IsNullOrWhiteSpace(keypair.Key)) + var sanitizedValue = Sanitize(keypair.Value); + if (string.IsNullOrEmpty(keypair.Key)) { - return string.Format(CultureInfo.InvariantCulture, "'{0}'", Sanitize(keypair.Value)); + return string.Format(CultureInfo.InvariantCulture, "'{0}'", sanitizedValue); } - return string.Format(CultureInfo.InvariantCulture, "{0}='{1}'", keypair.Key, Sanitize(keypair.Value)); + return string.Format(CultureInfo.InvariantCulture, "{0}='{1}'", keypair.Key, sanitizedValue); }) .ToArray()); - Console.WriteLine("{0}{1} {2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); + _writer.Write("{0}{1} {2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); } private static string Sanitize(string source) { - foreach (var charPair in _sanitizationTokens) + if (string.IsNullOrEmpty(source)) + { + return string.Empty; + } + + // When source does not contain special characters, then it is possible to skip building new string. + // This approach can possibly iterate through string 2 times, but special characters are exceptional. + if (source.IndexOfAny(_specialCharacters) < 0) + { + return source; + } + + var stringBuilder = new StringBuilder(source.Length * 2); + for (int index = 0; index < source.Length; index++) { - source = source.Replace(charPair.Key, charPair.Value); + char sourceChar = source[index]; + if (_sanitizationTokens.TryGetValue(sourceChar, out var replacement)) + { + stringBuilder.Append(replacement); + } + else + { + stringBuilder.Append(sourceChar); + } } - return source; + return stringBuilder.ToString(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/Rwx/Commands/RwxCommands.cs b/src/Cake.Common/Build/Rwx/Commands/RwxCommands.cs new file mode 100644 index 0000000000..4096bce6c3 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Commands/RwxCommands.cs @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Cake.Common.Build.Rwx.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.Rwx.Commands +{ + /// + /// Provides RWX commands for the current build, allowing tasks to write + /// output values, export environment variables to downstream tasks, and + /// upload artifacts at runtime via the filesystem conventions documented at + /// , + /// , + /// and . + /// + public sealed class RwxCommands + { + private readonly ICakeEnvironment _environment; + private readonly IFileSystem _fileSystem; + private readonly RwxEnvironmentInfo _rwxEnvironment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + /// The RWX environment. + public RwxCommands( + ICakeEnvironment environment, + IFileSystem fileSystem, + RwxEnvironmentInfo rwxEnvironment) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _rwxEnvironment = rwxEnvironment ?? throw new ArgumentNullException(nameof(rwxEnvironment)); + } + + /// + /// Sets an RWX output value for the current task. The value is written to + /// {RWX_VALUES}/{key}; each key is a separate file, so repeated + /// calls with the same key overwrite the previous value. Multiline values + /// are written verbatim. + /// + /// The value key. May not contain path separators or ... + /// The value contents. + public void SetValue(string key, string value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentNullException(nameof(key)); + } + + ArgumentNullException.ThrowIfNull(value); + + // RWX writes each value into its own file inside $RWX_VALUES, keyed by filename. + // A separator or `..` in the key would let callers write outside that directory. + if (key.Contains('/') || key.Contains('\\') || key.Contains("..")) + { + throw new CakeException("RWX value key may not contain path separators or '..'."); + } + + if (_rwxEnvironment.Runtime.ValuesPath == null) + { + throw new CakeException("RWX Runtime ValuesPath missing."); + } + + var valuesPath = _rwxEnvironment.Runtime.ValuesPath.MakeAbsolute(_environment); + var valuesDirectory = _fileSystem.GetDirectory(valuesPath); + if (!valuesDirectory.Exists) + { + valuesDirectory.Create(); + } + + var targetPath = valuesPath.CombineWithFilePath(key); + var file = _fileSystem.GetFile(targetPath); + using var stream = file.Open(FileMode.Create, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(value); + } + + /// + /// Exports an environment variable for downstream RWX tasks. The value is + /// written to {RWX_ENV}/{name}; each name is a separate file, so + /// repeated calls with the same name overwrite the previous value. The + /// variable becomes visible to tasks that depend on the current one via + /// use. Note that RWX trims a single trailing \n from the + /// file contents when materializing the variable. + /// + /// The environment variable name. May not contain path separators or ... + /// The environment variable value. + public void SetEnvironmentVariable(string name, string value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + ArgumentNullException.ThrowIfNull(value); + + // RWX writes each env var into its own file inside $RWX_ENV, keyed by filename; + // files in subdirectories are ignored by the runtime. A separator or `..` in the + // name would let callers write outside that directory or into an ignored location. + if (name.Contains('/') || name.Contains('\\') || name.Contains("..")) + { + throw new CakeException("RWX environment variable name may not contain path separators or '..'."); + } + + if (_rwxEnvironment.Runtime.EnvPath == null) + { + throw new CakeException("RWX Runtime EnvPath missing."); + } + + var envPath = _rwxEnvironment.Runtime.EnvPath.MakeAbsolute(_environment); + var envDirectory = _fileSystem.GetDirectory(envPath); + if (!envDirectory.Exists) + { + envDirectory.Create(); + } + + var targetPath = envPath.CombineWithFilePath(name); + var file = _fileSystem.GetFile(targetPath); + using var stream = file.Open(FileMode.Create, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(value); + } + + /// + /// Uploads a file as an RWX artifact for the current task. The file is + /// copied into {RWX_ARTIFACTS}/{filename}, where filename is + /// the leaf name of . + /// + /// Path to the local file to upload. + public void UploadArtifact(FilePath path) + { + ArgumentNullException.ThrowIfNull(path); + + if (_rwxEnvironment.Runtime.ArtifactsPath == null) + { + throw new CakeException("RWX Runtime ArtifactsPath missing."); + } + + var absoluteFilePath = path.MakeAbsolute(_environment); + var file = _fileSystem.GetFile(absoluteFilePath); + if (!file.Exists) + { + throw new FileNotFoundException("Artifact file not found.", absoluteFilePath.FullPath); + } + + var artifactsPath = _rwxEnvironment.Runtime.ArtifactsPath.MakeAbsolute(_environment); + var artifactsDirectory = _fileSystem.GetDirectory(artifactsPath); + if (!artifactsDirectory.Exists) + { + artifactsDirectory.Create(); + } + + var destination = artifactsPath.CombineWithFilePath(absoluteFilePath.GetFilename()); + file.Copy(destination, true); + } + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxActorInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxActorInfo.cs new file mode 100644 index 0000000000..19df1e2d7b --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxActorInfo.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX actor information for the current build. + /// + public sealed class RwxActorInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxActorInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the identifier of the RWX actor that started the run. + /// + /// + /// The actor identifier. + /// + public string Id => GetEnvironmentString("RWX_ACTOR_ID"); + + /// + /// Gets the name of the RWX actor that started the run. + /// + /// + /// The actor name. + /// + public string Name => GetEnvironmentString("RWX_ACTOR"); + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxEnvironmentInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxEnvironmentInfo.cs new file mode 100644 index 0000000000..499622044e --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxEnvironmentInfo.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX environment information for the current build. + /// + public sealed class RwxEnvironmentInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Run = new RwxRunInfo(environment); + Task = new RwxTaskInfo(environment); + Actor = new RwxActorInfo(environment); + Git = new RwxGitInfo(environment); + Runtime = new RwxRuntimeInfo(environment); + } + + /// + /// Gets RWX run information for the current build. + /// + /// + /// The run. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Run: + /// Id: {0} + /// Title: {1} + /// Url: {2}", + /// BuildSystem.Rwx.Environment.Run.Id, + /// BuildSystem.Rwx.Environment.Run.Title, + /// BuildSystem.Rwx.Environment.Run.Url + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + /// Via Rwx. + /// + /// + /// if (Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Run: + /// Id: {0} + /// Title: {1} + /// Url: {2}", + /// Rwx.Environment.Run.Id, + /// Rwx.Environment.Run.Title, + /// Rwx.Environment.Run.Url + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + public RwxRunInfo Run { get; } + + /// + /// Gets RWX task information for the current build. + /// + /// + /// The task. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Task: + /// Id: {0} + /// Url: {1} + /// AttemptNumber: {2}", + /// BuildSystem.Rwx.Environment.Task.Id, + /// BuildSystem.Rwx.Environment.Task.Url, + /// BuildSystem.Rwx.Environment.Task.AttemptNumber + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + /// Via Rwx. + /// + /// + /// if (Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Task: + /// Id: {0} + /// Url: {1} + /// AttemptNumber: {2}", + /// Rwx.Environment.Task.Id, + /// Rwx.Environment.Task.Url, + /// Rwx.Environment.Task.AttemptNumber + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + public RwxTaskInfo Task { get; } + + /// + /// Gets RWX actor information for the current build. + /// + /// + /// The actor. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Actor: + /// Id: {0} + /// Name: {1}", + /// BuildSystem.Rwx.Environment.Actor.Id, + /// BuildSystem.Rwx.Environment.Actor.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + /// Via Rwx. + /// + /// + /// if (Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Actor: + /// Id: {0} + /// Name: {1}", + /// Rwx.Environment.Actor.Id, + /// Rwx.Environment.Actor.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + public RwxActorInfo Actor { get; } + + /// + /// Gets RWX git information for the current build. + /// + /// + /// The git information. Populated by the RWX git/clone package, so it is only + /// available to tasks that depend (directly or transitively) on a task that ran + /// git/clone; otherwise the properties return empty strings. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Git: + /// RepositoryUrl: {0} + /// CommitSha: {1} + /// RefName: {2}", + /// BuildSystem.Rwx.Environment.Git.RepositoryUrl, + /// BuildSystem.Rwx.Environment.Git.CommitSha, + /// BuildSystem.Rwx.Environment.Git.RefName + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + /// Via Rwx. + /// + /// + /// if (Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Git: + /// RepositoryUrl: {0} + /// CommitSha: {1} + /// RefName: {2}", + /// Rwx.Environment.Git.RepositoryUrl, + /// Rwx.Environment.Git.CommitSha, + /// Rwx.Environment.Git.RefName + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + public RwxGitInfo Git { get; } + + /// + /// Gets RWX runtime information for the current build. Exposes the + /// filesystem locations RWX uses for runtime-written output values and + /// artifacts. Available inside any task running on RWX. + /// + /// + /// The runtime information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Runtime: + /// ValuesPath: {0} + /// ArtifactsPath: {1}", + /// BuildSystem.Rwx.Environment.Runtime.ValuesPath, + /// BuildSystem.Rwx.Environment.Runtime.ArtifactsPath + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + /// Via Rwx. + /// + /// + /// if (Rwx.IsRunningOnRwx) + /// { + /// Information( + /// @"Runtime: + /// ValuesPath: {0} + /// ArtifactsPath: {1}", + /// Rwx.Environment.Runtime.ValuesPath, + /// Rwx.Environment.Runtime.ArtifactsPath + /// ); + /// } + /// else + /// { + /// Information("Not running on RWX"); + /// } + /// + /// + public RwxRuntimeInfo Runtime { get; } + + /// + /// Gets a value indicating whether the current build is continuous integration. + /// + /// + /// true if ci; otherwise, false. + /// + public bool CI => GetEnvironmentBoolean("CI"); + + /// + /// Gets a value indicating whether the environment is RWX. + /// + /// + /// true if RWX; otherwise, false. + /// + public bool Rwx => GetEnvironmentBoolean("RWX"); + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxGitInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxGitInfo.cs new file mode 100644 index 0000000000..7819282a96 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxGitInfo.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX git information for the current build. + /// + /// + /// These values are populated by the RWX git/clone package and are + /// only available to tasks that depend (directly or transitively) on a task + /// that ran git/clone. When unavailable, properties return empty strings. + /// + public sealed class RwxGitInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxGitInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the URL of the git repository that was cloned. + /// + /// + /// The repository parameter provided to git/clone. + /// + public string RepositoryUrl => GetEnvironmentString("RWX_GIT_REPOSITORY_URL"); + + /// + /// Gets the repository identifier extracted from the repository URL. + /// + /// + /// The repository identifier (for example, cake-build/cake). + /// + public string RepositoryName => GetEnvironmentString("RWX_GIT_REPOSITORY_NAME"); + + /// + /// Gets the SHA of the resolved commit. + /// + /// + /// The resolved commit SHA. + /// + public string CommitSha => GetEnvironmentString("RWX_GIT_COMMIT_SHA"); + + /// + /// Gets the full message of the resolved commit. + /// + /// + /// The resolved commit message. + /// + public string CommitMessage => GetEnvironmentString("RWX_GIT_COMMIT_MESSAGE"); + + /// + /// Gets the summary line of the resolved commit's message. + /// + /// + /// The first line of the resolved commit message. + /// + public string CommitSummary => GetEnvironmentString("RWX_GIT_COMMIT_SUMMARY"); + + /// + /// Gets the committer name associated with the resolved commit. + /// + /// + /// The committer name. + /// + public string CommitterName => GetEnvironmentString("RWX_GIT_COMMITTER_NAME"); + + /// + /// Gets the committer email associated with the resolved commit. + /// + /// + /// The committer email. + /// + public string CommitterEmail => GetEnvironmentString("RWX_GIT_COMMITTER_EMAIL"); + + /// + /// Gets the unresolved ref associated with the commit. + /// + /// + /// The unresolved ref (for example, refs/heads/main or refs/tags/v1.0.0). + /// + public string Ref => GetEnvironmentString("RWX_GIT_REF"); + + /// + /// Gets the name of the unresolved ref associated with the commit. + /// + /// + /// The short ref name (for example, the branch or tag name extracted from the full ref path). + /// + public string RefName => GetEnvironmentString("RWX_GIT_REF_NAME"); + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxRunInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxRunInfo.cs new file mode 100644 index 0000000000..3eb208883d --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxRunInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX run information for the current build. + /// + public sealed class RwxRunInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxRunInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the RWX identifier of the current run. + /// + /// + /// The run identifier. + /// + public string Id => GetEnvironmentString("RWX_RUN_ID"); + + /// + /// Gets the title of the current RWX run. + /// + /// + /// The run title. + /// + public string Title => GetEnvironmentString("RWX_RUN_TITLE"); + + /// + /// Gets the link to the current run in RWX. + /// + /// + /// The run URL. + /// + public string Url => GetEnvironmentString("RWX_RUN_URL"); + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxRuntimeInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxRuntimeInfo.cs new file mode 100644 index 0000000000..4a46e50857 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxRuntimeInfo.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX runtime information for the current build, exposing the + /// directories RWX uses for output values and artifacts. These are written to + /// at task execution time and complement static outputs.values / + /// outputs.artifacts declarations in the task YAML. + /// + public sealed class RwxRuntimeInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxRuntimeInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the directory in which output values can be written. Each file + /// in this directory becomes an output value keyed by its filename. + /// + /// + /// The path resolved from the RWX_VALUES environment variable, or + /// null if the variable is not set. + /// + public DirectoryPath ValuesPath => GetEnvironmentDirectoryPath("RWX_VALUES"); + + /// + /// Gets the directory into which artifacts can be uploaded. Files copied + /// into this directory are captured as artifacts of the current task. + /// + /// + /// The path resolved from the RWX_ARTIFACTS environment variable, or + /// null if the variable is not set. + /// + public DirectoryPath ArtifactsPath => GetEnvironmentDirectoryPath("RWX_ARTIFACTS"); + + /// + /// Gets the directory into which environment variables can be exported + /// for downstream tasks. Each file in this directory becomes an + /// environment variable in tasks that depend on the current one via + /// use, keyed by filename. + /// + /// + /// The path resolved from the RWX_ENV environment variable, or + /// null if the variable is not set. + /// + public DirectoryPath EnvPath => GetEnvironmentDirectoryPath("RWX_ENV"); + + /// + /// Gets a value indicating whether the values, artifacts, and env + /// directories are all exposed by the current RWX runtime. + /// + /// + /// true if , , + /// and are all set; otherwise, false. + /// + public bool IsRuntimeAvailable => ValuesPath != null && ArtifactsPath != null && EnvPath != null; + } +} diff --git a/src/Cake.Common/Build/Rwx/Data/RwxTaskInfo.cs b/src/Cake.Common/Build/Rwx/Data/RwxTaskInfo.cs new file mode 100644 index 0000000000..78b14bfd27 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/Data/RwxTaskInfo.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.Rwx.Data +{ + /// + /// Provides RWX task information for the current build. + /// + public sealed class RwxTaskInfo : RwxInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public RwxTaskInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the RWX identifier of the current task. + /// + /// + /// The task identifier. + /// + public string Id => GetEnvironmentString("RWX_TASK_ID"); + + /// + /// Gets the link to the current task in RWX. + /// + /// + /// The task URL. + /// + public string Url => GetEnvironmentString("RWX_TASK_URL"); + + /// + /// Gets the attempt number of the current RWX task. + /// + /// + /// The task attempt number. + /// + public int AttemptNumber => GetEnvironmentInteger("RWX_TASK_ATTEMPT_NUMBER"); + } +} diff --git a/src/Cake.Common/Build/Rwx/IRwxProvider.cs b/src/Cake.Common/Build/Rwx/IRwxProvider.cs new file mode 100644 index 0000000000..a8891596e7 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/IRwxProvider.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.Rwx.Commands; +using Cake.Common.Build.Rwx.Data; + +namespace Cake.Common.Build.Rwx +{ + /// + /// Represents an RWX Provider. + /// + public interface IRwxProvider + { + /// + /// Gets a value indicating whether this instance is running on RWX. + /// + /// + /// true if this instance is running on RWX; otherwise, false. + /// + bool IsRunningOnRwx { get; } + + /// + /// Gets the environment. + /// + /// + /// The environment. + /// + RwxEnvironmentInfo Environment { get; } + + /// + /// Gets the RWX commands surface, used to write output values and upload + /// artifacts at task runtime. + /// + /// + /// The commands. + /// + RwxCommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/Rwx/RwxInfo.cs b/src/Cake.Common/Build/Rwx/RwxInfo.cs new file mode 100644 index 0000000000..b6c7215792 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/RwxInfo.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.Rwx +{ + /// + /// Base class used to provide information about the RWX environment. + /// + public abstract class RwxInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected RwxInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable, or null if it is not set. + protected DirectoryPath GetEnvironmentDirectoryPath(string variable) + { + return _environment.GetEnvironmentVariable(variable) is string value + ? DirectoryPath.FromString(value) + : null; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + } +} diff --git a/src/Cake.Common/Build/Rwx/RwxProvider.cs b/src/Cake.Common/Build/Rwx/RwxProvider.cs new file mode 100644 index 0000000000..3b40244798 --- /dev/null +++ b/src/Cake.Common/Build/Rwx/RwxProvider.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.Rwx.Commands; +using Cake.Common.Build.Rwx.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.Rwx +{ + /// + /// Responsible for communicating with RWX. + /// + public sealed class RwxProvider : IRwxProvider + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + public RwxProvider(ICakeEnvironment environment, IFileSystem fileSystem) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + ArgumentNullException.ThrowIfNull(fileSystem); + Environment = new RwxEnvironmentInfo(environment); + Commands = new RwxCommands(environment, fileSystem, Environment); + } + + /// + public bool IsRunningOnRwx => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("RWX")); + + /// + public RwxEnvironmentInfo Environment { get; } + + /// + public RwxCommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/TeamCity/Data/TeamCityBuildInfo.cs b/src/Cake.Common/Build/TeamCity/Data/TeamCityBuildInfo.cs new file mode 100644 index 0000000000..312e528eff --- /dev/null +++ b/src/Cake.Common/Build/TeamCity/Data/TeamCityBuildInfo.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.TeamCity.Data +{ + /// + /// Provides TeamCity build information for a current build. + /// + public class TeamCityBuildInfo : TeamCityInfo + { + private DateTimeOffset? _startDateTime; + + /// + /// Gets the build configuration name. + /// + /// + /// The build configuration name. + /// + public string BuildConfName => GetEnvironmentString("TEAMCITY_BUILDCONF_NAME"); + + /// + /// Gets the build number. + /// + /// + /// The build number. + /// + public string Number => GetEnvironmentString("BUILD_NUMBER"); + + /// + /// Gets the build start date and time. + /// + /// + /// The build start date and time if available, or . + /// + /// + /// The build start date and time are obtained from reading two environment variables + /// BUILD_START_DATE (yyyyMMdd) and BUILD_START_TIME (HHmmss) are automatically set by JetBrain's + /// Groovy plug plugin. + /// + public DateTimeOffset? StartDateTime + { + get + { + if (_startDateTime.HasValue) + { + return _startDateTime; + } + + var startDate = GetEnvironmentString("BUILD_START_DATE"); // yyyyMMdd + var startTime = GetEnvironmentString("BUILD_START_TIME"); // HHmmss + + if (DateTimeOffset.TryParseExact($"{startDate}{startTime}", "yyyyMMddHHmmss", CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, out var startDateTime)) + { + _startDateTime = startDateTime; + } + + return _startDateTime; + } + } + + /// + /// Gets the branch display name. + /// + /// + /// The branch display name. + /// + public string BranchName => ConfigProperties.ContainsKey("teamcity.build.branch") ? ConfigProperties["teamcity.build.branch"] : string.Empty; + + /// + /// Gets the vcs branch name. + /// + /// + /// The vcs branch name. + /// + public string VcsBranchName + { + get + { + var (key, value) = ConfigProperties.FirstOrDefault(_ => _.Key.StartsWith("teamcity.build.vcs.branch.")); + return !string.IsNullOrWhiteSpace(key) ? value : string.Empty; + } + } + + /// + /// Gets the TeamCity build properties. + /// + /// + /// The TeamCity build properties as a key/value dictionary. + /// + public Dictionary BuildProperties => _buildProperties.Value; + + /// + /// Gets the TeamCity config properties. + /// + /// + /// The TeamCity config properties as a key/value dictionary. + /// + public Dictionary ConfigProperties => _configProperties.Value; + + /// + /// Gets the TeamCity runner properties. + /// + /// + /// The TeamCity runner properties as a key/value dictionary. + /// + public Dictionary RunnerProperties => _runnerProperties.Value; + + private readonly Lazy> _buildProperties; + private readonly Lazy> _configProperties; + private readonly Lazy> _runnerProperties; + + private Dictionary ReadAndParseFile(IFileSystem fileSystem, string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + return new Dictionary(); + } + + var configurationPropertiesXmlFile = fileSystem.GetFile($"{fileName}.xml"); + if (!configurationPropertiesXmlFile.Exists) + { + return new Dictionary(); + } + + var configurationPropertiesXml = XDocument.Load(configurationPropertiesXmlFile.OpenRead()); + + return configurationPropertiesXml.XPathSelectElements("//entry") + .Where(entry => entry.Attribute("key") != null) + .ToDictionary(entry => entry.Attribute("key").Value, entry => entry.Value); + } + + private string BuildValueIfExists(string key) + { + return BuildProperties.ContainsKey(key) ? BuildProperties[key] : string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + public TeamCityBuildInfo(ICakeEnvironment environment, IFileSystem fileSystem) + : base(environment) + { + _buildProperties = new Lazy>(() => ReadAndParseFile(fileSystem, GetEnvironmentString("TEAMCITY_BUILD_PROPERTIES_FILE"))); + _configProperties = new Lazy>(() => ReadAndParseFile(fileSystem, BuildValueIfExists("teamcity.configuration.properties.file"))); + _runnerProperties = new Lazy>(() => ReadAndParseFile(fileSystem, BuildValueIfExists("teamcity.runner.properties.file"))); + } + } +} diff --git a/src/Cake.Common/Build/TeamCity/Data/TeamCityEnvironmentInfo.cs b/src/Cake.Common/Build/TeamCity/Data/TeamCityEnvironmentInfo.cs new file mode 100644 index 0000000000..a5159a0011 --- /dev/null +++ b/src/Cake.Common/Build/TeamCity/Data/TeamCityEnvironmentInfo.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.TeamCity.Data +{ + /// + /// Provides TeamCity environment information for current build. + /// + public class TeamCityEnvironmentInfo : TeamCityInfo + { + /// + /// Gets TeamCity project information. + /// + /// + /// The TeamCity project information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Project: + /// Name: {0}", + /// BuildSystem.TeamCity.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + /// Via TeamCity. + /// + /// + /// if (TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Project: + /// Name: {0}", + /// TeamCity.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + public TeamCityProjectInfo Project { get; } + + /// + /// Gets TeamCity build information. + /// + /// + /// The TeamCity build information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Build: + /// BuildConfName: {0} + /// Number: {1}", + /// BuildSystem.TeamCity.Environment.Build.BuildConfName, + /// BuildSystem.TeamCity.Environment.Build.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + /// Via TeamCity. + /// + /// + /// if (TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Build: + /// BuildConfName: {0} + /// Number: {1}", + /// TeamCity.Environment.Build.BuildConfName, + /// TeamCity.Environment.Build.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + public TeamCityBuildInfo Build { get; } + + /// + /// Gets TeamCity pull-request information. + /// + /// + /// The TeamCity pull-request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Number: {1}", + /// BuildSystem.TeamCity.Environment.PullRequest.IsPullRequest, + /// BuildSystem.TeamCity.Environment.PullRequest.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + /// Via TeamCity. + /// + /// + /// if (TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Number: {1}", + /// TeamCity.Environment.PullRequest.IsPullRequest, + /// TeamCity.Environment.PullRequest.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + public TeamCityPullRequestInfo PullRequest { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + public TeamCityEnvironmentInfo(ICakeEnvironment environment, IFileSystem fileSystem) + : base(environment) + { + Project = new TeamCityProjectInfo(environment); + Build = new TeamCityBuildInfo(environment, fileSystem); + PullRequest = new TeamCityPullRequestInfo(environment, Build); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/Data/TeamCityProjectInfo.cs b/src/Cake.Common/Build/TeamCity/Data/TeamCityProjectInfo.cs new file mode 100644 index 0000000000..957443d362 --- /dev/null +++ b/src/Cake.Common/Build/TeamCity/Data/TeamCityProjectInfo.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.TeamCity.Data +{ + /// + /// Provides TeamCity project information for current build. + /// + public class TeamCityProjectInfo : TeamCityInfo + { + /// + /// Gets the TeamCity project name. + /// + /// + /// The TeamCity project name. + /// + public string Name => GetEnvironmentString("TEAMCITY_PROJECT_NAME"); + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public TeamCityProjectInfo(ICakeEnvironment environment) + : base(environment) + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/Data/TeamCityPullRequestInfo.cs b/src/Cake.Common/Build/TeamCity/Data/TeamCityPullRequestInfo.cs new file mode 100644 index 0000000000..487116e007 --- /dev/null +++ b/src/Cake.Common/Build/TeamCity/Data/TeamCityPullRequestInfo.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.TeamCity.Data +{ + /// + /// Provides TeamCity pull request information for current build. + /// + public class TeamCityPullRequestInfo : TeamCityInfo + { + private readonly TeamCityBuildInfo _buildInfo; + + private bool InferIsPullRequest() + { + var gitReferenceName = GetBranchRef(); + + if (string.IsNullOrEmpty(gitReferenceName)) + { + return false; + } + + var branchSlices = gitReferenceName.Split('/'); + + if (branchSlices.Length >= 3) + { + switch (branchSlices[1].ToUpper()) + { + case "CHANGES": + case "MERGE-REQUESTS": + case "PULL": + case "PULL-REQUESTS": + return true; + default: + return false; + } + } + + return false; + } + + private int? GetPullRequestNumber() + { + var gitReferenceName = GetBranchRef(); + + if (string.IsNullOrEmpty(gitReferenceName)) + { + return null; + } + + var branchSlices = gitReferenceName.Split('/'); + + if (int.TryParse(branchSlices[2], out var pullRequestNumber)) + { + return pullRequestNumber; + } + + return null; + } + + private string GetBranchRef() + { + var gitBranch = GetEnvironmentString("Git_Branch"); + + if (string.IsNullOrWhiteSpace(gitBranch)) + { + gitBranch = _buildInfo.VcsBranchName; + } + + return gitBranch; + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + /// + /// env.Git_Branch is a required parameter in TeamCity for this to work. + /// + public bool IsPullRequest => InferIsPullRequest(); + + /// + /// Gets the pull request number. + /// + /// + /// The pull request number. + /// + /// + /// env.Git_Branch is a required parameter in TeamCity for this to work. + /// + public int? Number => IsPullRequest ? GetPullRequestNumber() : null; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The TeamCity build info. + public TeamCityPullRequestInfo(ICakeEnvironment environment, TeamCityBuildInfo buildInfo) + : base(environment) + { + _buildInfo = buildInfo; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/ITeamCityProvider.cs b/src/Cake.Common/Build/TeamCity/ITeamCityProvider.cs index 4d2840fc0d..b415cdb815 100644 --- a/src/Cake.Common/Build/TeamCity/ITeamCityProvider.cs +++ b/src/Cake.Common/Build/TeamCity/ITeamCityProvider.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Common.Build.TeamCity.Data; using Cake.Core.IO; namespace Cake.Common.Build.TeamCity @@ -10,12 +12,70 @@ namespace Cake.Common.Build.TeamCity /// public interface ITeamCityProvider { + /// + /// Gets a value indicating whether the current build is running on TeamCity. + /// + /// + /// true if the current build is running on TeamCity; otherwise, false. + /// + bool IsRunningOnTeamCity { get; } + + /// + /// Gets the TeamCity environment. + /// + /// + /// The TeamCity environment. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Environment: + /// PullRequest: {0} + /// Build Configuration Name: {1} + /// TeamCity Project Name: {2}", + /// BuildSystem.TeamCity.Environment.PullRequest.IsPullRequest, + /// BuildSystem.TeamCity.Environment.Build.BuildConfName, + /// BuildSystem.TeamCity.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + /// Via TeamCity. + /// + /// + /// if (TeamCity.IsRunningOnTeamCity) + /// { + /// Information( + /// @"Environment: + /// PullRequest: {0} + /// Build Configuration Name: {1} + /// TeamCity Project Name: {2}", + /// BuildSystem.TeamCity.Environment.PullRequest.IsPullRequest, + /// BuildSystem.TeamCity.Environment.Build.BuildConfName, + /// BuildSystem.TeamCity.Environment.Project.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on TeamCity"); + /// } + /// + /// + TeamCityEnvironmentInfo Environment { get; } + /// /// Report a build problem to TeamCity. /// - /// Description of build problem. - /// Build identity. - void BuildProblem(string description, string identity); + /// A human-readable plain text describing the build problem. By default, the description appears in the build status text and in the list of build's problems. The text is limited to 4000 symbols, and will be truncated if the limit is exceeded. + /// A unique problem ID (optional). Different problems must have different identity, same problems - same identity, which should not change throughout builds if the same problem, for example, the same compilation error occurs. It must be a valid Java ID up to 60 characters. If omitted, the identity is calculated based on the description text. + void BuildProblem(string description, string identity = null); /// /// Tell TeamCity to import data of a given type. @@ -31,14 +91,6 @@ public interface ITeamCityProvider /// The full path to the dotCover home folder to override the bundled dotCover. void ImportDotCoverCoverage(FilePath snapshotFile, DirectoryPath dotCoverHome = null); - /// - /// Gets a value indicating whether the current build is running on TeamCity. - /// - /// - /// true if the current build is running on TeamCity; otherwise, false. - /// - bool IsRunningOnTeamCity { get; } - /// /// Tells TeamCity to publish artifacts in the given directory. /// @@ -51,6 +103,13 @@ public interface ITeamCityProvider /// The required build number. void SetBuildNumber(string buildNumber); + /// + /// Tells TeamCity to set a named parameter with a given value. + /// + /// The name of the parameter to set. + /// The value to set for the named parameter. + void SetParameter(string parameterName, string parameterValue); + /// /// Write the end of a message block to the TeamCity build log. /// @@ -94,11 +153,39 @@ public interface ITeamCityProvider void WriteStartProgress(string message); /// - /// Write a status message to the TeamCity build log. + /// Write a message to the TeamCity build log. - Messages not added to the build status. /// /// Message contents. /// Build status. /// Error details if status is error. void WriteStatus(string message, string status = "NORMAL", string errorDetails = null); + + /// + /// Write a status message to the TeamCity build log. - Prepend message to build status. + /// + /// Message contents. + /// Build status. + void WritePrependBuildStatus(string message, string status = null); + + /// + /// Write a status message to the TeamCity build log. - append message to build status. + /// + /// Message contents. + /// Build status. + void WriteAppendBuildStatus(string message, string status = null); + + /// + /// Write a status message to the TeamCity build log. - replace existing build status. + /// + /// Message contents. + /// Build status. + void WriteReplacementBuildStatus(string message, string status = null); + + /// + /// Write a statistic message to the TeamCity build log. + /// + /// The statistic key. + /// The statistic value. + void WriteStatistic(string key, string value); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/TeamCityDisposableExtensions.cs b/src/Cake.Common/Build/TeamCity/TeamCityDisposableExtensions.cs index e0c24820ba..2ad6becd31 100644 --- a/src/Cake.Common/Build/TeamCity/TeamCityDisposableExtensions.cs +++ b/src/Cake.Common/Build/TeamCity/TeamCityDisposableExtensions.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using Cake.Core; namespace Cake.Common.Build.TeamCity { @@ -11,63 +13,29 @@ namespace Cake.Common.Build.TeamCity public static class TeamCityDisposableExtensions { /// - /// Writes the start of a TeamCity message block, then returns a disposable that write the end on Dispose. + /// Writes the start of a TeamCity message block, then returns a disposable that writes the report block end on dispose. /// /// TeamCity provider. /// The name of the report block. - /// A disposable wrapper the writes the report block end. + /// A disposable that writes the report block end. public static IDisposable Block(this ITeamCityProvider teamCityProvider, string blockName) { - if (teamCityProvider == null) - { - throw new ArgumentNullException("teamCityProvider"); - } + ArgumentNullException.ThrowIfNull(teamCityProvider); teamCityProvider.WriteStartBlock(blockName); - return new TeamCityActionDisposable(teamCityProvider, tc => tc.WriteEndBlock(blockName)); + return Disposable.Create(() => teamCityProvider.WriteEndBlock(blockName)); } /// - /// Writes the start of a TeamCity build block, then returns a disposable that write the end on Dispose. + /// Writes the start of a TeamCity build block, then returns a disposable that writes the build block end on dispose. /// /// TeamCity provider. /// The name of the build block. - /// A disposable wrapper the writes the build block end. + /// A disposable that writes the build block end. public static IDisposable BuildBlock(this ITeamCityProvider teamCityProvider, string compilerName) { - if (teamCityProvider == null) - { - throw new ArgumentNullException("teamCityProvider"); - } + ArgumentNullException.ThrowIfNull(teamCityProvider); teamCityProvider.WriteStartBuildBlock(compilerName); - return new TeamCityActionDisposable(teamCityProvider, tc => tc.WriteEndBuildBlock(compilerName)); - } - - /// - /// Disposable helper for writing TeamCity message blocks. - /// - internal sealed class TeamCityActionDisposable : IDisposable - { - private readonly ITeamCityProvider _teamCityProvider; - private readonly Action _disposeAction; - - /// - /// Initializes a new instance of the class. - /// - /// TeamCity provider. - /// The action to do on Dispose. - public TeamCityActionDisposable(ITeamCityProvider teamCityProvider, Action disposeAction) - { - _teamCityProvider = teamCityProvider; - _disposeAction = disposeAction; - } - - /// - /// Writes the end block for this message block. - /// - public void Dispose() - { - _disposeAction(_teamCityProvider); - } + return Disposable.Create(() => teamCityProvider.WriteEndBuildBlock(compilerName)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/TeamCityInfo.cs b/src/Cake.Common/Build/TeamCity/TeamCityInfo.cs new file mode 100644 index 0000000000..68745add0d --- /dev/null +++ b/src/Cake.Common/Build/TeamCity/TeamCityInfo.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.TeamCity +{ + /// + /// Base class used to provide information about the TeamCity environment. + /// + public abstract class TeamCityInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected TeamCityInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + int result; + if (int.TryParse(value, out result)) + { + return result; + } + } + return 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + if (!string.IsNullOrWhiteSpace(value)) + { + return value.Equals("true", StringComparison.OrdinalIgnoreCase); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TeamCity/TeamCityProvider.cs b/src/Cake.Common/Build/TeamCity/TeamCityProvider.cs index d659285e47..a130f0cac7 100644 --- a/src/Cake.Common/Build/TeamCity/TeamCityProvider.cs +++ b/src/Cake.Common/Build/TeamCity/TeamCityProvider.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using Cake.Common.Build.TeamCity.Data; using Cake.Core; -using Cake.Core.Diagnostics; using Cake.Core.IO; namespace Cake.Common.Build.TeamCity @@ -19,9 +20,18 @@ public sealed class TeamCityProvider : ITeamCityProvider { private const string MessagePrefix = "##teamcity["; private const string MessagePostfix = "]"; + private static readonly Dictionary _sanitizationTokens; + private readonly ICakeEnvironment _environment; - private readonly ICakeLog _log; + private readonly IFileSystem _fileSystem; + private readonly IBuildSystemServiceMessageWriter _writer; + + /// + public bool IsRunningOnTeamCity => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("TEAMCITY_VERSION")); + + /// + public TeamCityEnvironmentInfo Environment { get; } static TeamCityProvider() { @@ -40,103 +50,60 @@ static TeamCityProvider() /// Initializes a new instance of the class. /// /// The cake environment. - /// The cake log. - public TeamCityProvider(ICakeEnvironment environment, ICakeLog log) + /// The cake file system. + /// The build system service message writer. + public TeamCityProvider(ICakeEnvironment environment, IFileSystem fileSystem, IBuildSystemServiceMessageWriter writer) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); - if (log == null) - { - throw new ArgumentNullException("log"); - } - - _environment = environment; - _log = log; + Environment = new TeamCityEnvironmentInfo(environment, fileSystem); } - /// - /// Gets a value indicating whether the current build is running on TeamCity. - /// - /// - /// true if the current build is running on TeamCity; otherwise, false. - /// - public bool IsRunningOnTeamCity - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("TEAMCITY_VERSION")); } - } - - /// - /// Write a progress message to the TeamCity build log. - /// - /// Build log message. + /// public void WriteProgressMessage(string message) { WriteServiceMessage("progressMessage", message); } - /// - /// Write a progressStart message to the TeamCity build log. - /// - /// Build log message. + /// public void WriteStartProgress(string message) { - WriteServiceMessage("progressStart", "message", message); + WriteServiceMessage("progressStart", message); } - /// - /// Write a progressFinish message to the TeamCity build log. - /// - /// Build log message. + /// public void WriteEndProgress(string message) { - WriteServiceMessage("progressFinish", "message", message); + WriteServiceMessage("progressFinish", message); } - /// - /// Write the start of a message block to the TeamCity build log. - /// - /// Block name. + /// public void WriteStartBlock(string blockName) { WriteServiceMessage("blockOpened", "name", blockName); } - /// - /// Write the end of a message block to the TeamCity build log. - /// - /// Block name. + /// public void WriteEndBlock(string blockName) { WriteServiceMessage("blockClosed", "name", blockName); } - /// - /// Write the start of a build block to the TeamCity build log. - /// - /// Build compiler name. + /// public void WriteStartBuildBlock(string compilerName) { WriteServiceMessage("compilationStarted", "compiler", compilerName); } - /// - /// Write the end of a build block to the TeamCity build log. - /// - /// Build compiler name. + /// public void WriteEndBuildBlock(string compilerName) { WriteServiceMessage("compilationFinished", "compiler", compilerName); } - /// - /// Write a status message to the TeamCity build log. - /// - /// Message contents. - /// Build status. - /// Error details if status is error. + /// public void WriteStatus(string message, string status = "NORMAL", string errorDetails = null) { var attrs = new Dictionary @@ -153,22 +120,60 @@ public void WriteStatus(string message, string status = "NORMAL", string errorDe WriteServiceMessage("message", attrs); } - /// - /// Tell TeamCity to import data of a given type. - /// - /// Date type. - /// Data file path. - public void ImportData(string type, FilePath path) + /// + public void WriteAppendBuildStatus(string message, string status = null) + { + var attrs = new Dictionary + { + { "text", $"{{build.status.text}}; {message}" } + }; + + if (status != null) + { + attrs.Add("status", status); + } + + WriteServiceMessage("buildStatus", attrs); + } + + /// + public void WritePrependBuildStatus(string message, string status = null) { - if (type == null) + var attrs = new Dictionary + { + { "text", $"{message}; {{build.status.text}}" } + }; + + if (status != null) { - throw new ArgumentNullException("type"); + attrs.Add("status", status); } - if (path == null) + + WriteServiceMessage("buildStatus", attrs); + } + + /// + public void WriteReplacementBuildStatus(string message, string status = null) + { + var attrs = new Dictionary { - throw new ArgumentNullException("path"); + { "text", message } + }; + + if (status != null) + { + attrs.Add("status", status); } + WriteServiceMessage("buildStatus", attrs); + } + + /// + public void ImportData(string type, FilePath path) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(path); + WriteServiceMessage("importData", new Dictionary { { "type", type }, @@ -176,17 +181,10 @@ public void ImportData(string type, FilePath path) }); } - /// - /// Tell TeamCity to import coverage from dotCover snapshot file. - /// - /// Snapshot file path. - /// The full path to the dotCover home folder to override the bundled dotCover. + /// public void ImportDotCoverCoverage(FilePath snapshotFile, DirectoryPath dotCoverHome = null) { - if (snapshotFile == null) - { - throw new ArgumentNullException("snapshotFile"); - } + ArgumentNullException.ThrowIfNull(snapshotFile); var args = dotCoverHome == null ? new Dictionary() : @@ -205,38 +203,50 @@ public void ImportDotCoverCoverage(FilePath snapshotFile, DirectoryPath dotCover }); } - /// - /// Report a build problem to TeamCity. - /// - /// Description of build problem. - /// Build identity. - public void BuildProblem(string description, string identity) + /// + public void BuildProblem(string description, string identity = null) { - WriteServiceMessage("buildProblem", new Dictionary + var tokens = new Dictionary { { "description", description } }; + if (!string.IsNullOrEmpty(identity)) { - { "description", description }, - { "identity", identity } - }); + tokens.Add("identity", identity); + } + + WriteServiceMessage("buildProblem", tokens); } - /// - /// Tells TeamCity to publish artifacts in the given directory. - /// - /// Path to artifacts. + /// public void PublishArtifacts(string path) { WriteServiceMessage("publishArtifacts", " ", path); } - /// - /// Tells TeamCity to change the current build number. - /// - /// The required build number. + /// public void SetBuildNumber(string buildNumber) { WriteServiceMessage("buildNumber", buildNumber); } + /// + public void SetParameter(string parameterName, string parameterValue) + { + WriteServiceMessage("setParameter", new Dictionary + { + { "name", parameterName }, + { "value", parameterValue } + }); + } + + /// + public void WriteStatistic(string key, string value) + { + WriteServiceMessage("buildStatisticValue", new Dictionary + { + { "key", key }, + { "value", value } + }); + } + private void WriteServiceMessage(string messageName, string attributeValue) { WriteServiceMessage(messageName, new Dictionary { { " ", attributeValue } }); @@ -262,7 +272,7 @@ private void WriteServiceMessage(string messageName, Dictionary return string.Format(CultureInfo.InvariantCulture, "{0}='{1}'", keypair.Key, Sanitize(keypair.Value)); }) .ToArray()); - _log.Write(Verbosity.Quiet, LogLevel.Information, "{0}{1} {2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); + _writer.Write("{0}{1} {2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); } private static string Sanitize(string source) diff --git a/src/Cake.Common/Build/TravisCI/Data/TravisCIBuildInfo.cs b/src/Cake.Common/Build/TravisCI/Data/TravisCIBuildInfo.cs index d4ac7f40d7..012e703a27 100644 --- a/src/Cake.Common/Build/TravisCI/Data/TravisCIBuildInfo.cs +++ b/src/Cake.Common/Build/TravisCI/Data/TravisCIBuildInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.TravisCI.Data @@ -16,10 +17,7 @@ public sealed class TravisCIBuildInfo : TravisCIInfo /// /// The branch. /// - public string Branch - { - get { return GetEnvironmentString("TRAVIS_BRANCH"); } - } + public string Branch => GetEnvironmentString("TRAVIS_BRANCH"); /// /// Gets the build directory for the current build. @@ -27,10 +25,7 @@ public string Branch /// /// The build directory. /// - public string BuildDirectory - { - get { return GetEnvironmentString("TRAVIS_BUILD_DIR"); } - } + public string BuildDirectory => GetEnvironmentString("TRAVIS_BUILD_DIR"); /// /// Gets the build identifier for the current build. @@ -38,10 +33,7 @@ public string BuildDirectory /// /// The build identifier. /// - public string BuildId - { - get { return GetEnvironmentString("TRAVIS_BUILD_ID"); } - } + public string BuildId => GetEnvironmentString("TRAVIS_BUILD_ID"); /// /// Gets the build number for the current build. @@ -49,10 +41,7 @@ public string BuildId /// /// The build number. /// - public int BuildNumber - { - get { return GetEnvironmentInteger("TRAVIS_BUILD_NUMBER"); } - } + public int BuildNumber => GetEnvironmentInteger("TRAVIS_BUILD_NUMBER"); /// /// Gets the test result indicating if the current build is successful or broken. @@ -60,10 +49,7 @@ public int BuildNumber /// /// The test result. /// - public int TestResult - { - get { return GetEnvironmentInteger("TRAVIS_TEST_RESULT"); } - } + public int TestResult => GetEnvironmentInteger("TRAVIS_TEST_RESULT"); /// /// Gets the tag name for the current build. @@ -71,10 +57,7 @@ public int TestResult /// /// The tag. /// - public string Tag - { - get { return GetEnvironmentString("TRAVIS_TAG"); } - } + public string Tag => GetEnvironmentString("TRAVIS_TAG"); /// /// Initializes a new instance of the class. @@ -85,4 +68,4 @@ public TravisCIBuildInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/Data/TravisCIEnvironmentInfo.cs b/src/Cake.Common/Build/TravisCI/Data/TravisCIEnvironmentInfo.cs index c2ed57e766..a92878400f 100644 --- a/src/Cake.Common/Build/TravisCI/Data/TravisCIEnvironmentInfo.cs +++ b/src/Cake.Common/Build/TravisCI/Data/TravisCIEnvironmentInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.TravisCI.Data @@ -10,20 +11,101 @@ namespace Cake.Common.Build.TravisCI.Data /// public sealed class TravisCIEnvironmentInfo : TravisCIInfo { - private readonly TravisCIBuildInfo _buildProvider; - private readonly TravisCIJobInfo _jobProvider; - private readonly TravisCIRepositoryInfo _repositoryProvider; - /// /// Gets Travis CI build information for the current build. /// /// /// The build. /// - public TravisCIBuildInfo Build - { - get { return _buildProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Build: + /// Branch: {0} + /// BuildDirectory: {1} + /// BuildId: {2}", + /// BuildSystem.TravisCI.Environment.Build.Branch, + /// BuildSystem.TravisCI.Environment.Build.BuildDirectory, + /// BuildSystem.TravisCI.Environment.Build.BuildId + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Build: + /// Branch: {0} + /// BuildDirectory: {1} + /// BuildId: {2}", + /// TravisCI.Environment.Build.Branch, + /// TravisCI.Environment.Build.BuildDirectory, + /// TravisCI.Environment.Build.BuildId + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public TravisCIBuildInfo Build { get; } + + /// + /// Gets Travis CI pull request information. + /// + /// + /// The Travis CI pull request information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// BuildSystem.TravisCI.Environment.PullRequest.IsPullRequest, + /// BuildSystem.TravisCI.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"PullRequest: + /// IsPullRequest: {0} + /// Id: {1}", + /// TravisCI.Environment.PullRequest.IsPullRequest, + /// TravisCI.Environment.PullRequest.Id + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public TravisCIPullRequestInfo PullRequest { get; } /// /// Gets Travis CI job information for the current build. @@ -31,10 +113,49 @@ public TravisCIBuildInfo Build /// /// The job. /// - public TravisCIJobInfo Job - { - get { return _jobProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Job: + /// JobId: {0} + /// JobNumber: {1} + /// OSName: {2}", + /// BuildSystem.TravisCI.Environment.Job.JobId, + /// BuildSystem.TravisCI.Environment.Job.JobNumber, + /// BuildSystem.TravisCI.Environment.Job.OSName + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Job: + /// JobId: {0} + /// JobNumber: {1} + /// OSName: {2}", + /// TravisCI.Environment.Job.JobId, + /// TravisCI.Environment.Job.JobNumber, + /// TravisCI.Environment.Job.OSName + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public TravisCIJobInfo Job { get; } /// /// Gets Travis CI repository information for the current build. @@ -42,10 +163,49 @@ public TravisCIJobInfo Job /// /// The repository. /// - public TravisCIRepositoryInfo Repository - { - get { return _repositoryProvider; } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Repository: + /// Commit: {0} + /// CommitRange: {1} + /// PullRequest: {2}", + /// BuildSystem.TravisCI.Environment.Repository.Commit, + /// BuildSystem.TravisCI.Environment.Repository.CommitRange, + /// BuildSystem.TravisCI.Environment.Repository.PullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Repository: + /// Commit: {0} + /// CommitRange: {1} + /// PullRequest: {2}", + /// TravisCI.Environment.Repository.Commit, + /// TravisCI.Environment.Repository.CommitRange, + /// TravisCI.Environment.Repository.PullRequest + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public TravisCIRepositoryInfo Repository { get; } /// /// Gets a value indicating whether the current build is continuous integration. @@ -53,10 +213,39 @@ public TravisCIRepositoryInfo Repository /// /// true if ci; otherwise, false. /// - public bool CI - { - get { return GetEnvironmentBoolean("CI"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"CI: {0}", + /// BuildSystem.TravisCI.Environment.CI + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"CI: {0}", + /// TravisCI.Environment.CI + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public bool CI => GetEnvironmentBoolean("CI"); /// /// Gets the Travis CI home directory. @@ -64,10 +253,39 @@ public bool CI /// /// The home. /// - public string Home - { - get { return GetEnvironmentString("HOME"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Home: {0}", + /// BuildSystem.TravisCI.Environment.Home + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Home: {0}", + /// TravisCI.Environment.Home + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public string Home => GetEnvironmentString("HOME"); /// /// Gets a value indicating whether the environment is Travis. @@ -75,10 +293,39 @@ public string Home /// /// true if Travis; otherwise, false. /// - public bool Travis - { - get { return GetEnvironmentBoolean("TRAVIS"); } - } + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Travis: {0}", + /// BuildSystem.TravisCI.Environment.Travis + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + /// Via TravisCI. + /// + /// + /// if (TravisCI.IsRunningOnTravisCI) + /// { + /// Information( + /// @"Travis: {0}", + /// TravisCI.Environment.Travis + /// ); + /// } + /// else + /// { + /// Information("Not running on TravisCI"); + /// } + /// + /// + public bool Travis => GetEnvironmentBoolean("TRAVIS"); /// /// Initializes a new instance of the class. @@ -87,9 +334,10 @@ public bool Travis public TravisCIEnvironmentInfo(ICakeEnvironment environment) : base(environment) { - _buildProvider = new TravisCIBuildInfo(environment); - _jobProvider = new TravisCIJobInfo(environment); - _repositoryProvider = new TravisCIRepositoryInfo(environment); + Build = new TravisCIBuildInfo(environment); + PullRequest = new TravisCIPullRequestInfo(environment); + Job = new TravisCIJobInfo(environment); + Repository = new TravisCIRepositoryInfo(environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/Data/TravisCIJobInfo.cs b/src/Cake.Common/Build/TravisCI/Data/TravisCIJobInfo.cs index 7789ea05b9..6ba440c9b7 100644 --- a/src/Cake.Common/Build/TravisCI/Data/TravisCIJobInfo.cs +++ b/src/Cake.Common/Build/TravisCI/Data/TravisCIJobInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.TravisCI.Data @@ -11,15 +12,12 @@ namespace Cake.Common.Build.TravisCI.Data public sealed class TravisCIJobInfo : TravisCIInfo { /// - /// Gets the job identifier for the current job.. + /// Gets the job identifier for the current job. /// /// /// The job identifier. /// - public string JobId - { - get { return GetEnvironmentString("TRAVIS_JOB_ID"); } - } + public string JobId => GetEnvironmentString("TRAVIS_JOB_ID"); /// /// Gets the job number for the current job. @@ -27,10 +25,7 @@ public string JobId /// /// The job number. /// - public string JobNumber - { - get { return GetEnvironmentString("TRAVIS_JOB_NUMBER"); } - } + public string JobNumber => GetEnvironmentString("TRAVIS_JOB_NUMBER"); /// /// Gets the name of the operating system for the current job. @@ -38,10 +33,8 @@ public string JobNumber /// /// The name of the os. /// - public string OSName - { - get { return GetEnvironmentString("TRAVIS_OS_NAME"); } - } + // ReSharper disable once InconsistentNaming + public string OSName => GetEnvironmentString("TRAVIS_OS_NAME"); /// /// Gets a value indicating whether encrypted environment variables are being used for the current job. @@ -49,10 +42,7 @@ public string OSName /// /// true if [secure environment variables are in use]; otherwise, false. /// - public bool SecureEnvironmentVariables - { - get { return GetEnvironmentBoolean("TRAVIS_SECURE_ENV_VARS"); } - } + public bool SecureEnvironmentVariables => GetEnvironmentBoolean("TRAVIS_SECURE_ENV_VARS"); /// /// Initializes a new instance of the class. @@ -63,4 +53,4 @@ public TravisCIJobInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/Data/TravisCIPullRequestInfo.cs b/src/Cake.Common/Build/TravisCI/Data/TravisCIPullRequestInfo.cs new file mode 100644 index 0000000000..cf9199422c --- /dev/null +++ b/src/Cake.Common/Build/TravisCI/Data/TravisCIPullRequestInfo.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.TravisCI.Data +{ + /// + /// Provides TravisCI pull request information for a current build. + /// + public sealed class TravisCIPullRequestInfo : TravisCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public TravisCIPullRequestInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets a value indicating whether the current build was started by a pull request. + /// + /// + /// true if the current build was started by a pull request; otherwise, false. + /// + public bool IsPullRequest => Id > 0; + + /// + /// Gets the pull request id. + /// + /// + /// The pull request id. + /// + public int Id => GetEnvironmentInteger("TRAVIS_PULL_REQUEST"); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/Data/TravisCIRepositoryInfo.cs b/src/Cake.Common/Build/TravisCI/Data/TravisCIRepositoryInfo.cs index c06df43017..804f8e7e4e 100644 --- a/src/Cake.Common/Build/TravisCI/Data/TravisCIRepositoryInfo.cs +++ b/src/Cake.Common/Build/TravisCI/Data/TravisCIRepositoryInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; namespace Cake.Common.Build.TravisCI.Data @@ -16,10 +17,7 @@ public sealed class TravisCIRepositoryInfo : TravisCIInfo /// /// The commit. /// - public string Commit - { - get { return GetEnvironmentString("TRAVIS_COMMIT"); } - } + public string Commit => GetEnvironmentString("TRAVIS_COMMIT"); /// /// Gets the commit range for the current pull request. @@ -27,10 +25,7 @@ public string Commit /// /// The commit range. /// - public string CommitRange - { - get { return GetEnvironmentString("TRAVIS_COMMIT_RANGE"); } - } + public string CommitRange => GetEnvironmentString("TRAVIS_COMMIT_RANGE"); /// /// Gets the pull request. @@ -38,21 +33,15 @@ public string CommitRange /// /// The pull request. /// - public string PullRequest - { - get { return GetEnvironmentString("TRAVIS_PULL_REQUEST"); } - } + public string PullRequest => GetEnvironmentString("TRAVIS_PULL_REQUEST"); /// - /// Gets the slug of the respository currently being built. + /// Gets the slug of the repository currently being built. /// /// /// The slug. /// - public string Slug - { - get { return GetEnvironmentString("TRAVIS_REPO_SLUG"); } - } + public string Slug => GetEnvironmentString("TRAVIS_REPO_SLUG"); /// /// Initializes a new instance of the class. @@ -63,4 +52,4 @@ public TravisCIRepositoryInfo(ICakeEnvironment environment) { } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/ITravisCIProvider.cs b/src/Cake.Common/Build/TravisCI/ITravisCIProvider.cs index e9a493a213..ce79316b88 100644 --- a/src/Cake.Common/Build/TravisCI/ITravisCIProvider.cs +++ b/src/Cake.Common/Build/TravisCI/ITravisCIProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Common.Build.TravisCI.Data; namespace Cake.Common.Build.TravisCI @@ -38,4 +39,4 @@ public interface ITravisCIProvider /// Name of the group. void WriteEndFold(string name); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/TravisCIDisposableExtensions.cs b/src/Cake.Common/Build/TravisCI/TravisCIDisposableExtensions.cs index 617283f0dd..c716066042 100644 --- a/src/Cake.Common/Build/TravisCI/TravisCIDisposableExtensions.cs +++ b/src/Cake.Common/Build/TravisCI/TravisCIDisposableExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; namespace Cake.Common.Build.TravisCI @@ -18,10 +19,7 @@ public static class TravisCIDisposableExtensions /// An . public static IDisposable Fold(this ITravisCIProvider travisCIProvider, string name) { - if (travisCIProvider == null) - { - throw new ArgumentNullException("travisCIProvider"); - } + ArgumentNullException.ThrowIfNull(travisCIProvider); travisCIProvider.WriteStartFold(name); return new TravisCIActionDisposable(travisCIProvider, tci => tci.WriteEndFold(name)); } @@ -54,4 +52,4 @@ public void Dispose() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/TravisCIInfo.cs b/src/Cake.Common/Build/TravisCI/TravisCIInfo.cs index 5b48417d13..8fc38fdaa4 100644 --- a/src/Cake.Common/Build/TravisCI/TravisCIInfo.cs +++ b/src/Cake.Common/Build/TravisCI/TravisCIInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; @@ -66,4 +67,4 @@ protected bool GetEnvironmentBoolean(string variable) return false; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/TravisCI/TravisCIProvider.cs b/src/Cake.Common/Build/TravisCI/TravisCIProvider.cs index 282ad36906..b0691d5553 100644 --- a/src/Cake.Common/Build/TravisCI/TravisCIProvider.cs +++ b/src/Cake.Common/Build/TravisCI/TravisCIProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -8,7 +9,6 @@ using System.Linq; using Cake.Common.Build.TravisCI.Data; using Cake.Core; -using Cake.Core.Diagnostics; namespace Cake.Common.Build.TravisCI { @@ -19,9 +19,10 @@ public sealed class TravisCIProvider : ITravisCIProvider { private const string MessagePrefix = "travis_"; private const string MessagePostfix = "\r"; + private readonly ICakeEnvironment _environment; - private readonly ICakeLog _log; - private readonly TravisCIEnvironmentInfo _environmentInfo; + private readonly IBuildSystemServiceMessageWriter _writer; + private static readonly Dictionary _sanitizationTokens; static TravisCIProvider() @@ -41,63 +42,32 @@ static TravisCIProvider() /// Initializes a new instance of the class. /// /// The environment. - /// The log. - public TravisCIProvider(ICakeEnvironment environment, ICakeLog log) + /// The log. + public TravisCIProvider(ICakeEnvironment environment, IBuildSystemServiceMessageWriter writer) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - _environment = environment; - _log = log; - _environmentInfo = new TravisCIEnvironmentInfo(environment); + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + Environment = new TravisCIEnvironmentInfo(environment); } - /// - /// Gets the Travis CI environment. - /// - /// - /// The environment. - /// - public TravisCIEnvironmentInfo Environment - { - get { return _environmentInfo; } - } + /// + public bool IsRunningOnTravisCI => !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("TRAVIS")); - /// - /// Gets a value indicating whether this instance is running on Travis CI. - /// - /// - /// true if this instance is running on Travis CI; otherwise, false. - /// - public bool IsRunningOnTravisCI - { - get { return !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("TRAVIS")); } - } + /// + public TravisCIEnvironmentInfo Environment { get; } - /// - /// Write the start of a message fold to the Travis CI build log. - /// - /// Name of the group. + /// public void WriteStartFold(string name) { WriteServiceMessage("fold", "start", name); } - /// - /// Write the start of a message fold to the Travis CI build log. - /// - /// Name of the group. + /// public void WriteEndFold(string name) { WriteServiceMessage("fold", "end", name); } - private void WriteServiceMessage(string messageName, string attributeValue) - { - WriteServiceMessage(messageName, new Dictionary { { " ", attributeValue } }); - } - private void WriteServiceMessage(string messageName, string attributeName, string attributeValue) { WriteServiceMessage(messageName, new Dictionary { { attributeName, attributeValue } }); @@ -118,7 +88,7 @@ private void WriteServiceMessage(string messageName, Dictionary return string.Format(CultureInfo.InvariantCulture, ":{0}:{1}", keypair.Key, Sanitize(keypair.Value)); }) .ToArray()); - _log.Write(Verbosity.Quiet, LogLevel.Information, "{0}{1}{2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); + _writer.Write("{0}{1}{2}{3}", MessagePrefix, messageName, valueString, MessagePostfix); } private static string Sanitize(string source) @@ -130,4 +100,4 @@ private static string Sanitize(string source) return source; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Build/WoodpeckerCI/Commands/WoodpeckerCICommands.cs b/src/Cake.Common/Build/WoodpeckerCI/Commands/WoodpeckerCICommands.cs new file mode 100644 index 0000000000..ad6693fbb8 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Commands/WoodpeckerCICommands.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.WoodpeckerCI.Commands +{ + /// + /// Provides WoodpeckerCI commands for a current build. + /// + public sealed class WoodpeckerCICommands + { + private readonly ICakeEnvironment _environment; + private readonly IFileSystem _fileSystem; + private readonly WoodpeckerCIEnvironmentInfo _woodpeckerEnvironment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + /// The WoodpeckerCI environment. + public WoodpeckerCICommands( + ICakeEnvironment environment, + IFileSystem fileSystem, + WoodpeckerCIEnvironmentInfo woodpeckerEnvironment) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _woodpeckerEnvironment = woodpeckerEnvironment ?? throw new ArgumentNullException(nameof(woodpeckerEnvironment)); + } + + /// + /// Sets an environment variable that will be available to subsequent steps. + /// + /// The environment variable name. + /// The environment variable value. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// WoodpeckerCI.Commands.SetEnvironmentVariable("MY_VAR", "my_value"); + /// } + /// + /// + public void SetEnvironmentVariable(string name, string value) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Environment variable name cannot be null or empty.", nameof(name)); + } + + // Write to the WoodpeckerCI environment file for persistence between steps + var envFile = _woodpeckerEnvironment.Workspace?.CombineWithFilePath(".woodpecker/env"); + if (envFile != null) + { + var file = _fileSystem.GetFile(envFile); + using var stream = file.Open(FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(stream); + writer.Write(name); + writer.Write('='); + writer.Write(value); + writer.Write('\n'); + } + } + + /// + /// Gets an environment variable that was set by a previous step. + /// + /// The environment variable name. + /// The environment variable value, or null if not found. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// var value = WoodpeckerCI.Commands.GetEnvironmentVariable("MY_VAR"); + /// Information("MY_VAR = {0}", value); + /// } + /// + /// + public string GetEnvironmentVariable(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Environment variable name cannot be null or empty.", nameof(name)); + } + + return _environment.GetEnvironmentVariable(name); + } + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCICommitInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCICommitInfo.cs new file mode 100644 index 0000000000..00229e3997 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCICommitInfo.cs @@ -0,0 +1,159 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI commit information for the current build. + /// + public class WoodpeckerCICommitInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCICommitInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the commit SHA. + /// + /// + /// The commit SHA. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Commit SHA: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Commit.Sha + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Commit SHA: {0}", + /// WoodpeckerCI.Environment.Commit.Sha + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string Sha => GetEnvironmentString("CI_COMMIT_SHA"); + + /// + /// Gets the commit ref. + /// + /// + /// The commit ref. + /// + public string Ref => GetEnvironmentString("CI_COMMIT_REF"); + + /// + /// Gets the commit ref spec. + /// + /// + /// The commit ref spec. + /// + public string Refspec => GetEnvironmentString("CI_COMMIT_REFSPEC"); + + /// + /// Gets the commit branch. + /// + /// + /// The commit branch. + /// + public string Branch => GetEnvironmentString("CI_COMMIT_BRANCH"); + + /// + /// Gets the commit source branch. + /// + /// + /// The commit source branch. + /// + public string SourceBranch => GetEnvironmentString("CI_COMMIT_SOURCE_BRANCH"); + + /// + /// Gets the commit target branch. + /// + /// + /// The commit target branch. + /// + public string TargetBranch => GetEnvironmentString("CI_COMMIT_TARGET_BRANCH"); + + /// + /// Gets the commit tag name. + /// + /// + /// The commit tag name. + /// + public string Tag => GetEnvironmentString("CI_COMMIT_TAG"); + + /// + /// Gets the commit pull request number. + /// + /// + /// The commit pull request number. + /// + public string PullRequest => GetEnvironmentString("CI_COMMIT_PULL_REQUEST"); + + /// + /// Gets the commit pull request labels. + /// + /// + /// The commit pull request labels. + /// + public string PullRequestLabels => GetEnvironmentString("CI_COMMIT_PULL_REQUEST_LABELS"); + + /// + /// Gets the commit message. + /// + /// + /// The commit message. + /// + public string Message => GetEnvironmentString("CI_COMMIT_MESSAGE"); + + /// + /// Gets the commit author username. + /// + /// + /// The commit author username. + /// + public string Author => GetEnvironmentString("CI_COMMIT_AUTHOR"); + + /// + /// Gets the commit author email address. + /// + /// + /// The commit author email address. + /// + public string AuthorEmail => GetEnvironmentString("CI_COMMIT_AUTHOR_EMAIL"); + + /// + /// Gets a value indicating whether the release is a pre-release. + /// + /// + /// true if the release is a pre-release; otherwise, false. + /// + public bool PreRelease => GetEnvironmentBoolean("CI_COMMIT_PRERELEASE"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfo.cs new file mode 100644 index 0000000000..838f753806 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIEnvironmentInfo.cs @@ -0,0 +1,441 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI environment information for the current build. + /// + public sealed class WoodpeckerCIEnvironmentInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIEnvironmentInfo(ICakeEnvironment environment) + : base(environment) + { + Repository = new WoodpeckerCIRepositoryInfo(environment); + Commit = new WoodpeckerCICommitInfo(environment); + Pipeline = new WoodpeckerCIPipelineInfo(environment); + Workflow = new WoodpeckerCIWorkflowInfo(environment); + Step = new WoodpeckerCIStepInfo(environment); + System = new WoodpeckerCISystemInfo(environment); + Forge = new WoodpeckerCIForgeInfo(environment); + } + + /// + /// Gets the CI environment name. + /// + /// + /// The CI environment name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"CI Environment: {0}", + /// BuildSystem.WoodpeckerCI.Environment.CI + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"CI Environment: {0}", + /// WoodpeckerCI.Environment.CI + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string CI => GetEnvironmentString("CI"); + + /// + /// Gets the workspace path. + /// + /// + /// The workspace path. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workspace: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Workspace + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workspace: {0}", + /// WoodpeckerCI.Environment.Workspace + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public DirectoryPath Workspace => GetEnvironmentDirectoryPath("CI_WORKSPACE"); + + /// + /// Gets WoodpeckerCI repository information. + /// + /// + /// The WoodpeckerCI repository information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Repository: + /// Repo: {0} + /// Owner: {1} + /// Name: {2}", + /// BuildSystem.WoodpeckerCI.Environment.Repository.Repo, + /// BuildSystem.WoodpeckerCI.Environment.Repository.RepoOwner, + /// BuildSystem.WoodpeckerCI.Environment.Repository.RepoName + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Repository: + /// Repo: {0} + /// Owner: {1} + /// Name: {2}", + /// WoodpeckerCI.Environment.Repository.Repo, + /// WoodpeckerCI.Environment.Repository.RepoOwner, + /// WoodpeckerCI.Environment.Repository.RepoName + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIRepositoryInfo Repository { get; } + + /// + /// Gets WoodpeckerCI commit information. + /// + /// + /// The WoodpeckerCI commit information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Commit: + /// SHA: {0} + /// Branch: {1} + /// Message: {2}", + /// BuildSystem.WoodpeckerCI.Environment.Commit.Sha, + /// BuildSystem.WoodpeckerCI.Environment.Commit.Branch, + /// BuildSystem.WoodpeckerCI.Environment.Commit.Message + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Commit: + /// SHA: {0} + /// Branch: {1} + /// Message: {2}", + /// WoodpeckerCI.Environment.Commit.Sha, + /// WoodpeckerCI.Environment.Commit.Branch, + /// WoodpeckerCI.Environment.Commit.Message + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCICommitInfo Commit { get; } + + /// + /// Gets WoodpeckerCI pipeline information. + /// + /// + /// The WoodpeckerCI pipeline information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Pipeline: + /// Number: {0} + /// Event: {1}", + /// BuildSystem.WoodpeckerCI.Environment.Pipeline.Number, + /// BuildSystem.WoodpeckerCI.Environment.Pipeline.Event + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Pipeline: + /// Number: {0} + /// Event: {1}", + /// WoodpeckerCI.Environment.Pipeline.Number, + /// WoodpeckerCI.Environment.Pipeline.Event + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIPipelineInfo Pipeline { get; } + + /// + /// Gets WoodpeckerCI workflow information. + /// + /// + /// The WoodpeckerCI workflow information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workflow: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Workflow.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workflow: {0}", + /// WoodpeckerCI.Environment.Workflow.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIWorkflowInfo Workflow { get; } + + /// + /// Gets WoodpeckerCI step information. + /// + /// + /// The WoodpeckerCI step information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Step: + /// Name: {0} + /// Number: {1}", + /// BuildSystem.WoodpeckerCI.Environment.Step.Name, + /// BuildSystem.WoodpeckerCI.Environment.Step.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Step: + /// Name: {0} + /// Number: {1}", + /// WoodpeckerCI.Environment.Step.Name, + /// WoodpeckerCI.Environment.Step.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIStepInfo Step { get; } + + /// + /// Gets WoodpeckerCI system information. + /// + /// + /// The WoodpeckerCI system information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"System: + /// Name: {0} + /// Version: {1}", + /// BuildSystem.WoodpeckerCI.Environment.System.Name, + /// BuildSystem.WoodpeckerCI.Environment.System.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"System: + /// Name: {0} + /// Version: {1}", + /// WoodpeckerCI.Environment.System.Name, + /// WoodpeckerCI.Environment.System.Version + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCISystemInfo System { get; } + + /// + /// Gets WoodpeckerCI forge information. + /// + /// + /// The WoodpeckerCI forge information. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Forge: + /// Type: {0} + /// URL: {1}", + /// BuildSystem.WoodpeckerCI.Environment.Forge.Type, + /// BuildSystem.WoodpeckerCI.Environment.Forge.Url + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Forge: + /// Type: {0} + /// URL: {1}", + /// WoodpeckerCI.Environment.Forge.Type, + /// WoodpeckerCI.Environment.Forge.Url + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIForgeInfo Forge { get; } + + private DirectoryPath GetEnvironmentDirectoryPath(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) ? new DirectoryPath(value) : null; + } + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfo.cs new file mode 100644 index 0000000000..ed21d1c2c6 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeInfo.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI forge information for the current build. + /// + public class WoodpeckerCIForgeInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIForgeInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the forge type. + /// + /// + /// The forge type. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Forge Type: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Forge.Type + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Forge Type: {0}", + /// WoodpeckerCI.Environment.Forge.Type + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public WoodpeckerCIForgeType Type => WoodpeckerCIForgeTypeExtensions.ParseForgeType(GetEnvironmentString("CI_FORGE_TYPE")); + + /// + /// Gets the forge URL. + /// + /// + /// The forge URL. + /// + public Uri Url => GetEnvironmentUri("CI_FORGE_URL"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeType.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeType.cs new file mode 100644 index 0000000000..85944bc66c --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIForgeType.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Represents the type of forge used by WoodpeckerCI. + /// + public enum WoodpeckerCIForgeType + { + /// + /// Unknown forge type. + /// + Unknown = 0, + + /// + /// Bitbucket forge. + /// + Bitbucket = 1, + + /// + /// Bitbucket Data Center forge. + /// + BitbucketDC = 2, + + /// + /// Forgejo forge. + /// + Forgejo = 3, + + /// + /// Gitea forge. + /// + Gitea = 4, + + /// + /// GitHub forge. + /// + GitHub = 5, + + /// + /// GitLab forge. + /// + GitLab = 6 + } + + /// + /// Extension methods for . + /// + public static class WoodpeckerCIForgeTypeExtensions + { + /// + /// Parses a string value to a enum value. + /// + /// The string value to parse. + /// The parsed enum value, or if parsing fails. + public static WoodpeckerCIForgeType ParseForgeType(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return WoodpeckerCIForgeType.Unknown; + } + + // Try to parse case-insensitively + if (Enum.TryParse(value, true, out var result)) + { + return result; + } + + // Handle special cases for known values that don't match enum names exactly + var normalizedValue = value.ToLowerInvariant(); + return normalizedValue switch + { + "bitbucket" => WoodpeckerCIForgeType.Bitbucket, + "bitbucket_dc" => WoodpeckerCIForgeType.BitbucketDC, + "forgejo" => WoodpeckerCIForgeType.Forgejo, + "gitea" => WoodpeckerCIForgeType.Gitea, + "github" => WoodpeckerCIForgeType.GitHub, + "gitlab" => WoodpeckerCIForgeType.GitLab, + _ => WoodpeckerCIForgeType.Unknown + }; + } + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfo.cs new file mode 100644 index 0000000000..1d7b27239a --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIPipelineInfo.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI pipeline information for the current build. + /// + public class WoodpeckerCIPipelineInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIPipelineInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the pipeline number. + /// + /// + /// The pipeline number. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Pipeline Number: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Pipeline.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Pipeline Number: {0}", + /// WoodpeckerCI.Environment.Pipeline.Number + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public int Number => GetEnvironmentInteger("CI_PIPELINE_NUMBER"); + + /// + /// Gets the pipeline parent number. + /// + /// + /// The pipeline parent number. + /// + public int Parent => GetEnvironmentInteger("CI_PIPELINE_PARENT"); + + /// + /// Gets the pipeline event. + /// + /// + /// The pipeline event. + /// + public string Event => GetEnvironmentString("CI_PIPELINE_EVENT"); + + /// + /// Gets the pipeline URL. + /// + /// + /// The pipeline URL. + /// + public Uri Url => GetEnvironmentUri("CI_PIPELINE_URL"); + + /// + /// Gets the pipeline forge URL. + /// + /// + /// The pipeline forge URL. + /// + public Uri ForgeUrl => GetEnvironmentUri("CI_PIPELINE_FORGE_URL"); + + /// + /// Gets the pipeline deploy target. + /// + /// + /// The pipeline deploy target. + /// + public string DeployTarget => GetEnvironmentString("CI_PIPELINE_DEPLOY_TARGET"); + + /// + /// Gets the pipeline deploy task. + /// + /// + /// The pipeline deploy task. + /// + public string DeployTask => GetEnvironmentString("CI_PIPELINE_DEPLOY_TASK"); + + /// + /// Gets the pipeline created timestamp. + /// + /// + /// The pipeline created timestamp. + /// + public DateTimeOffset Created => GetEnvironmentDateTimeOffset("CI_PIPELINE_CREATED"); + + /// + /// Gets the pipeline started timestamp. + /// + /// + /// The pipeline started timestamp. + /// + public DateTimeOffset Started => GetEnvironmentDateTimeOffset("CI_PIPELINE_STARTED"); + + /// + /// Gets the pipeline files. + /// + /// + /// The pipeline files. + /// + public string Files => GetEnvironmentString("CI_PIPELINE_FILES"); + + /// + /// Gets the pipeline author username. + /// + /// + /// The pipeline author username. + /// + public string Author => GetEnvironmentString("CI_PIPELINE_AUTHOR"); + + /// + /// Gets the pipeline author avatar. + /// + /// + /// The pipeline author avatar. + /// + public Uri Avatar => GetEnvironmentUri("CI_PIPELINE_AVATAR"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfo.cs new file mode 100644 index 0000000000..832a2710f3 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIRepositoryInfo.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI repository information for the current build. + /// + public class WoodpeckerCIRepositoryInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIRepositoryInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the repository full name. + /// + /// + /// The repository full name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Repository: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Repository.Repo + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Repository: {0}", + /// WoodpeckerCI.Environment.Repository.Repo + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string Repo => GetEnvironmentString("CI_REPO"); + + /// + /// Gets the repository owner. + /// + /// + /// The repository owner. + /// + public string RepoOwner => GetEnvironmentString("CI_REPO_OWNER"); + + /// + /// Gets the repository name. + /// + /// + /// The repository name. + /// + public string RepoName => GetEnvironmentString("CI_REPO_NAME"); + + /// + /// Gets the repository remote ID. + /// + /// + /// The repository remote ID. + /// + public string RepoRemoteId => GetEnvironmentString("CI_REPO_REMOTE_ID"); + + /// + /// Gets the repository URL. + /// + /// + /// The repository URL. + /// + public Uri RepoUrl => GetEnvironmentUri("CI_REPO_URL"); + + /// + /// Gets the repository clone URL. + /// + /// + /// The repository clone URL. + /// + public Uri RepoCloneUrl => GetEnvironmentUri("CI_REPO_CLONE_URL"); + + /// + /// Gets the repository SSH clone URL. + /// + /// + /// The repository SSH clone URL. + /// + public string RepoCloneSshUrl => GetEnvironmentString("CI_REPO_CLONE_SSH_URL"); + + /// + /// Gets the repository default branch. + /// + /// + /// The repository default branch. + /// + public string RepoDefaultBranch => GetEnvironmentString("CI_REPO_DEFAULT_BRANCH"); + + /// + /// Gets a value indicating whether the repository is private. + /// + /// + /// true if the repository is private; otherwise, false. + /// + public bool RepoPrivate => GetEnvironmentBoolean("CI_REPO_PRIVATE"); + + /// + /// Gets a value indicating whether the repository has trusted network access. + /// + /// + /// true if the repository has trusted network access; otherwise, false. + /// + public bool RepoTrustedNetwork => GetEnvironmentBoolean("CI_REPO_TRUSTED_NETWORK"); + + /// + /// Gets a value indicating whether the repository has trusted volumes access. + /// + /// + /// true if the repository has trusted volumes access; otherwise, false. + /// + public bool RepoTrustedVolumes => GetEnvironmentBoolean("CI_REPO_TRUSTED_VOLUMES"); + + /// + /// Gets a value indicating whether the repository has trusted security access. + /// + /// + /// true if the repository has trusted security access; otherwise, false. + /// + public bool RepoTrustedSecurity => GetEnvironmentBoolean("CI_REPO_TRUSTED_SECURITY"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfo.cs new file mode 100644 index 0000000000..f9ae88c474 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIStepInfo.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI step information for the current build. + /// + public class WoodpeckerCIStepInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIStepInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the step name. + /// + /// + /// The step name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Step Name: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Step.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Step Name: {0}", + /// WoodpeckerCI.Environment.Step.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string Name => GetEnvironmentString("CI_STEP_NAME"); + + /// + /// Gets the step number. + /// + /// + /// The step number. + /// + public int Number => GetEnvironmentInteger("CI_STEP_NUMBER"); + + /// + /// Gets the step started timestamp. + /// + /// + /// The step started timestamp. + /// + public DateTimeOffset Started => GetEnvironmentDateTimeOffset("CI_STEP_STARTED"); + + /// + /// Gets the step URL. + /// + /// + /// The step URL. + /// + public Uri Url => GetEnvironmentUri("CI_STEP_URL"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCISystemInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCISystemInfo.cs new file mode 100644 index 0000000000..28ea95e724 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCISystemInfo.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI system information for the current build. + /// + public class WoodpeckerCISystemInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCISystemInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the system name. + /// + /// + /// The system name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"System Name: {0}", + /// BuildSystem.WoodpeckerCI.Environment.System.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"System Name: {0}", + /// WoodpeckerCI.Environment.System.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string Name => GetEnvironmentString("CI_SYSTEM_NAME"); + + /// + /// Gets the system URL. + /// + /// + /// The system URL. + /// + public string Url => GetEnvironmentString("CI_SYSTEM_URL"); + + /// + /// Gets the system host. + /// + /// + /// The system host. + /// + public string Host => GetEnvironmentString("CI_SYSTEM_HOST"); + + /// + /// Gets the system version. + /// + /// + /// The system version. + /// + public string Version => GetEnvironmentString("CI_SYSTEM_VERSION"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIWorkflowInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIWorkflowInfo.cs new file mode 100644 index 0000000000..77b9ff72a7 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/Data/WoodpeckerCIWorkflowInfo.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI.Data +{ + /// + /// Provides WoodpeckerCI workflow information for the current build. + /// + public class WoodpeckerCIWorkflowInfo : WoodpeckerCIInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The environment. + public WoodpeckerCIWorkflowInfo(ICakeEnvironment environment) + : base(environment) + { + } + + /// + /// Gets the workflow name. + /// + /// + /// The workflow name. + /// + /// Via BuildSystem. + /// + /// + /// if (BuildSystem.WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workflow Name: {0}", + /// BuildSystem.WoodpeckerCI.Environment.Workflow.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + /// Via WoodpeckerCI. + /// + /// + /// if (WoodpeckerCI.IsRunningOnWoodpeckerCI) + /// { + /// Information( + /// @"Workflow Name: {0}", + /// WoodpeckerCI.Environment.Workflow.Name + /// ); + /// } + /// else + /// { + /// Information("Not running on WoodpeckerCI"); + /// } + /// + /// + public string Name => GetEnvironmentString("CI_WORKFLOW_NAME"); + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/IWoodpeckerCIProvider.cs b/src/Cake.Common/Build/WoodpeckerCI/IWoodpeckerCIProvider.cs new file mode 100644 index 0000000000..bd67f11b43 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/IWoodpeckerCIProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Build.WoodpeckerCI.Commands; +using Cake.Common.Build.WoodpeckerCI.Data; + +namespace Cake.Common.Build.WoodpeckerCI +{ + /// + /// Represents a WoodpeckerCI provider. + /// + public interface IWoodpeckerCIProvider + { + /// + /// Gets a value indicating whether the current build is running on WoodpeckerCI. + /// + /// + /// true if the current build is running on WoodpeckerCI; otherwise, false. + /// + bool IsRunningOnWoodpeckerCI { get; } + + /// + /// Gets the WoodpeckerCI environment. + /// + /// + /// The WoodpeckerCI environment. + /// + WoodpeckerCIEnvironmentInfo Environment { get; } + + /// + /// Gets the WoodpeckerCI commands. + /// + /// + /// The WoodpeckerCI commands. + /// + WoodpeckerCICommands Commands { get; } + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIInfo.cs b/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIInfo.cs new file mode 100644 index 0000000000..f2d8cfbe98 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIInfo.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; + +namespace Cake.Common.Build.WoodpeckerCI +{ + /// + /// Base class used to provide information about the WoodpeckerCI environment. + /// + public abstract class WoodpeckerCIInfo + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + protected WoodpeckerCIInfo(ICakeEnvironment environment) + { + _environment = environment; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected string GetEnvironmentString(string variable) + { + return _environment.GetEnvironmentVariable(variable) ?? string.Empty; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected int GetEnvironmentInteger(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) && int.TryParse(value, out var result) ? result : 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected long GetEnvironmentLong(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) && long.TryParse(value, out var result) ? result : 0; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable. + protected bool GetEnvironmentBoolean(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) && bool.TryParse(value, out var result) && result; + } + + /// + /// Gets an environment variable as a . + /// + /// The environment variable name. + /// The environment variable as a Uri, or null if the value is not a valid absolute URI. + protected Uri GetEnvironmentUri(string variable) + { + var value = GetEnvironmentString(variable); + return !string.IsNullOrWhiteSpace(value) && Uri.TryCreate(value, UriKind.Absolute, out var result) ? result : null; + } + + /// + /// Gets an environment variable as a from a Unix timestamp. + /// + /// The environment variable name. + /// The environment variable as a DateTimeOffset, or DateTimeOffset.MinValue if the value is not a valid Unix timestamp. + protected DateTimeOffset GetEnvironmentDateTimeOffset(string variable) + { + var timestamp = GetEnvironmentLong(variable); + return timestamp > 0 ? DateTimeOffset.FromUnixTimeSeconds(timestamp) : DateTimeOffset.MinValue; + } + } +} diff --git a/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIProvider.cs b/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIProvider.cs new file mode 100644 index 0000000000..4745181a56 --- /dev/null +++ b/src/Cake.Common/Build/WoodpeckerCI/WoodpeckerCIProvider.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Build.WoodpeckerCI.Commands; +using Cake.Common.Build.WoodpeckerCI.Data; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Build.WoodpeckerCI +{ + /// + /// Responsible for communicating with WoodpeckerCI. + /// + public sealed class WoodpeckerCIProvider : IWoodpeckerCIProvider + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The environment. + /// The file system. + public WoodpeckerCIProvider(ICakeEnvironment environment, IFileSystem fileSystem) + { + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + Environment = new WoodpeckerCIEnvironmentInfo(environment); + Commands = new WoodpeckerCICommands(environment, fileSystem, Environment); + } + + /// + public bool IsRunningOnWoodpeckerCI => !string.IsNullOrWhiteSpace(Environment.CI) && + Environment.CI.Equals("woodpecker", StringComparison.OrdinalIgnoreCase); + + /// + public WoodpeckerCIEnvironmentInfo Environment { get; } + + /// + public WoodpeckerCICommands Commands { get; } + } +} diff --git a/src/Cake.Common/Cake.Common.csproj b/src/Cake.Common/Cake.Common.csproj index 26d2262fc0..b8746a591c 100644 --- a/src/Cake.Common/Cake.Common.csproj +++ b/src/Cake.Common/Cake.Common.csproj @@ -1,450 +1,19 @@ - - - - - Debug - AnyCPU - {ABC3F1CB-F84E-43ED-A120-0CCFE344D250} - Library - Properties - Cake.Common - Cake.Common - v4.5 - 512 - 7bed147e - false - 5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Cake.Common.xml - false - ..\Cake.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Cake.Common.xml - true - ..\Cake.ruleset - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {8074B833-11B8-459F-BB98-BFBA2BC5C698} - Cake.Core - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + Cake.Common + Library + AnyCpu + true + + + + Provides aliases (extension methods on Cake context) that support CI, build, unit tests, zip, signing, etc. for Cake. + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Common/Diagnostics/LoggingAliases.Disposable.cs b/src/Cake.Common/Diagnostics/LoggingAliases.Disposable.cs new file mode 100644 index 0000000000..8cf500e9c0 --- /dev/null +++ b/src/Cake.Common/Diagnostics/LoggingAliases.Disposable.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.Diagnostics; + +namespace Cake.Common.Diagnostics; + +public static partial class LoggingAliases +{ + /// + /// Sets the log verbosity to quiet and returns a disposable that restores the log verbosity on dispose. + /// + /// the context. + /// A disposable that restores the log verbosity. + /// + /// + /// using (QuietVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Hide me."); + /// Information("Hide me."); + /// Verbose("Hide me."); + /// Debug("Hide me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable QuietVerbosity(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.QuietVerbosity(); + } + + /// + /// Sets the log verbosity to minimal and returns a disposable that restores the log verbosity on dispose. + /// + /// the context. + /// A disposable that restores the log verbosity. + /// + /// + /// using (MinimalVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Show me."); + /// Information("Hide me."); + /// Verbose("Hide me."); + /// Debug("Hide me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable MinimalVerbosity(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.MinimalVerbosity(); + } + + /// + /// Sets the log verbosity to normal and returns a disposable that restores the log verbosity on dispose. + /// + /// the context. + /// A disposable that restores the log verbosity. + /// + /// + /// using (NormalVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Show me."); + /// Information("Show me."); + /// Verbose("Hide me."); + /// Debug("Hide me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable NormalVerbosity(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.NormalVerbosity(); + } + + /// + /// Sets the log verbosity to verbose and returns a disposable that restores the log verbosity on dispose. + /// + /// the context. + /// A disposable that restores the log verbosity. + /// + /// + /// using (VerboseVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Show me."); + /// Information("Show me."); + /// Verbose("Show me."); + /// Debug("Hide me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable VerboseVerbosity(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.VerboseVerbosity(); + } + + /// + /// Sets the log verbosity to diagnostic and returns a disposable that restores the log verbosity on dispose. + /// + /// the context. + /// A disposable that restores the log verbosity. + /// + /// + /// using (DiagnosticVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Show me."); + /// Information("Show me."); + /// Verbose("Show me."); + /// Debug("Show me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable DiagnosticVerbosity(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.DiagnosticVerbosity(); + } + + /// + /// Sets the log verbosity as specified and returns a disposable that restores the log verbosity on dispose. + /// + /// The context. + /// The verbosity. + /// A disposable that restores the log verbosity. + /// + /// + /// using (DiagnosticVerbosity()) + /// { + /// Error("Show me."); + /// Warning("Show me."); + /// Information("Show me."); + /// Verbose("Show me."); + /// Debug("Show me."); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbosity")] + public static IDisposable WithVerbosity(this ICakeContext context, Verbosity verbosity) + { + ArgumentNullException.ThrowIfNull(context); + return context.Log.WithVerbosity(verbosity); + } +} diff --git a/src/Cake.Common/Diagnostics/LoggingAliases.Formattable.cs b/src/Cake.Common/Diagnostics/LoggingAliases.Formattable.cs new file mode 100644 index 0000000000..664f13da47 --- /dev/null +++ b/src/Cake.Common/Diagnostics/LoggingAliases.Formattable.cs @@ -0,0 +1,189 @@ +using System; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.Diagnostics; + +namespace Cake.Common.Diagnostics; + +public static partial class LoggingAliases +{ + /// + /// Writes an error message to the log using the specified format information. + /// + /// The context. + /// The string to be formatted. + /// + /// + /// Error($"Hello {"World"}! Today is an {DateTime.Now:dddd}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Error")] + public static void Error(this ICakeContext context, FormattableString formattable) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Error(formattable); + } + + /// + /// Writes an warning message to the log using the specified format information. + /// + /// The context. + /// The string to be formatted. + /// + /// + /// Warning($"Hello {"World"}! Today is an {DateTime.Now:dddd}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Warning")] + public static void Warning(this ICakeContext context, FormattableString formattable) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Warning(formattable); + } + + /// + /// Writes an informational message to the log using the specified formattable string. + /// + /// The context. + /// The string to be formatted. + /// + /// + /// Information($"Hello {"World"}! Today is an {DateTime.Now:dddd}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Information")] + public static void Information(this ICakeContext context, FormattableString formattable) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Information(formattable); + } + + /// + /// Writes an verbose message to the log using the specified formattable string. + /// + /// The context. + /// The string to be formatted. + /// + /// + /// Verbose($"Hello {"World"}! Today is an {DateTime.Now:dddd}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbose")] + public static void Verbose(this ICakeContext context, FormattableString formattable) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Verbose(formattable); + } + + /// + /// Writes an debug message to the log using the specified formattable string. + /// + /// The context. + /// The string to be formatted. + /// + /// + /// Debug($"Hello {"World"}! Today is an {DateTime.Now:dddd}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Debug")] + public static void Debug(this ICakeContext context, FormattableString formattable) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Debug(formattable); + } + + /// + /// Writes an error message to the log using the specified format information. + /// + /// The context. + /// The log action. + /// + /// + /// Error(logAction => logAction($"Hello {"World"}! Today is an {DateTime.Now:dddd}")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Error")] + public static void Error(this ICakeContext context, FormattableLogAction formattableLogAction) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Error(formattableLogAction); + } + + /// + /// Writes an warning message to the log using the specified format information. + /// + /// The context. + /// The log action. + /// + /// + /// Warning(logAction => logAction($"Hello {"World"}! Today is an {DateTime.Now:dddd}")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Warning")] + public static void Warning(this ICakeContext context, FormattableLogAction formattableLogAction) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Warning(formattableLogAction); + } + + /// + /// Writes an informational message to the log using the specified formattable string. + /// + /// The context. + /// The log action. + /// + /// + /// Information(logAction => logAction($"Hello {"World"}! Today is an {DateTime.Now:dddd}")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Information")] + public static void Information(this ICakeContext context, FormattableLogAction formattableLogAction) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Information(formattableLogAction); + } + + /// + /// Writes an verbose message to the log using the specified formattable string. + /// + /// The context. + /// The log action. + /// + /// + /// Verbose(logAction => logAction($"Hello {"World"}! Today is an {DateTime.Now:dddd}")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbose")] + public static void Verbose(this ICakeContext context, FormattableLogAction formattableLogAction) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Verbose(formattableLogAction); + } + + /// + /// Writes an debug message to the log using the specified formattable string. + /// + /// The context. + /// The log action. + /// + /// + /// Debug(logAction => logAction($"Hello {"World"}! Today is an {DateTime.Now:dddd}")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Debug")] + public static void Debug(this ICakeContext context, FormattableLogAction formattableLogAction) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Debug(formattableLogAction); + } +} diff --git a/src/Cake.Common/Diagnostics/LoggingAliases.cs b/src/Cake.Common/Diagnostics/LoggingAliases.cs index 94de1ff831..3e7d5f8ea3 100644 --- a/src/Cake.Common/Diagnostics/LoggingAliases.cs +++ b/src/Cake.Common/Diagnostics/LoggingAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,7 +13,7 @@ namespace Cake.Common.Diagnostics /// Contains functionality related to logging. /// [CakeAliasCategory("Logging")] - public static class LoggingAliases + public static partial class LoggingAliases { /// /// Writes an error message to the log using the specified format information. @@ -26,12 +27,10 @@ public static class LoggingAliases /// /// [CakeMethodAlias] + [CakeAliasCategory("Error")] public static void Error(this ICakeContext context, string format, params object[] args) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Error(format, args); } @@ -47,15 +46,49 @@ public static void Error(this ICakeContext context, string format, params object /// /// [CakeMethodAlias] + [CakeAliasCategory("Error")] public static void Error(this ICakeContext context, LogAction logAction) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Error(logAction); } + /// + /// Writes an error message to the log using the specified value. + /// + /// the context. + /// The value. + /// + /// + /// Error(new {FirstName = "John", LastName="Doe"}); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Error")] + public static void Error(this ICakeContext context, object value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Error(value); + } + + /// + /// Writes an error message to the log using the specified string value. + /// + /// the context. + /// The value. + /// + /// + /// Error("{string}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Error")] + public static void Error(this ICakeContext context, string value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Error(value); + } + /// /// Writes a warning message to the log using the specified format information. /// @@ -68,12 +101,10 @@ public static void Error(this ICakeContext context, LogAction logAction) /// /// [CakeMethodAlias] + [CakeAliasCategory("Warning")] public static void Warning(this ICakeContext context, string format, params object[] args) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Warning(format, args); } @@ -89,15 +120,49 @@ public static void Warning(this ICakeContext context, string format, params obje /// /// [CakeMethodAlias] + [CakeAliasCategory("Warning")] public static void Warning(this ICakeContext context, LogAction logAction) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Warning(logAction); } + /// + /// Writes an warning message to the log using the specified value. + /// + /// the context. + /// The value. + /// + /// + /// Warning(new {FirstName = "John", LastName="Doe"}); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Warning")] + public static void Warning(this ICakeContext context, object value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Warning(value); + } + + /// + /// Writes an warning message to the log using the specified string value. + /// + /// the context. + /// The value. + /// + /// + /// Warning("{string}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Warning")] + public static void Warning(this ICakeContext context, string value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Warning(value); + } + /// /// Writes an informational message to the log using the specified format information. /// @@ -110,12 +175,10 @@ public static void Warning(this ICakeContext context, LogAction logAction) /// /// [CakeMethodAlias] + [CakeAliasCategory("Information")] public static void Information(this ICakeContext context, string format, params object[] args) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Information(format, args); } @@ -131,15 +194,49 @@ public static void Information(this ICakeContext context, string format, params /// /// [CakeMethodAlias] + [CakeAliasCategory("Information")] public static void Information(this ICakeContext context, LogAction logAction) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Information(logAction); } + /// + /// Writes an informational message to the log using the specified value. + /// + /// The context. + /// The value. + /// + /// + /// Information(new {FirstName = "John", LastName="Doe"}); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Information")] + public static void Information(this ICakeContext context, object value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Information(value); + } + + /// + /// Writes an informational message to the log using the specified string value. + /// + /// The context. + /// The value. + /// + /// + /// Information("{string}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Information")] + public static void Information(this ICakeContext context, string value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Information(value); + } + /// /// Writes a verbose message to the log using the specified format information. /// @@ -152,12 +249,10 @@ public static void Information(this ICakeContext context, LogAction logAction) /// /// [CakeMethodAlias] + [CakeAliasCategory("Verbose")] public static void Verbose(this ICakeContext context, string format, params object[] args) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Verbose(format, args); } @@ -173,15 +268,49 @@ public static void Verbose(this ICakeContext context, string format, params obje /// /// [CakeMethodAlias] + [CakeAliasCategory("Verbose")] public static void Verbose(this ICakeContext context, LogAction logAction) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Verbose(logAction); } + /// + /// Writes a verbose message to the log using the specified value. + /// + /// The context. + /// The value. + /// + /// + /// Verbose(new {FirstName = "John", LastName="Doe"}); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbose")] + public static void Verbose(this ICakeContext context, object value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Verbose(value); + } + + /// + /// Writes a verbose message to the log using the specified string value. + /// + /// The context. + /// The value. + /// + /// + /// Verbose("{string}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Verbose")] + public static void Verbose(this ICakeContext context, string value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Verbose(value); + } + /// /// Writes a debug message to the log using the specified format information. /// @@ -194,12 +323,10 @@ public static void Verbose(this ICakeContext context, LogAction logAction) /// /// [CakeMethodAlias] + [CakeAliasCategory("Debug")] public static void Debug(this ICakeContext context, string format, params object[] args) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Debug(format, args); } @@ -215,13 +342,47 @@ public static void Debug(this ICakeContext context, string format, params object /// /// [CakeMethodAlias] + [CakeAliasCategory("Debug")] public static void Debug(this ICakeContext context, LogAction logAction) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); context.Log.Debug(logAction); } + + /// + /// Writes a debug message to the log using the specified value. + /// + /// the context. + /// The value. + /// + /// + /// Debug(new {FirstName = "John", LastName="Doe"}); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Debug")] + public static void Debug(this ICakeContext context, object value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Debug(value); + } + + /// + /// Writes a debug message to the log using the specified string value. + /// + /// the context. + /// The value. + /// + /// + /// Debug("{string}"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Debug")] + public static void Debug(this ICakeContext context, string value) + { + ArgumentNullException.ThrowIfNull(context); + context.Log.Debug(value); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Diagnostics/ScriptCallerAliases.cs b/src/Cake.Common/Diagnostics/ScriptCallerAliases.cs new file mode 100644 index 0000000000..e907b6bcb4 --- /dev/null +++ b/src/Cake.Common/Diagnostics/ScriptCallerAliases.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.CompilerServices; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Diagnostics +{ + /// + /// Contains functionality related to diagnostics. + /// + [CakeAliasCategory("Diagnostics")] + public static class ScriptCallerAliases + { + /// + /// Performs script caller information. + /// + /// The context. + /// The member name. + /// The source file path. + /// The source line number. + /// A instance representing the caller information. + [CakeMethodAlias] + public static ScriptCallerInfo GetCallerInfo( + this ICakeContext context, + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + ArgumentNullException.ThrowIfNull(context); + + return new ScriptCallerInfo(memberName, sourceFilePath, sourceLineNumber); + } + } +} diff --git a/src/Cake.Common/Diagnostics/ScriptCallerInfo.cs b/src/Cake.Common/Diagnostics/ScriptCallerInfo.cs new file mode 100644 index 0000000000..9f50f70c23 --- /dev/null +++ b/src/Cake.Common/Diagnostics/ScriptCallerInfo.cs @@ -0,0 +1,46 @@ +using Cake.Core.IO; + +namespace Cake.Common.Diagnostics +{ + /// + /// Represents a caller information. + /// + public sealed class ScriptCallerInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The member name. + /// The source file path. + /// The source line number. + public ScriptCallerInfo(string memberName, FilePath sourceFilePath, int sourceLineNumber) + { + MemberName = memberName; + SourceFilePath = sourceFilePath; + SourceLineNumber = sourceLineNumber; + } + + /// + /// Gets the method or property name of the caller to the method. + /// + public string MemberName { get; } + + /// + /// Gets the full path of the source file that contains the caller. + /// + public FilePath SourceFilePath { get; } + + /// + /// Gets the line number in the source file at which the method is called. + /// + public int SourceLineNumber { get; } + + /// + /// Returns a containing the full path of the source file and the line number in the source file at which the method is called. + /// + /// + /// A containing the full path of the source file and the line number in the source file at which the method is called. + /// + public override string ToString() => $"{SourceFilePath}:{SourceLineNumber}"; + } +} diff --git a/src/Cake.Common/DryRunAliases.cs b/src/Cake.Common/DryRunAliases.cs new file mode 100644 index 0000000000..50ead5bbc6 --- /dev/null +++ b/src/Cake.Common/DryRunAliases.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common +{ + /// + /// Contains functionality related to arguments. + /// + [CakeAliasCategory("Dry Run")] + public static class DryRunAliases + { + /// + /// Determines whether or not the current script execution is a dry run. + /// + /// The context. + /// Whether or not the current script execution is a dry run. + /// + /// + /// Setup(context => + /// { + /// if (!context.IsDryRun()) + /// { + /// // Do things that you don't want to + /// // do during a dry run. + /// } + /// }); + /// + /// + [CakeMethodAlias] + public static bool IsDryRun(this ICakeContext context) + { + return (context.Argument("dryrun", false) ?? true) + || (context.Argument("noop", false) ?? true) + || (context.Argument("whatif", false) ?? true); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/EnviromentAliases.cs b/src/Cake.Common/EnviromentAliases.cs index 5b7a4465af..523e9d4b5c 100644 --- a/src/Cake.Common/EnviromentAliases.cs +++ b/src/Cake.Common/EnviromentAliases.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.ComponentModel; using Cake.Core; using Cake.Core.Annotations; @@ -15,7 +17,7 @@ namespace Cake.Common public static class EnvironmentAliases { /// - /// Retrieves the value of the environment variable or null if the environment variable do not exist. + /// Retrieves the value of the environment variable or null if the environment variable does not exist. /// /// /// @@ -24,24 +26,41 @@ public static class EnvironmentAliases /// /// The context. /// The environment variable. - /// The environment variable or null if the environment variable do not exist. + /// The environment variable or null if the environment variable does not exist. [CakeMethodAlias] [CakeAliasCategory("Environment Variables")] public static string EnvironmentVariable(this ICakeContext context, string variable) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (variable == null) - { - throw new ArgumentNullException("variable"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(variable); return context.Environment.GetEnvironmentVariable(variable); } /// - /// Retrieves all environment variables + /// Retrieves the value of the environment variable or if the environment variable does not exist. + /// + /// + /// + /// Information(EnvironmentVariable<int>("BUILD_NUMBER", 42)); + /// + /// + /// The environment variable type. + /// The context. + /// The environment variable. + /// The value to return if the environment variable does not exist. + /// The environment variable or if the environment variable does not exist. + [CakeMethodAlias] + [CakeAliasCategory("Environment Variables")] + public static T EnvironmentVariable(this ICakeContext context, string variable, T defaultValue) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(variable); + var value = context.Environment.GetEnvironmentVariable(variable); + return value == null ? defaultValue : Convert(value); + } + + /// + /// Retrieves all environment variables. /// /// /// @@ -53,7 +72,7 @@ public static string EnvironmentVariable(this ICakeContext context, string varia /// Information("Path: {0}", path); /// } /// - /// foreach(var envVar in envVars) + /// foreach (var envVar in envVars) /// { /// Information( /// "Key: {0}\tValue: \"{1}\"", @@ -64,15 +83,12 @@ public static string EnvironmentVariable(this ICakeContext context, string varia /// /// /// The context. - /// The environment variables + /// The environment variables. [CakeMethodAlias] [CakeAliasCategory("Environment Variables")] public static IDictionary EnvironmentVariables(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); return context.Environment.GetEnvironmentVariables(); } @@ -96,14 +112,8 @@ public static IDictionary EnvironmentVariables(this ICakeContext [CakeAliasCategory("Environment Variables")] public static bool HasEnvironmentVariable(this ICakeContext context, string variable) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (variable == null) - { - throw new ArgumentNullException("variable"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(variable); return context.Environment.GetEnvironmentVariable(variable) != null; } @@ -126,7 +136,8 @@ public static bool HasEnvironmentVariable(this ICakeContext context, string vari [CakeAliasCategory("Platform")] public static bool IsRunningOnWindows(this ICakeContext context) { - return !IsRunningOnUnix(context); + ArgumentNullException.ThrowIfNull(context); + return context.Environment.Platform.IsWindows(); } /// @@ -148,11 +159,83 @@ public static bool IsRunningOnWindows(this ICakeContext context) [CakeAliasCategory("Platform")] public static bool IsRunningOnUnix(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - return context.Environment.IsUnix(); + ArgumentNullException.ThrowIfNull(context); + return context.Environment.Platform.IsUnix(); + } + + /// + /// Determines whether the build script running on a macOS based system. + /// + /// + /// + /// if (IsRunningOnMacOs()) + /// { + /// Information("macOS!"); + /// } + /// + /// + /// The context. + /// + /// true if the build script running on a macOS based system; otherwise false. + /// + [CakeMethodAlias] + [CakeAliasCategory("Platform")] + public static bool IsRunningOnMacOs(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Environment.Platform.IsOSX(); + } + + /// + /// Determines whether the build script running on a FreeBSD based system. + /// + /// + /// + /// if (IsRunningOnFreeBSD()) + /// { + /// Information("FreeBSD!"); + /// } + /// + /// + /// The context. + /// + /// true if the build script running on a FreeBSD based system; otherwise false. + /// + [CakeMethodAlias] + [CakeAliasCategory("Platform")] + public static bool IsRunningOnFreeBSD(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Environment.Platform.IsFreeBSD(); + } + + /// + /// Determines whether the build script running on a Linux based system. + /// + /// + /// + /// if (IsRunningOnLinux()) + /// { + /// Information("Linux!"); + /// } + /// + /// + /// The context. + /// + /// true if the build script running on a Linux based system; otherwise false. + /// + [CakeMethodAlias] + [CakeAliasCategory("Platform")] + public static bool IsRunningOnLinux(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Environment.Platform.IsLinux(); + } + + private static T Convert(string value) + { + var converter = TypeDescriptor.GetConverter(typeof(T)); + return (T)converter.ConvertFromInvariantString(value); } } } diff --git a/src/Cake.Common/IO/CleanDirectorySettings.cs b/src/Cake.Common/IO/CleanDirectorySettings.cs new file mode 100644 index 0000000000..d2d9234553 --- /dev/null +++ b/src/Cake.Common/IO/CleanDirectorySettings.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.IO +{ + /// + /// Contains settings used by CleanDirectory. + /// + public class CleanDirectorySettings + { + /// + /// Gets or sets a value indicating whether to clean read-only files if set to true. + /// + /// It is set to false by default. + /// + /// + public bool Force { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DeleteDirectorySettings.cs b/src/Cake.Common/IO/DeleteDirectorySettings.cs new file mode 100644 index 0000000000..536b9fc25e --- /dev/null +++ b/src/Cake.Common/IO/DeleteDirectorySettings.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.IO +{ + /// + /// Contains settings used by DeleteDirectory. + /// + public class DeleteDirectorySettings + { + /// + /// Gets or sets a value indicating whether to perform a recursive delete if set to true. + /// + /// It is set to false by default. + /// + /// + public bool Recursive { get; set; } + + /// + /// Gets or sets a value indicating whether to delete read-only files if set to true. + /// + /// It is set to false by default. + /// + /// + public bool Force { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryAliases.cs b/src/Cake.Common/IO/DirectoryAliases.cs index 45806cbf26..b36d5fa349 100644 --- a/src/Cake.Common/IO/DirectoryAliases.cs +++ b/src/Cake.Common/IO/DirectoryAliases.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; +using Cake.Common.IO; using Cake.Common.IO.Paths; using Cake.Core; using Cake.Core.Annotations; @@ -38,14 +40,8 @@ public static class DirectoryAliases [CakeNamespaceImport("Cake.Common.IO.Paths")] public static ConvertableDirectoryPath Directory(this ICakeContext context, string path) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); return new ConvertableDirectoryPath(new DirectoryPath(path)); } @@ -58,24 +54,24 @@ public static ConvertableDirectoryPath Directory(this ICakeContext context, stri /// Directory("be"), /// Directory("gone") /// }; - /// DeleteDirectories(directoriesToDelete, recursive:true); + /// DeleteDirectories(directoriesToDelete, new DeleteDirectorySettings { + /// Recursive = true, + /// Force = true + /// }); /// /// /// The context. /// The directory paths. - /// Will perform a recursive delete if set to true. + /// The delete settings. [CakeMethodAlias] [CakeAliasCategory("Delete")] - public static void DeleteDirectories(this ICakeContext context, IEnumerable directories, bool recursive = false) + public static void DeleteDirectories(this ICakeContext context, IEnumerable directories, DeleteDirectorySettings settings) { - if (directories == null) - { - throw new ArgumentNullException("directories"); - } + ArgumentNullException.ThrowIfNull(directories); foreach (var directory in directories) { - DeleteDirectory(context, directory, recursive); + DeleteDirectory(context, directory, settings); } } @@ -88,25 +84,25 @@ public static void DeleteDirectories(this ICakeContext context, IEnumerable /// /// The context. /// The directory paths. - /// Will perform a recursive delete if set to true. + /// The delete settings. [CakeMethodAlias] [CakeAliasCategory("Delete")] - public static void DeleteDirectories(this ICakeContext context, IEnumerable directories, bool recursive = false) + public static void DeleteDirectories(this ICakeContext context, IEnumerable directories, DeleteDirectorySettings settings) { - if (directories == null) - { - throw new ArgumentNullException("directories"); - } + ArgumentNullException.ThrowIfNull(directories); var paths = directories.Select(p => new DirectoryPath(p)); foreach (var directory in paths) { - DeleteDirectory(context, directory, recursive); + DeleteDirectory(context, directory, settings); } } @@ -115,22 +111,25 @@ public static void DeleteDirectories(this ICakeContext context, IEnumerable /// /// - /// DeleteDirectory("./be/gone", recursive:true); + /// DeleteDirectory("./be/gone", new DeleteDirectorySettings { + /// Recursive = true, + /// Force = true + /// }); /// /// /// The context. /// The directory path. - /// Will perform a recursive delete if set to true. + /// The delete settings. [CakeMethodAlias] [CakeAliasCategory("Delete")] - public static void DeleteDirectory(this ICakeContext context, DirectoryPath path, bool recursive = false) + public static void DeleteDirectory(this ICakeContext context, DirectoryPath path, DeleteDirectorySettings settings) { - DirectoryDeleter.Delete(context, path, recursive); + DirectoryDeleter.Delete(context, path, settings); } /// /// Cleans the directories matching the specified pattern. - /// Cleaning the directory will remove all it's content but not the directory itself. + /// Cleaning the directory will remove all its content but not the directory itself. /// /// /// @@ -141,7 +140,7 @@ public static void DeleteDirectory(this ICakeContext context, DirectoryPath path /// The pattern to match. [CakeMethodAlias] [CakeAliasCategory("Clean")] - public static void CleanDirectories(this ICakeContext context, string pattern) + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern) { var directories = context.GetDirectories(pattern); if (directories.Count == 0) @@ -154,7 +153,32 @@ public static void CleanDirectories(this ICakeContext context, string pattern) /// /// Cleans the directories matching the specified pattern. - /// Cleaning the directory will remove all it's content but not the directory itself. + /// Cleaning the directory will remove all its content but not the directory itself. + /// + /// + /// + /// CleanDirectories("./src/**/bin/debug", new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The pattern to match. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern, CleanDirectorySettings settings) + { + var directories = context.GetDirectories(pattern); + if (directories.Count == 0) + { + context.Log.Verbose("The provided pattern did not match any directories."); + return; + } + CleanDirectories(context, directories, settings); + } + + /// + /// Cleans the directories matching the specified pattern. + /// Cleaning the directory will remove all its content but not the directory itself. /// /// /// @@ -170,9 +194,9 @@ public static void CleanDirectories(this ICakeContext context, string pattern) /// The predicate used to filter directories based on file system information. [CakeMethodAlias] [CakeAliasCategory("Clean")] - public static void CleanDirectories(this ICakeContext context, string pattern, Func predicate) + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern, Func predicate) { - var directories = context.GetDirectories(pattern, predicate); + var directories = context.GetDirectories(pattern, new GlobberSettings { Predicate = predicate }); if (directories.Count == 0) { context.Log.Verbose("The provided pattern did not match any directories."); @@ -181,9 +205,39 @@ public static void CleanDirectories(this ICakeContext context, string pattern, F CleanDirectories(context, directories); } + /// + /// Cleans the directories matching the specified pattern. + /// Cleaning the directory will remove all its content but not the directory itself. + /// + /// + /// + /// Func<IFileSystemInfo, bool> exclude_node_modules = + /// fileSystemInfo=>!fileSystemInfo.Path.FullPath.EndsWith( + /// "node_modules", + /// StringComparison.OrdinalIgnoreCase); + /// CleanDirectories("./src/**/bin/debug", exclude_node_modules, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The pattern to match. + /// The predicate used to filter directories based on file system information. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, GlobPattern pattern, Func predicate, CleanDirectorySettings settings) + { + var directories = context.GetDirectories(pattern, new GlobberSettings { Predicate = predicate }); + if (directories.Count == 0) + { + context.Log.Verbose("The provided pattern did not match any directories."); + return; + } + CleanDirectories(context, directories, settings); + } + /// /// Cleans the specified directories. - /// Cleaning a directory will remove all it's content but not the directory itself. + /// Cleaning a directory will remove all its content but not the directory itself. /// /// /// @@ -197,19 +251,40 @@ public static void CleanDirectories(this ICakeContext context, string pattern, F [CakeAliasCategory("Clean")] public static void CleanDirectories(this ICakeContext context, IEnumerable directories) { - if (directories == null) + ArgumentNullException.ThrowIfNull(directories); + foreach (var directory in directories) { - throw new ArgumentNullException("directories"); + CleanDirectory(context, directory); } + } + + /// + /// Cleans the specified directories. + /// Cleaning a directory will remove all its content but not the directory itself. + /// + /// + /// + /// var directoriesToClean = GetDirectories("./src/**/bin/"); + /// CleanDirectories(directoriesToClean, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The directory paths. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, IEnumerable directories, CleanDirectorySettings settings) + { + ArgumentNullException.ThrowIfNull(directories); foreach (var directory in directories) { - CleanDirectory(context, directory); + CleanDirectory(context, directory, settings); } } /// /// Cleans the specified directories. - /// Cleaning a directory will remove all it's content but not the directory itself. + /// Cleaning a directory will remove all its content but not the directory itself. /// /// /// @@ -226,14 +301,39 @@ public static void CleanDirectories(this ICakeContext context, IEnumerable directories) { - if (directories == null) + ArgumentNullException.ThrowIfNull(directories); + var paths = directories.Select(p => new DirectoryPath(p)); + foreach (var directory in paths) { - throw new ArgumentNullException("directories"); + CleanDirectory(context, directory); } + } + + /// + /// Cleans the specified directories. + /// Cleaning a directory will remove all its content but not the directory itself. + /// + /// + /// + /// var directoriesToClean = new []{ + /// "./src/Cake/obj", + /// "./src/Cake.Common/obj" + /// }; + /// CleanDirectories(directoriesToClean, new CleanDirectorySettings() { Force = true }); + /// + /// + /// The context. + /// The directory paths. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectories(this ICakeContext context, IEnumerable directories, CleanDirectorySettings settings) + { + ArgumentNullException.ThrowIfNull(directories); var paths = directories.Select(p => new DirectoryPath(p)); foreach (var directory in paths) { - CleanDirectory(context, directory); + CleanDirectory(context, directory, settings); } } @@ -254,6 +354,26 @@ public static void CleanDirectory(this ICakeContext context, DirectoryPath path) DirectoryCleaner.Clean(context, path); } + /// + /// Cleans the specified directory. + /// + /// + /// + /// CleanDirectory("./src/Cake.Common/obj", new CleanDirectorySettings { + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectory(this ICakeContext context, DirectoryPath path, CleanDirectorySettings settings) + { + DirectoryCleaner.Clean(context, path, settings); + } + /// /// Cleans the specified directory. /// @@ -272,6 +392,27 @@ public static void CleanDirectory(this ICakeContext context, DirectoryPath path, DirectoryCleaner.Clean(context, path, predicate); } + /// + /// Cleans the specified directory. + /// + /// + /// + /// CleanDirectory("./src/Cake.Common/obj", fileSystemInfo=>!fileSystemInfo.Hidden, new CleanDirectorySettings { + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// Predicate used to determine which files/directories should get deleted. + /// The clean settings. + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + public static void CleanDirectory(this ICakeContext context, DirectoryPath path, Func predicate, CleanDirectorySettings settings) + { + DirectoryCleaner.Clean(context, path, predicate, settings); + } + /// /// Creates the specified directory. /// @@ -309,6 +450,68 @@ public static void EnsureDirectoryExists(this ICakeContext context, DirectoryPat } } + /// + /// Deletes the specified directory and its contents if it exists. + /// + /// + /// + /// EnsureDirectoryDoesNotExist("./be/gone"); + /// + /// + /// The context. + /// The directory path. + [CakeMethodAlias] + [CakeAliasCategory("DoesNotExist")] + public static void EnsureDirectoryDoesNotExist(this ICakeContext context, DirectoryPath path) + { + context.EnsureDirectoryDoesNotExist(path, new DeleteDirectorySettings { Recursive = true, Force = true }); + } + + /// + /// Deletes the specified directory if it exists. + /// + /// + /// + /// EnsureDirectoryDoesNotExist("./be/gone", new DeleteDirectorySettings { + /// Recursive = true, + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// The delete settings. + [CakeMethodAlias] + [CakeAliasCategory("DoesNotExist")] + public static void EnsureDirectoryDoesNotExist(this ICakeContext context, DirectoryPath path, DeleteDirectorySettings settings) + { + if (context.DirectoryExists(path)) + { + context.DeleteDirectory(path, settings); + } + } + + /// + /// Deletes the specified directory if it exists. + /// + /// + /// + /// EnsureDirectoryDoesNotExist("./be/gone", new EnsureDirectoryDoesNotExistSettings { + /// Recursive = true, + /// Force = true + /// }); + /// + /// + /// The context. + /// The directory path. + /// The delete settings. + [CakeMethodAlias] + [CakeAliasCategory("DoesNotExist")] + public static void EnsureDirectoryDoesNotExist(this ICakeContext context, DirectoryPath path, EnsureDirectoryDoesNotExistSettings settings) + { + context.EnsureDirectoryDoesNotExist(path, settings as DeleteDirectorySettings); + } + /// /// Copies the contents of a directory, including subdirectories to the specified location. /// @@ -324,20 +527,11 @@ public static void EnsureDirectoryExists(this ICakeContext context, DirectoryPat [CakeAliasCategory("Copy")] public static void CopyDirectory(this ICakeContext context, DirectoryPath source, DirectoryPath destination) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (source == null) - { - throw new ArgumentNullException("source"); - } + ArgumentNullException.ThrowIfNull(source); - if (destination == null) - { - throw new ArgumentNullException("destination"); - } + ArgumentNullException.ThrowIfNull(destination); if (source.IsRelative) { @@ -366,6 +560,7 @@ public static void CopyDirectory(this ICakeContext context, DirectoryPath source foreach (var file in files) { var temppath = destinationDir.Path.CombineWithFilePath(file.Path.GetFilename()); + context.Log.Verbose("Copying file {0} to {1}", file.Path, temppath); file.Copy(temppath, true); } @@ -398,15 +593,9 @@ public static void CopyDirectory(this ICakeContext context, DirectoryPath source [CakeAliasCategory("Exists")] public static bool DirectoryExists(this ICakeContext context, DirectoryPath path) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(path); return context.FileSystem.GetDirectory(path).Exists; } @@ -426,17 +615,135 @@ public static bool DirectoryExists(this ICakeContext context, DirectoryPath path [CakeAliasCategory("Path")] public static DirectoryPath MakeAbsolute(this ICakeContext context, DirectoryPath path) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(path); + + return path.MakeAbsolute(context.Environment); + } + + /// + /// Makes the directory path relative (if absolute) to a specified root directory. If no root directory is defined + /// the current working directory is used as default root. + /// + /// + /// + /// var path = MakeRelative(Directory("C:\Cake\Tests\Integration")); + /// + /// + /// The context. + /// The path. + /// The root path. + /// A relative directory path. + [CakeMethodAlias] + [CakeAliasCategory("Path")] + public static DirectoryPath MakeRelative(this ICakeContext context, DirectoryPath path, DirectoryPath rootPath = null) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(path); + + var root = rootPath ?? context.Environment.WorkingDirectory; + return root.GetRelativePath(path); + } - if (path == null) + /// + /// Makes the file path relative (if absolute) to a specified root directory. If no root directory is defined + /// the current working directory is used as default root. + /// + /// + /// + /// var path = MakeRelative(Directory("C:\Cake\Tests\Integration\file.cake")); + /// + /// + /// The context. + /// The path. + /// The root path. + /// A relative file path. + [CakeMethodAlias] + [CakeAliasCategory("Path")] + public static FilePath MakeRelative(this ICakeContext context, FilePath path, DirectoryPath rootPath = null) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(path); + + var root = rootPath ?? context.Environment.WorkingDirectory; + return root.GetRelativePath(path); + } + + /// + /// Moves an existing directory to a new location, providing the option to specify a new directory name. + /// + /// The context. + /// The directory path. + /// The target directory path. + /// + /// + /// MoveDirectory("mydir", "newparent/newdir"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Move")] + public static void MoveDirectory(this ICakeContext context, DirectoryPath directoryPath, DirectoryPath targetDirectoryPath) + { + DirectoryMover.MoveDirectory(context, directoryPath, targetDirectoryPath); + } + + /// + /// Gets a list of all the directories inside a directory. + /// + /// + /// + /// var directories = GetSubDirectories("some/dir"); + /// + /// + /// The context. + /// The directory path. + /// An absolute directory path. + [CakeMethodAlias] + [CakeAliasCategory("List")] + public static DirectoryPathCollection GetSubDirectories(this ICakeContext context, DirectoryPath directoryPath) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(directoryPath); + + directoryPath = directoryPath.MakeAbsolute(context.Environment); + + // Get the directory and verify it exist. + var directory = context.FileSystem.GetDirectory(directoryPath); + if (!directory.Exists) { - throw new ArgumentNullException("path"); + const string format = "The directory '{0}' does not exist."; + var message = string.Format(System.Globalization.CultureInfo.InvariantCulture, format, directoryPath.FullPath); + throw new System.IO.DirectoryNotFoundException(message); } - return path.MakeAbsolute(context.Environment); + var directories = directory.GetDirectories("*", SearchScope.Current, fsi => true).Select(d => new DirectoryPath(d.Path.FullPath)).ToList(); + return new DirectoryPathCollection(directories); + } + + /// + /// Expands all environment variables in the provided . + /// + /// + /// + /// var path = new DirectoryPath("%APPDATA%/foo"); + /// var expanded = path.ExpandEnvironmentVariables(environment); + /// + /// + /// The context. + /// The path. + /// A new with each environment variable replaced by its value. + [CakeMethodAlias] + [CakeAliasCategory("Path")] + public static DirectoryPath ExpandEnvironmentVariables(this ICakeContext context, DirectoryPath directoryPath) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(directoryPath); + + return directoryPath.ExpandEnvironmentVariables(context.Environment); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryCleaner.cs b/src/Cake.Common/IO/DirectoryCleaner.cs index fa439ac9c8..5d2d844b2e 100644 --- a/src/Cake.Common/IO/DirectoryCleaner.cs +++ b/src/Cake.Common/IO/DirectoryCleaner.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.IO; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -12,19 +14,23 @@ internal static class DirectoryCleaner { public static void Clean(ICakeContext context, DirectoryPath path) { - Clean(context, path, null); + Clean(context, path, null, null); + } + + public static void Clean(ICakeContext context, DirectoryPath path, CleanDirectorySettings settings) + { + Clean(context, path, null, settings); } public static void Clean(ICakeContext context, DirectoryPath path, Func predicate) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } + Clean(context, path, predicate, null); + } + + public static void Clean(ICakeContext context, DirectoryPath path, Func predicate, CleanDirectorySettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); if (path.IsRelative) { @@ -42,10 +48,11 @@ public static void Clean(ICakeContext context, DirectoryPath path, Func true); - CleanDirectory(root, predicate, 0); + settings = settings ?? new CleanDirectorySettings(); + CleanDirectory(root, predicate, 0, settings); } - private static bool CleanDirectory(IDirectory root, Func predicate, int level) + private static bool CleanDirectory(IDirectory root, Func predicate, int level, CleanDirectorySettings settings) { var shouldDeleteRoot = predicate(root); @@ -53,7 +60,7 @@ private static bool CleanDirectory(IDirectory root, Func var directories = root.GetDirectories("*", SearchScope.Current); foreach (var directory in directories) { - if (!CleanDirectory(directory, predicate, level + 1)) + if (!CleanDirectory(directory, predicate, level + 1, settings)) { // Since the child directory reported it shouldn't be // removed, we should not remove the current directory either. @@ -67,6 +74,12 @@ private static bool CleanDirectory(IDirectory root, Func { if (predicate(file)) { + if (settings.Force) + { + // Remove the ReadOnly attribute on file (if set) + file.Attributes &= ~FileAttributes.ReadOnly; + } + file.Delete(); } else @@ -87,4 +100,4 @@ private static bool CleanDirectory(IDirectory root, Func return false; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryCreator.cs b/src/Cake.Common/IO/DirectoryCreator.cs index 38100207bc..eaabbeeeb3 100644 --- a/src/Cake.Common/IO/DirectoryCreator.cs +++ b/src/Cake.Common/IO/DirectoryCreator.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Diagnostics; @@ -12,14 +13,8 @@ internal static class DirectoryCreator { public static void Create(ICakeContext context, DirectoryPath path) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); if (path.IsRelative) { @@ -34,4 +29,4 @@ public static void Create(ICakeContext context, DirectoryPath path) } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryDeleter.cs b/src/Cake.Common/IO/DirectoryDeleter.cs index f0ae471017..523d55cb20 100644 --- a/src/Cake.Common/IO/DirectoryDeleter.cs +++ b/src/Cake.Common/IO/DirectoryDeleter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using System.IO; @@ -13,16 +14,10 @@ namespace Cake.Common.IO { internal static class DirectoryDeleter { - public static void Delete(ICakeContext context, DirectoryPath path, bool recursive) + public static void Delete(ICakeContext context, DirectoryPath path, DeleteDirectorySettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); if (path.IsRelative) { @@ -32,20 +27,31 @@ public static void Delete(ICakeContext context, DirectoryPath path, bool recursi var directory = context.FileSystem.GetDirectory(path); if (!directory.Exists) { - const string format = "The directory '{0}' do not exist."; + const string format = "The directory '{0}' does not exist."; throw new IOException(string.Format(CultureInfo.InvariantCulture, format, path.FullPath)); } var hasDirectories = directory.GetDirectories("*", SearchScope.Current).Any(); var hasFiles = directory.GetFiles("*", SearchScope.Current).Any(); - if (!recursive && (hasDirectories || hasFiles)) + if (!settings.Recursive && (hasDirectories || hasFiles)) { const string format = "Cannot delete directory '{0}' without recursion since it's not empty."; throw new IOException(string.Format(CultureInfo.InvariantCulture, format, path.FullPath)); } context.Log.Verbose("Deleting directory {0}", path); - directory.Delete(recursive); + + if (settings.Force) + { + context.Log.Verbose("Removing ReadOnly attributes on all files in directory {0}", path); + var searchOption = settings.Recursive ? SearchScope.Recursive : SearchScope.Current; + foreach (var file in directory.GetFiles("*", searchOption)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + } + + directory.Delete(settings.Recursive); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/DirectoryMover.cs b/src/Cake.Common/IO/DirectoryMover.cs new file mode 100644 index 0000000000..95774b3224 --- /dev/null +++ b/src/Cake.Common/IO/DirectoryMover.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.IO; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +namespace Cake.Common.IO +{ + internal static class DirectoryMover + { + public static void MoveDirectory(ICakeContext context, DirectoryPath directoryPath, DirectoryPath targetDirectoryPath) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(directoryPath); + ArgumentNullException.ThrowIfNull(targetDirectoryPath); + + directoryPath = directoryPath.MakeAbsolute(context.Environment); + targetDirectoryPath = targetDirectoryPath.MakeAbsolute(context.Environment); + + // Get the directory and verify it exist. + var directory = context.FileSystem.GetDirectory(directoryPath); + if (!directory.Exists) + { + const string format = "The directory '{0}' does not exist."; + var message = string.Format(CultureInfo.InvariantCulture, format, directoryPath.FullPath); + throw new DirectoryNotFoundException(message); + } + + // Move the directory. + context.Log.Verbose("Moving directory {0} to {1}", directoryPath.GetDirectoryName(), targetDirectoryPath); + directory.Move(targetDirectoryPath); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/IO/EnsureDirectoryDoesNotExistSettings.cs b/src/Cake.Common/IO/EnsureDirectoryDoesNotExistSettings.cs new file mode 100644 index 0000000000..27e174d158 --- /dev/null +++ b/src/Cake.Common/IO/EnsureDirectoryDoesNotExistSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.IO +{ + /// + /// Contains settings used by EnsureDirectoryDoesNotExistSettings. + /// + public class EnsureDirectoryDoesNotExistSettings : DeleteDirectorySettings + { + } +} diff --git a/src/Cake.Common/IO/FileAliases.cs b/src/Cake.Common/IO/FileAliases.cs index 0c7bc07794..8d1d6ff631 100644 --- a/src/Cake.Common/IO/FileAliases.cs +++ b/src/Cake.Common/IO/FileAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.IO; @@ -38,14 +39,9 @@ public static class FileAliases [CakeNamespaceImport("Cake.Common.IO.Paths")] public static ConvertableFilePath File(this ICakeContext context, string path) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); + return new ConvertableFilePath(new FilePath(path)); } @@ -98,9 +94,9 @@ public static void CopyFile(this ICakeContext context, FilePath filePath, FilePa /// [CakeMethodAlias] [CakeAliasCategory("Copy")] - public static void CopyFiles(this ICakeContext context, string pattern, DirectoryPath targetDirectoryPath) + public static void CopyFiles(this ICakeContext context, GlobPattern pattern, DirectoryPath targetDirectoryPath) { - FileCopier.CopyFiles(context, pattern, targetDirectoryPath); + FileCopier.CopyFiles(context, pattern, targetDirectoryPath, false); } /// @@ -119,7 +115,7 @@ public static void CopyFiles(this ICakeContext context, string pattern, Director [CakeAliasCategory("Copy")] public static void CopyFiles(this ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath) { - FileCopier.CopyFiles(context, filePaths, targetDirectoryPath); + FileCopier.CopyFiles(context, filePaths, targetDirectoryPath, false); } /// @@ -142,12 +138,74 @@ public static void CopyFiles(this ICakeContext context, IEnumerable fi [CakeAliasCategory("Copy")] public static void CopyFiles(this ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath) { - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } + ArgumentNullException.ThrowIfNull(filePaths); var paths = filePaths.Select(p => new FilePath(p)); - FileCopier.CopyFiles(context, paths, targetDirectoryPath); + FileCopier.CopyFiles(context, paths, targetDirectoryPath, false); + } + + /// + /// Copies all files matching the provided pattern to a new location. + /// + /// The context. + /// The pattern. + /// The target directory path. + /// Keep the folder structure. + /// + /// + /// CopyFiles("Cake.*", "./publish"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Copy")] + public static void CopyFiles(this ICakeContext context, GlobPattern pattern, DirectoryPath targetDirectoryPath, bool preserveFolderStructure) + { + FileCopier.CopyFiles(context, pattern, targetDirectoryPath, preserveFolderStructure); + } + + /// + /// Copies existing files to a new location. + /// + /// The context. + /// The file paths. + /// The target directory path. + /// Keep the folder structure. + /// + /// + /// var files = GetFiles("./**/Cake.*"); + /// CopyFiles(files, "destination"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Copy")] + public static void CopyFiles(this ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath, bool preserveFolderStructure) + { + FileCopier.CopyFiles(context, filePaths, targetDirectoryPath, preserveFolderStructure); + } + + /// + /// Copies existing files to a new location. + /// + /// The context. + /// The file paths. + /// The target directory path. + /// Keep the folder structure. + /// + /// + /// CreateDirectory("destination"); + /// var files = new [] { + /// "Cake.exe", + /// "Cake.pdb" + /// }; + /// CopyFiles(files, "destination"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Copy")] + public static void CopyFiles(this ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath, bool preserveFolderStructure) + { + ArgumentNullException.ThrowIfNull(filePaths); + var paths = filePaths.Select(p => new FilePath(p)); + FileCopier.CopyFiles(context, paths, targetDirectoryPath, preserveFolderStructure); } /// @@ -181,7 +239,7 @@ public static void MoveFileToDirectory(this ICakeContext context, FilePath fileP /// [CakeMethodAlias] [CakeAliasCategory("Move")] - public static void MoveFiles(this ICakeContext context, string pattern, DirectoryPath targetDirectoryPath) + public static void MoveFiles(this ICakeContext context, GlobPattern pattern, DirectoryPath targetDirectoryPath) { FileMover.MoveFiles(context, pattern, targetDirectoryPath); } @@ -235,7 +293,7 @@ public static void MoveFile(this ICakeContext context, FilePath filePath, FilePa /// [CakeMethodAlias] [CakeAliasCategory("Delete")] - public static void DeleteFiles(this ICakeContext context, string pattern) + public static void DeleteFiles(this ICakeContext context, GlobPattern pattern) { FileDeleter.DeleteFiles(context, pattern); } @@ -295,15 +353,8 @@ public static void DeleteFile(this ICakeContext context, FilePath filePath) [CakeAliasCategory("Exists")] public static bool FileExists(this ICakeContext context, FilePath filePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); return context.FileSystem.GetFile(filePath.MakeAbsolute(context.Environment)).Exists; } @@ -312,7 +363,7 @@ public static bool FileExists(this ICakeContext context, FilePath filePath) /// Makes the path absolute (if relative) using the current working directory. /// /// The context. - /// The path. + /// The path. /// An absolute file path. /// /// @@ -321,19 +372,12 @@ public static bool FileExists(this ICakeContext context, FilePath filePath) /// [CakeMethodAlias] [CakeAliasCategory("Path")] - public static FilePath MakeAbsolute(this ICakeContext context, FilePath path) + public static FilePath MakeAbsolute(this ICakeContext context, FilePath filePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); - if (path == null) - { - throw new ArgumentNullException("path"); - } - - return path.MakeAbsolute(context.Environment); + return filePath.MakeAbsolute(context.Environment); } /// @@ -351,18 +395,10 @@ public static FilePath MakeAbsolute(this ICakeContext context, FilePath path) [CakeAliasCategory("Exists")] public static long FileSize(this ICakeContext context, FilePath filePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); var file = context.FileSystem.GetFile(filePath.MakeAbsolute(context.Environment)); - if (!file.Exists) { throw new FileNotFoundException("Unable to find the specified file.", filePath.FullPath); @@ -370,5 +406,27 @@ public static long FileSize(this ICakeContext context, FilePath filePath) return file.Length; } + + /// + /// Expands all environment variables in the provided . + /// + /// + /// + /// var path = new FilePath("%APPDATA%/foo.bar"); + /// var expanded = path.ExpandEnvironmentVariables(environment); + /// + /// + /// The context. + /// The path. + /// A new with each environment variable replaced by its value. + [CakeMethodAlias] + [CakeAliasCategory("Path")] + public static FilePath ExpandEnvironmentVariables(this ICakeContext context, FilePath filePath) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); + + return filePath.ExpandEnvironmentVariables(context.Environment); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/FileCopier.cs b/src/Cake.Common/IO/FileCopier.cs index b9350b58a2..65dffa613b 100644 --- a/src/Cake.Common/IO/FileCopier.cs +++ b/src/Cake.Common/IO/FileCopier.cs @@ -1,10 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -15,117 +17,164 @@ internal static class FileCopier { public static void CopyFileToDirectory(ICakeContext context, FilePath filePath, DirectoryPath targetDirectoryPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - if (targetDirectoryPath == null) - { - throw new ArgumentNullException("targetDirectoryPath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(targetDirectoryPath); CopyFile(context, filePath, targetDirectoryPath.GetFilePath(filePath)); } public static void CopyFile(ICakeContext context, FilePath filePath, FilePath targetFilePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - if (targetFilePath == null) - { - throw new ArgumentNullException("targetFilePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(targetFilePath); var targetDirectoryPath = targetFilePath.GetDirectory().MakeAbsolute(context.Environment); // Make sure the target directory exist. if (!context.FileSystem.Exist(targetDirectoryPath)) { - const string format = "The directory '{0}' do not exist."; + const string format = "The directory '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, targetDirectoryPath.FullPath); throw new DirectoryNotFoundException(message); } - CopyFileCore(context, filePath, targetFilePath); + CopyFileCore(context, filePath, targetFilePath, null); } - public static void CopyFiles(ICakeContext context, string pattern, DirectoryPath targetDirectoryPath) + public static void CopyFiles(ICakeContext context, GlobPattern pattern, DirectoryPath targetDirectoryPath, bool preserverFolderStructure) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (pattern == null) - { - throw new ArgumentNullException("pattern"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(pattern); + var files = context.GetFiles(pattern); if (files.Count == 0) { context.Log.Verbose("The provided pattern did not match any files."); return; } - CopyFiles(context, files, targetDirectoryPath); + + CopyFiles(context, files, targetDirectoryPath, preserverFolderStructure); } - public static void CopyFiles(ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath) + public static void CopyFiles(ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath, bool preserverFolderStructure) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } - if (targetDirectoryPath == null) - { - throw new ArgumentNullException("targetDirectoryPath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePaths); + ArgumentNullException.ThrowIfNull(targetDirectoryPath); + // Make all path absolute var absoluteTargetDirectoryPath = targetDirectoryPath.MakeAbsolute(context.Environment); + var absoluteFilePaths = filePaths.Select(x => x.MakeAbsolute(context.Environment)).ToList(); + // Make sure the target directory exist. if (!context.FileSystem.Exist(absoluteTargetDirectoryPath)) { - const string format = "The directory '{0}' do not exist."; + const string format = "The directory '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, absoluteTargetDirectoryPath.FullPath); throw new DirectoryNotFoundException(message); } - // Iterate all files and copy them. - foreach (var filePath in filePaths) + if (preserverFolderStructure) { - CopyFileCore(context, filePath, absoluteTargetDirectoryPath.GetFilePath(filePath)); + var commonPath = string.Empty; + var separatedPath = absoluteFilePaths + .First(str => str.ToString().Length == absoluteFilePaths.Max(st2 => st2.ToString().Length)).ToString() + .Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + + foreach (string pathSegment in separatedPath) + { + if (commonPath.Length == 0 && absoluteFilePaths.All(str => str.ToString().StartsWith(pathSegment))) + { + commonPath = pathSegment; + } + else if (absoluteFilePaths.All(str => str.ToString().StartsWith(commonPath + "/" + pathSegment))) + { + commonPath += "/" + pathSegment; + } + else + { + break; + } + } + + if (absoluteFilePaths.Count == 1 && absoluteFilePaths.First().FullPath.Contains(context.Environment.WorkingDirectory.FullPath)) + { + var relativePath = absoluteFilePaths.First().FullPath.Remove(0, context.Environment.WorkingDirectory.FullPath.Length + 1); + var relativePathParts = relativePath.Split('/').ToList(); + + if (relativePathParts.Count > 2) + { + relativePathParts.RemoveAt(0); + var workdirRelativeStructurePath = string.Join("/", relativePathParts.ToArray()); + + var index = commonPath.IndexOf(workdirRelativeStructurePath, StringComparison.Ordinal); + commonPath = index < 0 + ? commonPath + : commonPath.Remove(index, workdirRelativeStructurePath.Length); + } + } + + // Iterate all files and copy them. + foreach (var filePath in absoluteFilePaths) + { + CopyFileCore(context, filePath, absoluteTargetDirectoryPath.GetFilePath(filePath), context.DirectoryExists(commonPath) ? commonPath : null); + } + } + else + { + // #1663: For empty enumerations, just return. + if (!absoluteFilePaths.Any()) + { + return; + } + + // Iterate all files and copy them. + foreach (var filePath in absoluteFilePaths) + { + CopyFileCore(context, filePath, absoluteTargetDirectoryPath.GetFilePath(filePath), null); + } } } - private static void CopyFileCore(ICakeContext context, FilePath filePath, FilePath targetFilePath) + private static void CopyFileCore(ICakeContext context, FilePath filePath, FilePath targetFilePath, string commonPath) { var absoluteFilePath = filePath.MakeAbsolute(context.Environment); // Get the file. if (!context.FileSystem.Exist(absoluteFilePath)) { - const string format = "The file '{0}' do not exist."; + const string format = "The file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, absoluteFilePath.FullPath); throw new FileNotFoundException(message, absoluteFilePath.FullPath); } // Copy the file. var absoluteTargetPath = targetFilePath.MakeAbsolute(context.Environment); - context.Log.Verbose("Copying file {0} to {1}", absoluteFilePath.GetFilename(), absoluteTargetPath); var file = context.FileSystem.GetFile(absoluteFilePath); - file.Copy(absoluteTargetPath, true); + + if (!string.IsNullOrEmpty(commonPath)) + { + // Get the parent folder structure and create it. + var newRelativeFolderPath = context.Directory(commonPath).Path.GetRelativePath(filePath.GetDirectory()); + var newTargetPath = targetFilePath.GetDirectory().Combine(newRelativeFolderPath); + var newAbsoluteTargetPath = newTargetPath.CombineWithFilePath(filePath.GetFilename()); + context.Log.Verbose("Copying file {0} to {1}", absoluteFilePath.GetFilename(), newAbsoluteTargetPath); + + if (!context.DirectoryExists(newTargetPath)) + { + context.CreateDirectory(newTargetPath); + } + + file.Copy(newAbsoluteTargetPath, true); + } + else + { + context.Log.Verbose("Copying file {0} to {1}", absoluteFilePath.GetFilename(), absoluteTargetPath); + file.Copy(absoluteTargetPath, true); + } } } } diff --git a/src/Cake.Common/IO/FileDeleter.cs b/src/Cake.Common/IO/FileDeleter.cs index 7c8319b498..5099717af8 100644 --- a/src/Cake.Common/IO/FileDeleter.cs +++ b/src/Cake.Common/IO/FileDeleter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -13,16 +14,10 @@ namespace Cake.Common.IO { internal static class FileDeleter { - public static void DeleteFiles(ICakeContext context, string pattern) + public static void DeleteFiles(ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (pattern == null) - { - throw new ArgumentNullException("pattern"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(pattern); var files = context.GetFiles(pattern); if (files.Count == 0) @@ -36,14 +31,8 @@ public static void DeleteFiles(ICakeContext context, string pattern) public static void DeleteFiles(ICakeContext context, IEnumerable filePaths) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePaths); foreach (var filePath in filePaths) { @@ -53,21 +42,15 @@ public static void DeleteFiles(ICakeContext context, IEnumerable fileP public static void DeleteFile(ICakeContext context, FilePath filePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); filePath = filePath.MakeAbsolute(context.Environment); var file = context.FileSystem.GetFile(filePath); if (!file.Exists) { - const string format = "The file '{0}' do not exist."; + const string format = "The file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, filePath.FullPath); throw new FileNotFoundException(message, filePath.FullPath); } @@ -76,4 +59,4 @@ public static void DeleteFile(ICakeContext context, FilePath filePath) file.Delete(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/FileMover.cs b/src/Cake.Common/IO/FileMover.cs index d14b22d4ab..bbdc8bb5e8 100644 --- a/src/Cake.Common/IO/FileMover.cs +++ b/src/Cake.Common/IO/FileMover.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -15,31 +16,16 @@ internal static class FileMover { public static void MoveFileToDirectory(ICakeContext context, FilePath filePath, DirectoryPath targetDirectoryPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - if (targetDirectoryPath == null) - { - throw new ArgumentNullException("targetDirectoryPath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(targetDirectoryPath); MoveFile(context, filePath, targetDirectoryPath.GetFilePath(filePath)); } - public static void MoveFiles(ICakeContext context, string pattern, DirectoryPath targetDirectoryPath) + public static void MoveFiles(ICakeContext context, GlobPattern pattern, DirectoryPath targetDirectoryPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (pattern == null) - { - throw new ArgumentNullException("pattern"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(pattern); var files = context.GetFiles(pattern); if (files.Count == 0) @@ -53,25 +39,16 @@ public static void MoveFiles(ICakeContext context, string pattern, DirectoryPath public static void MoveFiles(this ICakeContext context, IEnumerable filePaths, DirectoryPath targetDirectoryPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } - if (targetDirectoryPath == null) - { - throw new ArgumentNullException("targetDirectoryPath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePaths); + ArgumentNullException.ThrowIfNull(targetDirectoryPath); targetDirectoryPath = targetDirectoryPath.MakeAbsolute(context.Environment); // Make sure the target directory exist. if (!context.FileSystem.Exist(targetDirectoryPath)) { - const string format = "The directory '{0}' do not exist."; + const string format = "The directory '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, targetDirectoryPath.FullPath); throw new DirectoryNotFoundException(message); } @@ -85,18 +62,9 @@ public static void MoveFiles(this ICakeContext context, IEnumerable fi public static void MoveFile(ICakeContext context, FilePath filePath, FilePath targetFilePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - if (targetFilePath == null) - { - throw new ArgumentNullException("targetFilePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(targetFilePath); filePath = filePath.MakeAbsolute(context.Environment); targetFilePath = targetFilePath.MakeAbsolute(context.Environment); @@ -105,7 +73,7 @@ public static void MoveFile(ICakeContext context, FilePath filePath, FilePath ta var targetDirectoryPath = targetFilePath.GetDirectory().MakeAbsolute(context.Environment); if (!context.FileSystem.Exist(targetDirectoryPath)) { - const string format = "The directory '{0}' do not exist."; + const string format = "The directory '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, targetDirectoryPath.FullPath); throw new DirectoryNotFoundException(message); } @@ -114,7 +82,7 @@ public static void MoveFile(ICakeContext context, FilePath filePath, FilePath ta var file = context.FileSystem.GetFile(filePath); if (!file.Exists) { - const string format = "The file '{0}' do not exist."; + const string format = "The file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, filePath.FullPath); throw new FileNotFoundException(message, filePath.FullPath); } @@ -124,4 +92,4 @@ public static void MoveFile(ICakeContext context, FilePath filePath, FilePath ta file.Move(targetFilePath.MakeAbsolute(context.Environment)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/GlobbingAliases.cs b/src/Cake.Common/IO/GlobbingAliases.cs index 4c3dfa6958..5255abaeb2 100644 --- a/src/Cake.Common/IO/GlobbingAliases.cs +++ b/src/Cake.Common/IO/GlobbingAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Linq; using Cake.Core; @@ -21,7 +22,7 @@ public static class GlobbingAliases /// /// /// var files = GetFiles("./**/Cake.*.dll"); - /// foreach(var file in files) + /// foreach (var file in files) /// { /// Information("File: {0}", file); /// } @@ -32,15 +33,11 @@ public static class GlobbingAliases /// A . [CakeMethodAlias] [CakeAliasCategory("Files")] - public static FilePathCollection GetFiles(this ICakeContext context, string pattern) + public static FilePathCollection GetFiles(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - return new FilePathCollection(context.Globber.Match(pattern).OfType(), - new PathComparer(context.Environment.IsUnix())); + return new FilePathCollection(context.Globber.Match(pattern).OfType()); } /// @@ -49,11 +46,11 @@ public static FilePathCollection GetFiles(this ICakeContext context, string patt /// /// /// Func<IFileSystemInfo, bool> exclude_node_modules = - /// fileSystemInfo=>!fileSystemInfo.Path.FullPath.EndsWith( - /// "node_modules", - /// StringComparison.OrdinalIgnoreCase); - /// var files = GetFiles("./**/Cake.*.dll", exclude_node_modules); - /// foreach(var file in files) + /// fileSystemInfo => !fileSystemInfo.Path.FullPath.EndsWith( + /// "node_modules", StringComparison.OrdinalIgnoreCase); + /// + /// var files = GetFiles("./**/Cake.*.dll", new GlobberSettings { Predicate = exclude_node_modules }); + /// foreach (var file in files) /// { /// Information("File: {0}", file); /// } @@ -61,28 +58,24 @@ public static FilePathCollection GetFiles(this ICakeContext context, string patt /// /// The context. /// The glob pattern to match. - /// The predicate used to filter files based on file system information. + /// The globber settings. /// A . [CakeMethodAlias] [CakeAliasCategory("Files")] - public static FilePathCollection GetFiles(this ICakeContext context, string pattern, Func predicate) + public static FilePathCollection GetFiles(this ICakeContext context, GlobPattern pattern, GlobberSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - return new FilePathCollection(context.Globber.Match(pattern, predicate).OfType(), - new PathComparer(context.Environment.IsUnix())); + return new FilePathCollection(context.Globber.Match(pattern, settings).OfType()); } /// - /// Gets all directory matching the specified pattern. + /// Gets all directories matching the specified pattern. /// /// /// /// var directories = GetDirectories("./src/**/obj/*"); - /// foreach(var directory in directories) + /// foreach (var directory in directories) /// { /// Information("Directory: {0}", directory); /// } @@ -93,28 +86,24 @@ public static FilePathCollection GetFiles(this ICakeContext context, string patt /// A . [CakeMethodAlias] [CakeAliasCategory("Directories")] - public static DirectoryPathCollection GetDirectories(this ICakeContext context, string pattern) + public static DirectoryPathCollection GetDirectories(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - return new DirectoryPathCollection(context.Globber.Match(pattern).OfType(), - new PathComparer(context.Environment.IsUnix())); + return new DirectoryPathCollection(context.Globber.Match(pattern).OfType()); } /// - /// Gets all directory matching the specified pattern. + /// Gets all directories matching the specified pattern. /// /// /// /// Func<IFileSystemInfo, bool> exclude_node_modules = - /// fileSystemInfo=>!fileSystemInfo.Path.FullPath.EndsWith( - /// "node_modules", - /// StringComparison.OrdinalIgnoreCase); - /// var directories = GetDirectories("./src/**/obj/*", exclude_node_modules); - /// foreach(var directory in directories) + /// fileSystemInfo => !fileSystemInfo.Path.FullPath.EndsWith( + /// "node_modules", StringComparison.OrdinalIgnoreCase); + /// + /// var directories = GetDirectories("./src/**/obj/*", new GlobberSettings { Predicate = exclude_node_modules }); + /// foreach (var directory in directories) /// { /// Information("Directory: {0}", directory); /// } @@ -122,19 +111,68 @@ public static DirectoryPathCollection GetDirectories(this ICakeContext context, /// /// The context. /// The glob pattern to match. - /// The predicate used to filter directories based on file system information. + /// The globber settings. /// A . [CakeMethodAlias] [CakeAliasCategory("Directories")] - public static DirectoryPathCollection GetDirectories(this ICakeContext context, string pattern, Func predicate) + public static DirectoryPathCollection GetDirectories(this ICakeContext context, GlobPattern pattern, GlobberSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + return new DirectoryPathCollection(context.Globber.Match(pattern, settings).OfType()); + } + + /// + /// Gets all paths matching the specified pattern. + /// + /// + /// + /// var paths = GetPaths("./src/**/obj/*"); + /// foreach (var path in paths) + /// { + /// Information("Path: {0}", path); + /// } + /// + /// + /// The context. + /// The glob pattern to match. + /// A . + [CakeMethodAlias] + [CakeAliasCategory("Paths")] + public static PathCollection GetPaths(this ICakeContext context, GlobPattern pattern) + { + ArgumentNullException.ThrowIfNull(context); + + return new PathCollection(context.Globber.Match(pattern)); + } + + /// + /// Gets all paths matching the specified pattern. + /// + /// + /// + /// Func<IFileSystemInfo, bool> exclude_node_modules = + /// fileSystemInfo => !fileSystemInfo.Path.FullPath.EndsWith( + /// "node_modules", StringComparison.OrdinalIgnoreCase); + /// + /// var paths = GetPaths("./src/**/obj/*", new GlobberSettings { Predicate = exclude_node_modules }); + /// foreach (var path in paths) + /// { + /// Information("Path: {0}", path); + /// } + /// + /// + /// The context. + /// The glob pattern to match. + /// The globber settings. + /// A . + [CakeMethodAlias] + [CakeAliasCategory("Paths")] + public static PathCollection GetPaths(this ICakeContext context, GlobPattern pattern, GlobberSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - return new DirectoryPathCollection(context.Globber.Match(pattern, predicate).OfType(), - new PathComparer(context.Environment.IsUnix())); + return new PathCollection(context.Globber.Match(pattern, settings)); } } } diff --git a/src/Cake.Common/IO/Paths/ConvertableDirectoryPath.cs b/src/Cake.Common/IO/Paths/ConvertableDirectoryPath.cs index 538a56e4f5..2120830c1d 100644 --- a/src/Cake.Common/IO/Paths/ConvertableDirectoryPath.cs +++ b/src/Cake.Common/IO/Paths/ConvertableDirectoryPath.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; @@ -26,16 +27,11 @@ namespace Cake.Common.IO.Paths /// public sealed class ConvertableDirectoryPath { - private readonly DirectoryPath _path; - /// /// Gets the path. /// /// The path. - public DirectoryPath Path - { - get { return _path; } - } + public DirectoryPath Path { get; } /// /// Initializes a new instance of the class. @@ -43,11 +39,8 @@ public DirectoryPath Path /// The path. internal ConvertableDirectoryPath(DirectoryPath path) { - if (path == null) - { - throw new ArgumentNullException("path"); - } - _path = path; + ArgumentNullException.ThrowIfNull(path); + Path = path; } /// @@ -59,17 +52,27 @@ internal ConvertableDirectoryPath(DirectoryPath path) /// A new directory path representing a combination of the two provided paths. public static ConvertableDirectoryPath operator +(ConvertableDirectoryPath left, ConvertableDirectoryPath right) { - if (left == null) - { - throw new ArgumentNullException("left"); - } - if (right == null) - { - throw new ArgumentNullException("right"); - } + ArgumentNullException.ThrowIfNull(left); + ArgumentNullException.ThrowIfNull(right); return new ConvertableDirectoryPath(left.Path.Combine(right.Path)); } + /// + /// Operator that combines A instance + /// with a instance. + /// + /// The left directory path operand. + /// The right directory path operand. + /// A new directory path representing a combination of the two provided paths. + public static ConvertableDirectoryPath operator +(DirectoryPath left, ConvertableDirectoryPath right) + { + ArgumentNullException.ThrowIfNull(left); + + ArgumentNullException.ThrowIfNull(right); + + return new ConvertableDirectoryPath(left.Combine(right)); + } + /// /// Operator that combines A instance /// with a instance. @@ -79,14 +82,8 @@ internal ConvertableDirectoryPath(DirectoryPath path) /// A new directory path representing a combination of the two provided paths. public static ConvertableDirectoryPath operator +(ConvertableDirectoryPath left, DirectoryPath right) { - if (left == null) - { - throw new ArgumentNullException("left"); - } - if (right == null) - { - throw new ArgumentNullException("right"); - } + ArgumentNullException.ThrowIfNull(left); + ArgumentNullException.ThrowIfNull(right); return new ConvertableDirectoryPath(left.Path.Combine(right)); } @@ -99,14 +96,8 @@ internal ConvertableDirectoryPath(DirectoryPath path) /// A new file path representing a combination of the two provided paths. public static ConvertableFilePath operator +(ConvertableDirectoryPath directory, ConvertableFilePath file) { - if (directory == null) - { - throw new ArgumentNullException("directory"); - } - if (file == null) - { - throw new ArgumentNullException("file"); - } + ArgumentNullException.ThrowIfNull(directory); + ArgumentNullException.ThrowIfNull(file); return new ConvertableFilePath(directory.Path.CombineWithFilePath(file.Path)); } @@ -119,14 +110,8 @@ internal ConvertableDirectoryPath(DirectoryPath path) /// A new file path representing a combination of the two provided paths. public static ConvertableFilePath operator +(ConvertableDirectoryPath directory, FilePath file) { - if (directory == null) - { - throw new ArgumentNullException("directory"); - } - if (file == null) - { - throw new ArgumentNullException("file"); - } + ArgumentNullException.ThrowIfNull(directory); + ArgumentNullException.ThrowIfNull(file); return new ConvertableFilePath(directory.Path.CombineWithFilePath(file)); } @@ -137,11 +122,7 @@ internal ConvertableDirectoryPath(DirectoryPath path) /// The result of the conversion. public static implicit operator DirectoryPath(ConvertableDirectoryPath path) { - if (path == null) - { - return null; - } - return path.Path; + return path?.Path; } /// @@ -151,11 +132,7 @@ public static implicit operator DirectoryPath(ConvertableDirectoryPath path) /// The result of the conversion. public static implicit operator string(ConvertableDirectoryPath path) { - if (path == null) - { - return null; - } - return path.Path.FullPath; + return path?.Path.FullPath; } /// @@ -166,7 +143,7 @@ public static implicit operator string(ConvertableDirectoryPath path) /// public override string ToString() { - return _path.FullPath; + return Path.FullPath; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/Paths/ConvertableFilePath.cs b/src/Cake.Common/IO/Paths/ConvertableFilePath.cs index ac2f8cb2e2..943a30f380 100644 --- a/src/Cake.Common/IO/Paths/ConvertableFilePath.cs +++ b/src/Cake.Common/IO/Paths/ConvertableFilePath.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; @@ -11,29 +12,21 @@ namespace Cake.Common.IO.Paths /// public sealed class ConvertableFilePath { - private readonly FilePath _path; - /// /// Initializes a new instance of the class. /// /// The path. internal ConvertableFilePath(FilePath path) { - if (path == null) - { - throw new ArgumentNullException("path"); - } - _path = path; + ArgumentNullException.ThrowIfNull(path); + Path = path; } /// /// Gets the path. /// /// The actual path. - public FilePath Path - { - get { return _path; } - } + public FilePath Path { get; } /// /// Performs an implicit conversion from to . @@ -42,11 +35,7 @@ public FilePath Path /// The result of the conversion. public static implicit operator FilePath(ConvertableFilePath path) { - if (path == null) - { - return null; - } - return path.Path; + return path?.Path; } /// @@ -56,11 +45,7 @@ public static implicit operator FilePath(ConvertableFilePath path) /// The result of the conversion. public static implicit operator string(ConvertableFilePath path) { - if (path == null) - { - return null; - } - return path.Path.FullPath; + return path?.Path.FullPath; } /// @@ -71,7 +56,23 @@ public static implicit operator string(ConvertableFilePath path) /// public override string ToString() { - return _path.FullPath; + return Path.FullPath; + } + + /// + /// Combines the directory path and file path with the separator. + /// + /// DirectoryPath. + /// ConvertableFilePath. + /// + /// A that represents this instance. + /// + public static ConvertableFilePath operator +(DirectoryPath dir, ConvertableFilePath file) + { + ArgumentNullException.ThrowIfNull(dir); + ArgumentNullException.ThrowIfNull(file); + + return new ConvertableFilePath(dir.CombineWithFilePath(file)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/ZipAliases.cs b/src/Cake.Common/IO/ZipAliases.cs index 0d6c432d4b..7a953c7add 100644 --- a/src/Cake.Common/IO/ZipAliases.cs +++ b/src/Cake.Common/IO/ZipAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -31,8 +32,11 @@ public static class ZipAliases [CakeMethodAlias] public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePath outputPath) { - var filePaths = context.GetFiles(string.Concat(rootPath, "/**/*")); - Zip(context, rootPath, outputPath, filePaths); + ArgumentNullException.ThrowIfNull(context); + + var paths = context.GetPaths(string.Concat(rootPath, "/**/*")); + var zipper = new Zipper(context.FileSystem, context.Environment, context.Log); + zipper.Zip(rootPath, outputPath, paths); } /// @@ -44,7 +48,7 @@ public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePa /// The pattern. /// /// - /// Zip("./", "xmlfiles.zip", "./*.xml"); + /// Zip("./", "XmlFiles.zip", "./*.xml"); /// /// [CakeMethodAlias] @@ -69,16 +73,13 @@ public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePa /// /// /// var files = GetFiles("./**/Cake.*.dll"); - /// Zip("./", "cakeassemblies.zip", files); + /// Zip("./", "CakeAssemblies.zip", files); /// /// [CakeMethodAlias] public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePath outputPath, IEnumerable filePaths) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var zipper = new Zipper(context.FileSystem, context.Environment, context.Log); zipper.Zip(rootPath, outputPath, filePaths); @@ -99,16 +100,13 @@ public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePa /// "./src/Cake/bin/Debug/Cake.Core.dll", /// "./src/Cake/bin/Debug/Cake.exe" /// }; - /// Zip("./", "cakebinaries.zip", files); + /// Zip("./", "CakeBinaries.zip", files); /// /// [CakeMethodAlias] public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePath outputPath, IEnumerable filePaths) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var paths = filePaths.Select(p => new FilePath(p)); var zipper = new Zipper(context.FileSystem, context.Environment, context.Log); @@ -116,7 +114,7 @@ public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePa } /// - /// Unzips the specified file + /// Unzips the specified file. /// /// The context. /// Zip file to unzip. @@ -128,14 +126,27 @@ public static void Zip(this ICakeContext context, DirectoryPath rootPath, FilePa /// [CakeMethodAlias] public static void Unzip(this ICakeContext context, FilePath zipFile, DirectoryPath outputPath) + => context.Unzip(zipFile, outputPath, false); + + /// + /// Unzips the specified file. + /// + /// The context. + /// Zip file to unzip. + /// Output path to unzip into. + /// Flag for if files should be overwritten in output. + /// + /// + /// Unzip("Cake.zip", "./cake", true); + /// + /// + [CakeMethodAlias] + public static void Unzip(this ICakeContext context, FilePath zipFile, DirectoryPath outputPath, bool overwriteFiles) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var zipper = new Zipper(context.FileSystem, context.Environment, context.Log); - zipper.Unzip(zipFile, outputPath); + zipper.Unzip(zipFile, outputPath, overwriteFiles); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/IO/Zipper.cs b/src/Cake.Common/IO/Zipper.cs index 2804e9167e..4de187b9a7 100644 --- a/src/Cake.Common/IO/Zipper.cs +++ b/src/Cake.Common/IO/Zipper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -9,6 +10,9 @@ using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; +using Directory = Cake.Core.IO.Directory; +using File = Cake.Core.IO.File; +using Path = Cake.Core.IO.Path; namespace Cake.Common.IO { @@ -17,6 +21,9 @@ namespace Cake.Common.IO /// public sealed class Zipper { + private const int ValidZipDateYearMin = 1980; + private const int ValidZipDateYearMax = 2107; + private static readonly DateTimeOffset InvalidZipDateIndicator = new DateTimeOffset(ValidZipDateYearMin, 1, 1, 0, 0, 0, TimeSpan.Zero); private readonly IFileSystem _fileSystem; private readonly ICakeEnvironment _environment; private readonly ICakeLog _log; @@ -30,22 +37,13 @@ public sealed class Zipper /// The log. public Zipper(IFileSystem fileSystem, ICakeEnvironment environment, ICakeLog log) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - if (log == null) - { - throw new ArgumentNullException("log"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); + ArgumentNullException.ThrowIfNull(log); _fileSystem = fileSystem; _environment = environment; _log = log; - _comparison = environment.IsUnix() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + _comparison = environment.Platform.IsUnix() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; } /// @@ -53,22 +51,77 @@ public Zipper(IFileSystem fileSystem, ICakeEnvironment environment, ICakeLog log /// /// The root path. /// The output path. - /// The files to zip. + /// The paths to zip. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - public void Zip(DirectoryPath rootPath, FilePath outputPath, IEnumerable filePaths) + public void Zip(DirectoryPath rootPath, FilePath outputPath, IEnumerable paths) { - if (rootPath == null) - { - throw new ArgumentNullException("rootPath"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } - if (filePaths == null) + ArgumentNullException.ThrowIfNull(rootPath); + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(paths); + + // Make root path and output file path absolute. + rootPath = rootPath.MakeAbsolute(_environment); + outputPath = outputPath.MakeAbsolute(_environment); + + // Get the output file. + var outputFile = _fileSystem.GetFile(outputPath); + + // Open up a stream to the output file. + _log.Verbose("Creating zip file: {0}", outputPath.FullPath); + using (var outputStream = outputFile.Open(FileMode.Create, FileAccess.Write, FileShare.None)) + using (var archive = new ZipArchive(outputStream, ZipArchiveMode.Create)) { - throw new ArgumentNullException("filePaths"); + var directories = new HashSet((paths as PathCollection)?.Comparer ?? PathComparer.Default); + foreach (var path in paths) + { + var absoluteFilePath = (path as FilePath)?.MakeAbsolute(_environment); + var relativeFilePath = GetRelativePath(rootPath, absoluteFilePath); + var absoluteDirectoryPath = (path as DirectoryPath)?.MakeAbsolute(_environment) ?? absoluteFilePath?.GetDirectory(); + var relativeDirectoryPath = GetRelativePath(rootPath, absoluteDirectoryPath); + + if (absoluteDirectoryPath != null && + !string.IsNullOrEmpty(relativeDirectoryPath) && !directories.Contains(relativeDirectoryPath)) + { + // Create directory entry. + _log.Verbose("Storing directory {0}", absoluteDirectoryPath); + var directory = _fileSystem.GetDirectory(absoluteDirectoryPath); + var entry = archive.CreateEntry(relativeDirectoryPath + "/"); + entry.LastWriteTime = (directory as Directory)?.LastWriteTime ?? DateTimeOffset.Now; + directories.Add(relativeDirectoryPath); + } + + if (absoluteFilePath != null && !string.IsNullOrEmpty(relativeFilePath)) + { + // Create file entry. + _log.Verbose("Compressing file {0}", absoluteFilePath); + var file = _fileSystem.GetFile(absoluteFilePath); + using (var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var entry = archive.CreateEntry(relativeFilePath); + entry.LastWriteTime = (file as File)?.LastWriteTime ?? DateTimeOffset.Now; + using (var entryStream = entry.Open()) + { + fileStream.CopyTo(entryStream); + } + } + } + } } + _log.Verbose("Zip successfully created: {0}", outputPath.FullPath); + } + + /// + /// Zips the specified directory. + /// + /// The root path. + /// The output path. + /// The files to zip. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] + public void Zip(DirectoryPath rootPath, FilePath outputPath, IEnumerable filePaths) + { + ArgumentNullException.ThrowIfNull(rootPath); + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(filePaths); // Make root path and output file path absolute. rootPath = rootPath.MakeAbsolute(_environment); @@ -82,22 +135,35 @@ public void Zip(DirectoryPath rootPath, FilePath outputPath, IEnumerable((filePaths as FilePathCollection)?.Comparer ?? PathComparer.Default); + foreach (var filePath in filePaths) { - var absoluteInputPath = inputPath.MakeAbsolute(_environment); - var file = _fileSystem.GetFile(absoluteInputPath); - using (var inputStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) + var absoluteFilePath = filePath.MakeAbsolute(_environment); + var relativeFilePath = GetRelativePath(rootPath, absoluteFilePath); + var absoluteDirectoryPath = absoluteFilePath.GetDirectory(); + var relativeDirectoryPath = GetRelativePath(rootPath, absoluteDirectoryPath); + + if (absoluteDirectoryPath != null && + !string.IsNullOrEmpty(relativeDirectoryPath) && !directories.Contains(relativeDirectoryPath)) { - // Get the relative filename to the rootPath. - var relativeFilePath = GetRelativeFilePath(rootPath, absoluteInputPath); - _log.Verbose("Compressing file {0}", absoluteInputPath); + // Create directory entry. + _log.Verbose("Storing directory {0}", absoluteDirectoryPath); + var directory = _fileSystem.GetDirectory(absoluteDirectoryPath); + var entry = archive.CreateEntry(relativeDirectoryPath + "/"); + entry.LastWriteTime = GetValidZipDateTimeOffset((directory as Directory)?.LastWriteTime); + directories.Add(relativeDirectoryPath); + } - // Create the zip archive entry. - var entry = archive.CreateEntry(relativeFilePath.FullPath); + // Create file entry. + _log.Verbose("Compressing file {0}", absoluteFilePath); + var file = _fileSystem.GetFile(absoluteFilePath); + using (var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var entry = archive.CreateEntry(relativeFilePath); + entry.LastWriteTime = GetValidZipDateTimeOffset((file as File)?.LastWriteTime); using (var entryStream = entry.Open()) { - // Copy the content of the input stream to the entry stream. - inputStream.CopyTo(entryStream); + fileStream.CopyTo(entryStream); } } } @@ -106,37 +172,51 @@ public void Zip(DirectoryPath rootPath, FilePath outputPath, IEnumerable - /// Unzips the specified file to the specified output path + /// Unzips the specified file to the specified output path. /// /// Zip file path. /// Output directory path. public void Unzip(FilePath zipPath, DirectoryPath outputPath) + => Unzip(zipPath, outputPath, false); + + /// + /// Unzips the specified file to the specified output path. + /// + /// Zip file path. + /// Output directory path. + /// Flag for if files should be overwritten in output. + public void Unzip(FilePath zipPath, DirectoryPath outputPath, bool overwriteFiles) { - if (zipPath == null) - { - throw new ArgumentNullException("zipPath"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } + ArgumentNullException.ThrowIfNull(zipPath); + ArgumentNullException.ThrowIfNull(outputPath); // Make root path and output file path absolute. zipPath = zipPath.MakeAbsolute(_environment); outputPath = outputPath.MakeAbsolute(_environment); - _log.Verbose("Unzipping file {0} to {1}", zipPath.FullPath, outputPath.FullPath); - ZipFile.ExtractToDirectory(zipPath.FullPath, outputPath.FullPath); + _log.Verbose("Unzipping file {0} to {1} (overwrite files: {2})", zipPath.FullPath, outputPath.FullPath, overwriteFiles); + ZipFile.ExtractToDirectory(zipPath.FullPath, outputPath.FullPath, overwriteFiles); + } + + private string GetRelativePath(DirectoryPath root, Path path) + { + if (path != null && !path.FullPath.StartsWith(root.FullPath, _comparison)) + { + const string format = "Path '{0}' is not relative to root path '{1}'."; + throw new CakeException(string.Format(CultureInfo.InvariantCulture, format, path.FullPath, root.FullPath)); + } + return path?.FullPath.Substring(root.FullPath.Length + (root.FullPath.Length > 1 && path.FullPath.Length > root.FullPath.Length ? 1 : 0)); } - private FilePath GetRelativeFilePath(DirectoryPath root, FilePath file) + private static DateTimeOffset GetValidZipDateTimeOffset(DateTime? value) { - if (!file.FullPath.StartsWith(root.FullPath, _comparison)) + var offsetValue = value ?? DateTime.UtcNow; + if (offsetValue.Year >= ValidZipDateYearMin && offsetValue.Year <= ValidZipDateYearMax) { - const string format = "File '{0}' is not relative to root path '{1}'."; - throw new CakeException(string.Format(CultureInfo.InvariantCulture, format, file.FullPath, root.FullPath)); + return offsetValue; } - return file.FullPath.Substring(root.FullPath.Length + 1); + + return InvalidZipDateIndicator; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Modules/CommonModule.cs b/src/Cake.Common/Modules/CommonModule.cs new file mode 100644 index 0000000000..aa385828ff --- /dev/null +++ b/src/Cake.Common/Modules/CommonModule.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Composition; + +namespace Cake.Common.Modules +{ + /// + /// The module responsible for registering + /// default types in the Cake.Common assembly. + /// + public sealed class CommonModule : ICakeModule + { + /// + public void Register(ICakeContainerRegistrar registrar) + { + } + } +} diff --git a/src/Cake.Common/Net/DownloadFileSettings.cs b/src/Cake.Common/Net/DownloadFileSettings.cs new file mode 100644 index 0000000000..4ab65c5454 --- /dev/null +++ b/src/Cake.Common/Net/DownloadFileSettings.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Net +{ + /// + /// Contains settings for . + /// + public sealed class DownloadFileSettings + { + /// + /// Gets or sets the username to use when downloading the file. + /// + public string Username { get; set; } + + /// + /// Gets or sets the password to use when downloading the file. + /// + public string Password { get; set; } + + /// + /// Gets or sets a value indicating whether default credentials are sent when downloading the file. + /// + /// + /// If set to true, any username and password that has been specified will be ignored. + /// + public bool UseDefaultCredentials { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Net/HttpAliases.cs b/src/Cake.Common/Net/HttpAliases.cs index c43e2e0313..b4d470ba0c 100644 --- a/src/Cake.Common/Net/HttpAliases.cs +++ b/src/Cake.Common/Net/HttpAliases.cs @@ -1,8 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using Cake.Core; using Cake.Core.Annotations; using Cake.Core.Diagnostics; @@ -11,7 +15,7 @@ namespace Cake.Common.Net { /// - /// Contains functionality related to HTTP operations + /// Contains functionality related to HTTP operations. /// [CakeAliasCategory("HTTP Operations")] public static class HttpAliases @@ -31,7 +35,30 @@ public static class HttpAliases [CakeAliasCategory("Download")] public static FilePath DownloadFile(this ICakeContext context, string address) { - return DownloadFile(context, new Uri(address)); + return DownloadFile(context, address, new DownloadFileSettings()); + } + + /// + /// Downloads the specified resource over HTTP to a temporary file with specified settings. + /// + /// + /// + /// var resource = DownloadFile("http://www.example.org/index.html", new DownloadFileSettings() + /// { + /// Username = "bob", + /// Password = "builder" + /// }); + /// + /// + /// The context. + /// The URL of the resource to download. + /// The settings. + /// The path to the downloaded file. + [CakeMethodAlias] + [CakeAliasCategory("Download")] + public static FilePath DownloadFile(this ICakeContext context, string address, DownloadFileSettings settings) + { + return DownloadFile(context, new Uri(address), settings); } /// @@ -50,13 +77,38 @@ public static FilePath DownloadFile(this ICakeContext context, string address) [CakeAliasCategory("Download")] public static FilePath DownloadFile(this ICakeContext context, Uri address) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + var tempFolder = context.Environment.GetSpecialPath(SpecialPath.LocalTemp); + var tempFilename = tempFolder.CombineWithFilePath(new FilePath(System.IO.Path.GetRandomFileName())); + DownloadFile(context, address, tempFilename, new DownloadFileSettings()); + return tempFilename; + } + + /// + /// Downloads the specified resource over HTTP to a temporary file with specified settings. + /// + /// + /// + /// var address = new Uri("http://www.example.org/index.html"); + /// var resource = DownloadFile(address, new DownloadFileSettings() + /// { + /// Username = "bob", + /// Password = "builder" + /// }); + /// + /// + /// The context. + /// The URL of file to download. + /// The settings. + /// The path to the downloaded file. + [CakeMethodAlias] + [CakeAliasCategory("Download")] + public static FilePath DownloadFile(this ICakeContext context, Uri address, DownloadFileSettings settings) + { + ArgumentNullException.ThrowIfNull(context); var tempFolder = context.Environment.GetSpecialPath(SpecialPath.LocalTemp); var tempFilename = tempFolder.CombineWithFilePath(new FilePath(System.IO.Path.GetRandomFileName())); - DownloadFile(context, address, tempFilename); + DownloadFile(context, address, tempFilename, settings); return tempFilename; } @@ -65,7 +117,8 @@ public static FilePath DownloadFile(this ICakeContext context, Uri address) /// /// /// - /// DownloadFile("http://www.example.org/index.html", "./outputdir"); + /// var outputPath = File("./index.html"); + /// DownloadFile("http://www.example.org/index.html", outputPath); /// /// /// The context. @@ -75,7 +128,31 @@ public static FilePath DownloadFile(this ICakeContext context, Uri address) [CakeAliasCategory("Download")] public static void DownloadFile(this ICakeContext context, string address, FilePath outputPath) { - DownloadFile(context, new Uri(address), outputPath); + DownloadFile(context, new Uri(address), outputPath, new DownloadFileSettings()); + } + + /// + /// Downloads the specified resource over HTTP to the specified output path and settings. + /// + /// + /// + /// var outputPath = File("./index.html"); + /// DownloadFile("http://www.example.org/index.html", outputPath, new DownloadFileSettings() + /// { + /// Username = "bob", + /// Password = "builder" + /// }); + /// + /// + /// The context. + /// The URL of the resource to download. + /// The output path. + /// The settings. + [CakeMethodAlias] + [CakeAliasCategory("Download")] + public static void DownloadFile(this ICakeContext context, string address, FilePath outputPath, DownloadFileSettings settings) + { + DownloadFile(context, new Uri(address), outputPath, settings); } /// @@ -84,36 +161,42 @@ public static void DownloadFile(this ICakeContext context, string address, FileP /// /// /// var address = new Uri("http://www.example.org/index.html"); - /// DownloadFile(address, "./outputdir"); + /// var outputPath = File("./index.html"); + /// DownloadFile(address, outputPath, new DownloadFileSettings() + /// { + /// Username = "bob", + /// Password = "builder" + /// }); /// /// /// The context. /// The URL of the resource to download. /// The output path. + /// The settings. [CakeMethodAlias] [CakeAliasCategory("Download")] - public static void DownloadFile(this ICakeContext context, Uri address, FilePath outputPath) + public static void DownloadFile(this ICakeContext context, Uri address, FilePath outputPath, DownloadFileSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (address == null) - { - throw new ArgumentNullException("address"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(address); + ArgumentNullException.ThrowIfNull(outputPath); context.Log.Verbose("Downloading file: {0}", address); // We track the last posted value since the event seems to fire many times for the same value. var percentComplete = 0; - using (var http = new HttpClient()) + using (var http = GetHttpClient(context, settings.UseDefaultCredentials)) { + if (!settings.UseDefaultCredentials) + { + if (!string.IsNullOrWhiteSpace(settings.Username) && !string.IsNullOrWhiteSpace(settings.Password)) + { + var byteArray = Encoding.ASCII.GetBytes(string.Concat(settings.Username, ":", settings.Password)); + http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); + } + } + var progress = new Progress(progressPercentage => { // Only write to log if the value changed and only ever 5%. @@ -130,5 +213,147 @@ public static void DownloadFile(this ICakeContext context, Uri address, FilePath context.Log.Verbose("Download complete, saved to: {0}", outputPath.FullPath); } + + /// + /// Uploads the specified file via a HTTP POST to the specified uri using multipart/form-data. + /// + /// + /// + /// var address = new Uri("http://www.example.org/upload"); + /// UploadFile(address, @"path/to/file.txt", new UploadFileSettings() + /// { + /// Username = "bob", + /// Password = "builder" + /// } + /// + /// + /// The context. + /// The URL of the upload resource. + /// The file to upload. + /// The settings. + [CakeMethodAlias] + [CakeAliasCategory("Upload")] + public static void UploadFile(this ICakeContext context, Uri address, FilePath filePath, UploadFileSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(address); + ArgumentNullException.ThrowIfNull(filePath); + + context.Log.Verbose("Uploading file: {0}", address); + using (var client = GetHttpClient(context, settings.UseDefaultCredentials)) + { + if (!settings.UseDefaultCredentials) + { + if (!string.IsNullOrWhiteSpace(settings.Username) && !string.IsNullOrWhiteSpace(settings.Password)) + { + var byteArray = Encoding.ASCII.GetBytes(string.Concat(settings.Username, ":", settings.Password)); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); + } + } + + client.UploadFileAsync(address, filePath.FullPath).Wait(); + } + context.Log.Verbose("File upload complete"); + } + + /// + /// Uploads the specified file via a HTTP POST to the specified uri using multipart/form-data. + /// + /// + /// + /// var address = "http://www.example.org/upload"; + /// UploadFile(address, @"path/to/file.txt"); + /// + /// + /// The context. + /// The URL of the upload resource. + /// The file to upload. + [CakeMethodAlias] + [CakeAliasCategory("Upload")] + public static void UploadFile(this ICakeContext context, string address, FilePath filePath) + { + UploadFile(context, new Uri(address), filePath, new UploadFileSettings()); + } + + /// + /// Uploads the specified byte array via a HTTP POST to the specified uri using multipart/form-data. + /// + /// + /// + /// var address = new Uri("http://www.example.org/upload"); + /// UploadFile(address, @"path/to/file.txt", new UploadFileSettings() { + /// Username = "bob", + /// Password = "builder" + /// }); + /// + /// + /// The context. + /// The URL of the upload resource. + /// The data to upload. + /// The filename to give the uploaded data. + /// The settings. + [CakeMethodAlias] + [CakeAliasCategory("Upload")] + public static void UploadFile(this ICakeContext context, Uri address, byte[] data, string fileName, UploadFileSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(address); + ArgumentNullException.ThrowIfNull(data); + + context.Log.Verbose("Uploading file: {0}", address); + using (var client = GetHttpClient(context, settings.UseDefaultCredentials)) + { + if (!settings.UseDefaultCredentials) + { + if (!string.IsNullOrWhiteSpace(settings.Username) && !string.IsNullOrWhiteSpace(settings.Password)) + { + var byteArray = Encoding.ASCII.GetBytes(string.Concat(settings.Username, ":", settings.Password)); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); + } + } + + client.UploadFileAsync(address, data, fileName).Wait(); + } + context.Log.Verbose("File upload complete"); + } + + /// + /// Uploads the specified byte array via a HTTP POST to the specified uri using multipart/form-data. + /// + /// + /// + /// var address = "http://www.example.org/upload"; + /// UploadFile(address, @"path/to/file.txt"); + /// + /// + /// The context. + /// The URL of the upload resource. + /// The data to upload. + /// The filename to give the uploaded data. + [CakeMethodAlias] + [CakeAliasCategory("Upload")] + public static void UploadFile(this ICakeContext context, string address, byte[] data, string fileName) + { + UploadFile(context, new Uri(address), data, fileName, new UploadFileSettings()); + } + + /// + /// Gets an pre-populated with the correct default headers such as User-Agent. + /// The returned client should be disposed of by the caller. + /// + /// The current Cake context. + /// Indicates whether or not to use default credentials. + /// A instance. + private static HttpClient GetHttpClient(ICakeContext context, bool useDefaultCredentials) + { + var clientHandler = new HttpClientHandler + { + UseDefaultCredentials = useDefaultCredentials, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + var client = new HttpClient(clientHandler); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Cake", context.Environment.Runtime.CakeVersion.ToString())); + return client; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Net/HttpClientExtensions.cs b/src/Cake.Common/Net/HttpClientExtensions.cs index dd6a02f1f4..d73411677d 100644 --- a/src/Cake.Common/Net/HttpClientExtensions.cs +++ b/src/Cake.Common/Net/HttpClientExtensions.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Cake.Common.Net @@ -25,27 +27,74 @@ public static async Task DownloadFileAsync(this HttpClient client, Uri requestUr contentLength = response.Content.Headers.ContentLength.Value; } - using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - using (var fileStream = File.Create(path, DefaultBufferSize)) + await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await using var fileStream = File.Create(path, DefaultBufferSize); + int bytesRead; + var totalBytesRead = 0L; + var buffer = new byte[DefaultBufferSize]; + + while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) { - int bytesRead; - var totalBytesRead = 0L; - var buffer = new byte[DefaultBufferSize]; + await fileStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); - while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) + totalBytesRead += bytesRead; + + if (contentLength.HasValue) { - await fileStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + var progressPercentage = totalBytesRead * 1d / (contentLength.Value * 1d); + + progress.Report((int)(progressPercentage * 100)); + } + } + } - totalBytesRead += bytesRead; + public static async Task UploadFileAsync(this HttpClient client, Uri requestUri, string path, string contentType = "application/octet-stream") + { + if (!File.Exists(path)) + { + throw new FileNotFoundException("Unable to find specified file", path); + } - if (contentLength.HasValue) - { - var progressPercentage = totalBytesRead * 1d / (contentLength.Value * 1d); + var file = new FileInfo(path); + var fileName = file.Name; - progress.Report((int)(progressPercentage * 100)); - } - } + return await UploadFileAsync(client, requestUri, file.OpenRead(), fileName, contentType).ConfigureAwait(false); + } + + public static async Task UploadFileAsync(this HttpClient client, Uri requestUri, Stream stream, string fileName, string contentType = "application/octet-stream") + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream), "Stream can not be null"); + } + if (!stream.CanRead) + { + throw new IOException("Unable to read from stream"); + } + + var boundary = "------------" + DateTime.Now.Ticks.ToString("x"); + var multipartFormDataContent = new MultipartFormDataContent(boundary); + + stream.Position = 0; + var streamContent = new StreamContent(stream) + { + Headers = { ContentType = MediaTypeHeaderValue.Parse(contentType) } + }; + + multipartFormDataContent.Add(streamContent, fileName, fileName); + var response = await client.PostAsync(requestUri, multipartFormDataContent).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + return response; + } + + public static async Task UploadFileAsync(this HttpClient client, Uri requestUri, byte[] data, string fileName, string contentType = "application/octet-stream") + { + if (data == null) + { + throw new ArgumentNullException(nameof(data), "No data to send"); } + + return await UploadFileAsync(client, requestUri, new MemoryStream(data), fileName, contentType).ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Net/UploadFileSettings.cs b/src/Cake.Common/Net/UploadFileSettings.cs new file mode 100644 index 0000000000..4c6f7d66af --- /dev/null +++ b/src/Cake.Common/Net/UploadFileSettings.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Net +{ + /// + /// Contains settings for . + /// + public sealed class UploadFileSettings + { + /// + /// Gets or sets the username to use when uploadingthe file. + /// + public string Username { get; set; } + + /// + /// Gets or sets the password to use when uploading the file. + /// + public string Password { get; set; } + + /// + /// Gets or sets a value indicating whether default credentials are sent when uploading the file. + /// + /// + /// If set to true, any username and password that has been specified will be ignored. + /// + public bool UseDefaultCredentials { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Polyfill/XmlTransformationHelper.cs b/src/Cake.Common/Polyfill/XmlTransformationHelper.cs new file mode 100644 index 0000000000..a5cdd787bf --- /dev/null +++ b/src/Cake.Common/Polyfill/XmlTransformationHelper.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Xml; +using System.Xml.Xsl; + +namespace Cake.Common.Polyfill +{ + internal static class XmlTransformationHelper + { + public static void Transform(XmlReader xsl, XsltArgumentList arguments, XmlReader xml, XmlWriter result) + { + var xslTransform = new XslCompiledTransform(); + xslTransform.Load(xsl); + xslTransform.Transform(xml, arguments, result); + } + } +} diff --git a/src/Cake.Common/Polyfill/XmlWriterSettingsHelper.cs b/src/Cake.Common/Polyfill/XmlWriterSettingsHelper.cs new file mode 100644 index 0000000000..b67561ef63 --- /dev/null +++ b/src/Cake.Common/Polyfill/XmlWriterSettingsHelper.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Xml; + +namespace Cake.Common.Polyfill +{ + internal static class XmlWriterSettingsHelper + { + public static bool GetDoNotEscapeUriAttributes(XmlWriterSettings settings) + { + return settings.DoNotEscapeUriAttributes; + } + + public static void SetDoNotEscapeUriAttributes(XmlWriterSettings settings, bool value) + { + settings.DoNotEscapeUriAttributes = value; + } + } +} diff --git a/src/Cake.Common/ProcessAliases.cs b/src/Cake.Common/ProcessAliases.cs index 8a90b863a3..2859bb8070 100644 --- a/src/Cake.Common/ProcessAliases.cs +++ b/src/Cake.Common/ProcessAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -36,7 +37,7 @@ public static int StartProcess(this ICakeContext context, FilePath fileName) } /// - /// Starts the process resource that is specified by the filename and arguments + /// Starts the process resource that is specified by the filename and arguments. /// /// The context. /// Name of the file. @@ -72,8 +73,7 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, str [CakeMethodAlias] public static int StartProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings) { - IEnumerable redirectedOutput; - return StartProcess(context, fileName, settings, out redirectedOutput); + return StartProcess(context, fileName, settings, out var redirectedOutput); } /// @@ -82,26 +82,31 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro /// The context. /// Name of the file. /// The settings. - /// outputs process output RedirectStandardOutput is true + /// Returns process output if is true. + /// Otherwise null is returned. /// The exit code that the started process specified when it terminated. /// /// - /// IEnumerable<string> redirectedOutput; - /// var exitCodeWithArgument = StartProcess("ping", new ProcessSettings{ - /// Arguments = "localhost", - /// RedirectStandardOutput = true - /// }, - /// out redirectedOutput - /// ); - /// //Output last line of process output - /// Information("Last line of output: {0}", redirectedOutput.LastOrDefault()); + /// IEnumerable<string> redirectedStandardOutput; + /// var exitCodeWithArgument = + /// StartProcess( + /// "ping", + /// new ProcessSettings { + /// Arguments = "localhost", + /// RedirectStandardOutput = true + /// }, + /// out redirectedStandardOutput + /// ); + /// + /// // Output last line of process output. + /// Information("Last line of output: {0}", redirectedStandardOutput.LastOrDefault()); /// /// // This should output 0 as valid arguments supplied /// Information("Exit code: {0}", exitCodeWithArgument); /// /// [CakeMethodAlias] - public static int StartProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings, out IEnumerable redirectedOutput) + public static int StartProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings, out IEnumerable redirectedStandardOutput) { var process = StartAndReturnProcess(context, fileName, settings); @@ -123,7 +128,7 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro process.WaitForExit(); } - redirectedOutput = settings.RedirectStandardOutput + redirectedStandardOutput = settings.RedirectStandardOutput ? process.GetStandardOutput() : null; @@ -131,6 +136,89 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro return process.GetExitCode(); } + /// + /// Starts the process resource that is specified by the filename and settings. + /// + /// The context. + /// Name of the file. + /// The settings. + /// Returns process output if is true. + /// Otherwise null is returned. + /// Returns process error output if is true. + /// Otherwise null is returned. + /// The exit code that the started process specified when it terminated. + /// + /// + /// IEnumerable<string> redirectedStandardOutput; + /// IEnumerable<string> redirectedErrorOutput; + /// var exitCodeWithArgument = + /// StartProcess( + /// "ping", + /// new ProcessSettings { + /// Arguments = "localhost", + /// RedirectStandardOutput = true, + /// RedirectStandardError = true + /// }, + /// out redirectedStandardOutput, + /// out redirectedErrorOutput + /// ); + /// + /// // Output last line of process output. + /// Information("Last line of output: {0}", redirectedStandardOutput.LastOrDefault()); + /// + /// // Throw exception if anything was written to the standard error. + /// if (redirectedErrorOutput.Any()) + /// { + /// throw new Exception( + /// string.Format( + /// "Errors occurred: {0}", + /// string.Join(", ", redirectedErrorOutput))); + /// } + /// + /// // This should output 0 as valid arguments supplied + /// Information("Exit code: {0}", exitCodeWithArgument); + /// + /// + [CakeMethodAlias] + public static int StartProcess( + this ICakeContext context, + FilePath fileName, + ProcessSettings settings, + out IEnumerable redirectedStandardOutput, + out IEnumerable redirectedErrorOutput) + { + var process = StartAndReturnProcess(context, fileName, settings); + + // Wait for the process to stop. + if (settings.Timeout.HasValue) + { + if (!process.WaitForExit(settings.Timeout.Value)) + { + throw new TimeoutException( + string.Format( + CultureInfo.InvariantCulture, + "Process TimeOut ({0}): {1}", + settings.Timeout.Value, + fileName)); + } + } + else + { + process.WaitForExit(); + } + + redirectedStandardOutput = settings.RedirectStandardOutput + ? process.GetStandardOutput() + : null; + + redirectedErrorOutput = settings.RedirectStandardError + ? process.GetStandardError() + : null; + + // Return the exit code. + return process.GetExitCode(); + } + /// /// Starts the process resource that is specified by the filename and settings. /// @@ -140,7 +228,7 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro /// The newly started process. /// /// - /// using(var process = StartAndReturnProcess("ping", new ProcessSettings{ Arguments = "localhost" })) + /// using (var process = StartAndReturnProcess("ping", new ProcessSettings{ Arguments = "localhost" })) /// { /// process.WaitForExit(); /// // This should output 0 as valid arguments supplied @@ -152,23 +240,17 @@ public static int StartProcess(this ICakeContext context, FilePath fileName, Pro [CakeMethodAlias] public static IProcess StartAndReturnProcess(this ICakeContext context, FilePath fileName, ProcessSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (fileName == null) - { - throw new ArgumentNullException("fileName"); - } - if (settings == null) + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(fileName); + ArgumentNullException.ThrowIfNull(settings); + + if (!settings.NoWorkingDirectory) { - throw new ArgumentNullException("settings"); + // Set the working directory. + var workingDirectory = settings.WorkingDirectory ?? context.Environment.WorkingDirectory; + settings.WorkingDirectory = workingDirectory.MakeAbsolute(context.Environment); } - // Get the working directory. - var workingDirectory = settings.WorkingDirectory ?? context.Environment.WorkingDirectory; - settings.WorkingDirectory = workingDirectory.MakeAbsolute(context.Environment); - // Start the process. var process = context.ProcessRunner.Start(fileName, settings); if (process == null) @@ -187,7 +269,7 @@ public static IProcess StartAndReturnProcess(this ICakeContext context, FilePath /// The newly started process. /// /// - /// using(var process = StartAndReturnProcess("ping")) + /// using (var process = StartAndReturnProcess("ping")) /// { /// process.WaitForExit(); /// // This should output 0 as valid arguments supplied @@ -202,4 +284,4 @@ public static IProcess StartAndReturnProcess(this ICakeContext context, FilePath return StartAndReturnProcess(context, fileName, new ProcessSettings()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Properties/AssemblyInfo.cs b/src/Cake.Common/Properties/AssemblyInfo.cs index cd7ff77ef4..398dc71463 100644 --- a/src/Cake.Common/Properties/AssemblyInfo.cs +++ b/src/Cake.Common/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Reflection; using System.Runtime.CompilerServices; @@ -10,7 +11,6 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cake.Common")] -[assembly: AssemblyDescription("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -21,4 +21,4 @@ [assembly: CLSCompliant(true)] // Expose internals to the test assembly. -[assembly: InternalsVisibleTo("Cake.Common.Tests")] +[assembly: InternalsVisibleTo("Cake.Common.Tests")] \ No newline at end of file diff --git a/src/Cake.Common/Properties/Namespaces.cs b/src/Cake.Common/Properties/Namespaces.cs index bde6700b76..77ff2a432d 100644 --- a/src/Cake.Common/Properties/Namespaces.cs +++ b/src/Cake.Common/Properties/Namespaces.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Runtime.CompilerServices; // ReSharper disable once CheckNamespace @@ -476,4 +477,4 @@ namespace Cake.Common.Tools.Xml internal class NamespaceDoc { } -} +} \ No newline at end of file diff --git a/src/Cake.Common/ReleaseNotes.cs b/src/Cake.Common/ReleaseNotes.cs index 128df501e4..b02fefc893 100644 --- a/src/Cake.Common/ReleaseNotes.cs +++ b/src/Cake.Common/ReleaseNotes.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -12,25 +13,45 @@ namespace Cake.Common /// public sealed class ReleaseNotes { - private readonly Version _version; private readonly List _notes; /// /// Gets the version. /// /// The version. - public Version Version - { - get { return _version; } - } + public SemVersion SemVersion { get; } + + /// + /// Gets the version. + /// + /// The version. + public Version Version { get; } /// /// Gets the release notes. /// /// The release notes. - public IReadOnlyList Notes + public IReadOnlyList Notes => _notes; + + /// + /// Gets the raw text of the line that was extracted from. + /// + /// The raw text of the Version line. + public string RawVersionLine { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The semantic version. + /// The notes. + /// The raw text of the version line. + public ReleaseNotes(SemVersion semVersion, IEnumerable notes, string rawVersionLine) + : this( + semVersion?.AssemblyVersion ?? throw new ArgumentNullException(nameof(semVersion)), + semVersion, + notes, + rawVersionLine) { - get { return _notes; } } /// @@ -38,14 +59,22 @@ public IReadOnlyList Notes /// /// The version. /// The notes. - public ReleaseNotes(Version version, IEnumerable notes) + /// The raw text of the version line. + public ReleaseNotes(Version version, IEnumerable notes, string rawVersionLine) + : this( + version ?? throw new ArgumentNullException(nameof(version)), + new SemVersion(version.Major, version.Minor, version.Build), + notes, + rawVersionLine) + { + } + + private ReleaseNotes(Version version, SemVersion semVersion, IEnumerable notes, string rawVersionLine) { - if (version == null) - { - throw new ArgumentNullException("version"); - } - _version = version; + Version = version ?? throw new ArgumentNullException(nameof(version)); + SemVersion = semVersion ?? throw new ArgumentNullException(nameof(semVersion)); + RawVersionLine = rawVersionLine; _notes = new List(notes ?? Enumerable.Empty()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/ReleaseNotesAliases.cs b/src/Cake.Common/ReleaseNotesAliases.cs index 0a968d73ed..2c945bbfb0 100644 --- a/src/Cake.Common/ReleaseNotesAliases.cs +++ b/src/Cake.Common/ReleaseNotesAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -16,6 +17,7 @@ namespace Cake.Common /// Contains functionality related to release notes. /// [CakeAliasCategory("Release Notes")] + public static class ReleaseNotesAliases { private static readonly ReleaseNotesParser _parser; @@ -34,10 +36,10 @@ static ReleaseNotesAliases() /// /// /// var releaseNotes = ParseAllReleaseNotes("./ReleaseNotes.md"); - /// foreach(var releaseNote in releaseNotes) + /// foreach (var releaseNote in releaseNotes) /// { /// Information("Version: {0}", releaseNote.Version); - /// foreach(var note in releaseNote.Notes) + /// foreach (var note in releaseNote.Notes) /// { /// Information("\t{0}", note); /// } @@ -47,14 +49,8 @@ static ReleaseNotesAliases() [CakeMethodAlias] public static IReadOnlyList ParseAllReleaseNotes(this ICakeContext context, FilePath filePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(filePath); if (filePath.IsRelative) { @@ -65,7 +61,7 @@ public static IReadOnlyList ParseAllReleaseNotes(this ICakeContext var file = context.FileSystem.GetFile(filePath); if (!file.Exists) { - const string format = "Release notes file '{0}' do not exist."; + const string format = "Release notes file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, filePath.FullPath); throw new CakeException(message); } @@ -86,7 +82,7 @@ public static IReadOnlyList ParseAllReleaseNotes(this ICakeContext /// /// var releaseNote = ParseReleaseNotes("./ReleaseNotes.md"); /// Information("Version: {0}", releaseNote.Version); - /// foreach(var note in releaseNote.Notes) + /// foreach (var note in releaseNote.Notes) /// { /// Information("\t{0}", note); /// } @@ -98,4 +94,4 @@ public static ReleaseNotes ParseReleaseNotes(this ICakeContext context, FilePath return ParseAllReleaseNotes(context, filePath).First(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/ReleaseNotesParser.cs b/src/Cake.Common/ReleaseNotesParser.cs index ebae4544e1..4045263044 100644 --- a/src/Cake.Common/ReleaseNotesParser.cs +++ b/src/Cake.Common/ReleaseNotesParser.cs @@ -21,7 +21,7 @@ public sealed class ReleaseNotesParser /// public ReleaseNotesParser() { - _versionRegex = new Regex(@"([0-9]+\.)+[0-9]+"); + _versionRegex = new Regex(@"(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?"); } /// @@ -31,10 +31,7 @@ public ReleaseNotesParser() /// All release notes. public IReadOnlyList Parse(string content) { - if (content == null) - { - throw new ArgumentNullException("content"); - } + ArgumentNullException.ThrowIfNull(content); var lines = content.SplitLines(); if (lines.Length > 0) @@ -67,15 +64,15 @@ private IReadOnlyList ParseComplexFormat(string[] lines) break; } - // Parse header. - var versionResult = _versionRegex.Match(lines[lineIndex]); - if (!versionResult.Success) + // Create release notes. + var semVer = SemVersion.Zero; + var version = SemVersion.TryParse(lines[lineIndex], out semVer); + if (!version) { throw new CakeException("Could not parse version from release notes header."); } - // Create release notes. - var version = Version.Parse(versionResult.Value); + var rawVersionLine = lines[lineIndex]; // Increase the line index. lineIndex++; @@ -104,10 +101,10 @@ private IReadOnlyList ParseComplexFormat(string[] lines) lineIndex++; } - result.Add(new ReleaseNotes(version, notes)); + result.Add(new ReleaseNotes(semVer, notes, rawVersionLine)); } - return result.OrderByDescending(x => x.Version).ToArray(); + return result.OrderByDescending(x => x.SemVersion).ToArray(); } private IReadOnlyList ParseSimpleFormat(string[] lines) @@ -131,24 +128,22 @@ private IReadOnlyList ParseSimpleFormat(string[] lines) } // Parse header. - var versionResult = _versionRegex.Match(line); - if (!versionResult.Success) + var semVer = SemVersion.Zero; + var version = SemVersion.TryParse(lines[lineIndex], out semVer); + if (!version) { throw new CakeException("Could not parse version from release notes header."); } - - var version = Version.Parse(versionResult.Value); - // Parse the description. - line = line.Substring(versionResult.Length).Trim('-', ' '); + line = line.Substring(semVer.ToString().Length).Trim('-', ' '); // Add the release notes to the result. - result.Add(new ReleaseNotes(version, new[] { line })); + result.Add(new ReleaseNotes(semVer, new[] { line }, line)); lineIndex++; } - return result.OrderByDescending(x => x.Version).ToArray(); + return result.OrderByDescending(x => x.SemVersion).ToArray(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Security/DirectoryHash.cs b/src/Cake.Common/Security/DirectoryHash.cs new file mode 100644 index 0000000000..995c0b79cd --- /dev/null +++ b/src/Cake.Common/Security/DirectoryHash.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using Cake.Core.IO; + +namespace Cake.Common.Security +{ + /// + /// Represents a calculated directory hash. + /// + public sealed class DirectoryHash + { + private readonly byte[] _hash; + + /// + /// Initializes a new instance of the class. + /// + /// The directory path. + /// The computed hash. + /// The algorithm used. + /// List of all computed . + public DirectoryHash( + DirectoryPath directoryPath, + byte[] hash, + HashAlgorithm hashAlgorithm, + IEnumerable fileHashList) + { + ArgumentNullException.ThrowIfNull(directoryPath); + + ArgumentNullException.ThrowIfNull(hash); + + ArgumentNullException.ThrowIfNull(fileHashList); + + Path = directoryPath; + _hash = (byte[])hash.Clone(); + Algorithm = hashAlgorithm; + FileHashList.AddRange(fileHashList); + } + + /// + /// Gets the algorithm used for the hash computation. + /// + public HashAlgorithm Algorithm { get; } + + /// + /// Gets the for the directory. + /// + public DirectoryPath Path { get; } + + /// + /// Gets the list of for all files of the directory. + /// + public List FileHashList { get; } = new List(); + + /// + /// Gets the raw computed hash. + /// + public byte[] ComputedHash => (byte[])_hash.Clone(); + + /// + /// Convert the directory hash to a hexadecimal string. + /// + /// A hexadecimal string representing the computed hash. + public string ToHex() + { + // Each byte becomes two characters. Prepare the StringBuilder accordingly. + var builder = new StringBuilder(_hash.Length * 2); + + foreach (var b in _hash) + { + builder.AppendFormat("{0:x2}", b); + } + + return builder.ToString(); + } + } +} diff --git a/src/Cake.Common/Security/DirectoryHashCalculator.cs b/src/Cake.Common/Security/DirectoryHashCalculator.cs new file mode 100644 index 0000000000..62071af686 --- /dev/null +++ b/src/Cake.Common/Security/DirectoryHashCalculator.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Cake.Common.IO; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Security +{ + /// + /// Class for calculating a hash for a directory. + /// + public class DirectoryHashCalculator + { + private readonly ICakeContext _context; + private readonly IHashAlgorithmBuilder _hashAlgorithmBuilder; + private readonly FileHashCalculator _fileHashCalculator; + + /// + /// Initializes a new instance of the class. + /// + /// The cake context. + /// The hash algorithm builder. + public DirectoryHashCalculator(ICakeContext context, IHashAlgorithmBuilder hashAlgorithmBuilder) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(hashAlgorithmBuilder); + + _context = context; + _hashAlgorithmBuilder = hashAlgorithmBuilder; + _fileHashCalculator = new FileHashCalculator(_context.FileSystem, _hashAlgorithmBuilder); + } + + /// + /// Calculates the hash for a given directory. + /// + /// The directory path. + /// The glob pattern to match. + /// The hash algorithm to use. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a MD5 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs", HashAlgorithm.MD5).ToHex()); + /// + /// + public DirectoryHash Calculate( + DirectoryPath directoryPath, + IEnumerable pattern, + HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(directoryPath); + + ArgumentNullException.ThrowIfNull(pattern); + + using (var incrementalDirectoryHash = _hashAlgorithmBuilder.CreateHashAlgorithm(hashAlgorithm)) + { + var fileHashList = new List(); + var files = GetDirectoryFiles(directoryPath, pattern).OrderBy(file => file.FullPath); + if (files.Any()) + { + var lastFile = files.LastOrDefault(); + foreach (var file in GetDirectoryFiles(directoryPath, pattern)) + { + var fileContentAndNameHash = CalculateFileContentAndNameHash(file, directoryPath, hashAlgorithm); + fileHashList.Add(fileContentAndNameHash); + if (file == lastFile) + { + incrementalDirectoryHash.TransformFinalBlock( + fileContentAndNameHash.ComputedHash, 0, + fileContentAndNameHash.ComputedHash.Length); + } + else + { + incrementalDirectoryHash.TransformBlock( + fileContentAndNameHash.ComputedHash, 0, + fileContentAndNameHash.ComputedHash.Length, + fileContentAndNameHash.ComputedHash, 0); + } + } + + var directoryHash = incrementalDirectoryHash.Hash; + return new DirectoryHash(directoryPath, directoryHash, hashAlgorithm, fileHashList); + } + + // No files found. + return null; + } + } + + /// + /// Calculates the hash for a given directory. + /// + /// The directory path. + /// The glob pattern to match. + /// The hash algorithm to use. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a MD5 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs", HashAlgorithm.MD5).ToHex()); + /// + /// + public DirectoryHash Calculate( + DirectoryPath directoryPath, + IEnumerable pattern, + HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(directoryPath); + + ArgumentNullException.ThrowIfNull(pattern); + + return Calculate( + directoryPath, + pattern.Select(GlobPattern.FromString), + hashAlgorithm); + } + + private FilePathCollection GetDirectoryFiles( + DirectoryPath directoryPath, + IEnumerable pattern) + { + var directory = new Directory(directoryPath); + + if (!directory.Exists) + { + const string format = "Directory '{0}' does not exist."; + var message = string.Format(CultureInfo.InvariantCulture, format, directoryPath.FullPath); + throw new CakeException(message); + } + + var filePathCollection = new FilePathCollection(); + var fullGlobs = pattern.Select(x => directoryPath + x.Pattern).ToArray(); + + foreach (var glob in fullGlobs) + { + foreach (var file in _context.GetFiles(glob)) + { + filePathCollection.Add(file); + } + } + + return filePathCollection; + } + + private FileHash CalculateFileContentAndNameHash( + FilePath filePath, + DirectoryPath directoryPath, + HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(filePath); + + ArgumentNullException.ThrowIfNull(directoryPath); + + using (var incrementalFileHash = _hashAlgorithmBuilder.CreateHashAlgorithm(hashAlgorithm)) + { + // Calculate file content hash + var fileContentHash = _fileHashCalculator.Calculate(filePath, hashAlgorithm); + + // Combine file content hash + incrementalFileHash.TransformBlock( + fileContentHash.ComputedHash, 0, + fileContentHash.ComputedHash.Length, + fileContentHash.ComputedHash, 0); + + // combine filename hash + var relativeFilePath = filePath.GetRelativePath(directoryPath); + var fileFullNameArray = Encoding.ASCII.GetBytes(filePath.GetFilename().FullPath); + using (var hashAlgorithmInstance = _hashAlgorithmBuilder.CreateHashAlgorithm(hashAlgorithm)) + { + var filenameHash = hashAlgorithmInstance.ComputeHash(fileFullNameArray); + incrementalFileHash.TransformFinalBlock( + filenameHash, 0, + filenameHash.Length); + } + var fileContentAndNameHash = incrementalFileHash.Hash; + + return new FileHash(filePath, fileContentAndNameHash, hashAlgorithm); + } + } + } +} diff --git a/src/Cake.Common/Security/FileHash.cs b/src/Cake.Common/Security/FileHash.cs index ecd3c65790..7c174f32f4 100644 --- a/src/Cake.Common/Security/FileHash.cs +++ b/src/Cake.Common/Security/FileHash.cs @@ -1,83 +1,67 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Text; -using Cake.Core.IO; - -namespace Cake.Common.Security -{ - /// - /// Represents a calculated file hash. - /// - public sealed class FileHash - { - private readonly FilePath _filePath; - private readonly byte[] _hash; - private readonly HashAlgorithm _hashAlgorithm; - - /// - /// Initializes a new instance of the class. - /// - /// The file path. - /// The computed hash. - /// The algorithm used. - public FileHash(FilePath filePath, byte[] hash, HashAlgorithm hashAlgorithm) - { - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - - if (hash == null) - { - throw new ArgumentNullException("hash"); - } - - _filePath = filePath; - _hash = (byte[])hash.Clone(); - _hashAlgorithm = hashAlgorithm; - } - - /// - /// Gets the algorithm used for the hash computation. - /// - public HashAlgorithm Algorithm - { - get { return _hashAlgorithm; } - } - - /// - /// Gets the for the file. - /// - public FilePath Path - { - get { return _filePath; } - } - - /// - /// Gets the raw computed hash. - /// - public byte[] ComputedHash - { - get { return (byte[])_hash.Clone(); } - } - - /// - /// Convert the file hash to a hexadecimal string. - /// - /// A hexadecimal string representing the computed hash. - public string ToHex() - { - // Each byte becomes two characters. Prepare the StringBuilder accordingly. - var builder = new StringBuilder(_hash.Length * 2); - - foreach (var b in _hash) - { - builder.AppendFormat("{0:x2}", b); - } - - return builder.ToString(); - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text; +using Cake.Core.IO; + +namespace Cake.Common.Security +{ + /// + /// Represents a calculated file hash. + /// + public sealed class FileHash + { + private readonly byte[] _hash; + + /// + /// Initializes a new instance of the class. + /// + /// The file path. + /// The computed hash. + /// The algorithm used. + public FileHash(FilePath filePath, byte[] hash, HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(filePath); + + ArgumentNullException.ThrowIfNull(hash); + + Path = filePath; + _hash = (byte[])hash.Clone(); + Algorithm = hashAlgorithm; + } + + /// + /// Gets the algorithm used for the hash computation. + /// + public HashAlgorithm Algorithm { get; } + + /// + /// Gets the for the file. + /// + public FilePath Path { get; } + + /// + /// Gets the raw computed hash. + /// + public byte[] ComputedHash => (byte[])_hash.Clone(); + + /// + /// Convert the file hash to a hexadecimal string. + /// + /// A hexadecimal string representing the computed hash. + public string ToHex() + { + // Each byte becomes two characters. Prepare the StringBuilder accordingly. + var builder = new StringBuilder(_hash.Length * 2); + + foreach (var b in _hash) + { + builder.AppendFormat("{0:x2}", b); + } + + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Security/FileHashCalculator.cs b/src/Cake.Common/Security/FileHashCalculator.cs index f30ab6ce76..675b6f4505 100644 --- a/src/Cake.Common/Security/FileHashCalculator.cs +++ b/src/Cake.Common/Security/FileHashCalculator.cs @@ -1,80 +1,71 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Globalization; -using System.Security.Cryptography; -using Cake.Core; -using Cake.Core.IO; - -namespace Cake.Common.Security -{ - /// - /// Represents a file hash operation. - /// - public sealed class FileHashCalculator - { - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - public FileHashCalculator(IFileSystem fileSystem) - { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - - _fileSystem = fileSystem; - } - - /// - /// Calculates the hash for a file using the given algorithm. - /// - /// The file path. - /// The algorithm to use. - /// A instance representing the calculated hash. - public FileHash Calculate(FilePath filePath, HashAlgorithm hashAlgorithm) - { - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - - var file = _fileSystem.GetFile(filePath); - - if (!file.Exists) - { - const string format = "File '{0}' does not exist."; - var message = string.Format(CultureInfo.InvariantCulture, format, filePath.FullPath); - throw new CakeException(message); - } - - using (var hashAlgo = GetHashAlgorithm(hashAlgorithm)) - using (var readStream = file.OpenRead()) - { - var hash = hashAlgo.ComputeHash(readStream); - return new FileHash(filePath, hash, hashAlgorithm); - } - } - - private System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(HashAlgorithm hashAlgorithm) - { - switch (hashAlgorithm) - { - case HashAlgorithm.MD5: - return MD5.Create(); - - case HashAlgorithm.SHA256: - return SHA256.Create(); - - case HashAlgorithm.SHA512: - return SHA512.Create(); - } - - throw new NotSupportedException(hashAlgorithm.ToString()); - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Security +{ + /// + /// Represents a file hash operation. + /// + public sealed class FileHashCalculator + { + private readonly IFileSystem _fileSystem; + private readonly IHashAlgorithmBuilder _hashAlgorithmBuilder; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + public FileHashCalculator(IFileSystem fileSystem) + : this(fileSystem, new HashAlgorithmBuilder()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The hash algorithm builder. + public FileHashCalculator(IFileSystem fileSystem, IHashAlgorithmBuilder hashAlgorithmBuilder) + { + ArgumentNullException.ThrowIfNull(fileSystem); + + ArgumentNullException.ThrowIfNull(hashAlgorithmBuilder); + + _fileSystem = fileSystem; + _hashAlgorithmBuilder = hashAlgorithmBuilder; + } + + /// + /// Calculates the hash for a file using the given algorithm. + /// + /// The file path. + /// The algorithm to use. + /// A instance representing the calculated hash. + public FileHash Calculate(FilePath filePath, HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(filePath); + + var file = _fileSystem.GetFile(filePath); + + if (!file.Exists) + { + const string format = "File '{0}' does not exist."; + var message = string.Format(CultureInfo.InvariantCulture, format, filePath.FullPath); + throw new CakeException(message); + } + + using (var hashAlgo = _hashAlgorithmBuilder.CreateHashAlgorithm(hashAlgorithm)) + using (var readStream = file.OpenRead()) + { + var hash = hashAlgo.ComputeHash(readStream); + return new FileHash(filePath, hash, hashAlgorithm); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Security/HashAlgorithm.cs b/src/Cake.Common/Security/HashAlgorithm.cs index dce0ab44f4..e007a8e56d 100644 --- a/src/Cake.Common/Security/HashAlgorithm.cs +++ b/src/Cake.Common/Security/HashAlgorithm.cs @@ -1,29 +1,30 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Security -{ - /// - /// The hash algorithm to use for a specific operation. - /// - public enum HashAlgorithm - { - /// - /// The MD5 hash algorithm. - /// - // ReSharper disable once InconsistentNaming - MD5, - - /// - /// The SHA256 hash algorithm. - /// - // ReSharper disable once InconsistentNaming - SHA256, - - /// - /// The SHA512 hash algorithm. - /// - // ReSharper disable once InconsistentNaming - SHA512 - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Security +{ + /// + /// The hash algorithm to use for a specific operation. + /// + public enum HashAlgorithm + { + /// + /// The MD5 hash algorithm. + /// + // ReSharper disable once InconsistentNaming + MD5, + + /// + /// The SHA256 hash algorithm. + /// + // ReSharper disable once InconsistentNaming + SHA256, + + /// + /// The SHA512 hash algorithm. + /// + // ReSharper disable once InconsistentNaming + SHA512 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Security/HashAlgorithmBuilder.cs b/src/Cake.Common/Security/HashAlgorithmBuilder.cs new file mode 100644 index 0000000000..d74ea129db --- /dev/null +++ b/src/Cake.Common/Security/HashAlgorithmBuilder.cs @@ -0,0 +1,27 @@ +using System; +using System.Security.Cryptography; + +namespace Cake.Common.Security +{ + /// + public sealed class HashAlgorithmBuilder : IHashAlgorithmBuilder + { + /// + public System.Security.Cryptography.HashAlgorithm CreateHashAlgorithm(HashAlgorithm hashAlgorithm) + { + switch (hashAlgorithm) + { + case HashAlgorithm.MD5: + return MD5.Create(); + + case HashAlgorithm.SHA256: + return SHA256.Create(); + + case HashAlgorithm.SHA512: + return SHA512.Create(); + } + + throw new NotSupportedException(hashAlgorithm.ToString()); + } + } +} diff --git a/src/Cake.Common/Security/IHashAlgorithmBuilder.cs b/src/Cake.Common/Security/IHashAlgorithmBuilder.cs new file mode 100644 index 0000000000..6000e07a02 --- /dev/null +++ b/src/Cake.Common/Security/IHashAlgorithmBuilder.cs @@ -0,0 +1,17 @@ +namespace Cake.Common.Security +{ + /// + /// Creates a instance by + /// . + /// + public interface IHashAlgorithmBuilder + { + /// + /// Return a instance of by + /// enum value. + /// + /// Algorithm enum value. + /// A instance. + System.Security.Cryptography.HashAlgorithm CreateHashAlgorithm(HashAlgorithm hashAlgorithm); + } +} \ No newline at end of file diff --git a/src/Cake.Common/Security/SecurityAliases.cs b/src/Cake.Common/Security/SecurityAliases.cs index 4f7282686a..51c8691732 100644 --- a/src/Cake.Common/Security/SecurityAliases.cs +++ b/src/Cake.Common/Security/SecurityAliases.cs @@ -1,63 +1,154 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.Annotations; -using Cake.Core.IO; - -namespace Cake.Common.Security -{ - /// - /// Contains security related functionality, such as calculating file - /// hashes. - /// - [CakeAliasCategory("Security")] - public static class SecurityAliases - { - /// - /// Calculates the hash for a given file using the default (SHA256) algorithm. - /// - /// The context. - /// The file path. - /// A instance representing the calculated hash. - /// - /// - /// Information( - /// "Cake executable file SHA256 hash: {0}", - /// CalculateFileHash("Cake.exe").ToHex()); - /// - /// - [CakeMethodAlias] - public static FileHash CalculateFileHash(this ICakeContext context, FilePath filePath) - { - return CalculateFileHash(context, filePath, HashAlgorithm.SHA256); - } - - /// - /// Calculates the hash for a given file. - /// - /// The context. - /// The file path. - /// The hash algorithm to use. - /// A instance representing the calculated hash. - /// - /// - /// Information( - /// "Cake executable file MD5 hash: {0}", - /// CalculateFileHash("Cake.exe", HashAlgorithm.MD5).ToHex()); - /// - /// - [CakeMethodAlias] - public static FileHash CalculateFileHash(this ICakeContext context, FilePath filePath, HashAlgorithm hashAlgorithm) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - var fileHashCalculator = new FileHashCalculator(context.FileSystem); - return fileHashCalculator.Calculate(filePath, hashAlgorithm); - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Security +{ + /// + /// Contains security related functionality, such as calculating file + /// hashes. + /// + [CakeAliasCategory("Security")] + public static class SecurityAliases + { + /// + /// Calculates the hash for a given file using the default (SHA256) algorithm. + /// + /// The context. + /// The file path. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake executable file SHA256 hash: {0}", + /// CalculateFileHash("Cake.exe").ToHex()); + /// + /// + [CakeMethodAlias] + public static FileHash CalculateFileHash(this ICakeContext context, FilePath filePath) + { + return CalculateFileHash(context, filePath, HashAlgorithm.SHA256); + } + + /// + /// Calculates the hash for a given file. + /// + /// The context. + /// The file path. + /// The hash algorithm to use. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake executable file MD5 hash: {0}", + /// CalculateFileHash("Cake.exe", HashAlgorithm.MD5).ToHex()); + /// + /// + [CakeMethodAlias] + public static FileHash CalculateFileHash(this ICakeContext context, FilePath filePath, HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(context); + + var fileHashCalculator = new FileHashCalculator(context.FileSystem, new HashAlgorithmBuilder()); + return fileHashCalculator.Calculate(filePath, hashAlgorithm); + } + + /// + /// Calculates the hash for a given directory using the default (SHA256) algorithm. + /// + /// The context. + /// The file path. + /// The glob pattern to match. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a SHA256 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs").ToHex()); + /// + /// + [CakeMethodAlias] + public static DirectoryHash CalculateDirectoryHash(this ICakeContext context, + IEnumerable globs, DirectoryPath directoryPath) + { + return CalculateDirectoryHash(context, directoryPath, globs, HashAlgorithm.SHA256); + } + + /// + /// Calculates the hash for a given directory. + /// + /// The context. + /// The file path. + /// The glob pattern to match. + /// The hash algorithm to use. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a MD5 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs", HashAlgorithm.MD5).ToHex()); + /// + /// + [CakeMethodAlias] + public static DirectoryHash CalculateDirectoryHash(this ICakeContext context, + DirectoryPath directoryPath, IEnumerable globs, HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(context); + + var directoryHashCalculator = new DirectoryHashCalculator(context, new HashAlgorithmBuilder()); + return directoryHashCalculator.Calculate(directoryPath, globs, hashAlgorithm); + } + + /// + /// Calculates the hash for a given directory using the default (SHA256) algorithm. + /// + /// The context. + /// The file path. + /// The glob pattern to match. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a SHA256 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs").ToHex()); + /// + /// + [CakeMethodAlias] + public static DirectoryHash CalculateDirectoryHash(this ICakeContext context, + IEnumerable globs, DirectoryPath directoryPath) + { + return CalculateDirectoryHash(context, directoryPath, globs, HashAlgorithm.SHA256); + } + + /// + /// Calculates the hash for a given directory. + /// + /// The context. + /// The file path. + /// The glob pattern to match. + /// The hash algorithm to use. + /// A instance representing the calculated hash. + /// + /// + /// Information( + /// "Cake It calculates the hashes from all cs files in all subdirectories using a MD5 hash: {0}", + /// CalculateDirectoryHash("C:\directoryToHash", "./**/*.cs", HashAlgorithm.MD5).ToHex()); + /// + /// + [CakeMethodAlias] + public static DirectoryHash CalculateDirectoryHash(this ICakeContext context, + DirectoryPath directoryPath, IEnumerable globs, HashAlgorithm hashAlgorithm) + { + ArgumentNullException.ThrowIfNull(context); + + var directoryHashCalculator = new DirectoryHashCalculator(context, new HashAlgorithmBuilder()); + return directoryHashCalculator.Calculate(directoryPath, globs, hashAlgorithm); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/SemanticVersion.cs b/src/Cake.Common/SemanticVersion.cs new file mode 100644 index 0000000000..3d67dd35ad --- /dev/null +++ b/src/Cake.Common/SemanticVersion.cs @@ -0,0 +1,375 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace Cake.Common +{ + /// + /// Class for representing semantic versions. + /// + public class SemVersion : IComparable, IComparable, IEquatable + { + /// + /// Gets the default version of a SemanticVersion. + /// + public static SemVersion Zero { get; } = new SemVersion(0, 0, 0, null, null, "0.0.0"); + + /// + /// Regex property for parsing a semantic version number. + /// + public static readonly Regex SemVerRegex = + new Regex( + @"(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*)))?(?:\-(?[0-9A-Z\.-]+))?(?:\+(?[0-9A-Z\.-]+))?)?", + RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + + /// + /// Gets the major number of the version. + /// + public int Major { get; } + + /// + /// Gets the minor number of the version. + /// + public int Minor { get; } + + /// + /// Gets the patch number of the version. + /// + public int Patch { get; } + + /// + /// Gets the prerelease of the version. + /// + public string PreRelease { get; } + + /// + /// Gets the meta of the version. + /// + public string Meta { get; } + + /// + /// Gets a value indicating whether semantic version is a prerelease or not. + /// + public bool IsPreRelease { get; } + + /// + /// Gets a value indicating whether semantic version has meta or not. + /// + public bool HasMeta { get; } + + /// + /// Gets the VersionString of the semantic version. + /// + public string VersionString { get; } + + /// + /// Gets the AssemblyVersion of the semantic version. + /// + public Version AssemblyVersion { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Major number. + /// Minor number. + /// Patch number. + /// Prerelease string. + /// Meta string. + public SemVersion(int major, int minor, int patch, string preRelease = null, string meta = null) : this(major, minor, patch, preRelease, meta, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Major number. + /// Minor number. + /// Patch number. + /// Prerelease string. + /// Meta string. + /// The complete version number. + public SemVersion(int major, int minor, int patch, string preRelease, string meta, string versionString) + { + Major = major; + Minor = minor; + Patch = patch; + AssemblyVersion = new Version(major, minor, patch); + IsPreRelease = !string.IsNullOrEmpty(preRelease); + HasMeta = !string.IsNullOrEmpty(meta); + PreRelease = IsPreRelease ? preRelease : null; + Meta = HasMeta ? meta : null; + + if (!string.IsNullOrEmpty(versionString)) + { + VersionString = versionString; + } + else + { + var sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}.{2}", Major, Minor, Patch); + + if (IsPreRelease) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "-{0}", PreRelease); + } + + if (HasMeta) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "+{0}", Meta); + } + + VersionString = sb.ToString(); + } + } + + /// + /// Method which tries to parse a semantic version string. + /// + /// the version that should be parsed. + /// the out parameter the parsed version should be stored in. + /// Returns a boolean indicating if the parse was successful. + public static bool TryParse(string version, + out SemVersion semVersion) + { + semVersion = Zero; + + if (string.IsNullOrEmpty(version)) + { + return false; + } + + var match = SemVerRegex.Match(version); + if (!match.Success) + { + return false; + } + + if (!int.TryParse( + match.Groups["Major"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var major) || + !int.TryParse( + match.Groups["Minor"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var minor) || + !int.TryParse( + match.Groups["Patch"].Value, + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var patch)) + { + return false; + } + + semVersion = new SemVersion( + major, + minor, + patch, + match.Groups["PreRelease"]?.Value, + match.Groups["Meta"]?.Value, + version); + + return true; + } + + /// + /// Checks if two SemVersion objects are equal. + /// + /// the other SemVersion want to test equality to. + /// A boolean indicating whether the objecst we're equal or not. + public bool Equals(SemVersion other) + { + return other is object + && Major == other.Major + && Minor == other.Minor + && Patch == other.Patch + && string.Equals(PreRelease, other.PreRelease, StringComparison.OrdinalIgnoreCase) + && string.Equals(Meta, other.Meta, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Compares to SemVersion objects to and another. + /// + /// The SemVersion object we compare with. + /// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older. + public int CompareTo(SemVersion other) + { + if (other is null) + { + return 1; + } + + if (Equals(other)) + { + return 0; + } + + if (Major > other.Major) + { + return 1; + } + + if (Major < other.Major) + { + return -1; + } + + if (Minor > other.Minor) + { + return 1; + } + + if (Minor < other.Minor) + { + return -1; + } + + if (Patch > other.Patch) + { + return 1; + } + + if (Patch < other.Patch) + { + return -1; + } + + if (IsPreRelease != other.IsPreRelease) + { + return other.IsPreRelease ? 1 : -1; + } + + switch (StringComparer.InvariantCultureIgnoreCase.Compare(PreRelease, other.PreRelease)) + { + case 1: + return 1; + + case -1: + return -1; + + default: + { + return (string.IsNullOrEmpty(Meta) != string.IsNullOrEmpty(other.Meta)) + ? string.IsNullOrEmpty(Meta) ? 1 : -1 + : StringComparer.InvariantCultureIgnoreCase.Compare(Meta, other.Meta); + } + } + } + + /// + /// Compares to SemVersion objects to and another. + /// + /// The object we compare with. + /// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older. + public int CompareTo(object obj) + { + return (obj is SemVersion semVersion) + ? CompareTo(semVersion) + : -1; + } + + /// + /// Equals-method for the SemVersion class. + /// + /// the other SemVersion want to test equality to. + /// A boolean indicating whether the objecst we're equal or not. + public override bool Equals(object obj) + { + return (obj is SemVersion semVersion) + && Equals(semVersion); + } + + /// + /// Method for getting the hashcode of the SemVersion object. + /// + /// The hashcode of the SemVersion object. + public override int GetHashCode() + { + unchecked + { + var hashCode = Major; + hashCode = (hashCode * 397) ^ Minor; + hashCode = (hashCode * 397) ^ Patch; + hashCode = (hashCode * 397) ^ (PreRelease != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(PreRelease) : 0); + hashCode = (hashCode * 397) ^ (Meta != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Meta) : 0); + return hashCode; + } + } + + /// + /// Returns the string representation of an SemVersion object. + /// + /// The string representation of the object. + public override string ToString() + { + int[] verParts = { Major, Minor, Patch }; + string ver = string.Join(".", verParts); + return $"{ver}{(IsPreRelease ? "-" : string.Empty)}{PreRelease}{Meta}"; + } + + /// + /// The greater than-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was greater than operand2. + public static bool operator >(SemVersion operand1, SemVersion operand2) + => operand1 is { } && operand1.CompareTo(operand2) == 1; + + /// + /// The less than-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was less than operand2. + public static bool operator <(SemVersion operand1, SemVersion operand2) + => operand1 is { } + ? operand1.CompareTo(operand2) == -1 + : operand2 is { }; + + /// + /// The greater than or equal to-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was greater than or equal to operand2. + public static bool operator >=(SemVersion operand1, SemVersion operand2) + => operand1 is { } + ? operand1.CompareTo(operand2) >= 0 + : operand2 is null; + + /// + /// The lesser than or equal to-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was lesser than or equal to operand2. + public static bool operator <=(SemVersion operand1, SemVersion operand2) + => operand1 is null || operand1.CompareTo(operand2) <= 0; + + /// + /// The equal to-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was equal to operand2. + public static bool operator ==(SemVersion operand1, SemVersion operand2) + => operand1?.Equals(operand2) ?? operand2 is null; + + /// + /// The not equal to-operator for the SemVersion class. + /// + /// first SemVersion. + /// second. SemVersion. + /// A value indicating if the operand1 was not equal to operand2. + public static bool operator !=(SemVersion operand1, SemVersion operand2) + => !(operand1?.Equals(operand2) ?? operand2 is null); + } +} diff --git a/src/Cake.Common/Solution/Project/ProjectAliases.cs b/src/Cake.Common/Solution/Project/ProjectAliases.cs index b91cbd1cf1..e85673ebb2 100644 --- a/src/Cake.Common/Solution/Project/ProjectAliases.cs +++ b/src/Cake.Common/Solution/Project/ProjectAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -15,7 +16,7 @@ namespace Cake.Common.Solution.Project public static class ProjectAliases { /// - /// Parses project information from project file + /// Parses project information from project file. /// /// The context. /// The project file path. @@ -28,13 +29,15 @@ public static class ProjectAliases /// Configuration : {0} /// Platform : {1} /// OutputType : {2} - /// RootNameSpace : {3} - /// AssemblyName : {4} - /// TargetFrameworkVersion: {5} - /// Files : {6}", + /// OutputPath : {3} + /// RootNameSpace : {4} + /// AssemblyName : {5} + /// TargetFrameworkVersion: {6} + /// Files : {7}", /// parsedProject.Configuration, /// parsedProject.Platform, /// parsedProject.OutputType, + /// parsedProject.OutputPath, /// parsedProject.RootNameSpace, /// parsedProject.AssemblyName, /// parsedProject.TargetFrameworkVersion, @@ -54,13 +57,10 @@ public static class ProjectAliases [CakeMethodAlias] public static ProjectParserResult ParseProject(this ICakeContext context, FilePath projectPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var parser = new ProjectParser(context.FileSystem, context.Environment); return parser.Parse(projectPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectAssemblyReference.cs b/src/Cake.Common/Solution/Project/ProjectAssemblyReference.cs new file mode 100644 index 0000000000..1a384f9fa9 --- /dev/null +++ b/src/Cake.Common/Solution/Project/ProjectAssemblyReference.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Solution.Project +{ + /// + /// Represents a project assembly reference. + /// + /// + /// Schema from https://msdn.microsoft.com/en-us/library/ms164283.aspx + /// and https://msdn.microsoft.com/en-us/library/bb629388.aspx. + /// + public sealed class ProjectAssemblyReference + { + /// + /// Gets or sets the reference to include. + /// + /// + /// The reference to include. + /// + public string Include { get; set; } + + /// + /// Gets or sets the relative or absolute path of the assembly. + /// + /// + /// The relative or absolute path of the assembly. + /// + public FilePath HintPath { get; set; } + + /// + /// Gets or sets the display name of the assembly. + /// + /// + /// The display name of the assembly. + /// + public string Name { get; set; } + + /// + /// Gets or sets the simple or strong fusion name for the item. + /// + /// + /// The simple or strong fusion name for the item. + /// + public string FusionName { get; set; } + + /// + /// Gets or sets whether only the version in the fusion name should be referenced. + /// + /// + /// Whether only the version in the fusion name should be referenced. + /// + public bool? SpecificVersion { get; set; } + + /// + /// Gets or sets any aliases for the reference. + /// + /// + /// Any aliases for the reference. + /// + public string Aliases { get; set; } + + /// + /// Gets or sets whether the reference should be copied to the output folder. + /// + /// + /// Whether the reference should be copied to the output folder. + /// + public bool? Private { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectFile.cs b/src/Cake.Common/Solution/Project/ProjectFile.cs index 24a0ff11ad..777f815d39 100644 --- a/src/Cake.Common/Solution/Project/ProjectFile.cs +++ b/src/Cake.Common/Solution/Project/ProjectFile.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; namespace Cake.Common.Solution.Project @@ -32,4 +33,4 @@ public sealed class ProjectFile /// public bool Compile { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectParser.cs b/src/Cake.Common/Solution/Project/ProjectParser.cs index 3bf2de35ba..7763784c53 100644 --- a/src/Cake.Common/Solution/Project/ProjectParser.cs +++ b/src/Cake.Common/Solution/Project/ProjectParser.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using System.Linq; @@ -25,14 +26,8 @@ public sealed class ProjectParser /// The environment. public ProjectParser(IFileSystem fileSystem, ICakeEnvironment environment) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); _fileSystem = fileSystem; _environment = environment; } @@ -44,10 +39,7 @@ public ProjectParser(IFileSystem fileSystem, ICakeEnvironment environment) /// The parsed project. public ProjectParserResult Parse(FilePath projectPath) { - if (projectPath == null) - { - throw new ArgumentNullException("projectPath"); - } + ArgumentNullException.ThrowIfNull(projectPath); if (projectPath.IsRelative) { @@ -58,7 +50,7 @@ public ProjectParserResult Parse(FilePath projectPath) var file = _fileSystem.GetFile(projectPath); if (!file.Exists) { - const string format = "Project file '{0}' do not exist."; + const string format = "Project file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, projectPath.FullPath); throw new CakeException(message); } @@ -76,14 +68,18 @@ from propertyGroup in project.Elements(ProjectXElement.PropertyGroup) .Elements(ProjectXElement.Configuration) .Select(cfg => cfg.Value) .FirstOrDefault() + let platform = propertyGroup + .Elements(ProjectXElement.Platform) + .Select(cfg => cfg.Value) + .FirstOrDefault() + let configPropertyGroups = project.Elements(ProjectXElement.PropertyGroup) + .Where(x => x.Elements(ProjectXElement.OutputPath).Any() && x.Attribute("Condition") != null) + .Where(x => x.Attribute("Condition").Value.Contains(string.Concat("== '", configuration, "|", platform, "'"))) where !string.IsNullOrWhiteSpace(configuration) select new { Configuration = configuration, - Platform = propertyGroup - .Elements(ProjectXElement.Platform) - .Select(platform => platform.Value) - .FirstOrDefault(), + Platform = platform, ProjectGuid = propertyGroup .Elements(ProjectXElement.ProjectGuid) .Select(projectGuid => projectGuid.Value) @@ -92,6 +88,10 @@ from propertyGroup in project.Elements(ProjectXElement.PropertyGroup) .Elements(ProjectXElement.OutputType) .Select(outputType => outputType.Value) .FirstOrDefault(), + OutputPath = configPropertyGroups + .Elements(ProjectXElement.OutputPath) + .Select(outputPath => DirectoryPath.FromString(outputPath.Value)) + .FirstOrDefault(), RootNameSpace = propertyGroup .Elements(ProjectXElement.RootNamespace) .Select(rootNameSpace => rootNameSpace.Value) @@ -137,16 +137,68 @@ from include in element.Attributes("Include") Compile = element.Name == ProjectXElement.Compile }).ToArray(); + var references = + (from project in document.Elements(ProjectXElement.Project) + from itemGroup in project.Elements(ProjectXElement.ItemGroup) + from element in itemGroup.Elements() + where element.Name == ProjectXElement.Reference + from include in element.Attributes("Include") + let includeValue = include.Value + let hintPathElement = element.Element(ProjectXElement.HintPath) + let nameElement = element.Element(ProjectXElement.Name) + let fusionNameElement = element.Element(ProjectXElement.FusionName) + let specificVersionElement = element.Element(ProjectXElement.SpecificVersion) + let aliasesElement = element.Element(ProjectXElement.Aliases) + let privateElement = element.Element(ProjectXElement.Private) + select new ProjectAssemblyReference + { + Include = includeValue, + HintPath = string.IsNullOrEmpty(hintPathElement?.Value) ? null : PathHelper.IsPathRooted(hintPathElement?.Value) + ? hintPathElement?.Value : rootPath.CombineWithFilePath(hintPathElement.Value), + Name = nameElement?.Value, + FusionName = fusionNameElement?.Value, + SpecificVersion = specificVersionElement == null ? (bool?)null : bool.Parse(specificVersionElement.Value), + Aliases = aliasesElement?.Value, + Private = privateElement == null ? (bool?)null : bool.Parse(privateElement.Value) + }).ToArray(); + + var projectReferences = + (from project in document.Elements(ProjectXElement.Project) + from itemGroup in project.Elements(ProjectXElement.ItemGroup) + from element in itemGroup.Elements() + where element.Name == ProjectXElement.ProjectReference + from include in element.Attributes("Include") + let value = include.Value + where !string.IsNullOrEmpty(value) + let filePath = rootPath.CombineWithFilePath(value) + let nameElement = element.Element(ProjectXElement.Name) + let projectElement = element.Element(ProjectXElement.Project) + let packageElement = element.Element(ProjectXElement.Package) + let privateElement = element.Element(ProjectXElement.Private) + select new ProjectReference + { + FilePath = filePath, + RelativePath = value, + Name = nameElement?.Value, + Project = projectElement?.Value, + Package = string.IsNullOrEmpty(packageElement?.Value) + ? null : rootPath.CombineWithFilePath(packageElement.Value), + Private = privateElement == null ? (bool?)null : bool.Parse(privateElement.Value), + }).ToArray(); + return new ProjectParserResult( projectProperties.Configuration, projectProperties.Platform, projectProperties.ProjectGuid, projectProperties.OutputType, + projectProperties.OutputPath, projectProperties.RootNameSpace, projectProperties.AssemblyName, projectProperties.TargetFrameworkVersion, projectProperties.TargetFrameworkProfile, - projectFiles); + projectFiles, + references, + projectReferences); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectParserResult.cs b/src/Cake.Common/Solution/Project/ProjectParserResult.cs index 00ae2c1763..53ebd04b80 100644 --- a/src/Cake.Common/Solution/Project/ProjectParserResult.cs +++ b/src/Cake.Common/Solution/Project/ProjectParserResult.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; +using Cake.Core.IO; namespace Cake.Common.Solution.Project { @@ -11,129 +13,123 @@ namespace Cake.Common.Solution.Project /// public sealed class ProjectParserResult { - private readonly string _configuration; - private readonly string _platform; - private readonly string _projectGuid; - private readonly string _outputType; - private readonly string _rootNameSpace; - private readonly string _assemblyName; - private readonly string _targetFrameworkVersion; - private readonly string _targetFrameworkProfile; - private readonly ICollection _files; - /// /// Gets the build configuration. /// /// The build configuration. - public string Configuration - { - get { return _configuration; } - } + public string Configuration { get; } /// /// Gets the target platform. /// /// The platform. - public string Platform - { - get { return _platform; } - } + public string Platform { get; } /// /// Gets the unique project identifier. /// /// The unique project identifier. - public string ProjectGuid - { - get { return _projectGuid; } - } + public string ProjectGuid { get; } /// /// Gets the compiler output type, i.e. Exe/Library. /// /// The output type. - public string OutputType - { - get { return _outputType; } - } + public string OutputType { get; } + + /// + /// Gets the compiler output path. + /// + /// The output path. + public DirectoryPath OutputPath { get; } /// /// Gets the default root namespace. /// /// The root namespace. - public string RootNameSpace - { - get { return _rootNameSpace; } - } + public string RootNameSpace { get; } /// /// Gets the build target assembly name. /// /// The assembly name. - public string AssemblyName - { - get { return _assemblyName; } - } + public string AssemblyName { get; } /// /// Gets the compiler target framework version. /// /// The target framework version. - public string TargetFrameworkVersion - { - get { return _targetFrameworkVersion; } - } + public string TargetFrameworkVersion { get; } /// /// Gets the compiler target framework profile. /// /// The target framework profile. - public string TargetFrameworkProfile - { - get { return _targetFrameworkProfile; } - } + public string TargetFrameworkProfile { get; } /// /// Gets the project content files. /// /// The files. - public ICollection Files - { - get { return _files; } - } + public ICollection Files { get; } + + /// + /// Gets the references. + /// + /// + /// The references. + /// + public ICollection References { get; } + + /// + /// Gets the references to other projects. + /// + /// + /// The references. + /// + public ICollection ProjectReferences { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The build configuration. /// The target platform. /// The unique project identifier. /// The compiler output type. + /// The compiler output path. /// The default root namespace. /// Gets the build target assembly name. /// The compiler framework version. /// The compiler framework profile. /// The project content files. + /// The references. + /// The references to other projects. public ProjectParserResult( string configuration, string platform, string projectGuid, string outputType, + DirectoryPath outputPath, string rootNameSpace, string assemblyName, string targetFrameworkVersion, string targetFrameworkProfile, - IEnumerable files) + IEnumerable files, + IEnumerable references, + IEnumerable projectReferences) { - _configuration = configuration; - _platform = platform; - _projectGuid = projectGuid; - _outputType = outputType; - _rootNameSpace = rootNameSpace; - _assemblyName = assemblyName; - _targetFrameworkVersion = targetFrameworkVersion; - _targetFrameworkProfile = targetFrameworkProfile; - _files = files.ToList().AsReadOnly(); + Configuration = configuration; + Platform = platform; + ProjectGuid = projectGuid; + OutputType = outputType; + OutputPath = outputPath; + RootNameSpace = rootNameSpace; + AssemblyName = assemblyName; + TargetFrameworkVersion = targetFrameworkVersion; + TargetFrameworkProfile = targetFrameworkProfile; + Files = files.ToList().AsReadOnly(); + References = references.ToList().AsReadOnly(); + ProjectReferences = projectReferences.ToList().AsReadOnly(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectReference.cs b/src/Cake.Common/Solution/Project/ProjectReference.cs new file mode 100644 index 0000000000..3022a28044 --- /dev/null +++ b/src/Cake.Common/Solution/Project/ProjectReference.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Solution.Project +{ + /// + /// Represents a project reference to another project. + /// + /// + /// Schema from https://msdn.microsoft.com/en-us/library/ms164283.aspx + /// and https://msdn.microsoft.com/en-us/library/bb629388.aspx. + /// + public sealed class ProjectReference + { + /// + /// Gets or sets the path to the referenced project file. + /// + /// + /// The path to the referenced project file. + /// + public FilePath FilePath { get; set; } + + /// + /// Gets or sets the relative path to the referenced project file. + /// + /// + /// The relative path to the referenced project file. + /// + public string RelativePath { get; set; } + + /// + /// Gets or sets the display name of the reference. + /// + /// + /// The display name of the reference. + /// + public string Name { get; set; } + + /// + /// Gets or sets a GUID for the reference. + /// + /// + /// A GUID for the reference. + /// + public string Project { get; set; } + + /// + /// Gets or sets the path of the project file that is being referenced. + /// + /// + /// The path of the project file that is being referenced. + /// + public FilePath Package { get; set; } + + /// + /// Gets or sets whether the reference should be copied to the output folder. + /// + /// + /// Whether the reference should be copied to the output folder. + /// + public bool? Private { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/ProjectXElement.cs b/src/Cake.Common/Solution/Project/ProjectXElement.cs index 280a11f3e9..f0fd26b014 100644 --- a/src/Cake.Common/Solution/Project/ProjectXElement.cs +++ b/src/Cake.Common/Solution/Project/ProjectXElement.cs @@ -1,57 +1,63 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Solution.Project { /// - /// MSBuild Project Xml Element XNames + /// MSBuild Project Xml Element XNames. /// internal static class ProjectXElement { private const string XmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; /// - /// Project root element + /// Project root element. /// internal const string Project = "{" + XmlNamespace + "}Project"; /// - /// Item group element + /// Item group element. /// internal const string ItemGroup = "{" + XmlNamespace + "}ItemGroup"; /// - /// Assembly reference element + /// Assembly reference element. /// internal const string Reference = "{" + XmlNamespace + "}Reference"; /// - /// Namespace import element + /// Namespace import element. /// internal const string Import = "{" + XmlNamespace + "}Import"; /// - /// Namespace compile element + /// Namespace compile element. /// internal const string Compile = "{" + XmlNamespace + "}Compile"; /// - /// Namespace property group element + /// Namespace property group element. /// internal const string PropertyGroup = "{" + XmlNamespace + "}PropertyGroup"; /// - /// Namespace root namespace element + /// Namespace root namespace element. /// internal const string RootNamespace = "{" + XmlNamespace + "}RootNamespace"; /// - /// Namespace output type element + /// Namespace output type element. /// internal const string OutputType = "{" + XmlNamespace + "}OutputType"; /// - /// Namespace assembly name element + /// Namespace output path element. + /// + internal const string OutputPath = "{" + XmlNamespace + "}OutputPath"; + + /// + /// Namespace assembly name element. /// internal const string AssemblyName = "{" + XmlNamespace + "}AssemblyName"; @@ -94,5 +100,40 @@ internal static class ProjectXElement /// Gets the namespace for the service element. /// internal const string Service = "{" + XmlNamespace + "}Service"; + + /// + /// Gets the namespace for the hint path element. + /// + internal const string HintPath = "{" + XmlNamespace + "}HintPath"; + + /// + /// Gets the namespace for the name element. + /// + internal const string Name = "{" + XmlNamespace + "}Name"; + + /// + /// Gets the namespace for the fusion name element. + /// + internal const string FusionName = "{" + XmlNamespace + "}FusionName"; + + /// + /// Gets the namespace for the specific version element. + /// + internal const string SpecificVersion = "{" + XmlNamespace + "}SpecificVersion"; + + /// + /// Gets the namespace for the aliases element. + /// + internal const string Aliases = "{" + XmlNamespace + "}Aliases"; + + /// + /// Gets the namespace for the private element. + /// + internal const string Private = "{" + XmlNamespace + "}Private"; + + /// + /// Gets the namespace for the package element. + /// + internal const string Package = "{" + XmlNamespace + "}Package"; } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoAliases.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoAliases.cs index 79066b59e6..da1eb902a4 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoAliases.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -38,10 +39,7 @@ public static class AssemblyInfoAliases [CakeMethodAlias] public static void CreateAssemblyInfo(this ICakeContext context, FilePath outputPath, AssemblyInfoSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var creator = new AssemblyInfoCreator(context.FileSystem, context.Environment, context.Log); creator.Create(outputPath, settings); @@ -64,13 +62,10 @@ public static void CreateAssemblyInfo(this ICakeContext context, FilePath output [CakeMethodAlias] public static AssemblyInfoParseResult ParseAssemblyInfo(this ICakeContext context, FilePath assemblyInfoPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var parser = new AssemblyInfoParser(context.FileSystem, context.Environment); return parser.Parse(assemblyInfoPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreator.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreator.cs index e3447cb5ed..c3fe9a551e 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreator.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreator.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Linq; @@ -15,6 +16,17 @@ namespace Cake.Common.Solution.Project.Properties /// public sealed class AssemblyInfoCreator { + private const string CSharpComment = "//"; + private const string CSharpUsingFormat = "using {0};"; + private const string CSharpAttributeFormat = "[assembly: {0}]"; + private const string CSharpAttributeWithValueFormat = "[assembly: {0}({1})]"; + private const string CSharpAttributeWithKeyValueFormat = "[assembly: {0}({1}, {2})]"; + private const string VBComment = "'"; + private const string VBUsingFormat = "Imports {0}"; + private const string VBAttributeFormat = ""; + private const string VBAttributeWithValueFormat = ""; + private const string VBAttributeWithKeyValueFormat = ""; + private readonly IFileSystem _fileSystem; private readonly ICakeEnvironment _environment; private readonly ICakeLog _log; @@ -27,18 +39,9 @@ public sealed class AssemblyInfoCreator /// The log. public AssemblyInfoCreator(IFileSystem fileSystem, ICakeEnvironment environment, ICakeLog log) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - if (log == null) - { - throw new ArgumentNullException("log"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); + ArgumentNullException.ThrowIfNull(log); _fileSystem = fileSystem; _environment = environment; _log = log; @@ -49,19 +52,39 @@ public AssemblyInfoCreator(IFileSystem fileSystem, ICakeEnvironment environment, /// /// The output path. /// The settings. + /// The attribute format. + /// The attribute with value format. + /// The attribute with key value format. + /// The VB attribute format. + /// The VB attribute with value format. + /// The VB attribute with key value format. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - public void Create(FilePath outputPath, AssemblyInfoSettings settings) + public void Create(FilePath outputPath, AssemblyInfoSettings settings, + string attributeFormat = CSharpAttributeFormat, + string attributeWithValueFormat = CSharpAttributeWithValueFormat, + string attributeWithKeyValueFormat = CSharpAttributeWithKeyValueFormat, + string vbAttributeFormat = VBAttributeFormat, + string vbAttributeWithValueFormat = VBAttributeWithValueFormat, + string vbAttributeWithKeyValueFormat = VBAttributeWithKeyValueFormat) { - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } - if (settings == null) + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(settings); + string comment = CSharpComment; + string usingFormat = CSharpUsingFormat; + + var isVisualBasicAssemblyInfoFile = false; + + if (outputPath.GetExtension() == ".vb") { - throw new ArgumentNullException("settings"); + isVisualBasicAssemblyInfoFile = true; + comment = VBComment; + usingFormat = VBUsingFormat; + attributeFormat = vbAttributeFormat; + attributeWithValueFormat = vbAttributeWithValueFormat; + attributeWithKeyValueFormat = vbAttributeWithKeyValueFormat; } - var data = new AssemblyInfoCreatorData(settings); + var data = new AssemblyInfoCreatorData(settings, isVisualBasicAssemblyInfoFile); outputPath = outputPath.MakeAbsolute(_environment); _log.Verbose("Creating assembly info file: {0}", outputPath); @@ -69,15 +92,15 @@ public void Create(FilePath outputPath, AssemblyInfoSettings settings) using (var stream = _fileSystem.GetFile(outputPath).OpenWrite()) using (var writer = new StreamWriter(stream, System.Text.Encoding.UTF8)) { - writer.WriteLine("//------------------------------------------------------------------------------"); - writer.WriteLine("// "); - writer.WriteLine("// This code was generated by Cake."); - writer.WriteLine("// "); - writer.WriteLine("//------------------------------------------------------------------------------"); + writer.WriteLine(comment + "------------------------------------------------------------------------------"); + writer.WriteLine(comment + " "); + writer.WriteLine(comment + " This code was generated by Cake."); + writer.WriteLine(comment + " "); + writer.WriteLine(comment + "------------------------------------------------------------------------------"); if (data.Namespaces.Count > 0) { - var namespaces = data.Namespaces.Select(n => string.Concat("using ", n, ";")); + var namespaces = data.Namespaces.Select(n => string.Format(usingFormat, n)); foreach (var @namespace in namespaces) { writer.WriteLine(@namespace); @@ -89,7 +112,7 @@ public void Create(FilePath outputPath, AssemblyInfoSettings settings) { foreach (var attribute in data.Attributes) { - writer.WriteLine(string.Concat("[assembly: ", attribute.Key, "(", attribute.Value, ")]")); + writer.WriteLine(string.Format(attributeWithValueFormat, attribute.Key, attribute.Value)); } writer.WriteLine(); } @@ -98,11 +121,38 @@ public void Create(FilePath outputPath, AssemblyInfoSettings settings) { foreach (var temp in data.InternalVisibleTo) { - writer.WriteLine(string.Concat("[assembly: ", temp, "]")); + writer.WriteLine(string.Format(attributeFormat, temp)); } writer.WriteLine(); } + + if (data.SupportedOSPlatform.Count > 0) + { + foreach (var attribute in data.SupportedOSPlatform) + { + writer.WriteLine(string.Format(attributeFormat, attribute)); + } + } + + if (data.CustomAttributes.Count > 0) + { + writer.WriteLine(comment + " Custom Attributes"); + foreach (var attribute in data.CustomAttributes) + { + writer.WriteLine(string.Format(attributeWithValueFormat, attribute.Key, attribute.Value)); + } + } + + if (data.MetadataAttributes.Count > 0) + { + writer.WriteLine(comment + " Metadata Attributes"); + var mdAttribute = new AssemblyInfoMetadataAttribute(); + foreach (var attribute in data.MetadataAttributes) + { + writer.WriteLine(string.Format(attributeWithKeyValueFormat, mdAttribute.Name, attribute.Key, attribute.Value)); + } + } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreatorData.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreatorData.cs index addf71c6bb..2146be54a3 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreatorData.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCreatorData.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Cake.Core; @@ -11,29 +13,37 @@ namespace Cake.Common.Solution.Project.Properties internal sealed class AssemblyInfoCreatorData { private readonly Dictionary _dictionary; + private readonly Dictionary _customAttributes; + private readonly Dictionary _metadatattributes; private readonly HashSet _namespaces; private readonly HashSet _internalVisibleTo; + private readonly HashSet _supportedOSPlatform; + private readonly string _trueStringValue; + private readonly string _falseStringValue; - public IDictionary Attributes - { - get { return _dictionary; } - } + public IDictionary Attributes => _dictionary; - public ISet Namespaces - { - get { return _namespaces; } - } + public IDictionary CustomAttributes => _customAttributes; - public ISet InternalVisibleTo - { - get { return _internalVisibleTo; } - } + public IDictionary MetadataAttributes => _metadatattributes; + + public ISet Namespaces => _namespaces; + + public ISet InternalVisibleTo => _internalVisibleTo; - public AssemblyInfoCreatorData(AssemblyInfoSettings settings) + public ISet SupportedOSPlatform => _supportedOSPlatform; + + public AssemblyInfoCreatorData(AssemblyInfoSettings settings, bool isVisualBasicAssemblyInfoFile) { _dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + _customAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + _metadatattributes = new Dictionary(StringComparer.OrdinalIgnoreCase); _namespaces = new HashSet(StringComparer.OrdinalIgnoreCase); _internalVisibleTo = new HashSet(StringComparer.OrdinalIgnoreCase); + _supportedOSPlatform = new HashSet(StringComparer.OrdinalIgnoreCase); + + _falseStringValue = isVisualBasicAssemblyInfoFile ? "False" : "false"; + _trueStringValue = isVisualBasicAssemblyInfoFile ? "True" : "true"; // Add attributes. AddAttribute("AssemblyTitle", "System.Reflection", settings.Title); @@ -62,13 +72,39 @@ public AssemblyInfoCreatorData(AssemblyInfoSettings settings) _namespaces.Add("System.Runtime.CompilerServices"); } } + if (settings.SupportedOSPlatform != null) + { + foreach (var item in settings.SupportedOSPlatform.Where(item => item != null)) + { + _supportedOSPlatform.Add(string.Concat("SupportedOSPlatform(\"", item.UnQuote(), "\")")); + } + + if (_supportedOSPlatform.Count > 0) + { + _namespaces.Add("System.Runtime.Versioning"); + } + } + if (settings.CustomAttributes != null) + { + foreach (var item in settings.CustomAttributes.Where(item => item != null)) + { + AddCustomAttribute(item.Name, item.NameSpace, item.Value, item.UseRawValue); + } + } + if (settings.MetaDataAttributes != null) + { + foreach (var item in settings.MetaDataAttributes.Where(item => item != null)) + { + AddMetadataAttribute(item.NameSpace, item.Key, item.Value); + } + } } private void AddAttribute(string name, string @namespace, bool? value) { if (value != null) { - AddAttributeCore(name, @namespace, value.Value ? "true" : "false"); + AddAttributeCore(Attributes, name, @namespace, value.Value ? _trueStringValue : _falseStringValue); } } @@ -76,19 +112,61 @@ private void AddAttribute(string name, string @namespace, string value) { if (value != null) { - AddAttributeCore(name, @namespace, string.Concat("\"", value, "\"")); + AddAttributeCore(Attributes, name, @namespace, string.Concat("\"", value, "\"")); + } + } + + private void AddCustomAttribute(string name, string @namespace, object value, bool isRawValue) + { + var attributeValue = AttributeValueToString(value, isRawValue); + + AddAttributeCore(CustomAttributes, name, @namespace, attributeValue); + } + + private string AttributeValueToString(object value, bool isRawValue) + { + switch (value) + { + case null: + { + return string.Empty; + } + case bool boolValue: + { + return boolValue ? _trueStringValue : _falseStringValue; + } + case string stringValue: + { + return stringValue == string.Empty + ? string.Empty + : isRawValue + ? stringValue + : string.Concat("\"", stringValue.Replace("\"", "\\\""), "\""); + } + default: + { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + } + } + + private void AddMetadataAttribute(string @namespace, string key, string value) + { + if (key != null && value != null) + { + AddAttributeCore(MetadataAttributes, string.Concat("\"", key, "\""), @namespace, string.Concat("\"", value, "\"")); } } - private void AddAttributeCore(string name, string @namespace, string value) + private void AddAttributeCore(IDictionary dict, string name, string @namespace, string value) { - if (Attributes.ContainsKey(name)) + if (dict.ContainsKey(name)) { - Attributes[name] = value; + dict[name] = value; } else { - Attributes.Add(name, value); + dict.Add(name, value); } Namespaces.Add(@namespace); } diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCustomAttribute.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCustomAttribute.cs new file mode 100644 index 0000000000..46fa914211 --- /dev/null +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoCustomAttribute.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Solution.Project.Properties +{ + /// + /// Custom Attribute class used by . + /// + public sealed class AssemblyInfoCustomAttribute + { + /// + /// Gets or sets the name. + /// + /// The attribute name. + public string Name { get; set; } + + /// + /// Gets or sets the namespace. + /// + /// The namespace for the attribute. + public string NameSpace { get; set; } + + /// + /// Gets or sets the value. + /// + /// The value for the attribute. + public object Value { get; set; } + + /// + /// Gets or sets a value indicating whether the value is raw or should be quoted in the created attribute. + /// + /// + /// true if should be treated as raw; otherwise, false. + /// + public bool UseRawValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoMetadataAttribute.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoMetadataAttribute.cs new file mode 100644 index 0000000000..9ada724634 --- /dev/null +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoMetadataAttribute.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Solution.Project.Properties +{ + /// + /// Metadata Attribute class used by . + /// + public sealed class AssemblyInfoMetadataAttribute + { + /// + /// Gets the name. + /// + /// The attribute name. + public string Name { get; } = "AssemblyMetadata"; + + /// + /// Gets or sets the key for meta data. + /// + /// The key for meta data. + public string Key { get; set; } + + /// + /// Gets the namespace. + /// + /// The namespace for the meta data attribute. + public string NameSpace { get; } = "System.Reflection"; + + /// + /// Gets or sets the value. + /// + /// The value for the meta data. + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParseResult.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParseResult.cs index 017bff80bb..0f7de9a642 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParseResult.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParseResult.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; @@ -11,20 +12,8 @@ namespace Cake.Common.Solution.Project.Properties /// public sealed class AssemblyInfoParseResult { - private readonly bool _clsCompliant; - private readonly string _company; - private readonly bool _comVisible; - private readonly string _configuration; - private readonly string _copyright; - private readonly string _description; - private readonly string _assemblyFileVersion; - private readonly string _guid; - private readonly string _assemblyInformationalVersion; - private readonly string _product; - private readonly string _title; - private readonly string _trademark; - private readonly string _assemblyVersion; private readonly List _internalsVisibleTo; + private readonly List _supportedOSPlatform; /// /// Gets a value indicating whether the assembly is CLS compliant. @@ -32,19 +21,13 @@ public sealed class AssemblyInfoParseResult /// /// true if the assembly is CLS compliant; otherwise, false. /// - public bool ClsCompliant - { - get { return _clsCompliant; } - } + public bool ClsCompliant { get; } /// /// Gets the assembly company attribute. /// /// The assembly company attribute. - public string Company - { - get { return _company; } - } + public string Company { get; } /// /// Gets a value indicating whether the assembly is accessible from COM. @@ -52,109 +35,79 @@ public string Company /// /// true if the assembly is accessible from COM; otherwise, false. /// - public bool ComVisible - { - get { return _comVisible; } - } + public bool ComVisible { get; } /// /// Gets the assembly configuration attribute. /// /// The assembly Configuration attribute. - public string Configuration - { - get { return _configuration; } - } + public string Configuration { get; } /// /// Gets the assembly copyright attribute. /// /// The assembly copyright attribute. - public string Copyright - { - get { return _copyright; } - } + public string Copyright { get; } /// /// Gets the assembly's description attribute. /// /// The assembly's Description attribute. - public string Description - { - get { return _description; } - } + public string Description { get; } /// /// Gets the assembly file version. /// /// The assembly file version. - public string AssemblyFileVersion - { - get { return _assemblyFileVersion; } - } + public string AssemblyFileVersion { get; } /// /// Gets the assembly GUID attribute. /// /// The assembly GUID attribute. - public string Guid - { - get { return _guid; } - } + public string Guid { get; } /// /// Gets the assembly informational version. /// /// The assembly informational version. - public string AssemblyInformationalVersion - { - get { return _assemblyInformationalVersion; } - } + public string AssemblyInformationalVersion { get; } /// /// Gets the assembly product Attribute. /// /// The assembly product attribute. - public string Product - { - get { return _product; } - } + public string Product { get; } /// /// Gets the assembly title Attribute. /// /// The assembly Title attribute. - public string Title - { - get { return _title; } - } + public string Title { get; } /// /// Gets the assembly trademark Attribute. /// /// The assembly Trademark attribute. - public string Trademark - { - get { return _trademark; } - } + public string Trademark { get; } /// /// Gets the assembly version. /// /// The assembly version. - public string AssemblyVersion - { - get { return _assemblyVersion; } - } + public string AssemblyVersion { get; } /// /// Gets the assemblies that internals are visible to. /// /// The assemblies that internals are visible to. - public ICollection InternalsVisibleTo - { - get { return _internalsVisibleTo; } - } + public ICollection InternalsVisibleTo => _internalsVisibleTo; + + /// + /// Gets the operating system(s) or platform(s) which this assembly supports. + /// + /// The name(s), and optional version(s), of the supported platform(s). + public ICollection SupportedOSPlatform => _supportedOSPlatform; /// /// Initializes a new instance of the class. @@ -173,6 +126,7 @@ public ICollection InternalsVisibleTo /// The assembly trademark attribute. /// The assembly version. /// The assemblies that internals are visible to. + /// The operating system(s) or platform(s) which this assembly supports. public AssemblyInfoParseResult(string clsCompliant, string company, string comVisible, @@ -186,22 +140,24 @@ public AssemblyInfoParseResult(string clsCompliant, string title, string trademark, string assemblyVersion, - IEnumerable internalsVisibleTo) + IEnumerable internalsVisibleTo, + IEnumerable supportedOSPlatform) { - _clsCompliant = !string.IsNullOrWhiteSpace(clsCompliant) && bool.Parse(clsCompliant); - _company = company ?? string.Empty; - _comVisible = !string.IsNullOrWhiteSpace(comVisible) && bool.Parse(comVisible); - _configuration = configuration ?? string.Empty; - _copyright = copyright ?? string.Empty; - _description = description ?? string.Empty; - _assemblyFileVersion = assemblyFileVersion ?? string.Empty; - _guid = guid ?? string.Empty; - _assemblyInformationalVersion = assemblyInformationalVersion ?? string.Empty; - _product = product ?? string.Empty; - _title = title ?? string.Empty; - _trademark = trademark ?? string.Empty; - _assemblyVersion = assemblyVersion ?? string.Empty; + ClsCompliant = !string.IsNullOrWhiteSpace(clsCompliant) && bool.Parse(clsCompliant); + Company = company ?? string.Empty; + ComVisible = !string.IsNullOrWhiteSpace(comVisible) && bool.Parse(comVisible); + Configuration = configuration ?? string.Empty; + Copyright = copyright ?? string.Empty; + Description = description ?? string.Empty; + AssemblyFileVersion = assemblyFileVersion ?? string.Empty; + Guid = guid ?? string.Empty; + AssemblyInformationalVersion = assemblyInformationalVersion ?? string.Empty; + Product = product ?? string.Empty; + Title = title ?? string.Empty; + Trademark = trademark ?? string.Empty; + AssemblyVersion = assemblyVersion ?? string.Empty; _internalsVisibleTo = new List(internalsVisibleTo ?? Enumerable.Empty()); + _supportedOSPlatform = new List(supportedOSPlatform ?? Enumerable.Empty()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParser.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParser.cs index aacf6c53eb..3fd0de5415 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParser.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoParser.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -17,8 +18,10 @@ namespace Cake.Common.Solution.Project.Properties /// public sealed class AssemblyInfoParser { - private const string NonQuotedPattern = @"^\s*\[assembly: {0} ?\((?.*)\)"; - private const string QuotedPattern = @"^\s*\[assembly: {0} ?\(""(?.*)""\)"; + private const string CSharpNonQuotedPattern = @"^\s*\[assembly: (?:System\.Reflection\.)?{0}(?:Attribute)? ?\((?.*)\)"; + private const string CSharpQuotedPattern = @"^\s*\[assembly: (?:System\.Reflection\.)?{0}(?:Attribute)? ?\(\s*""(?.*)""\s*\)"; + private const string VBNonQuotedPattern = @"^\s*\.*)\)"; + private const string VBQuotedPattern = @"^\s*\.*)""\s*\)"; private const string DefaultVersion = "1.0.0.0"; private readonly IFileSystem _fileSystem; @@ -31,14 +34,8 @@ public sealed class AssemblyInfoParser /// The environment. public AssemblyInfoParser(IFileSystem fileSystem, ICakeEnvironment environment) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); _fileSystem = fileSystem; _environment = environment; } @@ -50,16 +47,16 @@ public AssemblyInfoParser(IFileSystem fileSystem, ICakeEnvironment environment) /// Information about the assembly info content. public AssemblyInfoParseResult Parse(FilePath assemblyInfoPath) { - if (assemblyInfoPath == null) - { - throw new ArgumentNullException("assemblyInfoPath"); - } + ArgumentNullException.ThrowIfNull(assemblyInfoPath); if (assemblyInfoPath.IsRelative) { assemblyInfoPath = assemblyInfoPath.MakeAbsolute(_environment); } + string nonQuotedPattern = CSharpNonQuotedPattern; + string quotedPattern = CSharpQuotedPattern; + // Get the release notes file. var file = _fileSystem.GetFile(assemblyInfoPath); if (!file.Exists) @@ -68,25 +65,31 @@ public AssemblyInfoParseResult Parse(FilePath assemblyInfoPath) var message = string.Format(CultureInfo.InvariantCulture, format, assemblyInfoPath.FullPath); throw new CakeException(message); } + if (file.Path.GetExtension() == ".vb") + { + nonQuotedPattern = VBNonQuotedPattern; + quotedPattern = VBQuotedPattern; + } using (var reader = new StreamReader(file.OpenRead())) { var content = reader.ReadToEnd(); return new AssemblyInfoParseResult( - ParseSingle(NonQuotedPattern, "CLSCompliant", content), - ParseSingle(QuotedPattern, "AssemblyCompany", content), - ParseSingle(NonQuotedPattern, "ComVisible", content), - ParseSingle(QuotedPattern, "AssemblyConfiguration", content), - ParseSingle(QuotedPattern, "AssemblyCopyright", content), - ParseSingle(QuotedPattern, "AssemblyDescription", content), - ParseSingle(QuotedPattern, "AssemblyFileVersion", content) ?? DefaultVersion, - ParseSingle(QuotedPattern, "Guid", content), - ParseSingle(QuotedPattern, "AssemblyInformationalVersion", content) ?? DefaultVersion, - ParseSingle(QuotedPattern, "AssemblyProduct", content), - ParseSingle(QuotedPattern, "AssemblyTitle", content), - ParseSingle(QuotedPattern, "AssemblyTrademark", content), - ParseSingle(QuotedPattern, "AssemblyVersion", content) ?? DefaultVersion, - ParseMultiple(QuotedPattern, "InternalsVisibleTo", content)); + ParseSingle(nonQuotedPattern, "CLSCompliant", content), + ParseSingle(quotedPattern, "AssemblyCompany", content), + ParseSingle(nonQuotedPattern, "ComVisible", content), + ParseSingle(quotedPattern, "AssemblyConfiguration", content), + ParseSingle(quotedPattern, "AssemblyCopyright", content), + ParseSingle(quotedPattern, "AssemblyDescription", content), + ParseSingle(quotedPattern, "AssemblyFileVersion", content) ?? DefaultVersion, + ParseSingle(quotedPattern, "Guid", content), + ParseSingle(quotedPattern, "AssemblyInformationalVersion", content) ?? DefaultVersion, + ParseSingle(quotedPattern, "AssemblyProduct", content), + ParseSingle(quotedPattern, "AssemblyTitle", content), + ParseSingle(quotedPattern, "AssemblyTrademark", content), + ParseSingle(quotedPattern, "AssemblyVersion", content) ?? DefaultVersion, + ParseMultiple(quotedPattern, "InternalsVisibleTo", content), + ParseMultiple(quotedPattern, "SupportedOSPlatform", content)); } } diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettings.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettings.cs index 7a0206ee5c..e8593a957b 100644 --- a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettings.cs +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettings.cs @@ -1,97 +1,116 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; - -namespace Cake.Common.Solution.Project.Properties -{ - /// - /// Contains settings used by . - /// - public sealed class AssemblyInfoSettings - { - /// - /// Gets or sets the title. - /// - /// The assembly title. - public string Title { get; set; } - - /// - /// Gets or sets the description. - /// - /// The assembly description. - public string Description { get; set; } - - /// - /// Gets or sets the unique identifier. - /// - /// The unique identifier. - public string Guid { get; set; } - - /// - /// Gets or sets the product. - /// - /// The assembly product. - public string Product { get; set; } - - /// - /// Gets or sets the copyright. - /// - /// The copyright. - public string Copyright { get; set; } - - /// - /// Gets or sets the trademark. - /// - /// The trademark. - public string Trademark { get; set; } - - /// - /// Gets or sets the version. - /// - /// The version. - public string Version { get; set; } - - /// - /// Gets or sets the file version. - /// - /// The file version. - public string FileVersion { get; set; } - - /// - /// Gets or sets the informational version. - /// - /// The informational version. - public string InformationalVersion { get; set; } - - /// - /// Gets or sets whether or not the assembly is COM visible. - /// - /// Whether or not the assembly is COM visible. - public bool? ComVisible { get; set; } - - /// - /// Gets or sets whether or not the assembly is CLS compliant. - /// - /// Whether or not the assembly is CLS compliant. - public bool? CLSCompliant { get; set; } - - /// - /// Gets or sets the company. - /// - /// The company. - public string Company { get; set; } - - /// - /// Gets or sets the name(s) of the assembly(s) that internals should be visible to. - /// - /// The name(s) of the assembly(s). - public ICollection InternalsVisibleTo { get; set; } - - /// - /// Gets or sets the configuration of the assembly. - /// - /// The configuration. - public string Configuration { get; set; } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Cake.Common.Solution.Project.Properties +{ + /// + /// Contains settings used by . + /// + public sealed class AssemblyInfoSettings + { + /// + /// Gets or sets the title. + /// + /// The assembly title. + public string Title { get; set; } + + /// + /// Gets or sets the description. + /// + /// The assembly description. + public string Description { get; set; } + + /// + /// Gets or sets the unique identifier. + /// + /// The unique identifier. + public string Guid { get; set; } + + /// + /// Gets or sets the product. + /// + /// The assembly product. + public string Product { get; set; } + + /// + /// Gets or sets the copyright. + /// + /// The copyright. + public string Copyright { get; set; } + + /// + /// Gets or sets the trademark. + /// + /// The trademark. + public string Trademark { get; set; } + + /// + /// Gets or sets the version. + /// + /// The version. + public string Version { get; set; } + + /// + /// Gets or sets the file version. + /// + /// The file version. + public string FileVersion { get; set; } + + /// + /// Gets or sets the informational version. + /// + /// The informational version. + public string InformationalVersion { get; set; } + + /// + /// Gets or sets whether or not the assembly is COM visible. + /// + /// Whether or not the assembly is COM visible. + public bool? ComVisible { get; set; } + + /// + /// Gets or sets whether or not the assembly is CLS compliant. + /// + /// Whether or not the assembly is CLS compliant. + public bool? CLSCompliant { get; set; } + + /// + /// Gets or sets the company. + /// + /// The company. + public string Company { get; set; } + + /// + /// Gets or sets the name(s) of the assembly(s) that internals should be visible to. + /// + /// The name(s) of the assembly(s). + public ICollection InternalsVisibleTo { get; set; } + + /// + /// Gets or sets the operating system(s) or platform(s) which this assembly supports. + /// + /// The name(s), and optional version(s), of the supported platform(s). + public ICollection SupportedOSPlatform { get; set; } + + /// + /// Gets or sets the configuration of the assembly. + /// + /// The configuration. + public string Configuration { get; set; } + + /// + /// Gets or sets the custom attribute(s) that should be added to the assembly info file. + /// + /// The namespace(s). + public ICollection CustomAttributes { get; set; } + + /// + /// Gets or sets the meta data attribute(s) that should be added to the assembly info file. + /// + /// The meta data. + public ICollection MetaDataAttributes { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettingsExtensions.cs b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettingsExtensions.cs new file mode 100644 index 0000000000..98012dcf9e --- /dev/null +++ b/src/Cake.Common/Solution/Project/Properties/AssemblyInfoSettingsExtensions.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Cake.Common.Solution.Project.Properties +{ + /// + /// Contains functionality related to AssemblyInfo settings. + /// + public static class AssemblyInfoSettingsExtensions + { + /// + /// Adds a custom attribute to the AssemblyInfo settings. + /// + /// The settings. + /// The name of the custom attribute. + /// The namespace for the custom attribute. + /// The value for the attribute. + /// The same instance so that multiple calls can be chained. + public static AssemblyInfoSettings AddCustomAttribute(this AssemblyInfoSettings settings, string name, string @namespace, string value) + { + if (settings.CustomAttributes == null) + { + settings.CustomAttributes = new List(); + } + settings.CustomAttributes.Add(new AssemblyInfoCustomAttribute() { Name = name, NameSpace = @namespace, Value = value }); + return settings; + } + + /// + /// Adds a meta data attribute to the AssemblyInfo settings. + /// + /// The settings. + /// The key of the meta data attribute. + /// The value for the attribute. + /// The same instance so that multiple calls can be chained. + public static AssemblyInfoSettings AddMetadataAttribute(this AssemblyInfoSettings settings, string key, string value) + { + if (settings.MetaDataAttributes == null) + { + settings.MetaDataAttributes = new List(); + } + settings.MetaDataAttributes.Add(new AssemblyInfoMetadataAttribute { Key = key, Value = value }); + return settings; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocAliases.cs b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocAliases.cs index 7ffb993929..e06eb076bd 100644 --- a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocAliases.cs +++ b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -24,7 +25,7 @@ public static class XmlDocAliases /// /// /// var exampleCodes = ParseXmlDocExampleCode("./Cake.Common.xml"); - /// foreach(var exampleCode in exampleCodes) + /// foreach (var exampleCode in exampleCodes) /// { /// Information( /// "{0}\r\n{1}", @@ -37,15 +38,9 @@ public static class XmlDocAliases [CakeMethodAlias] public static IEnumerable ParseXmlDocExampleCode(this ICakeContext context, FilePath xmlFilePath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (xmlFilePath == null) - { - throw new ArgumentNullException("xmlFilePath"); - } + ArgumentNullException.ThrowIfNull(xmlFilePath); var parser = new XmlDocExampleCodeParser(context.FileSystem, context.Globber, context.Log); return parser.Parse(xmlFilePath); @@ -60,7 +55,7 @@ public static IEnumerable ParseXmlDocExampleCode(this ICakeCo /// /// /// var filesExampleCodes = ParseXmlDocFilesExampleCode("./Cake.*.xml"); - /// foreach(var exampleCode in filesExampleCodes) + /// foreach (var exampleCode in filesExampleCodes) /// { /// Information( /// "{0}\r\n{1}", @@ -71,20 +66,17 @@ public static IEnumerable ParseXmlDocExampleCode(this ICakeCo /// /// [CakeMethodAlias] - public static IEnumerable ParseXmlDocFilesExampleCode(this ICakeContext context, string pattern) + public static IEnumerable ParseXmlDocFilesExampleCode(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (string.IsNullOrWhiteSpace(pattern)) + if (string.IsNullOrWhiteSpace(pattern?.Pattern)) { - throw new ArgumentNullException("pattern"); + throw new ArgumentNullException(nameof(pattern)); } var parser = new XmlDocExampleCodeParser(context.FileSystem, context.Globber, context.Log); return parser.ParseFiles(pattern); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCode.cs b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCode.cs index 3e0955832f..2fc5b1d83d 100644 --- a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCode.cs +++ b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCode.cs @@ -1,31 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Solution.Project.XmlDoc { /// - /// Parsed Xml documentation example code + /// Parsed Xml documentation example code. /// public sealed class XmlDocExampleCode { - private readonly string _name; - private readonly string _code; - /// - /// Gets Example code parent name + /// Gets Example code parent name. /// - public string Name - { - get { return _name; } - } + public string Name { get; } /// - /// Gets Example code + /// Gets Example code. /// - public string Code - { - get { return _code; } - } + public string Code { get; } /// /// Initializes a new instance of the class. @@ -34,8 +26,8 @@ public string Code /// The example code. public XmlDocExampleCode(string name, string code) { - _name = name; - _code = code; + Name = name; + Code = code; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCodeParser.cs b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCodeParser.cs index 90c79f5f36..c920411bd1 100644 --- a/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCodeParser.cs +++ b/src/Cake.Common/Solution/Project/XmlDoc/XmlDocExampleCodeParser.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -14,7 +15,7 @@ namespace Cake.Common.Solution.Project.XmlDoc { /// - /// The MSBuild Xml documentation example code parser + /// The MSBuild Xml documentation example code parser. /// public sealed class XmlDocExampleCodeParser { @@ -30,15 +31,9 @@ public sealed class XmlDocExampleCodeParser /// The log. public XmlDocExampleCodeParser(IFileSystem fileSystem, IGlobber globber, ICakeLog log) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } + ArgumentNullException.ThrowIfNull(fileSystem); - if (globber == null) - { - throw new ArgumentNullException("globber"); - } + ArgumentNullException.ThrowIfNull(globber); _fileSystem = fileSystem; _globber = globber; @@ -46,16 +41,16 @@ public XmlDocExampleCodeParser(IFileSystem fileSystem, IGlobber globber, ICakeLo } /// - /// Parses Xml documentation example code from given path + /// Parses Xml documentation example code from given path. /// /// Path to the file to parse. - /// Parsed Example Code + /// Parsed Example Code. [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public IEnumerable Parse(FilePath xmlFilePath) { if (xmlFilePath == null) { - throw new ArgumentNullException("xmlFilePath", "Invalid xml file path supplied."); + throw new ArgumentNullException(nameof(xmlFilePath), "Invalid xml file path supplied."); } var xmlFile = _fileSystem.GetFile(xmlFilePath); @@ -85,15 +80,15 @@ from code in example.Elements("code") } /// - /// Parses Xml documentation example code from file(s) using given pattern + /// Parses Xml documentation example code from file(s) using given pattern. /// /// The globber file pattern. - /// Parsed Example Code - public IEnumerable ParseFiles(string pattern) + /// Parsed Example Code. + public IEnumerable ParseFiles(GlobPattern pattern) { - if (string.IsNullOrWhiteSpace(pattern)) + if (string.IsNullOrWhiteSpace(pattern?.Pattern)) { - throw new ArgumentNullException("pattern", "Invalid pattern supplied."); + throw new ArgumentNullException(nameof(pattern), "Invalid pattern supplied."); } var files = _globber.GetFiles(pattern).ToArray(); @@ -106,4 +101,4 @@ public IEnumerable ParseFiles(string pattern) return files.SelectMany(Parse); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/SolutionAliases.cs b/src/Cake.Common/Solution/SolutionAliases.cs index 7f28e7d132..5c7f08611f 100644 --- a/src/Cake.Common/Solution/SolutionAliases.cs +++ b/src/Cake.Common/Solution/SolutionAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -25,7 +26,7 @@ public static class SolutionAliases /// var solutionPath = "./src/Cake.sln"; /// Information("Parsing {0}", solutionPath); /// var parsedSolution = ParseSolution(solutionPath); - /// foreach(var project in parsedSolution.Projects) + /// foreach (var project in parsedSolution.Projects) /// { /// Information( /// @"Solution project file: @@ -44,13 +45,10 @@ public static class SolutionAliases [CakeMethodAlias] public static SolutionParserResult ParseSolution(this ICakeContext context, FilePath solutionPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var parser = new SolutionParser(context.FileSystem, context.Environment); return parser.Parse(solutionPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/SolutionFolder.cs b/src/Cake.Common/Solution/SolutionFolder.cs new file mode 100644 index 0000000000..39d31d08b1 --- /dev/null +++ b/src/Cake.Common/Solution/SolutionFolder.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Solution +{ + /// + /// Represents a folder in a MSBuild solution. + /// + public sealed class SolutionFolder : SolutionProject + { + /// + /// Visual Studio project type guid for solution folder. + /// + /// + /// More information can be found http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs. + /// + public const string TypeIdentifier = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"; + + /// + /// Gets Child items of this folder. + /// + public List Items { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The folder project identity. + /// The folder name. + /// The folder path. + public SolutionFolder(string id, string name, FilePath path) : base(id, name, path, TypeIdentifier) + { + Items = new List(); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/SolutionParser.cs b/src/Cake.Common/Solution/SolutionParser.cs index 84c87e562f..4606273e4e 100644 --- a/src/Cake.Common/Solution/SolutionParser.cs +++ b/src/Cake.Common/Solution/SolutionParser.cs @@ -1,11 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; +using System.Xml; using Cake.Core; using Cake.Core.IO; @@ -16,7 +18,6 @@ namespace Cake.Common.Solution /// public sealed class SolutionParser { - private const string SolutionFolder = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"; private readonly IFileSystem _fileSystem; private readonly ICakeEnvironment _environment; @@ -27,14 +28,8 @@ public sealed class SolutionParser /// The environment. public SolutionParser(IFileSystem fileSystem, ICakeEnvironment environment) { - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); _fileSystem = fileSystem; _environment = environment; } @@ -46,40 +41,57 @@ public SolutionParser(IFileSystem fileSystem, ICakeEnvironment environment) /// A parsed solution. public SolutionParserResult Parse(FilePath solutionPath) { - if (solutionPath == null) - { - throw new ArgumentNullException("solutionPath"); - } - + ArgumentNullException.ThrowIfNull(solutionPath); if (solutionPath.IsRelative) { solutionPath = solutionPath.MakeAbsolute(_environment); } - // Get the release notes file. + // Get solution file. var file = _fileSystem.GetFile(solutionPath); if (!file.Exists) { - const string format = "Solution file '{0}' do not exist."; + const string format = "Solution file '{0}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, solutionPath.FullPath); throw new CakeException(message); } + var fileExtension = file.Path.GetExtension().ToLowerInvariant(); + + return fileExtension switch + { + ".sln" => ParseSlnSolution(file), + ".slnx" => ParseSlnxSolution(file), + _ => throw new CakeException($"Unknown file extension {fileExtension} for solution file '{solutionPath.FullPath}'."), + }; + } + + private static SolutionParserResult ParseSlnSolution(IFile file) + { string version = null, visualStudioVersion = null, minimumVisualStudioVersion = null; var projects = new List(); - + bool inNestedProjectsSection = false; foreach (var line in file.ReadLines(Encoding.UTF8)) { + var trimmed = line.Trim(); + + if (trimmed == string.Empty) + { + continue; + } + if (line.StartsWith("Project(\"{")) { var project = ParseSolutionProjectLine(file, line); - if (!StringComparer.OrdinalIgnoreCase.Equals(project.Type, SolutionFolder)) + if (StringComparer.OrdinalIgnoreCase.Equals(project.Type, SolutionFolder.TypeIdentifier)) { - projects.Add(project); + projects.Add(new SolutionFolder(project.Id, project.Name, project.Path)); + continue; } + projects.Add(project); } else if (line.StartsWith("Microsoft Visual Studio Solution File, ")) { @@ -93,26 +105,34 @@ public SolutionParserResult Parse(FilePath solutionPath) { minimumVisualStudioVersion = string.Concat(line.Skip(29)); } + else if (trimmed.StartsWith("GlobalSection(NestedProjects)")) + { + inNestedProjectsSection = true; + } + else if (inNestedProjectsSection && trimmed.StartsWith("EndGlobalSection")) + { + inNestedProjectsSection = false; + } + else if (inNestedProjectsSection) + { + ParseNestedProjectLine(projects, trimmed); + } } - var solutionParserResult = new SolutionParserResult( version, visualStudioVersion, minimumVisualStudioVersion, projects.AsReadOnly()); - return solutionParserResult; } private static SolutionProject ParseSolutionProjectLine(IFile file, string line) { var withinQuotes = false; - var projectTypeBuilder = new StringBuilder(); var nameBuilder = new StringBuilder(); var pathBuilder = new StringBuilder(); var idBuilder = new StringBuilder(); - var result = new[] { projectTypeBuilder, @@ -135,20 +155,154 @@ private static SolutionProject ParseSolutionProjectLine(IFile file, string line) } continue; } - if (!withinQuotes) { continue; } - result[position].Append(c); } + var projectPath = new FilePath(pathBuilder.ToString()); + + var projectFullPath = projectPath.IsRelative ? + file.Path.GetDirectory().CombineWithFilePath(projectPath) : + projectPath; + return new SolutionProject( idBuilder.ToString(), nameBuilder.ToString(), - file.Path.GetDirectory().CombineWithFilePath(pathBuilder.ToString()), + projectFullPath, projectTypeBuilder.ToString()); } + + private static void ParseNestedProjectLine(List projects, string line) + { + // pattern: {Child} = {Parent} + var projectIds = line.Split(new[] { " = " }, StringSplitOptions.RemoveEmptyEntries); + var child = projects.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Id, projectIds[0].Trim())); + if (child == null) + { + return; + } + + // Parent should be a folder + var parent = projects.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Id, projectIds[1].Trim())) as SolutionFolder; + if (parent == null) + { + return; + } + + parent.Items.Add(child); + child.Parent = parent; + } + + private static SolutionParserResult ParseSlnxSolution(IFile file) + { + var xmlReaderSettings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, // Prevent XXE + XmlResolver = null, // Disable external entity resolution + }; + using var xmlContentStream = file.OpenRead(); + var xmlDocument = new XmlDocument(); + + using var xmlReader = XmlReader.Create(xmlContentStream, xmlReaderSettings); + xmlDocument.Load(xmlReader); + + var root = xmlDocument.DocumentElement; + if (root == null) + { + throw new CakeException($"Solution file '{file.Path.FullPath}' does not contain a root element."); + } + + var projects = new List(); + foreach (var xmlElement in root.ChildNodes.OfType()) + { + var projectsFromElement = ParseSlnxElement(xmlElement, file); + projects.AddRange(projectsFromElement); + } + + // the new SLNX has no information about the file format version, VS version and minimal VS version, so it's omitted. + return new SolutionParserResult(string.Empty, string.Empty, string.Empty, projects.AsReadOnly()); + } + + private static List ParseSlnxElement(XmlElement xmlElement, IFile solutionFile) + { + return xmlElement.Name switch + { + "Folder" => ParseSlnxFolder(xmlElement, solutionFile), + "Project" => ParseSlnxProject(xmlElement, solutionFile), + _ =>[], + }; + } + + private static List ParseSlnxFolder(XmlElement xmlElement, IFile solutionFile) + { + var folderName = xmlElement.GetAttribute("Name"); + if (string.IsNullOrWhiteSpace(folderName)) + { + throw new CakeException($"Could not find solution folder name attribute in solution file '{solutionFile.Path.FullPath}'."); + } + + // the name of the folder is also its path. And it always has a starting and trailing "/", which break the check if its relative, so they are removed. + var folderPath = new FilePath(folderName.Trim('/')); + var folderFullPath = folderPath.IsRelative ? + solutionFile.Path.GetDirectory().CombineWithFilePath(folderPath) : + folderPath; + + var actualFolderName = folderName.Split('/', StringSplitOptions.RemoveEmptyEntries).Last(); + + // elements in the SLNX file don't have any IDs anymore, so it's omitted. + var solutionFolder = new SolutionFolder(string.Empty, actualFolderName, folderFullPath); + var projects = new List + { + solutionFolder, + }; + + foreach (var childElement in xmlElement.ChildNodes.OfType()) + { + var childProjects = ParseSlnxElement(childElement, solutionFile); + foreach (var childProject in childProjects) + { + childProject.Parent = solutionFolder; + solutionFolder.Items.Add(childProject); + projects.Add(childProject); + } + } + + return projects; + } + + private static List ParseSlnxProject(XmlElement xmlElement, IFile solutionFile) + { + const string defaultProjectTypeId = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; + + var projectPath = xmlElement.GetAttribute("Path"); + if (string.IsNullOrWhiteSpace(projectPath)) + { + throw new CakeException($"Could not find solution project path attribute in solution file '{solutionFile.Path.FullPath}'."); + } + + var projectTypeId = xmlElement.GetAttribute("Type"); + projectTypeId = string.IsNullOrWhiteSpace(projectTypeId) + ? defaultProjectTypeId + // the new project type id notation does not quite fit the known format, so it is adjusted. + : $"{{{projectTypeId.ToUpper()}}}"; + + var projectFilePath = new FilePath(projectPath); + var projectFileFullPath = projectFilePath.IsRelative ? + solutionFile.Path.GetDirectory().CombineWithFilePath(projectFilePath) : + projectFilePath; + + // the project name is part of its path. + var actualProjectName = projectFilePath.GetFilenameWithoutExtension().FullPath; + + // elements in the SLNX file don't have any IDs anymore, so it's omitted. + var solutionProject = new SolutionProject(string.Empty, actualProjectName, projectFileFullPath, projectTypeId); + return + [ + solutionProject, + ]; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/SolutionParserResult.cs b/src/Cake.Common/Solution/SolutionParserResult.cs index 745985937b..404c3ec0af 100644 --- a/src/Cake.Common/Solution/SolutionParserResult.cs +++ b/src/Cake.Common/Solution/SolutionParserResult.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; namespace Cake.Common.Solution @@ -10,42 +11,25 @@ namespace Cake.Common.Solution /// public sealed class SolutionParserResult { - private readonly string _version; - private readonly string _visualStudioVersion; - private readonly string _minimumVisualStudioVersion; - private readonly IReadOnlyCollection _projects; - /// /// Gets the file format version. /// - public string Version - { - get { return _version; } - } + public string Version { get; } /// /// Gets the version of Visual Studio that created the file. /// - public string VisualStudioVersion - { - get { return _visualStudioVersion; } - } + public string VisualStudioVersion { get; } /// /// Gets the minimum supported version of Visual Studio. /// - public string MinimumVisualStudioVersion - { - get { return _minimumVisualStudioVersion; } - } + public string MinimumVisualStudioVersion { get; } /// /// Gets all solution projects. /// - public IReadOnlyCollection Projects - { - get { return _projects; } - } + public IReadOnlyCollection Projects { get; } /// /// Initializes a new instance of the class. @@ -57,10 +41,10 @@ public IReadOnlyCollection Projects public SolutionParserResult(string version, string visualStudioVersion, string minimumVisualStudioVersion, IReadOnlyCollection projects) { - _version = version; - _visualStudioVersion = visualStudioVersion; - _minimumVisualStudioVersion = minimumVisualStudioVersion; - _projects = projects; + Version = version; + VisualStudioVersion = visualStudioVersion; + MinimumVisualStudioVersion = minimumVisualStudioVersion; + Projects = projects; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Solution/SolutionProject.cs b/src/Cake.Common/Solution/SolutionProject.cs index 5442be33c8..6b754c803b 100644 --- a/src/Cake.Common/Solution/SolutionProject.cs +++ b/src/Cake.Common/Solution/SolutionProject.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; namespace Cake.Common.Solution @@ -8,44 +9,32 @@ namespace Cake.Common.Solution /// /// Represents a project in a MSBuild solution. /// - public sealed class SolutionProject + public class SolutionProject { - private readonly string _id; - private readonly string _name; - private readonly FilePath _path; - private readonly string _type; - /// /// Gets the project identity. /// - public string Id - { - get { return _id; } - } + public string Id { get; } /// /// Gets the project name. /// - public string Name - { - get { return _name; } - } + public string Name { get; } /// /// Gets the project path. /// - public FilePath Path - { - get { return _path; } - } + public FilePath Path { get; } /// /// Gets the project type identity. /// - public string Type - { - get { return _type; } - } + public string Type { get; } + + /// + /// Gets the parent project if any, otherwise null. + /// + public SolutionProject Parent { get; internal set; } /// /// Initializes a new instance of the class. @@ -56,10 +45,10 @@ public string Type /// The project type identity. public SolutionProject(string id, string name, FilePath path, string type) { - _id = id; - _name = name; - _path = path; - _type = type; + Id = id; + Name = name; + Path = path; + Type = type; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Text/TextTransformation.cs b/src/Cake.Common/Text/TextTransformation.cs index 5c99aec3dd..427be6bbf0 100644 --- a/src/Cake.Common/Text/TextTransformation.cs +++ b/src/Cake.Common/Text/TextTransformation.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Text; @@ -20,16 +21,12 @@ public sealed class TextTransformation { private readonly IFileSystem _fileSystem; private readonly ICakeEnvironment _environment; - private readonly TTemplate _template; /// /// Gets the text transformation template. /// /// The text transformation template. - public TTemplate Template - { - get { return _template; } - } + public TTemplate Template { get; } /// /// Initializes a new instance of the class. @@ -40,26 +37,30 @@ public TTemplate Template public TextTransformation(IFileSystem fileSystem, ICakeEnvironment environment, TTemplate template) { - if (template == null) - { - throw new ArgumentNullException("template"); - } + ArgumentNullException.ThrowIfNull(template); _fileSystem = fileSystem; _environment = environment; - _template = template; + Template = template; } /// /// Saves the text transformation to a file. /// - /// The to save the test transformation to. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Stream writer leaves stream open.")] + /// The to save the text transformation to. public void Save(FilePath path) { - if (path == null) - { - throw new ArgumentNullException("path"); - } + Save(path, new UTF8Encoding(false)); + } + + /// + /// Saves the text transformation to a file. + /// + /// The to save the text transformation to. + /// The text encoding. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Stream writer leaves stream open.")] + public void Save(FilePath path, Encoding encoding) + { + ArgumentNullException.ThrowIfNull(path); // Make the path absolute if necessary. path = path.IsRelative ? path.MakeAbsolute(_environment) : path; @@ -67,9 +68,9 @@ public void Save(FilePath path) // Render the content to the file. var file = _fileSystem.GetFile(path); using (var stream = file.OpenWrite()) - using (var writer = new StreamWriter(stream, new UTF8Encoding(false), 1024, true)) + using (var writer = new StreamWriter(stream, encoding, 1024, true)) { - writer.Write(_template.Render()); + writer.Write(Template.Render()); } } @@ -79,7 +80,7 @@ public void Save(FilePath path) /// A string containing the rendered template. public override string ToString() { - return _template.Render(); + return Template.Render(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Text/TextTransformationAliases.cs b/src/Cake.Common/Text/TextTransformationAliases.cs index db10331a91..e77b6c428e 100644 --- a/src/Cake.Common/Text/TextTransformationAliases.cs +++ b/src/Cake.Common/Text/TextTransformationAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Text; @@ -62,18 +63,9 @@ public static TextTransformation TransformText( string leftPlaceholder, string rightPlaceholder) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (leftPlaceholder == null) - { - throw new ArgumentNullException("leftPlaceholder"); - } - if (rightPlaceholder == null) - { - throw new ArgumentNullException("rightPlaceholder"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(leftPlaceholder); + ArgumentNullException.ThrowIfNull(rightPlaceholder); // Create the placeholder. var placeholder = new Tuple(leftPlaceholder, rightPlaceholder); @@ -131,22 +123,10 @@ public static TextTransformation TransformTextFile( string leftPlaceholder, string rightPlaceholder) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (path == null) - { - throw new ArgumentNullException("path"); - } - if (leftPlaceholder == null) - { - throw new ArgumentNullException("leftPlaceholder"); - } - if (rightPlaceholder == null) - { - throw new ArgumentNullException("rightPlaceholder"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(leftPlaceholder); + ArgumentNullException.ThrowIfNull(rightPlaceholder); // Make the path absolute if necessary. path = path.IsRelative ? path.MakeAbsolute(context.Environment) : path; @@ -166,4 +146,4 @@ public static TextTransformation TransformTextFile( } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Text/TextTransformationExtensions.cs b/src/Cake.Common/Text/TextTransformationExtensions.cs index 5f39720d59..4a71e29bec 100644 --- a/src/Cake.Common/Text/TextTransformationExtensions.cs +++ b/src/Cake.Common/Text/TextTransformationExtensions.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using Cake.Core.Text; namespace Cake.Common.Text @@ -11,8 +14,7 @@ namespace Cake.Common.Text public static class TextTransformationExtensions { /// - /// Registers a key and a value to be used with the - /// text transformation. + /// Registers a key and a value to be used with the text transformation. /// /// The text transformation template. /// The text transformation. @@ -25,11 +27,31 @@ public static TextTransformation WithToken( this TextTransformation transformation, string key, object value) where TTemplate : class, ITextTransformationTemplate { - if (transformation != null) + transformation?.Template.Register(key, value); + return transformation; + } + + /// + /// Registers all keys and values in the enumerable for text transformation. + /// + /// The text transformation template. + /// The text transformation. + /// The tokens. + /// + /// The same instance so that multiple calls can be chained. + /// + public static TextTransformation WithTokens( + this TextTransformation transformation, IEnumerable> tokens) + where TTemplate : class, ITextTransformationTemplate + { + ArgumentNullException.ThrowIfNull(tokens); + + foreach (var token in tokens) { - transformation.Template.Register(key, value); + transformation?.Template.Register(token.Key, token.Value); } + return transformation; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Cake/CakeAliases.cs b/src/Cake.Common/Tools/Cake/CakeAliases.cs index d7e0c80906..b74644e00a 100644 --- a/src/Cake.Common/Tools/Cake/CakeAliases.cs +++ b/src/Cake.Common/Tools/Cake/CakeAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.Cake public static class CakeAliases { /// - /// Executes cake script out of process + /// Executes cake script out of process. /// /// The context. /// The script file. @@ -31,7 +32,7 @@ public static void CakeExecuteScript(this ICakeContext context, FilePath cakeScr } /// - /// Executes cake script out of process + /// Executes cake script out of process. /// /// The context. /// The script file. @@ -44,20 +45,17 @@ public static void CakeExecuteScript(this ICakeContext context, FilePath cakeScr [CakeMethodAlias] public static void CakeExecuteScript(this ICakeContext context, FilePath cakeScriptPath, CakeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var cakeRunner = new CakeRunner(context.FileSystem, context.Environment, context.Globber, context.ProcessRunner, context.Tools); cakeRunner.ExecuteScript(cakeScriptPath, settings); } /// - /// Executes Cake expression out of process + /// Executes Cake expression out of process. /// /// The context. - /// The cake expression + /// The cake expression. /// /// /// CakeExecuteExpression("Information(\"Hello {0}\", \"World\");"); @@ -70,10 +68,10 @@ public static void CakeExecuteExpression(this ICakeContext context, string cakeE } /// - /// Executes Cake expression out of process + /// Executes Cake expression out of process. /// /// The context. - /// The cake expression + /// The cake expression. /// The settings . /// /// @@ -88,13 +86,10 @@ public static void CakeExecuteExpression(this ICakeContext context, string cakeE [CakeMethodAlias] public static void CakeExecuteExpression(this ICakeContext context, string cakeExpression, CakeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var cakeRunner = new CakeRunner(context.FileSystem, context.Environment, context.Globber, context.ProcessRunner, context.Tools); cakeRunner.ExecuteExpression(cakeExpression, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Cake/CakeRunner.cs b/src/Cake.Common/Tools/Cake/CakeRunner.cs index df5e8d5be3..2f676de22f 100644 --- a/src/Cake.Common/Tools/Cake/CakeRunner.cs +++ b/src/Cake.Common/Tools/Cake/CakeRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -8,20 +9,37 @@ using System.IO; using System.Linq; using System.Text; +using Cake.Common.Tools.DotNet.Execute; using Cake.Core; using Cake.Core.IO; +using Cake.Core.Polyfill; using Cake.Core.Tooling; namespace Cake.Common.Tools.Cake { /// - /// Cake out process runner + /// Cake out process runner. /// public sealed class CakeRunner : Tool { private readonly ICakeEnvironment _environment; private readonly IFileSystem _fileSystem; private readonly IGlobber _globber; + private readonly DotNetExecutor _coreExecutor; + private static readonly IEnumerable _executingAssemblyToolPaths; + + /// + /// Initializes static members of the class. + /// + static CakeRunner() + { + var entryAssembly = AssemblyHelper.GetExecutingAssembly(); + var executingAssemblyToolPath = ((FilePath)entryAssembly.Location).GetDirectory(); + _executingAssemblyToolPaths = new[] + { + executingAssemblyToolPath.CombineWithFilePath("Cake.dll") + }; + } /// /// Initializes a new instance of the class. @@ -37,19 +55,17 @@ public CakeRunner(IFileSystem fileSystem, ICakeEnvironment environment, IGlobber _environment = environment; _fileSystem = fileSystem; _globber = globber; + _coreExecutor = new DotNetExecutor(fileSystem, environment, processRunner, tools); } /// - /// Executes supplied cake script in own process and supplied settings + /// Executes supplied cake script in own process and supplied settings. /// - /// Path to script to execute - /// optional cake settings + /// Path to script to execute. + /// optional cake settings. public void ExecuteScript(FilePath scriptPath, CakeSettings settings = null) { - if (scriptPath == null) - { - throw new ArgumentNullException("scriptPath"); - } + ArgumentNullException.ThrowIfNull(scriptPath); scriptPath = scriptPath.MakeAbsolute(_environment); if (!_fileSystem.GetFile(scriptPath).Exists) @@ -58,21 +74,37 @@ public void ExecuteScript(FilePath scriptPath, CakeSettings settings = null) } settings = settings ?? new CakeSettings(); - - Run(settings, GetArguments(scriptPath, settings)); + var toolPath = GetToolPath(settings); + if (toolPath?.GetFilename().FullPath == "Cake.dll") + { + _coreExecutor.Execute( + toolPath, + GetArguments(scriptPath, settings), + new DotNetExecuteSettings + { + ArgumentCustomization = settings.ArgumentCustomization, + EnvironmentVariables = settings.EnvironmentVariables, + ToolTimeout = settings.ToolTimeout, + WorkingDirectory = settings.WorkingDirectory + }); + } + else + { + Run(settings, GetArguments(scriptPath, settings)); + } } /// - /// Executes supplied cake code expression in own process and supplied settings + /// Executes supplied cake code expression in own process and supplied settings. /// - /// Code expression to execute - /// optional cake settings - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - public void ExecuteExpression(string cakeExpression, CakeSettings settings = null) + /// Code expression to execute. + /// optional cake settings. + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] + public void ExecuteExpression(string cakeExpression, CakeSettings settings = null) { if (string.IsNullOrWhiteSpace(cakeExpression)) { - throw new ArgumentNullException("cakeExpression"); + throw new ArgumentNullException(nameof(cakeExpression)); } DirectoryPath tempPath = _environment.GetEnvironmentVariable("TEMP") ?? "./"; var tempScriptFile = _fileSystem.GetFile(tempPath @@ -105,17 +137,20 @@ private ProcessArgumentBuilder GetArguments(FilePath scriptPath, CakeSettings se if (settings.Verbosity.HasValue) { - builder.Append(string.Concat("-verbosity=", settings.Verbosity.Value.ToString())); + builder.Append(string.Concat("--verbosity=", settings.Verbosity.Value.ToString())); } if (settings.Arguments != null) { foreach (var argument in settings.Arguments) { + var key = argument.Key.Length == 1 + ? $"-{argument.Key}" : $"--{argument.Key}"; + builder.Append(string.Format( CultureInfo.InvariantCulture, - "-{0}={1}", - argument.Key, + "{0}={1}", + key, (argument.Value ?? string.Empty).Quote())); } } @@ -137,11 +172,17 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "Cake.exe" }; + return new[] + { + "Cake.dll", + "cake", + "Cake", + "Cake.exe" + }; } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. @@ -149,21 +190,21 @@ protected override IEnumerable GetAlternativeToolPaths(CakeSettings se { const string homebrewCakePath = "/usr/local/Cellar/cake/"; - if (!_environment.IsUnix()) + if (!_environment.Platform.IsUnix()) { - return Enumerable.Empty(); + return _executingAssemblyToolPaths; } if (!_fileSystem.Exist(new DirectoryPath(homebrewCakePath))) { - return Enumerable.Empty(); + return _executingAssemblyToolPaths; } var files = _globber.GetFiles(homebrewCakePath + "**/Cake.exe"); var filePaths = files as FilePath[] ?? files.ToArray(); return filePaths.Length == 0 - ? Enumerable.Empty() + ? _executingAssemblyToolPaths : filePaths.OrderByDescending(f => f.FullPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Cake/CakeSettings.cs b/src/Cake.Common/Tools/Cake/CakeSettings.cs index 03f69aa6ec..f73a4729ce 100644 --- a/src/Cake.Common/Tools/Cake/CakeSettings.cs +++ b/src/Cake.Common/Tools/Cake/CakeSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.Diagnostics; using Cake.Core.Tooling; @@ -22,6 +24,6 @@ public sealed class CakeSettings : ToolSettings /// Gets or sets cake additional arguments. /// /// The properties. - public IDictionary Arguments { get; set; } + public IDictionary Arguments { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetter.cs b/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetter.cs index c8e686da7c..f688523dab 100644 --- a/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetter.cs +++ b/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySetter.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -34,95 +34,43 @@ public ChocolateyApiKeySetter( /// /// Pins Chocolatey packages using the specified package id and settings. /// - /// The API key. /// The Server URL where the API key is valid. /// The settings. - public void Set(string apiKey, string source, ChocolateyApiKeySettings settings) + public void Set(string source, ChocolateyApiKeySettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - if (string.IsNullOrWhiteSpace(apiKey)) - { - throw new ArgumentNullException("apiKey"); - } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); } - Run(settings, GetArguments(apiKey, source, settings)); + Run(settings, GetArguments(source, settings)); } - private ProcessArgumentBuilder GetArguments(string apiKey, string source, ChocolateyApiKeySettings settings) + private ProcessArgumentBuilder GetArguments(string source, ChocolateyApiKeySettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("apikey"); - builder.Append("-s"); - builder.AppendQuoted(source); - - builder.Append("-k"); - builder.AppendQuoted(apiKey); - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } + builder.AppendSwitchQuoted("--source", separator, source); - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) + if (!string.IsNullOrEmpty(settings.ApiKey)) { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); + builder.AppendSwitchQuoted("--api-key", separator, settings.ApiKey); } - // Allow Unofficial - if (settings.AllowUnofficial) + if (settings.Remove) { - builder.Append("--allowunofficial"); + builder.Append("--remove"); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySettings.cs b/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySettings.cs index 6751736876..fd7042e80b 100644 --- a/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/ApiKey/ChocolateyApiKeySettings.cs @@ -1,62 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.ApiKey { /// /// Contains settings used by . /// - public sealed class ChocolateyApiKeySettings : ToolSettings + public sealed class ChocolateyApiKeySettings : ChocolateySettings { /// - /// Gets or sets a value indicating whether to run in debug mode. + /// Gets or sets the API key for the server. /// - /// The debug flag - public bool Debug { get; set; } + /// The API key for the server. + public string ApiKey { get; set; } /// - /// Gets or sets a value indicating whether to run in verbose mode. + /// Gets or sets a value indicating whether to remove the selected source. /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. - /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } - - /// - /// Gets or sets the location of the download cache. - /// - /// The download cache location - public string CacheLocation { get; set; } - - /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. - /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + public bool Remove { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/ChocolateyAliases.cs b/src/Cake.Common/Tools/Chocolatey/ChocolateyAliases.cs index 258bdf5f17..146d800aed 100644 --- a/src/Cake.Common/Tools/Chocolatey/ChocolateyAliases.cs +++ b/src/Cake.Common/Tools/Chocolatey/ChocolateyAliases.cs @@ -1,15 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using Cake.Common.Tools.Chocolatey.ApiKey; using Cake.Common.Tools.Chocolatey.Config; +using Cake.Common.Tools.Chocolatey.Download; +using Cake.Common.Tools.Chocolatey.Export; using Cake.Common.Tools.Chocolatey.Features; using Cake.Common.Tools.Chocolatey.Install; +using Cake.Common.Tools.Chocolatey.New; using Cake.Common.Tools.Chocolatey.Pack; using Cake.Common.Tools.Chocolatey.Pin; using Cake.Common.Tools.Chocolatey.Push; using Cake.Common.Tools.Chocolatey.Sources; +using Cake.Common.Tools.Chocolatey.Uninstall; using Cake.Common.Tools.Chocolatey.Upgrade; using Cake.Core; using Cake.Core.Annotations; @@ -77,16 +83,71 @@ public static class ChocolateyAliases [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Pack")] public static void ChocolateyPack(this ICakeContext context, FilePath nuspecFilePath, ChocolateyPackSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Log, context.Tools, resolver); packer.Pack(nuspecFilePath, settings); } + /// + /// Creates Chocolatey packages using the specified Nuspec files. + /// + /// The context. + /// The nuspec file paths. + /// The settings. + /// + /// + /// var chocolateyPackSettings = new ChocolateyPackSettings { + /// Id = "TestChocolatey", + /// Title = "The tile of the package", + /// Version = "0.0.0.1", + /// Authors = new[] {"John Doe"}, + /// Owners = new[] {"Contoso"}, + /// Summary = "Excellent summary of what the package does", + /// Description = "The description of the package", + /// ProjectUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// PackageSourceUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// ProjectSourceUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// DocsUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// MailingListUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// BugTrackerUrl = new Uri("https://github.com/SomeUser/TestChocolatey/"), + /// Tags = new [] {"Cake", "Script", "Build"}, + /// Copyright = "Some company 2015", + /// LicenseUrl = new Uri("https://github.com/SomeUser/TestChocolatey/blob/master/LICENSE.md"), + /// RequireLicenseAcceptance= false, + /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestChocolatey/master/icons/testchocolatey.png"), + /// ReleaseNotes = new [] {"Bug fixes", "Issue fixes", "Typos"}, + /// Files = new [] { + /// new ChocolateyNuSpecContent {Source = "bin/TestChocolatey.dll", Target = "bin"}, + /// }, + /// Debug = false, + /// Verbose = false, + /// Force = false, + /// Noop = false, + /// LimitOutput = false, + /// ExecutionTimeout = 13, + /// CacheLocation = @"C:\temp", + /// AllowUnofficial = false + /// }; + /// + /// var nuspecFiles = GetFiles("./**/*.nuspec"); + /// ChocolateyPack(nuspecFiles, chocolateyPackSettings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Pack")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Pack")] + public static void ChocolateyPack(this ICakeContext context, IEnumerable filePaths, ChocolateyPackSettings settings) + { + ArgumentNullException.ThrowIfNull(filePaths); + + foreach (var filePath in filePaths) + { + ChocolateyPack(context, filePath, settings); + } + } + /// /// Creates a Chocolatey package using the specified settings. /// @@ -135,10 +196,7 @@ public static void ChocolateyPack(this ICakeContext context, FilePath nuspecFile [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Pack")] public static void ChocolateyPack(this ICakeContext context, ChocolateyPackSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Log, context.Tools, resolver); @@ -205,10 +263,7 @@ public static void ChocolateyInstall(this ICakeContext context, string packageId [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Install")] public static void ChocolateyInstall(this ICakeContext context, string packageId, ChocolateyInstallSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -275,16 +330,153 @@ public static void ChocolateyInstallFromConfig(this ICakeContext context, FilePa [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Install")] public static void ChocolateyInstallFromConfig(this ICakeContext context, FilePath packageConfigPath, ChocolateyInstallSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); runner.InstallFromConfig(packageConfigPath, settings); } + /// + /// Uninstalls a Chocolatey package. + /// + /// The context. + /// The id of the package to uninstall. + /// + /// + /// ChocolateyUninstall("MyChocolateyPackage"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Uninstall")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Uninstall")] + public static void ChocolateyUninstall(this ICakeContext context, string packageId) + { + var settings = new ChocolateyUninstallSettings(); + ChocolateyUninstall(context, packageId, settings); + } + + /// + /// Uninstalls a Chocolatey package using the specified settings. + /// + /// The context. + /// The id of the package to uninstall. + /// The settings. + /// + /// + /// ChocolateyUninstall("MyChocolateyPackage", new ChocolateyUninstallSettings { + /// Source = true, + /// Version = "1.2.3", + /// UninstallArguments = "arg1", + /// OverrideArguments = false, + /// NotSilent = false, + /// PackageParameters = "param1", + /// SideBySide = false, + /// IgnoreDependencies = false, + /// ForceDependencies = false, + /// SkipPowerShell = false, + /// Debug = false, + /// Verbose = false, + /// FailOnStandardError = false, + /// UseSystemPowershell = false, + /// AllVersions = false, + /// Force = false, + /// Noop = false, + /// LimitOutput = false, + /// ExecutionTimeout = 13, + /// CacheLocation = @"C:\temp", + /// AllowUnofficial = false, + /// GlobalArguments = false, + /// GlobalPackageParameters = false, + /// IgnorePackageExitCodes = false, + /// UsePackageExitCodes = false, + /// UseAutoUninstaller = false, + /// SkipAutoUninstaller = false, + /// FailOnAutoUninstaller = false, + /// IgnoreAutoUninstaller = false + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Uninstall")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Uninstall")] + public static void ChocolateyUninstall(this ICakeContext context, string packageId, ChocolateyUninstallSettings settings) + { + ChocolateyUninstall(context, new[] { packageId }, settings); + } + + /// + /// Uninstalls a Chocolatey package. + /// + /// The context. + /// The ids of the packages to uninstall. + /// + /// + /// ChocolateyUninstall("MyChocolateyPackage"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Uninstall")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Uninstall")] + public static void ChocolateyUninstall(this ICakeContext context, IEnumerable packageIds) + { + var settings = new ChocolateyUninstallSettings(); + ChocolateyUninstall(context, packageIds, settings); + } + + /// + /// Uninstalls Chocolatey packages using the specified settings. + /// + /// The context. + /// The ids of the packages to uninstall. + /// The settings. + /// + /// + /// ChocolateyUninstall("MyChocolateyPackage", new ChocolateyUninstallSettings { + /// Source = true, + /// Version = "1.2.3", + /// UninstallArguments = "arg1", + /// OverrideArguments = false, + /// NotSilent = false, + /// PackageParameters = "param1", + /// SideBySide = false, + /// IgnoreDependencies = false, + /// ForceDependencies = false, + /// SkipPowerShell = false, + /// Debug = false, + /// Verbose = false, + /// FailOnStandardError = false, + /// UseSystemPowershell = false, + /// AllVersions = false, + /// Force = false, + /// Noop = false, + /// LimitOutput = false, + /// ExecutionTimeout = 13, + /// CacheLocation = @"C:\temp", + /// AllowUnofficial = false, + /// GlobalArguments = false, + /// GlobalPackageParameters = false, + /// IgnorePackageExitCodes = false, + /// UsePackageExitCodes = false, + /// UseAutoUninstaller = false, + /// SkipAutoUninstaller = false, + /// FailOnAutoUninstaller = false, + /// IgnoreAutoUninstaller = false + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Uninstall")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Uninstall")] + public static void ChocolateyUninstall(this ICakeContext context, IEnumerable packageIds, ChocolateyUninstallSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); + var runner = new ChocolateyUninstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.Uninstall(packageIds, settings); + } + /// /// Pins a Chocolatey package using the specified settings. /// @@ -311,10 +503,7 @@ public static void ChocolateyInstallFromConfig(this ICakeContext context, FilePa [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Pin")] public static void ChocolateyPin(this ICakeContext context, string name, ChocolateyPinSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyPinner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -325,12 +514,12 @@ public static void ChocolateyPin(this ICakeContext context, string name, Chocola /// Sets the Api Key for a Chocolatey Source using the specified settings. /// /// The context. - /// The API Key. /// The source. /// The settings. /// /// - /// ChocolateyApiKey("myApiKey", "http://www.mysource.com", new ChocolateyApiKeySettings { + /// ChocolateyApiKey("http://www.mysource.com", new ChocolateyApiKeySettings { + /// ApiKey = "myApiKey", /// Debug = false, /// Verbose = false, /// Force = false, @@ -345,16 +534,13 @@ public static void ChocolateyPin(this ICakeContext context, string name, Chocola [CakeMethodAlias] [CakeAliasCategory("ApiKey")] [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.ApiKey")] - public static void ChocolateyApiKey(this ICakeContext context, string apiKey, string source, ChocolateyApiKeySettings settings) + public static void ChocolateyApiKey(this ICakeContext context, string source, ChocolateyApiKeySettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyApiKeySetter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); - packer.Set(apiKey, source, settings); + packer.Set(source, settings); } /// @@ -383,10 +569,7 @@ public static void ChocolateyApiKey(this ICakeContext context, string apiKey, st [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Config")] public static void ChocolateyConfig(this ICakeContext context, string name, string value, ChocolateyConfigSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyConfigSetter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -394,7 +577,7 @@ public static void ChocolateyConfig(this ICakeContext context, string name, stri } /// - /// Enables a Chocolatey Feature using the specified name + /// Enables a Chocolatey Feature using the specified name. /// /// The context. /// Name of the feature. @@ -412,7 +595,7 @@ public static void ChocolateyEnableFeature(this ICakeContext context, string nam } /// - /// Enables a Chocolatey Feature using the specified name and settings + /// Enables a Chocolatey Feature using the specified name and settings. /// /// The context. /// Name of the feature. @@ -436,10 +619,7 @@ public static void ChocolateyEnableFeature(this ICakeContext context, string nam [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Features")] public static void ChocolateyEnableFeature(this ICakeContext context, string name, ChocolateyFeatureSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyFeatureToggler(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -447,7 +627,7 @@ public static void ChocolateyEnableFeature(this ICakeContext context, string nam } /// - /// Disables a Chocolatey Feature using the specified name + /// Disables a Chocolatey Feature using the specified name. /// /// The context. /// Name of the feature. @@ -465,7 +645,7 @@ public static void ChocolateyDisableFeature(this ICakeContext context, string na } /// - /// Disables a Chocolatey Feature using the specified name and settings + /// Disables a Chocolatey Feature using the specified name and settings. /// /// The context. /// Name of the feature. @@ -489,10 +669,7 @@ public static void ChocolateyDisableFeature(this ICakeContext context, string na [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Features")] public static void ChocolateyDisableFeature(this ICakeContext context, string name, ChocolateyFeatureSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyFeatureToggler(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -500,7 +677,7 @@ public static void ChocolateyDisableFeature(this ICakeContext context, string na } /// - /// Adds Chocolatey package source using the specified name &source to global user config + /// Adds Chocolatey package source using the specified name &source to global user config. /// /// The context. /// Name of the source. @@ -519,7 +696,7 @@ public static void ChocolateyAddSource(this ICakeContext context, string name, s } /// - /// Adds Chocolatey package source using the specified name, source & settings to global user config + /// Adds Chocolatey package source using the specified name, source & settings to global user config. /// /// The context. /// Name of the source. @@ -547,10 +724,7 @@ public static void ChocolateyAddSource(this ICakeContext context, string name, s [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Sources")] public static void ChocolateyAddSource(this ICakeContext context, string name, string source, ChocolateySourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateySources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -558,7 +732,7 @@ public static void ChocolateyAddSource(this ICakeContext context, string name, s } /// - /// Removes Chocolatey package source using the specified name & source from global user config + /// Removes Chocolatey package source using the specified name & source from global user config. /// /// The context. /// Name of the source. @@ -576,7 +750,7 @@ public static void ChocolateyRemoveSource(this ICakeContext context, string name } /// - /// Removes Chocolatey package source using the specified name, source & settings from global user config + /// Removes Chocolatey package source using the specified name, source & settings from global user config. /// /// The context. /// Name of the source. @@ -600,10 +774,7 @@ public static void ChocolateyRemoveSource(this ICakeContext context, string name [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Sources")] public static void ChocolateyRemoveSource(this ICakeContext context, string name, ChocolateySourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateySources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -611,7 +782,7 @@ public static void ChocolateyRemoveSource(this ICakeContext context, string name } /// - /// Enables a Chocolatey Source using the specified name + /// Enables a Chocolatey Source using the specified name. /// /// The context. /// Name of the source. @@ -629,7 +800,7 @@ public static void ChocolateyEnableSource(this ICakeContext context, string name } /// - /// Enables a Chocolatey Source using the specified name and settings + /// Enables a Chocolatey Source using the specified name and settings. /// /// The context. /// Name of the source. @@ -653,10 +824,7 @@ public static void ChocolateyEnableSource(this ICakeContext context, string name [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Sources")] public static void ChocolateyEnableSource(this ICakeContext context, string name, ChocolateySourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateySources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -664,7 +832,7 @@ public static void ChocolateyEnableSource(this ICakeContext context, string name } /// - /// Disables a Chocolatey Source using the specified name + /// Disables a Chocolatey Source using the specified name. /// /// The context. /// Name of the source. @@ -682,7 +850,7 @@ public static void ChocolateyDisableSource(this ICakeContext context, string nam } /// - /// Disables a Chocolatey Source using the specified name and settings + /// Disables a Chocolatey Source using the specified name and settings. /// /// The context. /// Name of the source. @@ -706,10 +874,7 @@ public static void ChocolateyDisableSource(this ICakeContext context, string nam [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Sources")] public static void ChocolateyDisableSource(this ICakeContext context, string name, ChocolateySourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateySources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -748,16 +913,53 @@ public static void ChocolateyDisableSource(this ICakeContext context, string nam [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Push")] public static void ChocolateyPush(this ICakeContext context, FilePath packageFilePath, ChocolateyPushSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var packer = new ChocolateyPusher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); packer.Push(packageFilePath, settings); } + /// + /// Pushes Chocolatey packages to a Chocolatey server and publishes them. + /// + /// The context. + /// The .nupkg file paths. + /// The settings. + /// + /// + /// // Get the paths to the packages. + /// var packages = GetFiles("./**/*.nupkg"); + /// + /// // Push the package. + /// ChocolateyPush(packages, new ChocolateyPushSettings { + /// Source = "http://example.com/chocolateyfeed", + /// ApiKey = "4003d786-cc37-4004-bfdf-c4f3e8ef9b3a" + /// Timeout = 300 + /// Debug = false, + /// Verbose = false, + /// Force = false, + /// Noop = false, + /// LimitOutput = false, + /// ExecutionTimeout = 13, + /// CacheLocation = @"C:\temp", + /// AllowUnofficial = false + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Push")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Push")] + public static void ChocolateyPush(this ICakeContext context, IEnumerable packageFilePaths, ChocolateyPushSettings settings) + { + ArgumentNullException.ThrowIfNull(packageFilePaths); + + foreach (var packageFilePath in packageFilePaths) + { + ChocolateyPush(context, packageFilePath, settings); + } + } + /// /// Upgrades Chocolatey package. /// @@ -773,10 +975,7 @@ public static void ChocolateyPush(this ICakeContext context, FilePath packageFil [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Upgrade")] public static void ChocolateyUpgrade(this ICakeContext context, string packageId) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyUpgrader(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -825,14 +1024,168 @@ public static void ChocolateyUpgrade(this ICakeContext context, string packageId [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Upgrade")] public static void ChocolateyUpgrade(this ICakeContext context, string packageId, ChocolateyUpgradeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); var runner = new ChocolateyUpgrader(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); runner.Upgrade(packageId, settings); } + + /// + /// Generate package specification files for a new package using the default settings. + /// + /// The context. + /// The id of the package to create. + /// + /// + /// ChocolateyNew("MyChocolateyPackage"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("New")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.New")] + public static void ChocolateyNew(this ICakeContext context, string packageId) + { + ChocolateyNew(context, packageId, new ChocolateyNewSettings()); + } + + /// + /// Generate package specification files for a new package using the specified settings. + /// + /// The context. + /// The id of the package to create. + /// The settings. + /// + /// + /// ChocolateyNew("MyChocolateyPackage", new ChocolateyNewSettings { + /// PackageVersion = "1.2.3", + /// MaintainerName = "John Doe", + /// MaintainerRepo = "johndoe" + /// }); + /// + /// + /// + /// + /// var settings = new ChocolateyNewSettings { + /// MaintainerName = "John Doe" + /// } + /// settings.AdditionalPropertyValues("Tags", "CustomPackage"); + /// ChocolateyNew("MyChocolateyPackage", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("New")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.New")] + public static void ChocolateyNew(this ICakeContext context, string packageId, ChocolateyNewSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); + var runner = new ChocolateyScaffolder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.CreatePackage(packageId, settings); + } + + /// + /// Downloads a Chocolatey package to the current working directory. + /// Requires Chocolatey licensed edition. + /// + /// The context. + /// The id of the package to download. + /// + /// + /// ChocolateyDownload("MyChocolateyPackage"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Download")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Download")] + public static void ChocolateyDownload(this ICakeContext context, string packageId) + { + var settings = new ChocolateyDownloadSettings(); + ChocolateyDownload(context, packageId, settings); + } + + /// + /// Downloads a Chocolatey package using the specified settings. + /// Requires Chocolatey licensed edition. + /// Features requiring Chocolatey for Business or a minimum version are documented + /// in . + /// + /// The context. + /// The id of the package to install. + /// The settings. + /// + /// Download a package to a specific folder: + /// + /// ChocolateyDownload( + /// "MyChocolateyPackage", + /// new ChocolateyDownloadSettings { + /// OutputDirectory = @"C:\download\" + /// }); + /// + /// Download and internalize a package: + /// + /// ChocolateyDownload( + /// "MyChocolateyPackage", + /// new ChocolateyDownloadSettings { + /// Internalize = true + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Download")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Download")] + public static void ChocolateyDownload(this ICakeContext context, string packageId, ChocolateyDownloadSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); + var runner = new ChocolateyDownloader(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.Download(packageId, settings); + } + + /// + /// Exports the currently installed Chocolatey packages to a packages.config file in the current working directory. + /// + /// The context. + /// + /// + /// ChocolateyExport(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Export")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Export")] + public static void ChocolateyExport(this ICakeContext context) + { + var settings = new ChocolateyExportSettings(); + ChocolateyExport(context, settings); + } + + /// + /// Exports the currently installed Chocolatey packages using the specified settings. + /// + /// The context. + /// The settings. + /// + /// Exported information should contain the package version numbers: + /// + /// ChocolateyExport( + /// new ChocolateyExportSettings { + /// IncludeVersionNumbers = true + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Export")] + [CakeNamespaceImport("Cake.Common.Tools.Chocolatey.Export")] + public static void ChocolateyExport(this ICakeContext context, ChocolateyExportSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new ChocolateyToolResolver(context.FileSystem, context.Environment); + var runner = new ChocolateyExporter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.Export(settings); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/ChocolateySettings.cs b/src/Cake.Common/Tools/Chocolatey/ChocolateySettings.cs new file mode 100644 index 0000000000..d08ddebda4 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/ChocolateySettings.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Chocolatey +{ + /// + /// Contains settings used by . + /// + public class ChocolateySettings : ToolSettings + { + /// + /// Gets or sets a value indicating whether to run in debug mode. + /// + /// The debug flag. + public bool Debug { get; set; } + + /// + /// Gets or sets a value indicating whether to run in verbose mode. + /// + /// The verbose flag. + public bool Verbose { get; set; } + + /// + /// Gets or sets a value indicating whether to run in trace mode. + /// + /// The trace flag. + public bool Trace { get; set; } + + /// + /// Gets or sets a value indicating whether to run in no color mode. + /// + /// The no-color flag. + public bool NoColor { get; set; } + + /// + /// Gets or sets a value indicating whether to accept license for package. + /// + /// The accept-license flag. + public bool AcceptLicense { get; set; } + + /// + /// Gets or sets a value indicating whether to run in forced mode. + /// + /// The force flag. + public bool Force { get; set; } + + /// + /// Gets or sets a value indicating whether to run in noop mode. + /// + /// The noop flag. + public bool Noop { get; set; } + + /// + /// Gets or sets a value indicating whether to run in limited output mode. + /// + /// The limit-output flag. + public bool LimitOutput { get; set; } + + /// + /// Gets or sets the execution timeout value. + /// + /// The execution timeout. + /// Default is 2700 seconds. + public int ExecutionTimeout { get; set; } + + /// + /// Gets or sets the location of the download cache. + /// + /// The download cache location. + public string CacheLocation { get; set; } + + /// + /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// + /// The allow-unofficial flag. + public bool AllowUnofficial { get; set; } + + /// + /// Gets or sets a value indicating whether to faile when error output is detected. + /// + /// The fail-on-error-output flag. + public bool FailOnErrorOutput { get; set; } + + /// + /// Gets or sets a value indicating whether to run using system installed version of PowerShell. + /// + /// The use-system-powershell flag. + public bool UseSystemPowerShell { get; set; } + + /// + /// Gets or sets a value indicating whether to run while not showing download progress. + /// + /// The no-progress flag. + public bool NoProgress { get; set; } + + /// + /// Gets or sets the explicit proxy location. + /// + /// The proxy location. + public string Proxy { get; set; } + + /// + /// Gets or sets the explicit proxy user. + /// + /// The proxy user. + public string ProxyUser { get; set; } + + /// + /// Gets or sets the explicit proxy password. + /// + /// The proxy password. + public string ProxyPassword { get; set; } + + /// + /// Gets or sets the comma separated list of regex location to bypass on proxy. + /// + /// The bypass proxy list. + public string ProxyByPassList { get; set; } + + /// + /// Gets or sets a value indicating whether to bypass proxy for local connections. + /// + /// The proxy-bypass-on-local flag. + public bool ProxyBypassOnLocal { get; set; } + + /// + /// Gets or sets the path to the file where all log entries will be sent. + /// + public FilePath LogFile { get; set; } + + /// + /// Gets or sets a value indicating whether to skip all compatibility checks. + /// + /// The skip-compatibility-checks flag. + public bool SkipCompatibilityChecks { get; set; } + } +} diff --git a/src/Cake.Common/Tools/Chocolatey/ChocolateySharedSettings.cs b/src/Cake.Common/Tools/Chocolatey/ChocolateySharedSettings.cs new file mode 100644 index 0000000000..b3ee79d4d1 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/ChocolateySharedSettings.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.Chocolatey +{ + /// + /// Contains settings used by . + /// + public class ChocolateySharedSettings : ChocolateySettings + { + /// + /// Gets or sets the source to find the package(s). + /// + /// The URL or source name. + public string Source { get; set; } + + /// + /// Gets or sets the specific version of the package. + /// + public string Version { get; set; } + + /// + /// Gets or sets a value indicating whether install arguments be used exclusively without appending to current package passed arguments. + /// + public bool OverrideArguments { get; set; } + + /// + /// Gets or sets a value indicating whether to process package silently. + /// + public bool NotSilent { get; set; } + + /// + /// Gets or sets parameters to pass to the package. + /// + public string PackageParameters { get; set; } + + /// + /// Gets or sets a value indicating whether install arguments be applied to dependent packages. + /// + public bool ApplyInstallArgumentsToDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether package parameters be applied to dependent packages. + /// + public bool ApplyPackageParametersToDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether multiple versions of a package be installed. + /// + public bool SideBySide { get; set; } + + /// + /// Gets or sets a value indicating whether to run chocolateyInstall.ps1. + /// + public bool SkipPowerShell { get; set; } + + /// + /// Gets or sets a value indicating whether to Exit with a 0 for success and 1 for non-success + /// no matter what package scripts provide for exit codes. + /// + /// The ignore package exit codes flag. + public bool IgnorePackageExitCodes { get; set; } + + /// + /// Gets or sets a value indicating whether to use package exit codes. + /// + /// The use package exit codes flag. + public bool UsePackageExitCodes { get; set; } + + /// + /// Gets or sets a value indicating whether to stop of the first failure of a package. + /// + /// The stop of first failure flag. + public bool StopOnFirstFailure { get; set; } + + /// + /// Gets or sets a value indicating whether to exit when a reboot is detected. + /// + /// The exit when reboot detected flag. + public bool ExitWhenRebootDetected { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore detected reboots. + /// + /// The ignore detected reboots flag. + public bool IgnoreDetectedReboot { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the running of package hook scripts. + /// + /// The skip hooks flag. + public bool SkipHooks { get; set; } + } +} diff --git a/src/Cake.Common/Tools/Chocolatey/ChocolateyTool.cs b/src/Cake.Common/Tools/Chocolatey/ChocolateyTool.cs index 6f389918be..b0b01a84f5 100644 --- a/src/Cake.Common/Tools/Chocolatey/ChocolateyTool.cs +++ b/src/Cake.Common/Tools/Chocolatey/ChocolateyTool.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; +using System.Globalization; using System.Linq; using Cake.Core; using Cake.Core.IO; @@ -16,7 +18,10 @@ namespace Cake.Common.Tools.Chocolatey public abstract class ChocolateyTool : Tool where TSettings : ToolSettings { + private const string Separator = "="; + private readonly IChocolateyToolResolver _resolver; + private readonly ICakeEnvironment _environment; /// /// Initializes a new instance of the class. @@ -34,6 +39,7 @@ protected ChocolateyTool( IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools) { _resolver = resolver; + _environment = environment; } /// @@ -55,7 +61,7 @@ protected sealed override IEnumerable GetToolExecutableNames() } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. @@ -68,5 +74,247 @@ protected sealed override IEnumerable GetAlternativeToolPaths(TSetting } return Enumerable.Empty(); } + + /// + /// Adds common arguments to the process builder. + /// + /// The settings. + /// The process argument builder. + /// The process argument builder. + protected ProcessArgumentBuilder AddGlobalArguments(ChocolateySettings settings, ProcessArgumentBuilder builder) + { + // Debug + if (settings.Debug) + { + builder.Append("--debug"); + } + + // Verbose + if (settings.Verbose) + { + builder.Append("--verbose"); + } + + // Trace + if (settings.Trace) + { + builder.Append("--trace"); + } + + // NoColor + if (settings.NoColor) + { + builder.Append("--no-color"); + } + + // Accept License + if (settings.AcceptLicense) + { + builder.Append("--accept-license"); + } + + // Always say yes, so as to not show interactive prompt + builder.Append("--confirm"); + + // Force + if (settings.Force) + { + builder.Append("--force"); + } + + // Noop + if (settings.Noop) + { + builder.Append("--what-if"); + } + + // Limit Output + if (settings.LimitOutput) + { + builder.Append("--limit-output"); + } + + // Execution Timeout + if (settings.ExecutionTimeout != 0) + { + builder.AppendSwitchQuoted("--execution-timeout", Separator, settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); + } + + // Cache Location + if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) + { + builder.AppendSwitchQuoted("--cache-location", Separator, settings.CacheLocation); + } + + // Allow Unofficial + if (settings.AllowUnofficial) + { + builder.Append("--allow-unofficial"); + } + + // Fail On Error Output + if (settings.FailOnErrorOutput) + { + builder.Append("--fail-on-error-output"); + } + + // Use System PowerShell + if (settings.UseSystemPowerShell) + { + builder.Append("--use-system-powershell"); + } + + // No Progress + if (settings.NoProgress) + { + builder.Append("--no-progress"); + } + + // Proxy + if (!string.IsNullOrEmpty(settings.Proxy)) + { + builder.AppendSwitchQuoted("--proxy", Separator, settings.Proxy); + } + + // Proxy User + if (!string.IsNullOrEmpty(settings.ProxyUser)) + { + builder.AppendSwitchQuoted("--proxy-user", Separator, settings.ProxyUser); + } + + // Proxy Password + if (!string.IsNullOrEmpty(settings.ProxyPassword)) + { + builder.AppendSwitchQuotedSecret("--proxy-password", Separator, settings.ProxyPassword); + } + + // Proxy By-Pass List + if (!string.IsNullOrEmpty(settings.ProxyByPassList)) + { + builder.AppendSwitchQuoted("--proxy-bypass-list", Separator, settings.ProxyByPassList); + } + + // Proxy ByPass On Local + if (settings.ProxyBypassOnLocal) + { + builder.Append("--proxy-bypass-on-local"); + } + + // Log File + if (settings.LogFile != null) + { + var logFilePath = settings.LogFile.MakeAbsolute(_environment); + builder.AppendSwitchQuoted("--log-file", Separator, logFilePath.FullPath); + } + + // Skip Compatibility Checks + if (settings.SkipCompatibilityChecks) + { + builder.Append("--skip-compatibility-checks"); + } + + return builder; + } + + /// + /// Adds shared arguments to the process builder. + /// + /// The settings. + /// The process arguments builder. + /// The process arguments builder. + protected ProcessArgumentBuilder AddSharedArguments(ChocolateySharedSettings settings, ProcessArgumentBuilder builder) + { + // Source + if (!string.IsNullOrEmpty(settings.Source)) + { + builder.AppendSwitchQuoted("--source", Separator, settings.Source); + } + + // Version + if (!string.IsNullOrEmpty(settings.Version)) + { + builder.AppendSwitchQuoted("--version", Separator, settings.Version); + } + + // Override Arguments + if (settings.OverrideArguments) + { + builder.Append("--override-arguments"); + } + + // Not Silent + if (settings.NotSilent) + { + builder.Append("--not-silent"); + } + + // Package Parameters + if (!string.IsNullOrEmpty(settings.PackageParameters)) + { + builder.AppendSwitchQuoted("--package-parameters", Separator, settings.PackageParameters); + } + + // Global Arguments + if (settings.ApplyInstallArgumentsToDependencies) + { + builder.Append("--apply-install-arguments-to-dependencies"); + } + + // Global Package Parameters + if (settings.ApplyPackageParametersToDependencies) + { + builder.Append("--apply-package-parameters-to-dependencies"); + } + + // Side By Side + if (settings.SideBySide) + { + builder.Append("--side-by-side"); + } + + // Skip PowerShell + if (settings.SkipPowerShell) + { + builder.Append("--skip-automation-scripts"); + } + + // Ignore Package Codes + if (settings.IgnorePackageExitCodes) + { + builder.Append("--ignore-package-exit-codes"); + } + + // Use Package Exit Codes + if (settings.UsePackageExitCodes) + { + builder.Append("--use-package-exit-codes"); + } + + // Stop On First Failure + if (settings.StopOnFirstFailure) + { + builder.Append("--stop-on-first-package-failure"); + } + + // Exit Where Reboot Detected + if (settings.ExitWhenRebootDetected) + { + builder.Append("--exit-when-reboot-detected"); + } + + // Ignore Detected Reboot + if (settings.IgnoreDetectedReboot) + { + builder.Append("--ignore-detected-reboot"); + } + + // Skip Hooks + if (settings.SkipHooks) + { + builder.Append("--skip-hooks"); + } + + return builder; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/ChocolateyToolResolver.cs b/src/Cake.Common/Tools/Chocolatey/ChocolateyToolResolver.cs index 9eaefdd244..c837c560f2 100644 --- a/src/Cake.Common/Tools/Chocolatey/ChocolateyToolResolver.cs +++ b/src/Cake.Common/Tools/Chocolatey/ChocolateyToolResolver.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Linq; using Cake.Core; @@ -9,7 +10,7 @@ namespace Cake.Common.Tools.Chocolatey { /// - /// Contains Chocolatey path resolver functionality + /// Contains Chocolatey path resolver functionality. /// public sealed class ChocolateyToolResolver : IChocolateyToolResolver { @@ -27,23 +28,14 @@ public ChocolateyToolResolver(IFileSystem fileSystem, ICakeEnvironment environme _fileSystem = fileSystem; _environment = environment; - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(environment); } - /// - /// Resolves the path to choco.exe. - /// - /// The path to choco.exe. + /// public FilePath ResolvePath() { - // Check if path allready resolved + // Check if path already resolved if (_cachedPath != null && _cachedPath.Exists) { return _cachedPath.Path; @@ -53,7 +45,7 @@ public FilePath ResolvePath() var chocolateyInstallationFolder = _environment.GetEnvironmentVariable("ChocolateyInstall"); if (!string.IsNullOrWhiteSpace(chocolateyInstallationFolder)) { - var envFile = _fileSystem.GetFile(System.IO.Path.Combine(chocolateyInstallationFolder, "choco.exe")); + var envFile = _fileSystem.GetFile(PathHelper.Combine(chocolateyInstallationFolder, "choco.exe")); if (envFile.Exists) { _cachedPath = envFile; @@ -83,4 +75,4 @@ public FilePath ResolvePath() throw new CakeException("Could not locate choco.exe."); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSetter.cs b/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSetter.cs index 5ca9a1910b..50adb06464 100644 --- a/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSetter.cs +++ b/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSetter.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -32,26 +32,23 @@ public ChocolateyConfigSetter( } /// - /// Sets Chocolatey configuration paramaters using the settings. + /// Sets Chocolatey configuration parameters using the settings. /// /// The name of the config parameter. /// The value to assign to the parameter. /// The settings. public void Set(string name, string value, ChocolateyConfigSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } if (string.IsNullOrWhiteSpace(value)) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } Run(settings, GetArguments(name, value, settings)); @@ -59,71 +56,20 @@ public void Set(string name, string value, ChocolateyConfigSettings settings) private ProcessArgumentBuilder GetArguments(string name, string value, ChocolateyConfigSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("config"); builder.Append("set"); - builder.Append("--name"); - builder.AppendQuoted(name); - - builder.Append("--value"); - builder.AppendQuoted(value); - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } + builder.AppendSwitchQuoted("--name", separator, name); - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); + builder.AppendSwitchQuoted("--value", separator, value); - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } - - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) - { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); - } - - // Allow Unofficial - if (settings.AllowUnofficial) - { - builder.Append("--allowunofficial"); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSettings.cs b/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSettings.cs index 537b617f9f..83fe477da6 100644 --- a/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Config/ChocolateyConfigSettings.cs @@ -1,62 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Config { /// /// Contains settings used by . /// - public sealed class ChocolateyConfigSettings : ToolSettings + public sealed class ChocolateyConfigSettings : ChocolateySettings { - /// - /// Gets or sets a value indicating whether to run in debug mode. - /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. - /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } - - /// - /// Gets or sets the location of the download cache. - /// - /// The download cache location - public string CacheLocation { get; set; } - - /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. - /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloadSettings.cs b/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloadSettings.cs new file mode 100644 index 0000000000..348836fef6 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloadSettings.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.Chocolatey.Download +{ + /// + /// Contains settings used by . + /// + public sealed class ChocolateyDownloadSettings : ChocolateySettings + { + /// + /// Gets or sets the source to find the package(s) to download. Defaults to default feeds. + /// + /// The server URL. + public string Source { get; set; } + + /// + /// Gets or sets the version of the package to download. + /// If none specified, the latest will be used. + /// + public string Version { get; set; } + + /// + /// Gets or sets a value indicating whether to allow downloading of prerelease packages. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets the user for authenticated feeds. + /// + public string User { get; set; } + + /// + /// Gets or sets the password for authenticated feeds. + /// + public string Password { get; set; } + + /// + /// Gets or sets the path to a PFX certificate for use with x509 authenticated feeds. + /// + public FilePath Certificate { get; set; } + + /// + /// Gets or sets the password for the . + /// + public string CertificatePassword { get; set; } + + /// + /// Gets or sets the directory for the downloaded Chocolatey packages. + /// By default the current working directory is used. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore dependencies. + /// Requires Chocolatey licensed edition. + /// + public bool IgnoreDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to download all currently installed Chocolatey packages. + /// + public bool Installed { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore unfound packages if downloading more than one at a time. + /// + public bool IgnoreUnfound { get; set; } + + /// + /// Gets or sets a value indicating whether to use optimizations for reducing bandwidth when communicating with repositories. + /// + public bool DisableRepositoryOptimizations { get; set; } + + /// + /// Gets or sets a value indicating whether all external resources should be downloaded + /// and the package be recompiled to use the local resources instead. + /// Requires Chocolatey business edition. + /// + public bool Internalize { get; set; } + + /// + /// Gets or sets the location for downloaded resource when is set. + /// null or if downloaded resources should be embedded in the package. + /// Can be a file share or an internal URL location. + /// When it is a file share, it will attempt to download to that location. + /// When it is an internal url, it will download locally and give further instructions + /// where it should be uploaded to match package edits. + /// By default resources are embedded in the package. + /// Requires Chocolatey business edition. + /// + public string ResourcesLocation { get; set; } + + /// + /// Gets or sets the location where resources should be downloaded to when is set. + /// null or if should be used. + /// By default is used. + /// Requires Chocolatey business edition. + /// + public string DownloadLocation { get; set; } + + /// + /// Gets or sets a value indicating whether all URLs, not only from known helpers, should be internalized. + /// Requires Chocolatey business edition (Licensed version 1.11.1+). + /// + public bool InternalizeAllUrls { get; set; } + + /// + /// Gets or sets a value indicating whether the -useOriginalLocation parameter will be passed to any + /// Install-ChocolateyPackage call in the package and avoiding downloading of resources when + /// is set. + /// Overrides the global internalizeAppendUseOriginalLocation feature. + /// By default set to false. + /// Requires Chocolatey business edition and Chocolatey v0.10.1 or newer. + /// + public bool AppendUseOriginalLocation { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the package download cache. + /// + public bool SkipDownloadCache { get; set; } + + /// + /// Gets or sets a value indicating whether to use the package download cache. + /// + public bool UseDownloadCache { get; set; } + + /// + /// Gets or sets a value indicating whether skip the virus checking for a package. + /// + public bool SkipVirusCheck { get; set; } + + /// + /// Gets or sets a value indicating whether to force virus checking for a package. + /// + public bool VirusCheck { get; set; } + + /// + /// Gets or sets the minimum allowed number of virus positives. + /// + public int VirusPositivesMinimum { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloader.cs b/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloader.cs new file mode 100644 index 0000000000..dedb06d99a --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Download/ChocolateyDownloader.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Chocolatey.Download +{ + /// + /// The Chocolatey package downloader used to download Chocolatey packages. + /// + public sealed class ChocolateyDownloader : ChocolateyTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The Chocolatey tool resolver. + public ChocolateyDownloader( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + } + + /// + /// Downloads Chocolatey packages using the specified package id and settings. + /// Requires Chocolatey licensed edition. + /// Features requiring Chocolatey for Business or a minimum version are documented + /// in . + /// + /// The source package id. + /// The settings. + public void Download(string packageId, ChocolateyDownloadSettings settings) + { + if (string.IsNullOrWhiteSpace(packageId)) + { + throw new ArgumentNullException(nameof(packageId)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(packageId, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageId, ChocolateyDownloadSettings settings) + { + const string separator = "="; + var builder = new ProcessArgumentBuilder(); + + builder.Append("download"); + builder.AppendQuoted(packageId); + + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Source + if (!string.IsNullOrEmpty(settings.Source)) + { + builder.AppendSwitchQuoted("--source", separator, settings.Source); + } + + // Version + if (!string.IsNullOrEmpty(settings.Version)) + { + builder.AppendSwitchQuoted("--version", separator, settings.Version); + } + + // Prerelease + if (settings.Prerelease) + { + builder.Append("--pre"); + } + + // User + if (!string.IsNullOrWhiteSpace(settings.User)) + { + builder.AppendSwitchQuoted("--user", separator, settings.User); + } + + // Password + if (!string.IsNullOrWhiteSpace(settings.Password)) + { + builder.AppendSwitchQuoted("--password", separator, settings.Password); + } + + // Certificate + if (settings.Certificate != null) + { + builder.AppendSwitchQuoted("--cert", separator, settings.Certificate.MakeAbsolute(_environment).FullPath); + } + + // Certificate Password + if (!string.IsNullOrEmpty(settings.CertificatePassword)) + { + builder.AppendSwitchQuoted("--certpassword", separator, settings.CertificatePassword); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.AppendSwitchQuoted("--output-directory", separator, settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Ignore Dependencies + if (settings.IgnoreDependencies) + { + builder.Append("--ignore-dependencies"); + } + + // Installed + if (settings.Installed) + { + builder.Append("--installed-packages"); + } + + // Ignore Unfound + if (settings.IgnoreUnfound) + { + builder.Append("--ignore-unfound"); + } + + // Disable Repository Optimizations + if (settings.DisableRepositoryOptimizations) + { + builder.Append("--disable-repository-optimizations"); + } + + // Internalize + if (settings.Internalize) + { + builder.Append("--internalize"); + + // Internalize All + if (settings.InternalizeAllUrls) + { + builder.Append("--internalize-all-urls"); + } + + // Resources Location + if (!string.IsNullOrWhiteSpace(settings.ResourcesLocation)) + { + builder.AppendSwitchQuoted("--resources-location", separator, settings.ResourcesLocation); + + // Download Location + if (!string.IsNullOrWhiteSpace(settings.DownloadLocation)) + { + builder.AppendSwitchQuoted("--download-location", separator, settings.DownloadLocation); + } + } + + // Append -UseOriginalLocation + if (settings.AppendUseOriginalLocation) + { + builder.Append("--append-use-original-location"); + } + } + + // Skip Download Cache + if (settings.SkipDownloadCache) + { + builder.Append("--skip-download-cache"); + } + + // Use Download Cache + if (settings.UseDownloadCache) + { + builder.Append("--use-download-cache"); + } + + // Skip Virus Check + if (settings.SkipVirusCheck) + { + builder.Append("--skip-virus-check"); + } + + // Virus Check + if (settings.VirusCheck) + { + builder.Append("--virus-check"); + } + + // Virus Positive Minimum + if (settings.VirusPositivesMinimum != 0) + { + builder.AppendSwitchQuoted("--virus-positives-minimum", separator, settings.VirusPositivesMinimum.ToString(CultureInfo.InvariantCulture)); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporter.cs b/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporter.cs new file mode 100644 index 0000000000..f7dad9c1de --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporter.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Chocolatey.Export +{ + /// + /// The Chocolatey package exporter used to export Chocolatey packages. + /// + public sealed class ChocolateyExporter : ChocolateyTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The Chocolatey tool resolver. + public ChocolateyExporter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + } + + /// + /// Exports the currently installed Chocolatey packages using the specified settings. + /// + /// The settings. + public void Export(ChocolateyExportSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(ChocolateyExportSettings settings) + { + const string separator = "="; + var builder = new ProcessArgumentBuilder(); + + builder.Append("export"); + + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Output File Path + if (settings.OutputFilePath != null) + { + builder.AppendSwitchQuoted("--output-file-path", separator, settings.OutputFilePath.FullPath); + } + + // Include Version Numbers + if (settings.IncludeVersionNumbers) + { + builder.Append("--include-version-numbers"); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporterSettings.cs b/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporterSettings.cs new file mode 100644 index 0000000000..ee710986f1 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Export/ChocolateyExporterSettings.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.Chocolatey.Export +{ + /// + /// Contains settings used by . + /// + public sealed class ChocolateyExportSettings : ChocolateySettings + { + /// + /// Gets or sets the path to the file that will be generated. + /// + /// The path to the file that is generated. + public FilePath OutputFilePath { get; set; } + + /// + /// Gets or sets a value indicating whether to include version numbers for installed packages. + /// + /// The include version numbers flag. + public bool IncludeVersionNumbers { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureSettings.cs b/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureSettings.cs index 9794b35e5f..6670a85fba 100644 --- a/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureSettings.cs @@ -1,62 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Features { /// /// Contains settings used by . /// - public sealed class ChocolateyFeatureSettings : ToolSettings + public sealed class ChocolateyFeatureSettings : ChocolateySettings { - /// - /// Gets or sets a value indicating whether to run in debug mode. - /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. - /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } - - /// - /// Gets or sets the location of the download cache. - /// - /// The download cache location - public string CacheLocation { get; set; } - - /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. - /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureToggler.cs b/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureToggler.cs index ff4564d858..ab3704cfb7 100644 --- a/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureToggler.cs +++ b/src/Cake.Common/Tools/Chocolatey/Features/ChocolateyFeatureToggler.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -38,14 +38,11 @@ public ChocolateyFeatureToggler( /// The settings. public void EnableFeature(string name, ChocolateyFeatureSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } Run(settings, GetArguments(true, name, settings)); @@ -58,14 +55,11 @@ public void EnableFeature(string name, ChocolateyFeatureSettings settings) /// The settings. public void DisableFeature(string name, ChocolateyFeatureSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } Run(settings, GetArguments(false, name, settings)); @@ -73,69 +67,19 @@ public void DisableFeature(string name, ChocolateyFeatureSettings settings) private ProcessArgumentBuilder GetArguments(bool enableDisableToggle, string name, ChocolateyFeatureSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("feature"); builder.Append(enableDisableToggle ? "enable" : "disable"); - builder.Append("-n"); - builder.AppendQuoted(name); - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } + builder.AppendSwitchQuoted("--name", separator, name); - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } - - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) - { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); - } - - // Allow Unofficial - if (settings.AllowUnofficial) - { - builder.Append("--allowunofficial"); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/IChocolateyToolResolver.cs b/src/Cake.Common/Tools/Chocolatey/IChocolateyToolResolver.cs index e57ff030cc..b91cccd9ac 100644 --- a/src/Cake.Common/Tools/Chocolatey/IChocolateyToolResolver.cs +++ b/src/Cake.Common/Tools/Chocolatey/IChocolateyToolResolver.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; namespace Cake.Common.Tools.Chocolatey @@ -16,4 +17,4 @@ public interface IChocolateyToolResolver /// The path to choco.exe. FilePath ResolvePath(); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstallSettings.cs b/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstallSettings.cs index f1c4f04300..2b757a2f16 100644 --- a/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstallSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstallSettings.cs @@ -1,163 +1,188 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Install { + using global::Cake.Core.IO; + /// /// Contains settings used by . /// - public sealed class ChocolateyInstallSettings : ToolSettings + public sealed class ChocolateyInstallSettings : ChocolateySharedSettings { /// - /// Gets or sets a value indicating whether to run in debug mode. + /// Gets or sets a value indicating whether to allow installation of prerelease packages. + /// This flag is not required when restoring packages by installing from packages.config. /// - /// The debug flag - public bool Debug { get; set; } + /// + /// true to allow installation of prerelease packages; otherwise, false. + /// + public bool Prerelease { get; set; } /// - /// Gets or sets a value indicating whether to run in verbose mode. + /// Gets or sets a value indicating whether to force installation of the x86 version of package. /// - /// The verbose flag. - public bool Verbose { get; set; } + /// The force x86 flag. + public bool Forcex86 { get; set; } /// - /// Gets or sets a value indicating whether to accept license for package. + /// Gets or sets the install arguments to pass to native installer. /// - /// The accept license flag - public bool AcceptLicense { get; set; } + public string InstallArguments { get; set; } /// - /// Gets or sets a value indicating whether to run in forced mode. + /// Gets or sets a value indicating whether to allow downgrade of package. /// - /// The force flag - public bool Force { get; set; } + /// The downgrade package flag. + public bool AllowDowngrade { get; set; } /// - /// Gets or sets a value indicating whether to run in noop mode. + /// Gets or sets a value indicating whether to ignore dependencies. /// - /// The noop flag. - public bool Noop { get; set; } + /// The ignore dependencies flag. + public bool IgnoreDependencies { get; set; } /// - /// Gets or sets a value indicating whether to run in limited output mode. + /// Gets or sets a value indicating whether to force dependencies. /// - /// The limit output flag - public bool LimitOutput { get; set; } + /// The force dependencies flag. + public bool ForceDependencies { get; set; } /// - /// Gets or sets the execution timeout value. + /// Gets or sets the user for authenticated feeds. /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } + public string User { get; set; } /// - /// Gets or sets the location of the download cache. + /// Gets or sets the password for authenticated feeds. /// - /// The download cache location - public string CacheLocation { get; set; } + public string Password { get; set; } /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// Gets or sets the path to a PFX certificate for use with x509 authenticated feeds. /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + public FilePath Certificate { get; set; } /// - /// Gets or sets a package sources to use for this command. + /// Gets or sets the password for the . /// - /// The package source to use for this command. - public string Source { get; set; } + public string CertificatePassword { get; set; } /// - /// Gets or sets the version of the package to install. - /// If none specified, the latest will be used. + /// Gets or sets a value indicating whether to ignore checksums. /// - public string Version { get; set; } + /// The ignore checksums flag. + public bool IgnoreChecksums { get; set; } /// - /// Gets or sets a value indicating whether to allow installation of prerelease packages. - /// This flag is not required when restoring packages by installing from packages.config. + /// Gets or sets a value indicating whether to allow empty checksums for bare HTTP URLs during package installation. /// - /// - /// true to allow installation of prerelease packages; otherwise, false. - /// - public bool Prerelease { get; set; } + public bool AllowEmptyChecksums { get; set; } /// - /// Gets or sets a value indicating whether to force installation of the x86 version of package. + /// Gets or sets a value indicating whether to allow empty checksums for HTTPS URLs during package installation. /// - /// The force x86 flag - public bool Forcex86 { get; set; } + public bool AllowEmptyChecksumsSecure { get; set; } /// - /// Gets or sets the install arguments to pass to native installer. + /// Gets or sets a value indicating whether checksums are required during package installation. /// - public string InstallArguments { get; set; } + public bool RequireChecksums { get; set; } /// - /// Gets or sets a value indicating whether to override the passed arguments. + /// Gets or sets the checksum for 32 bit installation. /// - /// The override arguments flag - public bool OverrideArguments { get; set; } + public string Checksum { get; set; } /// - /// Gets or sets a value indicating whether or not to install silently. + /// Gets or sets the checksum for 64 bit installation. /// - /// The not silent flag - public bool NotSilent { get; set; } + public string Checksum64 { get; set; } /// - /// Gets or sets the parameters to pass to the package. + /// Gets or sets the checksum type to use for 32 bit installation. /// - public string PackageParameters { get; set; } + public string ChecksumType { get; set; } /// - /// Gets or sets a value indicating whether to allow downgrade of package. + /// Gets or sets the checksum type to use for 64 bit installation. /// - /// The downgrade package flag - public bool AllowDowngrade { get; set; } + public string ChecksumType64 { get; set; } /// - /// Gets or sets a value indicating whether to allow side by side installation. + /// Gets or sets a value indicating whether to use optimizations for reducing bandwidth when communicating with repositories. /// - /// The side by side installation flag - public bool SideBySide { get; set; } + public bool DisableRepositoryOptimizations { get; set; } /// - /// Gets or sets a value indicating whether to ignore dependencies. + /// Gets or sets a value indicating whether to pin the package once installed. /// - /// The ignore dependencies flag - public bool IgnoreDependencies { get; set; } + public bool Pin { get; set; } /// - /// Gets or sets a value indicating whether to force dependencies. + /// Gets or sets a value indicating whether to skip the package download cache. /// - /// The force dependencies flag - public bool ForceDependencies { get; set; } + public bool SkipDownloadCache { get; set; } /// - /// Gets or sets a value indicating whether to skip the PowerShell installation of package. + /// Gets or sets a value indicating whether to use the package download cache. /// - /// The skip powershell flag - public bool SkipPowerShell { get; set; } + public bool UseDownloadCache { get; set; } /// - /// Gets or sets the user for authenticated feeds. + /// Gets or sets a value indicating whether skip the virus checking for a package. /// - public string User { get; set; } + public bool SkipVirusCheck { get; set; } /// - /// Gets or sets the password for authenticated feeds. + /// Gets or sets a value indicating whether to force virus checking for a package. /// - public string Password { get; set; } + public bool VirusCheck { get; set; } /// - /// Gets or sets a value indicating whether to ignore checksums. + /// Gets or sets the minimum allowed number of virus positives. /// - /// The ignore checksums flag - public bool IgnoreChecksums { get; set; } + public int VirusPositivesMinimum { get; set; } + + /// + /// Gets or sets the install arguments sensitive to pass to native installer. + /// + public string InstallArgumentsSensitive { get; set; } + + /// + /// Gets or sets sensitive parameters to pass to the package. + /// + public string PackageParametersSensitive { get; set; } + + /// + /// Gets or sets the default application installation directory. + /// + public DirectoryPath InstallDirectory { get; set; } + + /// + /// Gets or sets the maximum download bits per second when downloading a package. + /// + public int MaximumDownloadBitsPerSecond { get; set; } + + /// + /// Gets or sets a value indicating whether package size should be reduced on installation. + /// + public bool ReducePackageSize { get; set; } + + /// + /// Gets or sets a value indicating whether there should be no reduction in package size on installation. + /// + public bool NoReducePackageSize { get; set; } + + /// + /// Gets or sets a value indicating whether only the nupkg file size should be reduced. + /// + public bool ReduceNupkgOnly { get; set; } + + /// + /// Gets or sets a reason for pinning a package during installation. + /// + public string PinReason { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstaller.cs b/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstaller.cs index 9241d2fe10..016404eca0 100644 --- a/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstaller.cs +++ b/src/Cake.Common/Tools/Chocolatey/Install/ChocolateyInstaller.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using Cake.Core; @@ -41,14 +42,9 @@ public ChocolateyInstaller( /// The settings. public void InstallFromConfig(FilePath packageConfigPath, ChocolateyInstallSettings settings) { - if (packageConfigPath == null) - { - throw new ArgumentNullException("packageConfigPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(packageConfigPath); + + ArgumentNullException.ThrowIfNull(settings); var packageId = packageConfigPath.MakeAbsolute(_environment).FullPath; @@ -64,185 +60,227 @@ public void Install(string packageId, ChocolateyInstallSettings settings) { if (string.IsNullOrWhiteSpace(packageId)) { - throw new ArgumentNullException("packageId"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(packageId)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetArguments(packageId, settings)); } private ProcessArgumentBuilder GetArguments(string packageId, ChocolateyInstallSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("install"); builder.AppendQuoted(packageId); - // Debug - if (settings.Debug) + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Add shared arguments using the inherited method + AddSharedArguments(settings, builder); + + // Prerelease + if (settings.Prerelease) { - builder.Append("-d"); + builder.Append("--pre"); } - // Verbose - if (settings.Verbose) + // Forcex86 + if (settings.Forcex86) { - builder.Append("-v"); + builder.Append("--forcex86"); } - // Accept License - if (settings.AcceptLicense) + // Install Arguments + if (!string.IsNullOrWhiteSpace(settings.InstallArguments)) { - builder.Append("--acceptLicense"); + builder.AppendSwitchQuoted("--install-arguments", separator, settings.InstallArguments); } - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); + // Allow Downgrade + if (settings.AllowDowngrade) + { + builder.Append("--allow-downgrade"); + } - // Force - if (settings.Force) + // Ignore Dependencies + if (settings.IgnoreDependencies) { - builder.Append("-f"); + builder.Append("--ignore-dependencies"); } - // Noop - if (settings.Noop) + // Force Dependencies + if (settings.ForceDependencies) { - builder.Append("--noop"); + builder.Append("--force-dependencies"); } - // Limit Output - if (settings.LimitOutput) + // User + if (!string.IsNullOrWhiteSpace(settings.User)) { - builder.Append("-r"); + builder.AppendSwitchQuoted("--user", separator, settings.User); } - // Execution Timeout - if (settings.ExecutionTimeout != 0) + // Password + if (!string.IsNullOrWhiteSpace(settings.Password)) { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); + builder.AppendSwitchQuoted("--password", separator, settings.Password); } - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) + // Certificate + if (settings.Certificate != null) { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); + builder.AppendSwitchQuoted("--cert", separator, settings.Certificate.MakeAbsolute(_environment).FullPath); } - // Allow Unofficial - if (settings.AllowUnofficial) + // Certificate Password + if (!string.IsNullOrEmpty(settings.CertificatePassword)) { - builder.Append("--allowunofficial"); + builder.AppendSwitchQuoted("--certpassword", separator, settings.CertificatePassword); } - // Package source - if (!string.IsNullOrWhiteSpace(settings.Source)) + // Ignore Checksums + if (settings.IgnoreChecksums) { - builder.Append("-s"); - builder.AppendQuoted(settings.Source); + builder.Append("--ignore-checksums"); } - // Version - if (settings.Version != null) + // Allow Empty Checksums + if (settings.AllowEmptyChecksums) { - builder.Append("--version"); - builder.AppendQuoted(settings.Version); + builder.Append("--allow-empty-checksums"); } - // Prerelease - if (settings.Prerelease) + // Allow Empty Checksums Secure + if (settings.AllowEmptyChecksumsSecure) { - builder.Append("--pre"); + builder.Append("--allow-empty-checksums-secure"); } - // Forcex86 - if (settings.Forcex86) + // Require Checksums + if (settings.RequireChecksums) { - builder.Append("--x86"); + builder.Append("--require-checksums"); } - // Install Arguments - if (!string.IsNullOrWhiteSpace(settings.InstallArguments)) + // Checksum + if (!string.IsNullOrEmpty(settings.Checksum)) { - builder.Append("--ia"); - builder.AppendQuoted(settings.InstallArguments); + builder.AppendSwitchQuoted("--download-checksum", separator, settings.Checksum); } - // OverrideArguments - if (settings.OverrideArguments) + // Checksum 64 + if (!string.IsNullOrEmpty(settings.Checksum64)) { - builder.Append("-o"); + builder.AppendSwitchQuoted("--download-checksum-x64", separator, settings.Checksum64); } - // NotSilent - if (settings.NotSilent) + // Checksum Type + if (!string.IsNullOrEmpty(settings.ChecksumType)) { - builder.Append("--notSilent"); + builder.AppendSwitchQuoted("--download-checksum-type", separator, settings.ChecksumType); } - // Package Parameters - if (!string.IsNullOrWhiteSpace(settings.PackageParameters)) + // Checksum Type 64 + if (!string.IsNullOrEmpty(settings.ChecksumType64)) { - builder.Append("--params"); - builder.AppendQuoted(settings.PackageParameters); + builder.AppendSwitchQuoted("--download-checksum-type-x64", separator, settings.ChecksumType64); } - // Allow Downgrade - if (settings.AllowDowngrade) + // Disable Repository Optimizations + if (settings.DisableRepositoryOptimizations) { - builder.Append("--allowdowngrade"); + builder.Append("--disable-repository-optimizations"); } - // Side by side installation - if (settings.SideBySide) + // Pin + if (settings.Pin) { - builder.Append("-m"); + builder.Append("--pin-package"); } - // Ignore Dependencies - if (settings.IgnoreDependencies) + // Skip Download Cache + if (settings.SkipDownloadCache) { - builder.Append("-i"); + builder.Append("--skip-download-cache"); } - // Force Dependencies - if (settings.ForceDependencies) + // Use Download Cache + if (settings.UseDownloadCache) { - builder.Append("-x"); + builder.Append("--use-download-cache"); } - // Skip PowerShell - if (settings.SkipPowerShell) + // Skip Virus Check + if (settings.SkipVirusCheck) { - builder.Append("-n"); + builder.Append("--skip-virus-check"); } - // User - if (!string.IsNullOrWhiteSpace(settings.User)) + // Virus Check + if (settings.VirusCheck) { - builder.Append("-u"); - builder.AppendQuoted(settings.User); + builder.Append("--virus-check"); } - // Password - if (!string.IsNullOrWhiteSpace(settings.Password)) + // Virus Positive Minimum + if (settings.VirusPositivesMinimum != 0) { - builder.Append("-p"); - builder.AppendQuoted(settings.Password); + builder.AppendSwitchQuoted("--virus-positives-minimum", separator, settings.VirusPositivesMinimum.ToString(CultureInfo.InvariantCulture)); } - // Ignore Checksums - if (settings.IgnoreChecksums) + // Install Arguments Sensitive + if (!string.IsNullOrWhiteSpace(settings.InstallArgumentsSensitive)) + { + builder.AppendSwitchQuoted("--install-arguments-sensitive", separator, settings.InstallArgumentsSensitive); + } + + // Package Parameters Sensitive + if (!string.IsNullOrEmpty(settings.PackageParametersSensitive)) + { + builder.AppendSwitchQuoted("--package-parameters-sensitive", separator, settings.PackageParametersSensitive); + } + + // Install Directory + if (settings.InstallDirectory != null) + { + builder.AppendSwitchQuoted("--install-directory", separator, settings.InstallDirectory.MakeAbsolute(_environment).FullPath); + } + + // Maximum Download Bits Per Second + if (settings.MaximumDownloadBitsPerSecond != 0) + { + builder.AppendSwitchQuoted("--maximum-download-bits-per-second", separator, settings.MaximumDownloadBitsPerSecond.ToString(CultureInfo.InvariantCulture)); + } + + // Reduce Package Size + if (settings.ReducePackageSize) + { + builder.Append("--reduce-package-size"); + } + + // No Reduce Package Size + if (settings.NoReducePackageSize) + { + builder.Append("--no-reduce-package-size"); + } + + // Reduce Nupkg Only + if (settings.ReduceNupkgOnly) + { + builder.Append("--reduce-nupkg-only"); + } + + // Pin Reason + if (!string.IsNullOrEmpty(settings.PinReason)) { - builder.Append("--ignorechecksums"); + builder.AppendSwitchQuoted("--pin-reason", separator, settings.PinReason); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/New/ChocolateyNewSettings.cs b/src/Cake.Common/Tools/Chocolatey/New/ChocolateyNewSettings.cs new file mode 100644 index 0000000000..3e6d59d831 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/New/ChocolateyNewSettings.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.Chocolatey.New +{ + /// + /// Contains settings used by . + /// + public sealed class ChocolateyNewSettings : ChocolateySettings + { + private readonly Dictionary _additionalPropertyValues = new Dictionary(); + + /// + /// Gets or sets a value indicating whether to generate automatic package instead or normal. + /// + public bool AutomaticPackage { get; set; } + + /// + /// Gets or sets the name of the template to generate new package with. + /// + public string TemplateName { get; set; } + + /// + /// Gets or sets the path where the package will be created. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to use original built-in template, rather than an overridden one. + /// + public bool BuiltInTemplate { get; set; } + + /// + /// Gets or sets the version of the package to be created. + /// + public string PackageVersion { get; set; } + + /// + /// Gets or sets the owner of the package to be created. + /// + public string MaintainerName { get; set; } + + /// + /// Gets or sets the repository of the package source. + /// + public string MaintainerRepo { get; set; } + + /// + /// Gets or sets the type of the installer. + /// + public string InstallerType { get; set; } + + /// + /// Gets or sets the URL where the software to be installed can be downloaded from. + /// + public string Url { get; set; } + + /// + /// Gets or sets the URL where the 64-Bit version of the software to be installed can be downloaded from. + /// + public string Url64 { get; set; } + + /// + /// Gets or sets the arguments for running the installer silently. + /// + public string SilentArgs { get; set; } + + /// + /// Gets the list of additional property values which should be passed to the template. + /// + public Dictionary AdditionalPropertyValues + { + get + { + return _additionalPropertyValues; + } + } + + /// + /// Gets or sets the file or URL to binary used for auto-detection and generation of package. + /// + public string File { get; set; } + + /// + /// Gets or sets the file or URL to 64-bit binary used for auto-detection and generation of package. + /// + public string File64 { get; set; } + + /// + /// Gets or sets a value indicating whether to use original location of binary in packaging. + /// + public bool UseOriginalFilesLocation { get; set; } + + /// + /// Gets or sets the checksum for 32 bit installation. + /// + public string Checksum { get; set; } + + /// + /// Gets or sets the checksum for 64 bit installation. + /// + public string Checksum64 { get; set; } + + /// + /// Gets or sets the checksum type to use for 32 bit installation. + /// + public string ChecksumType { get; set; } + + /// + /// Gets or sets a value indicating whether to pause when there is an error creating a package. + /// + public bool PauseOnError { get; set; } + + /// + /// Gets or sets a value indicating whether to attempt to compile the package after creating it. + /// + public bool BuildPackage { get; set; } + + /// + /// Gets or sets a value indicating whether to generate packages from the currenty installed software on a system. + /// + public bool GeneratePackagesFromInstalledSoftware { get; set; } + + /// + /// Gets or sets a value indicating whether to remove x86, x64, etc. from generated package id. + /// + public bool RemoveArchitectureFromName { get; set; } + + /// + /// Gets or sets a value indicating whether to leave x86, x64, etc. as part of the generated package id. + /// + public bool IncludeArchitectureInPackageName { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/New/ChocolateyScaffolder.cs b/src/Cake.Common/Tools/Chocolatey/New/ChocolateyScaffolder.cs new file mode 100644 index 0000000000..5479bc0681 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/New/ChocolateyScaffolder.cs @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Chocolatey.New +{ + /// + /// The Chocolatey project scaffolder used to generate package specification files for a new package. + /// + public sealed class ChocolateyScaffolder : ChocolateyTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The Chocolatey tool resolver. + public ChocolateyScaffolder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + } + + /// + /// Generate package specification files for a new package. + /// + /// Id of the new package. + /// The settings. + public void CreatePackage(string packageId, ChocolateyNewSettings settings) + { + if (string.IsNullOrWhiteSpace(packageId)) + { + throw new ArgumentNullException(nameof(packageId)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(packageId, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageId, ChocolateyNewSettings settings) + { + const string separator = "="; + var builder = new ProcessArgumentBuilder(); + + builder.Append("new"); + builder.AppendQuoted(packageId); + + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Automatic Packages + if (settings.AutomaticPackage) + { + builder.Append("--automaticpackage"); + } + + // Template Name + if (!string.IsNullOrEmpty(settings.TemplateName)) + { + builder.AppendSwitchQuoted("--template-name", separator, settings.TemplateName); + } + + // Package version + if (!string.IsNullOrWhiteSpace(settings.PackageVersion)) + { + builder.AppendSwitchQuoted("packageversion", separator, settings.PackageVersion); + } + + // Owner + if (!string.IsNullOrWhiteSpace(settings.MaintainerName)) + { + builder.AppendSwitchQuoted("maintainername", separator, settings.MaintainerName); + } + + // Package source repository + if (!string.IsNullOrWhiteSpace(settings.MaintainerRepo)) + { + builder.AppendSwitchQuoted("maintainerrepo", separator, settings.MaintainerRepo); + } + + // Installer type + if (!string.IsNullOrWhiteSpace(settings.InstallerType)) + { + builder.AppendSwitchQuoted("installertype", separator, settings.InstallerType); + } + + // URL + if (!string.IsNullOrWhiteSpace(settings.Url)) + { + builder.AppendSwitchQuoted("url", separator, settings.Url); + } + + // URL64 + if (!string.IsNullOrWhiteSpace(settings.Url64)) + { + builder.AppendSwitchQuoted("url64", separator, settings.Url64); + } + + // Silent arguments + if (!string.IsNullOrWhiteSpace(settings.SilentArgs)) + { + builder.AppendSwitchQuoted("silentargs", separator, settings.SilentArgs); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.AppendSwitchQuoted("--output-directory", separator, settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Built In Template + if (settings.BuiltInTemplate) + { + builder.Append("--built-in-template"); + } + + // Additional properties + foreach (var propertyValue in settings.AdditionalPropertyValues) + { + builder.AppendSwitchQuoted(propertyValue.Key, separator, propertyValue.Value); + } + + // File + if (!string.IsNullOrEmpty(settings.File)) + { + builder.AppendSwitchQuoted("--file", separator, settings.File); + } + + // File 64 + if (!string.IsNullOrEmpty(settings.File64)) + { + builder.AppendSwitchQuoted("--file64", separator, settings.File64); + } + + // Use Original Files Location + if (settings.UseOriginalFilesLocation) + { + builder.Append("--use-original-files-location"); + } + + // Checksum + if (!string.IsNullOrEmpty(settings.Checksum)) + { + builder.AppendSwitchQuoted("--download-checksum", separator, settings.Checksum); + } + + // Checksum 64 + if (!string.IsNullOrEmpty(settings.Checksum64)) + { + builder.AppendSwitchQuoted("--download-checksum-x64", separator, settings.Checksum64); + } + + // Checksum Type + if (!string.IsNullOrEmpty(settings.ChecksumType)) + { + builder.AppendSwitchQuoted("--download-checksum-type", separator, settings.ChecksumType); + } + + // Pause On Error + if (settings.PauseOnError) + { + builder.Append("--pause-on-error"); + } + + // Build Package + if (settings.BuildPackage) + { + builder.Append("--build-package"); + } + + // Generate Packages From Installed Software + if (settings.GeneratePackagesFromInstalledSoftware) + { + builder.Append("--from-programs-and-features"); + } + + // Remove Architecture From Name + if (settings.RemoveArchitectureFromName) + { + builder.Append("--remove-architecture-from-name"); + } + + // Include Architecture In Package Name + if (settings.IncludeArchitectureInPackageName) + { + builder.Append("--include-architecture-in-name"); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecContent.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecContent.cs index 6a03ab723d..03367f15ac 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecContent.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecContent.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.Chocolatey.Pack { /// - /// Represents a Chocolatey nuspec file + /// Represents a Chocolatey nuspec file. /// public sealed class ChocolateyNuSpecContent { @@ -29,4 +30,4 @@ public sealed class ChocolateyNuSpecContent /// public string Exclude { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecDependency.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecDependency.cs new file mode 100644 index 0000000000..800adeafab --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecDependency.cs @@ -0,0 +1,20 @@ +namespace Cake.Common.Tools.Chocolatey.Pack +{ + /// + /// Represents a Chocolatey NuGet nuspec dependency. + /// + public class ChocolateyNuSpecDependency + { + /// + /// Gets or sets the dependency's package ID. + /// + /// The dependency's package ID. + public string Id { get; set; } + + /// + /// Gets or sets the dependency's version. + /// + /// The dependency's version. + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecProcessor.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecProcessor.cs index ce9e134fbf..4c8175a755 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecProcessor.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecProcessor.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Globalization; using System.Xml; using Cake.Core; @@ -116,4 +117,4 @@ private FilePath SaveNuspecXml(FilePath nuspecFilePath, XmlDocument document) return nuspecFile.Path; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecTransformer.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecTransformer.cs index 8292fb0916..990e4de872 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecTransformer.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyNuSpecTransformer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -12,9 +13,10 @@ namespace Cake.Common.Tools.Chocolatey.Pack { internal static class ChocolateyNuSpecTransformer { + private const string ChocolateyNuSpecXsd = "http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"; + private static readonly Dictionary> _mappings; private static readonly List _cdataElements; - private const string ChocolateyNuSpecXsd = "http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"; static ChocolateyNuSpecTransformer() { @@ -53,9 +55,9 @@ public static void Transform(XmlDocument document, ChocolateyPackSettings settin var namespaceManager = new XmlNamespaceManager(document.NameTable); namespaceManager.AddNamespace("nu", ChocolateyNuSpecXsd); - foreach (var elementName in _mappings.Keys) + foreach (var (elementName, element) in _mappings) { - var content = _mappings[elementName](settings); + var content = element(settings); if (content != null) { // Replace the node content. @@ -93,6 +95,20 @@ public static void Transform(XmlDocument document, ChocolateyPackSettings settin fileElement.AddAttributeIfSpecified(file.Target, "target"); } } + + if (settings.Dependencies != null && settings.Dependencies.Count > 0) + { + var dependenciesElement = FindOrCreateElement(document, namespaceManager, "dependencies"); + + // Add or update the dependency references + dependenciesElement.RemoveAll(); + foreach (var dependency in settings.Dependencies) + { + var dependencyElement = document.CreateAndAppendElement(dependenciesElement, "dependency"); + dependencyElement.AddAttributeIfSpecified(dependency.Id, "id"); + dependencyElement.AddAttributeIfSpecified(dependency.Version, "version"); + } + } } private static XmlNode GetPackageElement(XmlDocument document) @@ -158,7 +174,7 @@ private static string ToString(string value) private static string ToString(Uri value) { - return value == null ? null : value.ToString().TrimEnd('/'); + return value?.ToString().TrimEnd('/'); } private static string ToString(bool value) @@ -166,25 +182,25 @@ private static string ToString(bool value) return value.ToString().ToLowerInvariant(); } - private static string ToCommaSeparatedString(IEnumerable values) + private static string ToCommaSeparatedString(ICollection values) { - return values != null - ? string.Join(",", values) + return values != null && values.Count != 0 + ? string.Join(',', values) : null; } - private static string ToMultiLineString(IEnumerable values) + private static string ToMultiLineString(ICollection values) { - return values != null + return values != null && values.Count != 0 ? string.Join("\r\n", values).NormalizeLineEndings() : null; } - private static string ToSpaceSeparatedString(IEnumerable values) + private static string ToSpaceSeparatedString(ICollection values) { - return values != null - ? string.Join(" ", values.Select(x => x.Replace(" ", "-"))) + return values != null && values.Count != 0 + ? string.Join(' ', values.Select(x => x.Replace(" ", "-"))) : null; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPackSettings.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPackSettings.cs index 1e64cea46d..a2fcb25bc0 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPackSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPackSettings.cs @@ -1,17 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Pack { /// /// Contains settings used by . /// - public sealed class ChocolateyPackSettings : ToolSettings + public sealed class ChocolateyPackSettings : ChocolateySettings { /// /// Gets or sets the package ID. @@ -25,23 +25,17 @@ public sealed class ChocolateyPackSettings : ToolSettings /// The package title. public string Title { get; set; } - /// - /// Gets or sets the Nuspec version. - /// - /// The Nuspec version. - public string Version { get; set; } - /// /// Gets or sets the package authors. /// /// The package authors. - public ICollection Authors { get; set; } + public ICollection Authors { get; set; } = new List(); /// /// Gets or sets the package owners. /// /// The package owners. - public ICollection Owners { get; set; } + public ICollection Owners { get; set; } = new List(); /// /// Gets or sets the package summary. @@ -53,7 +47,7 @@ public sealed class ChocolateyPackSettings : ToolSettings /// Gets or sets the package description. /// /// The package description. - /// Markdown format is allowed for this property + /// Markdown format is allowed for this property. public string Description { get; set; } /// @@ -79,7 +73,7 @@ public sealed class ChocolateyPackSettings : ToolSettings /// /// Gets or sets the package documentation URL. /// - /// The package documenation URL. + /// The package documentation URL. /// Requires at least Chocolatey 0.9.9.7. public Uri DocsUrl { get; set; } @@ -101,7 +95,7 @@ public sealed class ChocolateyPackSettings : ToolSettings /// Gets or sets the package tags. /// /// The package tags. - public ICollection Tags { get; set; } + public ICollection Tags { get; set; } = new List(); /// /// Gets or sets the package copyright. @@ -133,67 +127,30 @@ public sealed class ChocolateyPackSettings : ToolSettings /// Gets or sets the package release notes. /// /// The package release notes. - /// Markdown format is allowed for this property - public ICollection ReleaseNotes { get; set; } + /// Markdown format is allowed for this property. + public ICollection ReleaseNotes { get; set; } = new List(); /// /// Gets or sets the package files. /// /// The package files. - public ICollection Files { get; set; } + public ICollection Files { get; set; } = new List(); /// - /// Gets or sets a value indicating whether to run in debug mode. + /// Gets or sets the package dependencies. /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. - /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } - - /// - /// Gets or sets the location of the download cache. - /// - /// The download cache location - public string CacheLocation { get; set; } + /// The package files. + public ICollection Dependencies { get; set; } = new List(); /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// Gets or sets the Nuspec version. /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + /// The Nuspec version. + public string Version { get; set; } /// /// Gets or sets a value indicating the Working Directory that should be used while running choco.exe. /// public DirectoryPath OutputDirectory { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPacker.cs b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPacker.cs index 36ff786ca4..594df91313 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPacker.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pack/ChocolateyPacker.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -27,7 +27,7 @@ public sealed class ChocolateyPacker : ChocolateyTool /// The process runner. /// The log. /// The tool locator. - /// The Chocolatey tool resolver + /// The Chocolatey tool resolver. public ChocolateyPacker( IFileSystem fileSystem, ICakeEnvironment environment, @@ -47,26 +47,28 @@ public ChocolateyPacker( /// The settings. public void Pack(ChocolateyPackSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); + if (string.IsNullOrWhiteSpace(settings.Id)) { throw new CakeException("Required setting Id not specified."); } + if (string.IsNullOrWhiteSpace(settings.Version)) { throw new CakeException("Required setting Version not specified."); } + if (settings.Authors == null || settings.Authors.Count == 0) { throw new CakeException("Required setting Authors not specified."); } + if (string.IsNullOrWhiteSpace(settings.Description)) { throw new CakeException("Required setting Description not specified."); } + if (settings.Files == null || settings.Files.Count == 0) { throw new CakeException("Required setting Files not specified."); @@ -82,14 +84,9 @@ public void Pack(ChocolateyPackSettings settings) /// The settings. public void Pack(FilePath nuspecFilePath, ChocolateyPackSettings settings) { - if (nuspecFilePath == null) - { - throw new ArgumentNullException("nuspecFilePath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(nuspecFilePath); + + ArgumentNullException.ThrowIfNull(settings); Pack(settings, () => _processor.Process(nuspecFilePath, settings)); } @@ -103,7 +100,7 @@ private void Pack(ChocolateyPackSettings settings, Func process) processedNuspecFilePath = process(); // Start the process. - Run(settings, GetArguments(processedNuspecFilePath, settings), new ProcessSettings { WorkingDirectory = settings.OutputDirectory }, null); + Run(settings, GetArguments(processedNuspecFilePath, settings), null, null); } finally { @@ -121,73 +118,30 @@ private void Pack(ChocolateyPackSettings settings, Func process) private ProcessArgumentBuilder GetArguments(FilePath nuspecFilePath, ChocolateyPackSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); - builder.Append("pack"); - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } + builder.Append("pack"); - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) - { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); - } + // Nuspec file + builder.AppendQuoted(nuspecFilePath.MakeAbsolute(_environment).FullPath); - // Allow Unofficial - if (settings.AllowUnofficial) - { - builder.Append("--allowunofficial"); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); // Version if (!string.IsNullOrWhiteSpace(settings.Version)) { - builder.Append("--version"); - builder.AppendQuoted(settings.Version); + builder.AppendSwitchQuoted("--version", separator, settings.Version); } - // Nuspec file - builder.AppendQuoted(nuspecFilePath.MakeAbsolute(_environment).FullPath); + // Output Directory + if (settings.OutputDirectory != null) + { + builder.AppendSwitchQuoted("--output-directory", separator, settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinSettings.cs b/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinSettings.cs index f95179ff77..53352995aa 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinSettings.cs @@ -1,68 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Pin { /// /// Contains settings used by . /// - public sealed class ChocolateyPinSettings : ToolSettings + public sealed class ChocolateyPinSettings : ChocolateySettings { /// - /// Gets or sets a value indicating whether to run in debug mode. + /// Gets or sets the version of the package to pin. /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. - /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } - - /// - /// Gets or sets the location of the download cache. - /// - /// The download cache location - public string CacheLocation { get; set; } - - /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. - /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + public string Version { get; set; } /// - /// Gets or sets the version of the package to install. - /// If none specified, the latest will be used. + /// Gets or sets a reason for pinning a package during installation. /// - public string Version { get; set; } + public string PinReason { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinner.cs b/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinner.cs index d695665d52..da8ab84c62 100644 --- a/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinner.cs +++ b/src/Cake.Common/Tools/Chocolatey/Pin/ChocolateyPinner.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -38,14 +38,11 @@ public ChocolateyPinner( /// The settings. public void Pin(string name, ChocolateyPinSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } Run(settings, GetArguments(name, settings)); @@ -53,75 +50,30 @@ public void Pin(string name, ChocolateyPinSettings settings) private ProcessArgumentBuilder GetArguments(string name, ChocolateyPinSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("pin"); builder.Append("add"); - builder.Append("-n"); - builder.AppendQuoted(name); + builder.AppendSwitchQuoted("--name", separator, name); + + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); // Version if (settings.Version != null) { - builder.Append("--version"); - builder.AppendQuoted(settings.Version); - } - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } - - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) - { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); + builder.AppendSwitchQuoted("--version", separator, settings.Version); } - // Allow Unofficial - if (settings.AllowUnofficial) + // Pin Reason + if (!string.IsNullOrEmpty(settings.PinReason)) { - builder.Append("--allowunofficial"); + builder.AppendSwitchQuoted("--pin-reason", separator, settings.PinReason); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPushSettings.cs b/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPushSettings.cs index 6f3b89746b..5467671eac 100644 --- a/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPushSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPushSettings.cs @@ -1,83 +1,44 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Push { - using System; - /// /// Contains settings used by . /// - public sealed class ChocolateyPushSettings : ToolSettings + public sealed class ChocolateyPushSettings : ChocolateySettings { /// - /// Gets or sets a value indicating whether to run in debug mode. - /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. - /// - /// The noop flag. - public bool Noop { get; set; } - - /// - /// Gets or sets a value indicating whether to run in limited output mode. - /// - /// The limit output flag - public bool LimitOutput { get; set; } - - /// - /// Gets or sets the execution timeout value. + /// Gets or sets the source. /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } + /// The source. + public string Source { get; set; } /// - /// Gets or sets the location of the download cache. + /// Gets or sets the API key for the server. /// - /// The download cache location - public string CacheLocation { get; set; } + /// The API key for the server. + public string ApiKey { get; set; } /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// Gets or sets the client code generated for delegating access vy a user to the Intune endpoints. /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + public string ClientCode { get; set; } /// - /// Gets or sets the server URL. If not specified, chocolatey.org is used. + /// Gets or sets the URL used when requesting the client code. /// - /// The server URL. - public string Source { get; set; } + public string RedirectUrl { get; set; } /// - /// Gets or sets the API key for the server. + /// Gets or sets the Intune API endpoint to use. /// - /// The API key for the server. - public string ApiKey { get; set; } + public string EndPoint { get; set; } /// - /// Gets or sets the timeout for pushing to a server. - /// Defaults to 300 seconds (5 minutes). + /// Gets or sets a value indicating whether to skip cleanup local files when pushing to Intune. /// - /// The timeout for pushing to a server. - public TimeSpan? Timeout { get; set; } + public bool SkipCleanup { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPusher.cs b/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPusher.cs index 518789c7d5..0d7f42f58f 100644 --- a/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPusher.cs +++ b/src/Cake.Common/Tools/Chocolatey/Push/ChocolateyPusher.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -41,97 +41,62 @@ public ChocolateyPusher( /// The settings. public void Push(FilePath packageFilePath, ChocolateyPushSettings settings) { - if (packageFilePath == null) - { - throw new ArgumentNullException("packageFilePath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(packageFilePath); + + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(packageFilePath, settings)); } private ProcessArgumentBuilder GetArguments(FilePath packageFilePath, ChocolateyPushSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); + builder.Append("push"); builder.AppendQuoted(packageFilePath.MakeAbsolute(_environment).FullPath); - if (settings.Source != null) - { - builder.Append("-s"); - builder.AppendQuoted(settings.Source); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); - if (settings.Timeout != null) + // Source + if (settings.Source != null) { - builder.Append("-t"); - builder.Append(Convert.ToInt32(settings.Timeout.Value.TotalSeconds).ToString(CultureInfo.InvariantCulture)); + builder.AppendSwitchQuoted("--source", separator, settings.Source); } + // Api Key if (settings.ApiKey != null) { - builder.Append("-k"); - builder.AppendSecret(settings.ApiKey); - } - - // Debug - if (settings.Debug) - { - builder.Append("-d"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); + builder.AppendSwitchQuoted("--api-key", separator, settings.ApiKey); } - // Limit Output - if (settings.LimitOutput) + // Client Code + if (!string.IsNullOrEmpty(settings.ClientCode)) { - builder.Append("-r"); + builder.AppendSwitchQuoted("--client-code", separator, settings.ClientCode); } - // Execution Timeout - if (settings.ExecutionTimeout != 0) + // Redirect URL + if (!string.IsNullOrEmpty(settings.RedirectUrl)) { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); + builder.AppendSwitchQuoted("--redirect-url", separator, settings.RedirectUrl); } - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) + // Endpoint + if (!string.IsNullOrEmpty(settings.EndPoint)) { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); + builder.AppendSwitchQuoted("--endpoint", separator, settings.EndPoint); } - // Allow Unofficial - if (settings.AllowUnofficial) + // Skip Cleanup + if (settings.SkipCleanup) { - builder.Append("--allowunofficial"); + builder.Append("--skip-cleanup"); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySources.cs b/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySources.cs index 7f5d256b3a..6f7ca20b09 100644 --- a/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySources.cs +++ b/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySources.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using Cake.Core; @@ -10,10 +11,14 @@ namespace Cake.Common.Tools.Chocolatey.Sources { /// - /// The Chocolatey sources is used to work with user config feeds & credentials + /// The Chocolatey sources is used to work with user config feeds & credentials. /// public sealed class ChocolateySources : ChocolateyTool { + private const string Separator = "="; + + private readonly ICakeEnvironment _environment; + /// /// Initializes a new instance of the class. /// @@ -29,110 +34,94 @@ public ChocolateySources( IToolLocator tools, IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) { + _environment = environment; } /// - /// Adds Chocolatey package source using the specified settings to global user config + /// Adds Chocolatey package source using the specified settings to global user config. /// /// Name of the source. /// Path to the package(s) source. /// The settings. public void AddSource(string name, string source, ChocolateySourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (source == null) - { - throw new ArgumentNullException("source"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + + ArgumentNullException.ThrowIfNull(source); + if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentException("Source cannot be empty.", "source"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source cannot be empty.", nameof(source)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetAddArguments(name, source, settings)); } /// - /// Remove specified Chocolatey package source + /// Remove specified Chocolatey package source. /// /// Name of the source. /// The settings. public void RemoveSource(string name, ChocolateySourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetRemoveArguments(name, settings)); } /// - /// Enable specified Chocolatey package source + /// Enable specified Chocolatey package source. /// /// Name of the source. /// The settings. public void EnableSource(string name, ChocolateySourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetEnableArguments(name, settings)); } /// - /// Disable specified Chocolatey package source + /// Disable specified Chocolatey package source. /// /// Name of the source. /// The settings. public void DisableSource(string name, ChocolateySourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetDisableArguments(name, settings)); } - private static ProcessArgumentBuilder GetAddArguments(string name, string source, ChocolateySourcesSettings settings) + private ProcessArgumentBuilder GetAddArguments(string name, string source, ChocolateySourcesSettings settings) { var builder = new ProcessArgumentBuilder(); @@ -143,21 +132,49 @@ private static ProcessArgumentBuilder GetAddArguments(string name, string source // User name specified? if (!string.IsNullOrWhiteSpace(settings.UserName)) { - builder.Append("-u"); - builder.AppendQuoted(settings.UserName); + builder.AppendSwitchQuoted("--user", Separator, settings.UserName); } // Password specified? if (!string.IsNullOrWhiteSpace(settings.Password)) { - builder.Append("-p"); - builder.AppendQuotedSecret(settings.Password); + builder.AppendSwitchQuoted("--password", Separator, settings.Password); + } + + // Certificate + if (settings.Certificate != null) + { + builder.AppendSwitchQuoted("--cert", Separator, settings.Certificate.MakeAbsolute(_environment).FullPath); + } + + // Certificate Password + if (!string.IsNullOrEmpty(settings.CertificatePassword)) + { + builder.AppendSwitchQuoted("--certpassword", Separator, settings.CertificatePassword); + } + + // By Pass Proxy + if (settings.ByPassProxy) + { + builder.Append("--bypass-proxy"); + } + + // Allow Self Service + if (settings.AllowSelfService) + { + builder.Append("--allow-self-service"); + } + + // Admin Only + if (settings.AdminOnly) + { + builder.Append("--admin-only"); } return builder; } - private static ProcessArgumentBuilder GetRemoveArguments(string name, ChocolateySourcesSettings settings) + private ProcessArgumentBuilder GetRemoveArguments(string name, ChocolateySourcesSettings settings) { var builder = new ProcessArgumentBuilder(); @@ -168,7 +185,7 @@ private static ProcessArgumentBuilder GetRemoveArguments(string name, Chocolatey return builder; } - private static ProcessArgumentBuilder GetEnableArguments(string name, ChocolateySourcesSettings settings) + private ProcessArgumentBuilder GetEnableArguments(string name, ChocolateySourcesSettings settings) { var builder = new ProcessArgumentBuilder(); @@ -179,7 +196,7 @@ private static ProcessArgumentBuilder GetEnableArguments(string name, Chocolatey return builder; } - private static ProcessArgumentBuilder GetDisableArguments(string name, ChocolateySourcesSettings settings) + private ProcessArgumentBuilder GetDisableArguments(string name, ChocolateySourcesSettings settings) { var builder = new ProcessArgumentBuilder(); @@ -190,75 +207,22 @@ private static ProcessArgumentBuilder GetDisableArguments(string name, Chocolate return builder; } - private static void AddCommonParameters(string name, string source, ChocolateySourcesSettings settings, ProcessArgumentBuilder builder) + private void AddCommonParameters(string name, string source, ChocolateySourcesSettings settings, ProcessArgumentBuilder builder) { - builder.Append("-n"); - builder.AppendQuoted(name); + builder.AppendSwitchQuoted("--name", Separator, name); if (!string.IsNullOrWhiteSpace(source)) { - builder.Append("-s"); - builder.AppendQuoted(source); - } - - // Debug - if (settings.Debug) - { - builder.Append("-d"); + builder.AppendSwitchQuoted("--source", Separator, source); } - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); - - // Force - if (settings.Force) - { - builder.Append("-f"); - } - - // Noop - if (settings.Noop) - { - builder.Append("--noop"); - } - - // Limit Output - if (settings.LimitOutput) - { - builder.Append("-r"); - } - - // Execution Timeout - if (settings.ExecutionTimeout != 0) - { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); - } - - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) - { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); - } - - // Allow Unofficial - if (settings.AllowUnofficial) - { - builder.Append("--allowunofficial"); - } + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); if (settings.Priority > 0) { - builder.Append("--priority"); - builder.AppendQuoted(settings.Priority.ToString(CultureInfo.InvariantCulture)); + builder.AppendSwitchQuoted("--priority", Separator, settings.Priority.ToString(CultureInfo.InvariantCulture)); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySourcesSettings.cs b/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySourcesSettings.cs index f80f8d5f0b..5a50f16fae 100644 --- a/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySourcesSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Sources/ChocolateySourcesSettings.cs @@ -1,80 +1,57 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Sources { + using global::Cake.Core.IO; + /// /// Contains settings used by . /// - public sealed class ChocolateySourcesSettings : ToolSettings + public sealed class ChocolateySourcesSettings : ChocolateySettings { /// - /// Gets or sets a value indicating whether to run in debug mode. - /// - /// The debug flag - public bool Debug { get; set; } - - /// - /// Gets or sets a value indicating whether to run in verbose mode. - /// - /// The verbose flag. - public bool Verbose { get; set; } - - /// - /// Gets or sets a value indicating whether to run in forced mode. - /// - /// The force flag - public bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to run in noop mode. + /// Gets or sets the (optional) user name. /// - /// The noop flag. - public bool Noop { get; set; } + /// Optional user name to be used when connecting to an authenticated source. + public string UserName { get; set; } /// - /// Gets or sets a value indicating whether to run in limited output mode. + /// Gets or sets the (optional) password. /// - /// The limit output flag - public bool LimitOutput { get; set; } + /// Optional password to be used when connecting to an authenticated source. + public string Password { get; set; } /// - /// Gets or sets the execution timeout value. + /// Gets or sets the path to a PFX certificate for use with x509 authenticated feeds. /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } + public FilePath Certificate { get; set; } /// - /// Gets or sets the location of the download cache. + /// Gets or sets the password for the . /// - /// The download cache location - public string CacheLocation { get; set; } + public string CertificatePassword { get; set; } /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// Gets or sets the (optional) priority. /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + /// Optional priority to be used when creating source. + public int Priority { get; set; } /// - /// Gets or sets the (optional) user name. + /// Gets or sets a value indicating whether source should explicitly bypass any explicitly set proxy. /// - /// Optional user name to be used when connecting to an authenticated source. - public string UserName { get; set; } + public bool ByPassProxy { get; set; } /// - /// Gets or sets the (optional) password. + /// Gets or sets a value indicating whether source should be allowed to be used in self service mode. /// - /// Optional password to be used when connecting to an authenticated source. - public string Password { get; set; } + public bool AllowSelfService { get; set; } /// - /// Gets or sets the (optional) priority. + /// Gets or sets a value indicating whether source should be visible to non-admin users. /// - /// Optional priority to be used when creating source. - public int Priority { get; set; } + public bool AdminOnly { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstallSettings.cs b/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstallSettings.cs new file mode 100644 index 0000000000..9de9d8c92e --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstallSettings.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.Chocolatey.Uninstall +{ + /// + /// Contains settings used by . + /// + public sealed class ChocolateyUninstallSettings : ChocolateySharedSettings + { + /// + /// Gets or sets a value indicating whether to uninstall all versions. + /// + /// The all versions flag. + public bool AllVersions { get; set; } + + /// + /// Gets or sets the uninstall arguments to pass to native installer. + /// + public string UninstallArguments { get; set; } + + /// + /// Gets or sets a value indicating whether to force dependencies. + /// + /// The force dependencies flag. + public bool ForceDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to use auto uninstaller service when uninstalling. + /// + /// The use auto uninstaller flag. + public bool UseAutoUninstaller { get; set; } + + /// + /// Gets or sets a value indicating whether to skip auto uninstaller service when uninstalling. + /// + /// The skip auto uninstaller flag. + public bool SkipAutoUninstaller { get; set; } + + /// + /// Gets or sets a value indicating whether to fail the package uninstall if the auto + /// uninstaller reports and error. + /// + /// The fail auto uninstaller flag. + public bool FailOnAutoUninstaller { get; set; } + + /// + /// Gets or sets a value indicating whether to not fail the package if auto + /// uninstaller reports an error. + /// + /// The ignore auto uninstaller flag. + public bool IgnoreAutoUninstallerFailure { get; set; } + + /// + /// Gets or sets a value indicating whether to uninstall a program from programs and features. + /// + public bool FromProgramsAndFeatures { get; set; } + } +} diff --git a/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstaller.cs b/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstaller.cs new file mode 100644 index 0000000000..7b236e1583 --- /dev/null +++ b/src/Cake.Common/Tools/Chocolatey/Uninstall/ChocolateyUninstaller.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Chocolatey.Uninstall +{ + /// + /// The Chocolatey package uninstall used to uninstall Chocolatey packages. + /// + public sealed class ChocolateyUninstaller : ChocolateyTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The Chocolatey tool resolver. + public ChocolateyUninstaller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + } + + /// + /// Uninstalls Chocolatey packages using the specified package id and settings. + /// + /// List of package ids to uninstall. + /// The settings. + public void Uninstall(IEnumerable packageIds, ChocolateyUninstallSettings settings) + { + ArgumentNullException.ThrowIfNull(packageIds); + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(packageIds, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable packageIds, ChocolateyUninstallSettings settings) + { + const string separator = "="; + var builder = new ProcessArgumentBuilder(); + + builder.Append("uninstall"); + foreach (var packageId in packageIds) + { + builder.AppendQuoted(packageId); + } + + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Add shared arguments using the inherited method + AddSharedArguments(settings, builder); + + // All Versions + if (settings.AllVersions) + { + builder.Append("--all-versions"); + } + + // Uninstall Arguments + if (!string.IsNullOrEmpty(settings.UninstallArguments)) + { + builder.AppendSwitchQuoted("--uninstall-arguments", separator, settings.UninstallArguments); + } + + // Force Dependencies + if (settings.ForceDependencies) + { + builder.Append("--force-dependencies"); + } + + // Use Auto Uninstaller + if (settings.UseAutoUninstaller) + { + builder.Append("--use-autouninstaller"); + } + + // Skip Auto Uninstaller + if (settings.SkipAutoUninstaller) + { + builder.Append("--skip-autouninstaller"); + } + + // Fail on Auto Uninstaller + if (settings.FailOnAutoUninstaller) + { + builder.Append("--fail-on-autouninstaller"); + } + + // Ignore Auto Uninstaller failure + if (settings.IgnoreAutoUninstallerFailure) + { + builder.Append("--ignore-autouninstaller-failure"); + } + + if (settings.FromProgramsAndFeatures) + { + builder.Append("--from-programs-and-features"); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettings.cs b/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettings.cs index 4257854460..5a384ff7ca 100644 --- a/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettings.cs +++ b/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgradeSettings.cs @@ -1,168 +1,239 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Upgrade { + using global::Cake.Core.IO; + /// /// Contains settings used by . /// - public sealed class ChocolateyUpgradeSettings : ToolSettings + public sealed class ChocolateyUpgradeSettings : ChocolateySharedSettings { /// - /// Gets or sets a value indicating whether to run in debug mode. + /// Gets or sets a value indicating whether to allow upgrading to prerelease versions. + /// This flag is not required when updating prerelease packages that are already installed. /// - /// The debug flag - public bool Debug { get; set; } + /// + /// true to allow updating to prerelease versions; otherwise, false. + /// + public bool Prerelease { get; set; } /// - /// Gets or sets a value indicating whether to run in verbose mode. + /// Gets or sets a value indicating whether to force installation of the x86 version of package. /// - /// The verbose flag. - public bool Verbose { get; set; } + /// The force x86 flag. + public bool Forcex86 { get; set; } /// - /// Gets or sets a value indicating whether to accept license for package. + /// Gets or sets the install arguments to pass to native installer. /// - /// The accept license flag - public bool AcceptLicense { get; set; } + public string InstallArguments { get; set; } /// - /// Gets or sets a value indicating whether to run in forced mode. + /// Gets or sets a value indicating whether to allow downgrade of package. /// - /// The force flag - public bool Force { get; set; } + /// The downgrade package flag. + public bool AllowDowngrade { get; set; } /// - /// Gets or sets a value indicating whether to run in noop mode. + /// Gets or sets a value indicating whether to ignore dependencies. /// - /// The noop flag. - public bool Noop { get; set; } + /// The ignore dependencies flag. + public bool IgnoreDependencies { get; set; } /// - /// Gets or sets a value indicating whether to run in limited output mode. + /// Gets or sets a value indicating whether to fail on unfound packages. /// - /// The limit output flag - public bool LimitOutput { get; set; } + /// The skip powershell flag. + public bool FailOnUnfound { get; set; } /// - /// Gets or sets the execution timeout value. + /// Gets or sets a value indicating whether to ignore unfound packages if downloading more than one at a time. /// - /// The execution timeout - /// Default is 2700 seconds - public int ExecutionTimeout { get; set; } + public bool IgnoreUnfound { get; set; } /// - /// Gets or sets the location of the download cache. + /// Gets or sets a value indicating whether to fail on not installed packages. /// - /// The download cache location - public string CacheLocation { get; set; } + /// The skip powershell flag. + public bool FailOnNotInstalled { get; set; } /// - /// Gets or sets a value indicating whether to run in allow unofficial mode. + /// Gets or sets the user for authenticated feeds. /// - /// The allow unofficial flag - public bool AllowUnofficial { get; set; } + public string User { get; set; } /// - /// Gets or sets the source to use for this command. + /// Gets or sets the password for authenticated feeds. /// - public string Source { get; set; } + public string Password { get; set; } /// - /// Gets or sets the Nuspec version. + /// Gets or sets the path to a PFX certificate for use with x509 authenticated feeds. /// - /// The Nuspec version. - public string Version { get; set; } + public FilePath Certificate { get; set; } /// - /// Gets or sets a value indicating whether to allow upgrading to prerelease versions. - /// This flag is not required when updating prerelease packages that are already installed. + /// Gets or sets the password for the . /// - /// - /// true to allow updating to prerelease versions; otherwise, false. - /// - public bool Prerelease { get; set; } + public string CertificatePassword { get; set; } /// - /// Gets or sets a value indicating whether to force installation of the x86 version of package. + /// Gets or sets a value indicating whether to ignore checksums. /// - /// The force x86 flag - public bool Forcex86 { get; set; } + /// The ignore checksums flag. + public bool IgnoreChecksums { get; set; } /// - /// Gets or sets the install arguments to pass to native installer. + /// Gets or sets a value indicating whether to allow empty checksums for bare HTTP URLs during package installation. /// - public string InstallArguments { get; set; } + public bool AllowEmptyChecksums { get; set; } /// - /// Gets or sets a value indicating whether to override the passed arguments. + /// Gets or sets a value indicating whether to allow empty checksums for HTTPS URLs during package installation. /// - /// The override arguments flag - public bool OverrideArguments { get; set; } + public bool AllowEmptyChecksumsSecure { get; set; } /// - /// Gets or sets a value indicating whether or not to install silently. + /// Gets or sets a value indicating whether checksums are required during package installation. /// - /// The not silent flag - public bool NotSilent { get; set; } + public bool RequireChecksums { get; set; } /// - /// Gets or sets the parameters to pass to the package. + /// Gets or sets the checksum for 32 bit installation. /// - public string PackageParameters { get; set; } + public string Checksum { get; set; } /// - /// Gets or sets a value indicating whether to allow downgrade of package. + /// Gets or sets the checksum for 64 bit installation. /// - /// The downgrade package flag - public bool AllowDowngrade { get; set; } + public string Checksum64 { get; set; } /// - /// Gets or sets a value indicating whether to allow side by side installation. + /// Gets or sets the checksum type to use for 32 bit installation. /// - /// The side by side installation flag - public bool SideBySide { get; set; } + public string ChecksumType { get; set; } /// - /// Gets or sets a value indicating whether to ignore dependencies. + /// Gets or sets the checksum type to use for 64 bit installation. /// - /// The ignore dependencies flag - public bool IgnoreDependencies { get; set; } + public string ChecksumType64 { get; set; } /// - /// Gets or sets a value indicating whether to skip the PowerShell installation of package. + /// Gets or sets a comma separated list of package names that should not be upgraded when running upgrade all. /// - /// The skip powershell flag - public bool SkipPowerShell { get; set; } + public string Except { get; set; } /// - /// Gets or sets a value indicating whether to fail on unfound packages. + /// Gets or sets a value indicating whether a package will be skipped if it is not installed during upgrade operation. /// - /// The skip powershell flag - public bool FailOnUnfound { get; set; } + public bool SkipIfNotInstalled { get; set; } /// - /// Gets or sets a value indicating whether to fail on not installed packages. + /// Gets or sets a value indicating whether to install a package that is not installed during the upgrade operation. /// - /// The skip powershell flag - public bool FailOnNotInstalled { get; set; } + public bool InstallIfNotInstalled { get; set; } /// - /// Gets or sets the user for authenticated feeds. + /// Gets or sets a value indicating whether prerelease packages should be ignored for upgrades. /// - public string User { get; set; } + public bool ExcludePrerelease { get; set; } /// - /// Gets or sets the password for authenticated feeds. + /// Gets or sets a value indicating whether to use the remembered arguments for a package. /// - public string Password { get; set; } + public bool UseRememberedArguments { get; set; } /// - /// Gets or sets a value indicating whether to ignore checksums. + /// Gets or sets a value indicating whether to ignore the remembered arguments for package. /// - /// The ignore checksums flag - public bool IgnoreChecksums { get; set; } + public bool IgnoreRememeredArguments { get; set; } + + /// + /// Gets or sets a value indicating whether to use optimizations for reducing bandwidth when communicating with repositories. + /// + public bool DisableRepositoryOptimizations { get; set; } + + /// + /// Gets or sets a value indicating whether to pin the package once installed. + /// + public bool Pin { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the package download cache. + /// + public bool SkipDownloadCache { get; set; } + + /// + /// Gets or sets a value indicating whether to use the package download cache. + /// + public bool UseDownloadCache { get; set; } + + /// + /// Gets or sets a value indicating whether skip the virus checking for a package. + /// + public bool SkipVirusCheck { get; set; } + + /// + /// Gets or sets a value indicating whether to force virus checking for a package. + /// + public bool VirusCheck { get; set; } + + /// + /// Gets or sets the minimum allowed number of virus positives. + /// + public int VirusPositivesMinimum { get; set; } + + /// + /// Gets or sets the install arguments sensitive to pass to native installer. + /// + public string InstallArgumentsSensitive { get; set; } + + /// + /// Gets or sets sensitive parameters to pass to the package. + /// + public string PackageParametersSensitive { get; set; } + + /// + /// Gets or sets the default application installation directory. + /// + public DirectoryPath InstallDirectory { get; set; } + + /// + /// Gets or sets the maximum download bits per second when downloading a package. + /// + public int MaximumDownloadBitsPerSecond { get; set; } + + /// + /// Gets or sets a value indicating whether package size should be reduced on installation. + /// + public bool ReducePackageSize { get; set; } + + /// + /// Gets or sets a value indicating whether there should be no reduction in package size on installation. + /// + public bool NoReducePackageSize { get; set; } + + /// + /// Gets or sets a value indicating whether only the nupkg file size should be reduced. + /// + public bool ReduceNupkgOnly { get; set; } + + /// + /// Gets or sets a value indicating whether to exclude all Chocolatey owned packages during upgrade all operation. + /// + public bool ExcludeChocolateyPackagesDuringUpgradeAll { get; set; } + + /// + /// Gets or sets a value indicating whether to include all Chocolatey owned packages during upgrade all operation. + /// + public bool IncludeChocolateyPackagesDuringUpgradeAll { get; set; } + + /// + /// Gets or sets a reason for pinning a package during installation. + /// + public string PinReason { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgrader.cs b/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgrader.cs index 65736e7925..5fb126665e 100644 --- a/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgrader.cs +++ b/src/Cake.Common/Tools/Chocolatey/Upgrade/ChocolateyUpgrader.cs @@ -1,20 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; namespace Cake.Common.Tools.Chocolatey.Upgrade { - using System.Globalization; - /// /// The Chocolatey package upgrader. /// public sealed class ChocolateyUpgrader : ChocolateyTool { + private readonly ICakeEnvironment _environment; + /// /// Initializes a new instance of the class. /// @@ -30,6 +32,7 @@ public ChocolateyUpgrader(IFileSystem fileSystem, IChocolateyToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) { + _environment = environment; } /// @@ -41,191 +44,287 @@ public void Upgrade(string packageId, ChocolateyUpgradeSettings settings) { if (string.IsNullOrWhiteSpace(packageId)) { - throw new ArgumentNullException("packageId"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(packageId)); } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetArguments(packageId, settings)); } private ProcessArgumentBuilder GetArguments(string packageId, ChocolateyUpgradeSettings settings) { + const string separator = "="; var builder = new ProcessArgumentBuilder(); builder.Append("upgrade"); builder.AppendQuoted(packageId); - // Debug - if (settings.Debug) + // Add common arguments using the inherited method + AddGlobalArguments(settings, builder); + + // Add shared arguments using the inherited method + AddSharedArguments(settings, builder); + + // Prerelease + if (settings.Prerelease) { - builder.Append("-d"); + builder.Append("--pre"); } - // Verbose - if (settings.Verbose) + // Forcex86 + if (settings.Forcex86) { - builder.Append("-v"); + builder.Append("--forcex86"); } - // Accept License - if (settings.AcceptLicense) + // Install Arguments + if (!string.IsNullOrWhiteSpace(settings.InstallArguments)) { - builder.Append("--acceptLicense"); + builder.AppendSwitchQuoted("--install-arguments", separator, settings.InstallArguments); } - // Always say yes, so as to not show interactive prompt - builder.Append("-y"); + // Allow Downgrade + if (settings.AllowDowngrade) + { + builder.Append("--allow-downgrade"); + } - // Force - if (settings.Force) + // Ignore Dependencies + if (settings.IgnoreDependencies) { - builder.Append("-f"); + builder.Append("--ignore-dependencies"); } - // Noop - if (settings.Noop) + // Fail on Unfound + if (settings.FailOnUnfound) { - builder.Append("--noop"); + builder.Append("--fail-on-unfound"); } - // Limit Output - if (settings.LimitOutput) + // Ignore Unfound + if (settings.IgnoreUnfound) { - builder.Append("-r"); + builder.Append("--ignore-unfound"); } - // Execution Timeout - if (settings.ExecutionTimeout != 0) + // Fail on Not Installed + if (settings.FailOnNotInstalled) { - builder.Append("--execution-timeout"); - builder.AppendQuoted(settings.ExecutionTimeout.ToString(CultureInfo.InvariantCulture)); + builder.Append("--fail-on-not-installed"); } - // Cache Location - if (!string.IsNullOrWhiteSpace(settings.CacheLocation)) + // User + if (!string.IsNullOrWhiteSpace(settings.User)) { - builder.Append("-c"); - builder.AppendQuoted(settings.CacheLocation); + builder.AppendSwitchQuoted("--user", separator, settings.User); } - // Allow Unofficial - if (settings.AllowUnofficial) + // Password + if (!string.IsNullOrWhiteSpace(settings.Password)) { - builder.Append("--allowunofficial"); + builder.AppendSwitchQuoted("--password", separator, settings.Password); } - // Package source - if (!string.IsNullOrWhiteSpace(settings.Source)) + // Certificate + if (settings.Certificate != null) { - builder.Append("-s"); - builder.AppendQuoted(settings.Source); + builder.AppendSwitchQuoted("--cert", separator, settings.Certificate.MakeAbsolute(_environment).FullPath); } - // Version - if (settings.Version != null) + // Certificate Password + if (!string.IsNullOrEmpty(settings.CertificatePassword)) { - builder.Append("--version"); - builder.AppendQuoted(settings.Version); + builder.AppendSwitchQuoted("--certpassword", separator, settings.CertificatePassword); } - // Prerelease - if (settings.Prerelease) + // Ignore Checksums + if (settings.IgnoreChecksums) { - builder.Append("--pre"); + builder.Append("--ignore-checksums"); } - // Forcex86 - if (settings.Forcex86) + // Allow Empty Checksums + if (settings.AllowEmptyChecksums) { - builder.Append("--x86"); + builder.Append("--allow-empty-checksums"); } - // Install Arguments - if (!string.IsNullOrWhiteSpace(settings.InstallArguments)) + // Allow Empty Checksums Secure + if (settings.AllowEmptyChecksumsSecure) { - builder.Append("--ia"); - builder.AppendQuoted(settings.InstallArguments); + builder.Append("--allow-empty-checksums-secure"); } - // OverrideArguments - if (settings.OverrideArguments) + // Require Checksums + if (settings.RequireChecksums) { - builder.Append("-o"); + builder.Append("--require-checksums"); } - // NotSilent - if (settings.NotSilent) + // Checksum + if (!string.IsNullOrEmpty(settings.Checksum)) { - builder.Append("--notSilent"); + builder.AppendSwitchQuoted("--download-checksum", separator, settings.Checksum); } - // Package Parameters - if (!string.IsNullOrWhiteSpace(settings.PackageParameters)) + // Checksum 64 + if (!string.IsNullOrEmpty(settings.Checksum64)) { - builder.Append("--params"); - builder.AppendQuoted(settings.PackageParameters); + builder.AppendSwitchQuoted("--download-checksum-x64", separator, settings.Checksum64); } - // Allow Downgrade - if (settings.AllowDowngrade) + // Checksum Type + if (!string.IsNullOrEmpty(settings.ChecksumType)) { - builder.Append("--allowdowngrade"); + builder.AppendSwitchQuoted("--download-checksum-type", separator, settings.ChecksumType); } - // Side by side installation - if (settings.SideBySide) + // Checksum Type 64 + if (!string.IsNullOrEmpty(settings.ChecksumType64)) { - builder.Append("-m"); + builder.AppendSwitchQuoted("--download-checksum-type-x64", separator, settings.ChecksumType64); } - // Ignore Dependencies - if (settings.IgnoreDependencies) + // Except + if (!string.IsNullOrEmpty(settings.Except)) { - builder.Append("-i"); + builder.AppendSwitchQuoted("--except", separator, settings.Except); } - // Skip PowerShell - if (settings.SkipPowerShell) + // Skip If Not Installed + if (settings.SkipIfNotInstalled) { - builder.Append("-n"); + builder.Append("--skip-if-not-installed"); } - // Fail on Unfound - if (settings.FailOnUnfound) + // Install If Not Installed + if (settings.InstallIfNotInstalled) { - builder.Append("--failonunfound"); + builder.Append("--install-if-not-installed"); } - // Fail on Not Installed - if (settings.FailOnNotInstalled) + // Exclude Prerelease + if (settings.ExcludePrerelease) { - builder.Append("--failonnotinstalled"); + builder.Append("--exclude-prerelease"); } - // User - if (!string.IsNullOrWhiteSpace(settings.User)) + // Use Remembered Arguments + if (settings.UseRememberedArguments) { - builder.Append("-u"); - builder.AppendQuoted(settings.User); + builder.Append("--use-remembered-arguments"); } - // Password - if (!string.IsNullOrWhiteSpace(settings.Password)) + // Ignore Remembered Arguments + if (settings.IgnoreRememeredArguments) { - builder.Append("-p"); - builder.AppendQuoted(settings.Password); + builder.Append("--ignore-remembered-arguments"); } - // Ignore Checksums - if (settings.IgnoreChecksums) + // Disable Repository Optimizations + if (settings.DisableRepositoryOptimizations) + { + builder.Append("--disable-repository-optimizations"); + } + + // Pin + if (settings.Pin) + { + builder.Append("--pin-package"); + } + + // Skip Download Cache + if (settings.SkipDownloadCache) + { + builder.Append("--skip-download-cache"); + } + + // Use Download Cache + if (settings.UseDownloadCache) + { + builder.Append("--use-download-cache"); + } + + // Skip Virus Check + if (settings.SkipVirusCheck) + { + builder.Append("--skip-virus-check"); + } + + // Virus Check + if (settings.VirusCheck) + { + builder.Append("--virus-check"); + } + + // Virus Positive Minimum + if (settings.VirusPositivesMinimum != 0) + { + builder.AppendSwitchQuoted("--virus-positives-minimum", separator, settings.VirusPositivesMinimum.ToString(CultureInfo.InvariantCulture)); + } + + // Install Arguments Sensitive + if (!string.IsNullOrWhiteSpace(settings.InstallArgumentsSensitive)) + { + builder.AppendSwitchQuoted("--install-arguments-sensitive", separator, settings.InstallArgumentsSensitive); + } + + // Package Parameters Sensitive + if (!string.IsNullOrEmpty(settings.PackageParametersSensitive)) + { + builder.AppendSwitchQuoted("--package-parameters-sensitive", separator, settings.PackageParametersSensitive); + } + + // Install Directory + if (settings.InstallDirectory != null) + { + builder.AppendSwitchQuoted("--install-directory", separator, settings.InstallDirectory.MakeAbsolute(_environment).FullPath); + } + + // Maximum Download Bits Per Second + if (settings.MaximumDownloadBitsPerSecond != 0) + { + builder.AppendSwitchQuoted("--maximum-download-bits-per-second", separator, settings.MaximumDownloadBitsPerSecond.ToString(CultureInfo.InvariantCulture)); + } + + // Reduce Package Size + if (settings.ReducePackageSize) + { + builder.Append("--reduce-package-size"); + } + + // No Reduce Package Size + if (settings.NoReducePackageSize) + { + builder.Append("--no-reduce-package-size"); + } + + // Reduce Nupkg Only + if (settings.ReduceNupkgOnly) + { + builder.Append("--reduce-nupkg-only"); + } + + // Exclude Chocolatey Packages During Upgrade All + if (settings.ExcludeChocolateyPackagesDuringUpgradeAll) + { + builder.Append("--exclude-chocolatey-packages-during-upgrade-all"); + } + + // Include Chocolatey Packages During Upgrade All + if (settings.IncludeChocolateyPackagesDuringUpgradeAll) + { + builder.Append("--include-chocolatey-packages-during-upgrade-all"); + } + + // Pin Reason + if (!string.IsNullOrEmpty(settings.PinReason)) { - builder.Append("--ignorechecksums"); + builder.AppendSwitchQuoted("--pin-reason", separator, settings.PinReason); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Command/CommandAliases.cs b/src/Cake.Common/Tools/Command/CommandAliases.cs new file mode 100644 index 0000000000..7c9f65c6a8 --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandAliases.cs @@ -0,0 +1,452 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains generic functionality for simplifying the execution tools with no dedicated alias available yet. + /// + [CakeAliasCategory("Command")] + public static class CommandAliases + { + /// + /// Executes a generic tool/process based on arguments and settings. + /// + /// The context. + /// The tool executable names. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization (default null). + /// Thrown if or is null or empty. + /// + /// + /// // Example with ProcessArgumentBuilder + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// Command( + /// new []{ "dpi", "dpi.exe"}, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "TABLE") + /// .Append("analyze") + /// ); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// "--version" + /// ); + /// + /// + /// // Example specify expected exit code + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// expectedExitCode: -2147450751 + /// ); + /// + /// + /// // Example settings customization + /// Command( + /// new []{ "dotnet", "dotnet.exe"}, + /// settingsCustomization: settings => settings + /// .WithToolName(".NET tool") + /// .WithExpectedExitCode(1) + /// .WithArgumentCustomization(args => args.Append("tool")) + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static void Command( + this ICakeContext context, + ICollection toolExecutableNames, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + arguments); + + /// + /// Executes a generic command based on arguments and settings. + /// + /// The context. + /// The settings. + /// The optional arguments. + /// Thrown if or is null. + /// + /// + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = "DPI", + /// ToolExecutableNames = new []{ "dpi", "dpi.exe"}, + /// }; + /// + /// // Example with ProcessArgumentBuilder + /// Command( + /// settings, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "TABLE") + /// .Append("analyze") + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// Command( + /// settings, + /// $"nuget --output TABLE analyze" + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static void Command( + this ICakeContext context, + CommandSettings settings, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + runner.RunCommand(arguments); + } + + /// + /// Executes a generic tool/process based on arguments, tool executable names and redirects standard output. + /// + /// The context. + /// The tool executable names. + /// The standard output. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization. + /// Thrown if , or is null. + /// The exit code. + /// + /// + /// using System.Text.Json.Serialization; + /// using System.Text.Json; + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var standardOutput, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "JSON") + /// .Append("analyze") + /// ); + /// + /// var packageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// standardOutput + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var implicitStandardOutput, + /// $"nuget --output JSON analyze" + /// ); + /// + /// var implicitPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// implicitStandardOutput + /// ); + /// + /// // Example settings customization + /// var settingsCustomizationExitCode = Command( + /// new []{ "dpi", "dpi.exe"}, + /// out var settingsCustomizationStandardOutput, + /// $"nuget --output JSON analyze", + /// settingsCustomization: settings => settings + /// .WithToolName("DPI") + /// .WithArgumentCustomization(args => args.AppendSwitchQuoted("--buildversion", " ", "1.0.0")) + /// ); + /// + /// var settingsCustomizationPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// settingsCustomizationStandardOutput + /// ); + /// + /// // Record used in example above + /// public record DPIPackageReference( + /// [property: JsonPropertyName("source")] + /// string Source, + /// [property: JsonPropertyName("sourceType")] + /// string SourceType, + /// [property: JsonPropertyName("packageId")] + /// string PackageId, + /// [property: JsonPropertyName("version")] + /// string Version + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + ICollection toolExecutableNames, + out string standardOutput, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + out standardOutput, + arguments); + + /// + /// Executes a generic tool/process based on arguments, settings and redirects standard output. + /// + /// The context. + /// The settings. + /// The standard output. + /// The optional arguments. + /// Thrown if or is null. + /// The exit code. + /// + /// + /// using System.Text.Json.Serialization; + /// using System.Text.Json; + /// #tool dotnet:?package=DPI&version=2022.8.21.54 + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = "DPI", + /// ToolExecutableNames = new []{ "dpi", "dpi.exe" }, + /// }; + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// settings, + /// out var standardOutput, + /// new ProcessArgumentBuilder() + /// .Append("nuget") + /// .AppendQuoted(Context.Environment.WorkingDirectory.FullPath) + /// .AppendSwitch("--output", " ", "JSON") + /// .Append("analyze") + /// ); + /// + /// var packageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// standardOutput + /// ); + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// settings, + /// out var implicitStandardOutput, + /// $"nuget --output JSON analyze" + /// ); + /// + /// var implicitPackageReferences = JsonSerializer.Deserialize<DPIPackageReference[]>( + /// implicitStandardOutput + /// ); + /// + /// // Record used in example above + /// public record DPIPackageReference( + /// [property: JsonPropertyName("source")] + /// string Source, + /// [property: JsonPropertyName("sourceType")] + /// string SourceType, + /// [property: JsonPropertyName("packageId")] + /// string PackageId, + /// [property: JsonPropertyName("version")] + /// string Version + /// ); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + CommandSettings settings, + out string standardOutput, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + return runner.RunCommand(arguments, out standardOutput); + } + + /// + /// Executes a generic tool/process based on arguments, settings, redirects standard output and standard error. + /// + /// The context. + /// The tool executable names. + /// The standard output. + /// The standard error. + /// The optional arguments. + /// The expected exit code (default 0). + /// The optional settings customization (default null). + /// Thrown if or is null. + /// The exit code. + /// + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var standardOutput, + /// out var standardError, + /// new ProcessArgumentBuilder() + /// .Append("tool"), + /// expectedExitCode:1 + /// ); + /// + /// Verbose("Exit code: {0}", exitCode); + /// Information("Output: {0}", standardOutput); + /// Error("Error: {0}", standardError); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var implicitStandardOutput, + /// out var implicitStandardError, + /// "tool", + /// expectedExitCode:1 + /// ); + /// + /// Verbose("Exit code: {0}", implicitExitCode); + /// Information("Output: {0}", implicitStandardOutput); + /// Error("Error: {0}", implicitStandardError); + /// + /// + /// // Example settings customization + /// var settingsCustomizationExitCode = Command( + /// new []{ "dotnet", "dotnet.exe" }, + /// out var settingsCustomizationStandardOutput, + /// out var settingsCustomizationStandardError, + /// settingsCustomization: settings => settings + /// .WithToolName(".NET Tool") + /// .WithArgumentCustomization(args => args.Append("tool")) + /// .WithExpectedExitCode(1) + /// ); + /// + /// Verbose("Exit code: {0}", settingsCustomizationExitCode); + /// Information("Output: {0}", settingsCustomizationStandardOutput); + /// Error("Error: {0}", settingsCustomizationStandardError); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + ICollection toolExecutableNames, + out string standardOutput, + out string standardError, + ProcessArgumentBuilder arguments = null, + int expectedExitCode = 0, + Func settingsCustomization = null) + => context.Command( + GetSettings(toolExecutableNames, expectedExitCode, settingsCustomization), + out standardOutput, + out standardError, + arguments); + + /// + /// Executes a generic tool/process based on arguments and settings. + /// + /// The context. + /// The settings. + /// The standard output. + /// The standard error. + /// The optional arguments. + /// Thrown if or is null. + /// The exit code. + /// + /// + /// // Reusable tools settings i.e. created in setup. + /// var settings = new CommandSettings { + /// ToolName = ".NET CLI", + /// ToolExecutableNames = new []{ "dotnet", "dotnet.exe" }, + /// }.WithExpectedExitCode(1); + /// + /// // Example with ProcessArgumentBuilder + /// var exitCode = Command( + /// settings, + /// out var standardOutput, + /// out var standardError, + /// new ProcessArgumentBuilder() + /// .Append("tool") + /// ); + /// + /// Verbose("Exit code: {0}", exitCode); + /// Information("Output: {0}", standardOutput); + /// Error("Error: {0}", standardError); + /// + /// + /// // Example with implicit ProcessArgumentBuilder + /// var implicitExitCode = Command( + /// settings, + /// out var implicitStandardOutput, + /// out var implicitStandardError, + /// "tool" + /// ); + /// + /// Verbose("Exit code: {0}", implicitExitCode); + /// Information("Output: {0}", implicitStandardOutput); + /// Error("Error: {0}", implicitStandardError); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Command")] + public static int Command( + this ICakeContext context, + CommandSettings settings, + out string standardOutput, + out string standardError, + ProcessArgumentBuilder arguments = null) + { + var runner = GetRunner(context, settings, ref arguments); + + return runner.RunCommand(arguments, out standardOutput, out standardError); + } + + private static CommandSettings GetSettings( + ICollection toolExecutableNames, + int expectedExitCode, + Func settingsCustomization) + { + if (toolExecutableNames is null || toolExecutableNames.Count < 1) + { + throw new ArgumentNullException(nameof(toolExecutableNames)); + } + + var settings = new CommandSettings + { + ToolName = toolExecutableNames.First(), + ToolExecutableNames = toolExecutableNames, + HandleExitCode = exitCode => exitCode == expectedExitCode + }; + + return settingsCustomization?.Invoke(settings) ?? settings; + } + + private static CommandRunner GetRunner(ICakeContext context, CommandSettings settings, ref ProcessArgumentBuilder arguments) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(settings); + + arguments ??= new ProcessArgumentBuilder(); + + var runner = new CommandRunner( + settings, + context.FileSystem, + context.Environment, + context.ProcessRunner, + context.Tools); + + return runner; + } + } +} diff --git a/src/Cake.Common/Tools/Command/CommandRunner.cs b/src/Cake.Common/Tools/Command/CommandRunner.cs new file mode 100644 index 0000000000..d329357b0c --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandRunner.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Command +{ + /// + /// The generic command runner. + /// + public sealed class CommandRunner : Tool + { + private CommandSettings Settings { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// The file system. + /// The environment. + /// The process runner. + /// The globber. + public CommandRunner( + CommandSettings settings, + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + /// + protected override IEnumerable GetToolExecutableNames() + => Settings.ToolExecutableNames; + + /// + protected override string GetToolName() + => Settings.ToolName; + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + public void RunCommand(ProcessArgumentBuilder arguments) + => RunCommand(arguments, null, null); + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The standard output. + /// The exit code. + public int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput) + => RunCommand( + arguments, + out standardOutput, + out _, + new ProcessSettings + { + RedirectStandardOutput = true + }); + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The standard output. + /// The standard error output. + /// The exit code. + public int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput, out string standardError) + => RunCommand( + arguments, + out standardOutput, + out standardError, + new ProcessSettings + { + RedirectStandardOutput = true, + RedirectStandardError = true + }); + + private int RunCommand(ProcessArgumentBuilder arguments, out string standardOutput, out string standardError, ProcessSettings processSettings) + { + IEnumerable + standardOutputResult = null, standardErrorResult = null; + int returnCode = -1; + + RunCommand(arguments, processSettings, + process => + { + standardOutputResult = processSettings.RedirectStandardOutput ? process.GetStandardOutput() : Array.Empty(); + standardErrorResult = processSettings.RedirectStandardError ? process.GetStandardError() : Array.Empty(); + returnCode = process.GetExitCode(); + }); + + standardOutput = string.Join(Environment.NewLine, standardOutputResult); + standardError = string.Join(Environment.NewLine, standardErrorResult); + + return returnCode; + } + + /// + /// Runs the command using the specified settings. + /// + /// The arguments. + /// The process settings. + /// If specified called after process exit. + private void RunCommand(ProcessArgumentBuilder arguments, ProcessSettings processSettings, Action postAction) + { + ArgumentNullException.ThrowIfNull(arguments); + + if (string.IsNullOrWhiteSpace(Settings.ToolName)) + { + throw new ArgumentNullException(nameof(Settings.ToolName)); + } + + if (Settings.ToolExecutableNames == null || !Settings.ToolExecutableNames.Any()) + { + throw new ArgumentNullException(nameof(Settings.ToolExecutableNames)); + } + + Run(Settings, arguments, processSettings, postAction); + } + } +} diff --git a/src/Cake.Common/Tools/Command/CommandSettings.cs b/src/Cake.Common/Tools/Command/CommandSettings.cs new file mode 100644 index 0000000000..5d9129a06f --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandSettings.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains settings used by . + /// + public class CommandSettings : ToolSettings + { + /// + /// Gets or sets the name of the tool. + /// + public virtual string ToolName { get; set; } + + /// + /// Gets or sets the tool executable names. + /// + public virtual ICollection ToolExecutableNames { get; set; } = Array.Empty(); + } +} diff --git a/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs b/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs new file mode 100644 index 0000000000..f28046bdb2 --- /dev/null +++ b/src/Cake.Common/Tools/Command/CommandSettingsExtensions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.Command +{ + /// + /// Contains functionality related to . + /// + public static class CommandSettingsExtensions + { + /// + /// Sets the tool executable names. + /// + /// The tools settings. + /// The tool executable names. + /// The ToolSettings type. + /// The tools settings. + public static T WithExecutableNames(this T toolSettings, params string[] toolExecutableNames) + where T : CommandSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolExecutableNames = toolExecutableNames); + + /// + /// Sets the tool name. + /// + /// The tools settings. + /// The tool name. + /// The ToolSettings type. + /// The tools settings. + public static T WithToolName(this T toolSettings, string toolName) + where T : CommandSettings + => toolSettings.WithToolSettings(toolSettings => toolSettings.ToolName = toolName); + } +} diff --git a/src/Cake.Common/Tools/DNU/Build/DNUBuildSettings.cs b/src/Cake.Common/Tools/DNU/Build/DNUBuildSettings.cs deleted file mode 100644 index f77fb69ec0..0000000000 --- a/src/Cake.Common/Tools/DNU/Build/DNUBuildSettings.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Build -{ - /// - /// Contains settings used by . - /// - public class DNUBuildSettings : ToolSettings - { - /// - /// Gets or sets a list of frameworks to use. - /// - public ICollection Frameworks { get; set; } - - /// - /// Gets or sets a list of configurations to use. - /// - public ICollection Configurations { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets a value indicating whether to not show output such as dependencies in use. - /// - public bool Quiet { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DNU/Build/DNUBuilder.cs b/src/Cake.Common/Tools/DNU/Build/DNUBuilder.cs deleted file mode 100644 index 9559be19a7..0000000000 --- a/src/Cake.Common/Tools/DNU/Build/DNUBuilder.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Build -{ - /// - /// DNU project builder. - /// - public sealed class DNUBuilder : DNUTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DNUBuilder( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) - : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Build the project using the specified path and settings. - /// - /// The target file path. - /// The settings. - public void Build(string path, DNUBuildSettings settings) - { - if (path == null) - { - throw new ArgumentNullException("path"); - } - - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(path, settings)); - } - - private ProcessArgumentBuilder GetArguments(string path, DNUBuildSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - builder.Append("build"); - - // Specific path? - if (path != null) - { - builder.AppendQuoted(path); - } - - // List of frameworks - if (settings.Frameworks != null && settings.Frameworks.Count > 0) - { - foreach (var framework in settings.Frameworks) - { - builder.Append("--framework"); - builder.AppendQuoted(framework); - } - } - - // List of configurations - if (settings.Configurations != null && settings.Configurations.Count > 0) - { - foreach (var configuration in settings.Configurations) - { - builder.Append("--configuration"); - builder.AppendQuoted(configuration); - } - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--out"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Quiet? - if (settings.Quiet) - { - builder.Append("--quiet"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DNU/DNUAliases.cs b/src/Cake.Common/Tools/DNU/DNUAliases.cs deleted file mode 100644 index 80af9819a9..0000000000 --- a/src/Cake.Common/Tools/DNU/DNUAliases.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Common.Tools.DNU.Build; -using Cake.Common.Tools.DNU.Pack; -using Cake.Common.Tools.DNU.Restore; -using Cake.Common.Tools.DotNetCore; -using Cake.Core; -using Cake.Core.Annotations; -using Cake.Core.IO; - -namespace Cake.Common.Tools.DNU -{ - /// - /// Contains functionality for working with the DNU Utility. - /// These aliases have been marked as Obsolete. Use the instead. - /// - [CakeAliasCategory("DNU")] - public static class DNUAliases - { - /// - /// Restore all NuGet Packages. - /// - /// The context. - /// - /// - /// DNURestore(); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Restore")] - [Obsolete("Please use DotNetCoreRestore() instead.", false)] - public static void DNURestore(this ICakeContext context) - { - context.DNURestore(null, null); - } - - /// - /// Restore all NuGet Packages in the specified path. - /// - /// The context. - /// The file to restore. - /// - /// - /// var projects = GetFiles("./src/**/project.json"); - /// foreach(var project in projects) - /// { - /// DNURestore(project); - /// } - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Restore")] - [Obsolete("Please use DotNetCoreRestore(string) instead.", false)] - public static void DNURestore(this ICakeContext context, FilePath filePath) - { - context.DNURestore(filePath, null); - } - - /// - /// Restore all NuGet Packages with the settings. - /// - /// The context. - /// The settings. - /// - /// - /// var settings = new DNURestoreSettings - /// { - /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, - /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, - /// Proxy = "exampleproxy", - /// NoCache = true, - /// Packages = "./packages", - /// IgnoreFailedSources = true, - /// Quiet = true, - /// Parallel = true, - /// Locked = DNULocked.Lock, - /// Runtimes = new[] {"runtime1", "runtime2"} - /// }; - /// - /// DNURestore(settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Restore")] - [Obsolete("Please use DotNetCoreRestore(DotNetCoreRestoreSettings) instead.", false)] - public static void DNURestore(this ICakeContext context, DNURestoreSettings settings) - { - context.DNURestore(null, settings); - } - - /// - /// Restore all NuGet Packages in the specified path with settings. - /// - /// The context. - /// The file to restore. - /// The settings. - /// - /// - /// var settings = new DNURestoreSettings - /// { - /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, - /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, - /// Proxy = "exampleproxy", - /// NoCache = true, - /// Packages = "./packages", - /// IgnoreFailedSources = true, - /// Quiet = true, - /// Parallel = true, - /// Locked = DNULocked.Lock, - /// Runtimes = new[] {"runtime1", "runtime2"} - /// }; - /// - /// var projects = GetFiles("./src/**/project.json"); - /// foreach(var project in projects) - /// { - /// DNURestore(project, settings); - /// } - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Restore")] - [Obsolete("Please use DotNetCoreRestore(string, DotNetCoreRestoreSettings) instead.", false)] - public static void DNURestore(this ICakeContext context, FilePath filePath, DNURestoreSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DNURestoreSettings(); - } - - var restorer = new DNURestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - restorer.Restore(filePath, settings); - } - - /// - /// Build all projects. - /// - /// The context. - /// The projects path. - /// - /// - /// DNUBuild("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Build")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Build")] - [Obsolete("Please use DotNetCoreBuild(string) instead.", false)] - public static void DNUBuild(this ICakeContext context, string path) - { - context.DNUBuild(path, null); - } - - /// - /// Build all projects. - /// - /// The context. - /// The projects path. - /// The settings. - /// - /// - /// var settings = new DNUBuildSettings - /// { - /// Frameworks = new[] { "dnx451", "dnxcore50" }, - /// Configurations = new[] { "Debug", "Release" }, - /// OutputDirectory = "./artifacts/", - /// Quiet = true - /// }; - /// - /// DNUBuild("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Build")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Build")] - [Obsolete("Please use DotNetCoreBuild(string, DotNetCoreBuildSettings) instead.", false)] - public static void DNUBuild(this ICakeContext context, string path, DNUBuildSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DNUBuildSettings(); - } - - var restorer = new DNUBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - restorer.Build(path, settings); - } - - /// - /// Pack all projects. - /// - /// The context. - /// The projects path. - /// - /// - /// DNUPack("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Pack")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Pack")] - [Obsolete("Please use DotNetCorePack(string) instead.", false)] - public static void DNUPack(this ICakeContext context, string path) - { - context.DNUPack(path, null); - } - - /// - /// Pack all projects. - /// - /// The context. - /// The projects path. - /// The settings. - /// - /// - /// var settings = new DNUPackSettings - /// { - /// Frameworks = new[] { "dnx451", "dnxcore50" }, - /// Configurations = new[] { "Debug", "Release" }, - /// OutputDirectory = "./artifacts/", - /// Quiet = true - /// }; - /// - /// DNUPack("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Pack")] - [CakeNamespaceImport("Cake.Common.Tools.DNU.Pack")] - [Obsolete("Please use DotNetCorePack(string, DotNetCorePackSettings) instead.", false)] - public static void DNUPack(this ICakeContext context, string path, DNUPackSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DNUPackSettings(); - } - - var restorer = new DNUPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - restorer.Pack(path, settings); - } - } -} diff --git a/src/Cake.Common/Tools/DNU/DNUTool.cs b/src/Cake.Common/Tools/DNU/DNUTool.cs deleted file mode 100644 index c446b50721..0000000000 --- a/src/Cake.Common/Tools/DNU/DNUTool.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU -{ - /// - /// Base class for all DNU related tools - /// - /// The settings type - public abstract class DNUTool : Tool - where TSettings : ToolSettings - { - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - protected DNUTool( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return "DNU"; - } - - /// - /// Gets the possible names of the tool executable. - /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "dnu.cmd", "dnu" }; - } - } -} diff --git a/src/Cake.Common/Tools/DNU/Pack/DNUPackSettings.cs b/src/Cake.Common/Tools/DNU/Pack/DNUPackSettings.cs deleted file mode 100644 index 2a48985fd5..0000000000 --- a/src/Cake.Common/Tools/DNU/Pack/DNUPackSettings.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Pack -{ - /// - /// Contains settings used by . - /// - public class DNUPackSettings : ToolSettings - { - /// - /// Gets or sets a list of frameworks to use. - /// - public ICollection Frameworks { get; set; } - - /// - /// Gets or sets a list of configurations to use. - /// - public ICollection Configurations { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets a value indicating whether to not show output such as dependencies in use. - /// - public bool Quiet { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DNU/Pack/DNUPacker.cs b/src/Cake.Common/Tools/DNU/Pack/DNUPacker.cs deleted file mode 100644 index 5ae0271d57..0000000000 --- a/src/Cake.Common/Tools/DNU/Pack/DNUPacker.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Pack -{ - /// - /// DNU NuGet package packer. - /// - public sealed class DNUPacker : DNUTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DNUPacker( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) - : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Create NuGet packages using the specified path and settings. - /// - /// The target file path. - /// The settings. - public void Pack(string path, DNUPackSettings settings) - { - if (path == null) - { - throw new ArgumentNullException("path"); - } - - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(path, settings)); - } - - private ProcessArgumentBuilder GetArguments(string path, DNUPackSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - builder.Append("pack"); - - // Specific path? - if (path != null) - { - builder.AppendQuoted(path); - } - - // List of frameworks - if (settings.Frameworks != null && settings.Frameworks.Count > 0) - { - foreach (var framework in settings.Frameworks) - { - builder.Append("--framework"); - builder.AppendQuoted(framework); - } - } - - // List of configurations - if (settings.Configurations != null && settings.Configurations.Count > 0) - { - foreach (var configuration in settings.Configurations) - { - builder.Append("--configuration"); - builder.AppendQuoted(configuration); - } - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--out"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Quiet? - if (settings.Quiet) - { - builder.Append("--quiet"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DNU/Restore/DNULocked.cs b/src/Cake.Common/Tools/DNU/Restore/DNULocked.cs deleted file mode 100644 index a8ba18b41a..0000000000 --- a/src/Cake.Common/Tools/DNU/Restore/DNULocked.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.DNU.Restore -{ - /// - /// Represents DNU Locked - /// - public enum DNULocked - { - /// - /// Locked: Lock - /// - Lock, - - /// - /// Locked: Unlock - /// - Unlock - } -} diff --git a/src/Cake.Common/Tools/DNU/Restore/DNURestoreSettings.cs b/src/Cake.Common/Tools/DNU/Restore/DNURestoreSettings.cs deleted file mode 100644 index aa5c0ababe..0000000000 --- a/src/Cake.Common/Tools/DNU/Restore/DNURestoreSettings.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Restore -{ - /// - /// Contains settings used by . - /// - public class DNURestoreSettings : ToolSettings - { - /// - /// Gets or sets a list of packages sources to use for this command. - /// - public ICollection Sources { get; set; } - - /// - /// Gets or sets a list of packages sources to use as a fallback. - /// - public ICollection FallbackSources { get; set; } - - /// - /// Gets or sets the HTTP proxy to use when retrieving packages. - /// - public string Proxy { get; set; } - - /// - /// Gets or sets a value indicating whether to not use local cache. - /// - public bool NoCache { get; set; } - - /// - /// Gets or sets the path to restore packages. - /// - public DirectoryPath Packages { get; set; } - - /// - /// Gets or sets a value indicating whether to ignore failed remote sources if there are local packages meeting version requirements. - /// - public bool IgnoreFailedSources { get; set; } - - /// - /// Gets or sets a value indicating whether to not show output such as HTTP request/cache information. - /// - public bool Quiet { get; set; } - - /// - /// Gets or sets a value indicating whether to restores in parallel when more than one project.json is discovered. - /// - public bool Parallel { get; set; } - - /// - /// Gets or sets to creates dependencies file with locked property. Overwrites file if it exists. - /// - public DNULocked? Locked { get; set; } - - /// - /// Gets or sets a list of runtime identifiers to restore for. - /// - public ICollection Runtimes { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DNU/Restore/DNURestorer.cs b/src/Cake.Common/Tools/DNU/Restore/DNURestorer.cs deleted file mode 100644 index 35bd2f264c..0000000000 --- a/src/Cake.Common/Tools/DNU/Restore/DNURestorer.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DNU.Restore -{ - /// - /// DNU NuGet package restorer. - /// - public sealed class DNURestorer : DNUTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DNURestorer( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) - : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Restores NuGet packages using the specified path and settings. - /// - /// The project path. - /// The settings. - public void Restore(FilePath path, DNURestoreSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(path, settings)); - } - - private ProcessArgumentBuilder GetArguments(FilePath path, DNURestoreSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - builder.Append("restore"); - - // Specific project path? - if (path != null) - { - builder.AppendQuoted(path.MakeAbsolute(_environment).FullPath); - } - - // List of package sources - if (settings.Sources != null && settings.Sources.Count > 0) - { - foreach (var source in settings.Sources) - { - builder.Append("--source"); - builder.AppendQuoted(source); - } - } - - // List of fallback package sources - if (settings.FallbackSources != null && settings.FallbackSources.Count > 0) - { - foreach (var fallbacksource in settings.FallbackSources) - { - builder.Append("--fallbacksource"); - builder.AppendQuoted(fallbacksource); - } - } - - // Proxy - if (settings.Proxy != null) - { - builder.Append("--proxy"); - builder.AppendQuoted(settings.Proxy); - } - - // No Cache? - if (settings.NoCache) - { - builder.Append("--no-cache"); - } - - // Packages - if (settings.Packages != null) - { - builder.Append("--packages"); - builder.AppendQuoted(settings.Packages.MakeAbsolute(_environment).FullPath); - } - - // Ignore failed sources? - if (settings.IgnoreFailedSources) - { - builder.Append("--ignore-failed-sources"); - } - - // Quiet? - if (settings.Quiet) - { - builder.Append("--quiet"); - } - - // Parallel? - if (settings.Parallel) - { - builder.Append("--parallel"); - } - - // Locked? - if (settings.Locked.HasValue) - { - switch (settings.Locked) - { - case DNULocked.Lock: - builder.Append("--lock"); - break; - case DNULocked.Unlock: - builder.Append("--unlock"); - break; - } - } - - // List of runtime identifiers - if (settings.Runtimes != null && settings.Runtimes.Count > 0) - { - foreach (var runtime in settings.Runtimes) - { - builder.Append("--runtime"); - builder.AppendQuoted(runtime); - } - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyseSettings.cs b/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyseSettings.cs index 692f9bab27..9fdfeabad1 100644 --- a/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyseSettings.cs +++ b/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyseSettings.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.DotCover.Analyse { /// /// Contains settings used by . /// - public sealed class DotCoverAnalyseSettings : DotCoverSettings + public sealed class DotCoverAnalyseSettings : DotCoverCoverageSettings { /// /// Gets or sets the type of the report. @@ -15,4 +16,4 @@ public sealed class DotCoverAnalyseSettings : DotCoverSettings /// public DotCoverReportType ReportType { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyser.cs b/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyser.cs index e08604f0ee..e092db166f 100644 --- a/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyser.cs +++ b/src/Cake.Common/Tools/DotCover/Analyse/DotCoverAnalyser.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -11,7 +12,7 @@ namespace Cake.Common.Tools.DotCover.Analyse /// /// DotCover Analyser builder. /// - public sealed class DotCoverAnalyser : DotCoverTool + public sealed class DotCoverAnalyser : DotCoverCoverageTool { private readonly ICakeEnvironment _environment; @@ -43,48 +44,18 @@ public void Analyse(ICakeContext context, FilePath outputPath, DotCoverAnalyseSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (action == null) - { - throw new ArgumentNullException("action"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - // Run the tool using the interceptor. - var interceptor = InterceptAction(context, action); + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(settings); // Run the tool. - Run(settings, GetArguments(interceptor, settings, outputPath)); - } - - private static DotCoverContext InterceptAction( - ICakeContext context, - Action action) - { - var interceptor = new DotCoverContext(context); - action(interceptor); - - // Validate arguments. - if (interceptor.FilePath == null) - { - throw new CakeException("No tool was started."); - } - - return interceptor; + Run(settings, GetArguments(context, action, settings, outputPath)); } private ProcessArgumentBuilder GetArguments( - DotCoverContext context, + ICakeContext context, + Action action, DotCoverAnalyseSettings settings, FilePath outputPath) { @@ -92,19 +63,11 @@ private ProcessArgumentBuilder GetArguments( builder.Append("Analyse"); - // The target application to call. - builder.AppendSwitch("/TargetExecutable", "=", context.FilePath.FullPath.Quote()); + // Set configuration file if exists. + GetConfigurationFileArgument(settings).CopyTo(builder); - // The arguments to the target application. - if (context.Settings != null && context.Settings.Arguments != null) - { - var arguments = context.Settings.Arguments.Render(); - if (!string.IsNullOrWhiteSpace(arguments)) - { - arguments = arguments.Replace("\"", "\\\""); - builder.AppendSwitch("/TargetArguments", "=", arguments.Quote()); - } - } + // Get Target executable arguments + GetTargetArguments(context, action).CopyTo(builder); // Set the output file. outputPath = outputPath.MakeAbsolute(_environment); @@ -116,40 +79,13 @@ private ProcessArgumentBuilder GetArguments( builder.AppendSwitch("/ReportType", "=", settings.ReportType.ToString()); } - // TargetWorkingDir - if (settings.TargetWorkingDir != null) - { - builder.AppendSwitch("/TargetWorkingDir", "=", settings.TargetWorkingDir.MakeAbsolute(_environment).FullPath.Quote()); - } - - // Scope - if (settings.Scope.Count > 0) - { - var scope = string.Join(";", settings.Scope); - builder.AppendSwitch("/Scope", "=", scope.Quote()); - } + // Get Coverage arguments + GetCoverageArguments(settings).CopyTo(builder); - // Filters - if (settings.Filters.Count > 0) - { - var filters = string.Join(";", settings.Filters); - builder.AppendSwitch("/Filters", "=", filters.Quote()); - } - - // Filters - if (settings.AttributeFilters.Count > 0) - { - var attributeFilters = string.Join(";", settings.AttributeFilters); - builder.AppendSwitch("/AttributeFilters", "=", attributeFilters.Quote()); - } - - // DisableDefaultFilters - if (settings.DisableDefaultFilters) - { - builder.Append("/DisableDefaultFilters"); - } + // Get base arguments + GetArguments(settings).CopyTo(builder); return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs index 2c4b010182..eadc7ce291 100644 --- a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs @@ -1,12 +1,64 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + namespace Cake.Common.Tools.DotCover.Cover { /// /// Contains settings used by . /// - public sealed class DotCoverCoverSettings : DotCoverSettings + public sealed class DotCoverCoverSettings : DotCoverCoverageSettings { + /// + /// Gets or sets the path to save formatted JSON report. + /// This represents the --json-report-output option. + /// + public FilePath JsonReportOutput { get; set; } + + /// + /// Gets or sets the granularity for including covering tests in JSON reports. + /// This represents the --json-report-covering-tests-scope option. + /// + public DotCoverReportScope? JsonReportCoveringTestsScope { get; set; } + + /// + /// Gets or sets the path to save formatted XML report. + /// This represents the --xml-report-output option. + /// + public FilePath XmlReportOutput { get; set; } + + /// + /// Gets or sets the granularity for including covering tests in XML reports. + /// This represents the --xml-report-covering-tests-scope option. + /// + public DotCoverReportScope? XmlReportCoveringTestsScope { get; set; } + + /// + /// Gets or sets the directory for temporary files. + /// This represents the --temporary-directory option. + /// + public DirectoryPath TemporaryDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to control the coverage session using the profiler API. + /// This represents the --use-api option. + /// + public bool UseApi { get; set; } + + /// + /// Gets or sets a value indicating whether to disable loading of NGen images during coverage. + /// This represents the --no-ngen option. + /// + public bool NoNGen { get; set; } + + /// + /// Gets or sets a value indicating whether to use the legacy command syntax. + /// When true, uses old format like '/TargetExecutable="/path"'. + /// When false, uses new format like '--target-executable "/path"'. + /// Default is false (new format). + /// + public bool UseLegacySyntax { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs new file mode 100644 index 0000000000..f8b5263f6c --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotCover.Cover +{ + /// + /// Contains extensions for . + /// + public static class DotCoverCoverSettingsExtensions + { + /// + /// Sets the JSON report output path. + /// + /// The settings. + /// The JSON report output path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithJsonReportOutput(this DotCoverCoverSettings settings, FilePath outputPath) + { + ArgumentNullException.ThrowIfNull(settings); + settings.JsonReportOutput = outputPath; + return settings; + } + + /// + /// Sets the JSON report covering tests scope. + /// + /// The settings. + /// The granularity for including covering tests in JSON reports. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithJsonReportCoveringTestsScope(this DotCoverCoverSettings settings, DotCoverReportScope scope) + { + ArgumentNullException.ThrowIfNull(settings); + settings.JsonReportCoveringTestsScope = scope; + return settings; + } + + /// + /// Sets the XML report output path. + /// + /// The settings. + /// The XML report output path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithXmlReportOutput(this DotCoverCoverSettings settings, FilePath outputPath) + { + ArgumentNullException.ThrowIfNull(settings); + settings.XmlReportOutput = outputPath; + return settings; + } + + /// + /// Sets the XML report covering tests scope. + /// + /// The settings. + /// The granularity for including covering tests in XML reports. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithXmlReportCoveringTestsScope(this DotCoverCoverSettings settings, DotCoverReportScope scope) + { + ArgumentNullException.ThrowIfNull(settings); + settings.XmlReportCoveringTestsScope = scope; + return settings; + } + + /// + /// Sets the temporary directory for files. + /// + /// The settings. + /// The temporary directory path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithTemporaryDirectory(this DotCoverCoverSettings settings, DirectoryPath directory) + { + ArgumentNullException.ThrowIfNull(settings); + settings.TemporaryDirectory = directory; + return settings; + } + + /// + /// Enables control of the coverage session using the profiler API. + /// + /// The settings. + /// Whether to use the API. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithUseApi(this DotCoverCoverSettings settings, bool useApi = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.UseApi = useApi; + return settings; + } + + /// + /// Disables loading of NGen images during coverage. + /// + /// The settings. + /// Whether to disable NGen. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithNoNGen(this DotCoverCoverSettings settings, bool noNGen = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.NoNGen = noNGen; + return settings; + } + + /// + /// Configures whether to use legacy command syntax. + /// + /// The settings. + /// Whether to use legacy syntax. Default is false (new syntax). + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithLegacySyntax(this DotCoverCoverSettings settings, bool useLegacySyntax = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.UseLegacySyntax = useLegacySyntax; + return settings; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs index 8a16472214..c3d53ab657 100644 --- a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -11,7 +12,7 @@ namespace Cake.Common.Tools.DotCover.Cover /// /// DotCover Coverer builder. /// - public sealed class DotCoverCoverer : DotCoverTool + public sealed class DotCoverCoverer : DotCoverCoverageTool { private readonly ICakeEnvironment _environment; @@ -43,100 +44,156 @@ public void Cover(ICakeContext context, FilePath outputPath, DotCoverCoverSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (action == null) - { - throw new ArgumentNullException("action"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - // Run the tool using the interceptor. - var interceptor = InterceptAction(context, action); + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(settings); // Run the tool. - Run(settings, GetArguments(interceptor, settings, outputPath)); + Run(settings, GetArguments(context, action, settings, outputPath)); } - private static DotCoverContext InterceptAction( + private ProcessArgumentBuilder GetArguments( ICakeContext context, - Action action) + Action action, + DotCoverCoverSettings settings, + FilePath outputPath) { - var interceptor = new DotCoverContext(context); - action(interceptor); + var builder = new ProcessArgumentBuilder(); + + // Command name - always lowercase 'cover' for both formats + builder.Append("cover"); - // Validate arguments. - if (interceptor.FilePath == null) + // Set configuration file if exists. + GetConfigurationFileArgument(settings).CopyTo(builder); + + if (settings.UseLegacySyntax) { - throw new CakeException("No tool was started."); + // Use legacy format + GetTargetArguments(context, action).CopyTo(builder); + // Set the output file - legacy format + outputPath = outputPath.MakeAbsolute(_environment); + builder.AppendSwitch("/Output", "=", outputPath.FullPath.Quote()); + // Get Coverage arguments - legacy format + GetCoverageArguments(settings).CopyTo(builder); + // Get base arguments - legacy format + GetArguments(settings).CopyTo(builder); } + else + { + // Use new format + GetCoverTargetArguments(context, action).CopyTo(builder); + // Set the output file - new format + outputPath = outputPath.MakeAbsolute(_environment); + builder.AppendSwitch("--snapshot-output", outputPath.FullPath.Quote()); + // Get Coverage arguments - new format + GetCoverCoverageArguments(settings).CopyTo(builder); + // New report options (only available in new format) + if (settings.JsonReportOutput != null) + { + builder.AppendSwitch("--json-report-output", settings.JsonReportOutput.MakeAbsolute(_environment).FullPath.Quote()); + } - return interceptor; - } + if (settings.JsonReportCoveringTestsScope.HasValue) + { + builder.AppendSwitch("--json-report-covering-tests-scope", settings.JsonReportCoveringTestsScope.Value.ToString().ToLowerInvariant().Quote()); + } - private ProcessArgumentBuilder GetArguments( - DotCoverContext context, - DotCoverCoverSettings settings, - FilePath outputPath) - { - var builder = new ProcessArgumentBuilder(); + if (settings.XmlReportOutput != null) + { + builder.AppendSwitch("--xml-report-output", settings.XmlReportOutput.MakeAbsolute(_environment).FullPath.Quote()); + } - builder.Append("Cover"); + if (settings.XmlReportCoveringTestsScope.HasValue) + { + builder.AppendSwitch("--xml-report-covering-tests-scope", settings.XmlReportCoveringTestsScope.Value.ToString().ToLowerInvariant().Quote()); + } - // The target application to call. - builder.AppendSwitch("/TargetExecutable", "=", context.FilePath.FullPath.Quote()); + if (settings.TemporaryDirectory != null) + { + builder.AppendSwitch("--temporary-directory", settings.TemporaryDirectory.MakeAbsolute(_environment).FullPath.Quote()); + } - // The arguments to the target application. - if (context.Settings != null && context.Settings.Arguments != null) - { - var arguments = context.Settings.Arguments.Render(); - if (!string.IsNullOrWhiteSpace(arguments)) + if (settings.UseApi) + { + builder.Append("--use-api"); + } + + if (settings.NoNGen) { - arguments = arguments.Replace("\"", "\\\""); - builder.AppendSwitch("/TargetArguments", "=", arguments.Quote()); + builder.Append("--no-ngen"); } + + // Get base arguments - new format + GetCoverArguments(settings).CopyTo(builder); } - // Set the output file. - outputPath = outputPath.MakeAbsolute(_environment); - builder.AppendSwitch("/Output", "=", outputPath.FullPath.Quote()); + return builder; + } + + /// + /// Get arguments from coverage settings for Cover command (using new format). + /// + /// The settings. + /// The process arguments. + private ProcessArgumentBuilder GetCoverCoverageArguments(DotCoverCoverageSettings settings) + { + var builder = new ProcessArgumentBuilder(); - // TargetWorkingDir + // TargetWorkingDir - using new format for Cover command if (settings.TargetWorkingDir != null) { - builder.AppendSwitch("/TargetWorkingDir", "=", settings.TargetWorkingDir.MakeAbsolute(_environment).FullPath.Quote()); + builder.AppendSwitch("--target-working-directory", settings.TargetWorkingDir.MakeAbsolute(_environment).FullPath.Quote()); + } + + // New filtering options (only available in new format) + if (settings.ExcludeAssemblies.Count > 0) + { + var excludeAssemblies = string.Join(',', settings.ExcludeAssemblies); + builder.AppendSwitch("--exclude-assemblies", excludeAssemblies.Quote()); + } + + if (settings.ExcludeAttributes.Count > 0) + { + var excludeAttributes = string.Join(',', settings.ExcludeAttributes); + builder.AppendSwitch("--exclude-attributes", excludeAttributes.Quote()); + } + + if (settings.ExcludeProcesses.Count > 0) + { + var excludeProcesses = string.Join(',', settings.ExcludeProcesses); + builder.AppendSwitch("--exclude-processes", excludeProcesses.Quote()); } + // Legacy filtering options (maintain backward compatibility with old format) // Scope if (settings.Scope.Count > 0) { - var scope = string.Join(";", settings.Scope); + var scope = string.Join(';', settings.Scope); builder.AppendSwitch("/Scope", "=", scope.Quote()); } // Filters if (settings.Filters.Count > 0) { - var filters = string.Join(";", settings.Filters); + var filters = string.Join(';', settings.Filters); builder.AppendSwitch("/Filters", "=", filters.Quote()); } - // Filters + // AttributeFilters if (settings.AttributeFilters.Count > 0) { - var attributeFilters = string.Join(";", settings.AttributeFilters); + var attributeFilters = string.Join(';', settings.AttributeFilters); builder.AppendSwitch("/AttributeFilters", "=", attributeFilters.Quote()); } + // ProcessFilters + if (settings.ProcessFilters.Count > 0) + { + var processFilters = string.Join(';', settings.ProcessFilters); + builder.AppendSwitch("/ProcessFilters", "=", processFilters.Quote()); + } + // DisableDefaultFilters if (settings.DisableDefaultFilters) { @@ -145,5 +202,24 @@ private ProcessArgumentBuilder GetArguments( return builder; } + + /// + /// Get arguments from global settings for Cover command (using new format). + /// + /// The settings. + /// The process arguments. + private ProcessArgumentBuilder GetCoverArguments(DotCoverSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // LogFile - using new format for Cover command + if (settings.LogFile != null) + { + var logFilePath = settings.LogFile.MakeAbsolute(_environment); + builder.AppendSwitch("--log-file", logFilePath.FullPath.Quote()); + } + + return builder; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverAliases.cs b/src/Cake.Common/Tools/DotCover/DotCoverAliases.cs index 6e8064151d..cc0cab137f 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverAliases.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverAliases.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using Cake.Common.Tools.DotCover.Analyse; using Cake.Common.Tools.DotCover.Cover; +using Cake.Common.Tools.DotCover.Merge; +using Cake.Common.Tools.DotCover.Report; using Cake.Core; using Cake.Core.Annotations; using Cake.Core.IO; @@ -14,7 +18,7 @@ namespace Cake.Common.Tools.DotCover /// Contains functionality related to DotCover. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the appropriate settings class: + /// install from nuget.org, or specify the ToolPath within the appropriate settings class: /// /// #tool "nuget:?package=JetBrains.dotCover.CommandLineTools" /// @@ -54,10 +58,7 @@ public static void DotCoverAnalyse( FilePath outputFile, DotCoverAnalyseSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); if (settings == null) { @@ -104,10 +105,7 @@ public static void DotCoverCover( FilePath outputFile, DotCoverCoverSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); if (settings == null) { @@ -122,5 +120,119 @@ public static void DotCoverCover( // Run DotCover cover. coverer.Cover(context, action, outputFile, settings); } + + /// + /// Runs DotCover Report + /// for the specified action and settings. + /// + /// The context. + /// The DotCover coverage snapshot file name. + /// The DotCover output file. + /// The settings. + /// + /// + /// DotCoverReport(new FilePath("./result.dcvr"), + /// new FilePath("./result.html"), + /// new DotCoverReportSettings { + /// ReportType = DotCoverReportType.HTML + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Report")] + [CakeNamespaceImport("Cake.Common.Tools.DotCover.Report")] + public static void DotCoverReport( + this ICakeContext context, + FilePath sourceFile, + FilePath outputFile, + DotCoverReportSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotCoverReportSettings(); + } + + // Create the DotCover reporter. + var reporter = new DotCoverReporter( + context.FileSystem, context.Environment, + context.ProcessRunner, context.Tools); + + // Run DotCover report. + reporter.Report(sourceFile, outputFile, settings); + } + + /// + /// Runs DotCover Merge + /// for the specified action and settings. + /// + /// The context. + /// The list of DotCover coverage snapshot files. + /// The merged output file. + /// + /// + /// DotCoverMerge(new[] { + /// new FilePath("./result1.dcvr"), + /// new FilePath("./result2.dcvr") + /// }, + /// new FilePath("./merged.dcvr")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Merge")] + [CakeNamespaceImport("Cake.Common.Tools.DotCover.Merge")] + public static void DotCoverMerge( + this ICakeContext context, + IEnumerable sourceFiles, + FilePath outputFile) + { + DotCoverMerge(context, sourceFiles, outputFile, new DotCoverMergeSettings()); + } + + /// + /// Runs DotCover Merge + /// for the specified action and settings. + /// + /// The context. + /// The list of DotCover coverage snapshot files. + /// The merged output file. + /// The settings. + /// + /// + /// DotCoverMerge(new[] { + /// new FilePath("./result1.dcvr"), + /// new FilePath("./result2.dcvr") + /// }, + /// new FilePath("./merged.dcvr"), + /// new DotCoverMergeSettings { + /// LogFile = new FilePath("./log.txt") + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Merge")] + [CakeNamespaceImport("Cake.Common.Tools.DotCover.Merge")] + public static void DotCoverMerge( + this ICakeContext context, + IEnumerable sourceFiles, + FilePath outputFile, + DotCoverMergeSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotCoverMergeSettings(); + } + + // Create the DotCover merger. + var merger = new DotCoverMerger( + context.FileSystem, context.Environment, + context.ProcessRunner, context.Tools); + + // Run DotCover report. + merger.Merge(sourceFiles, outputFile, settings); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverContext.cs b/src/Cake.Common/Tools/DotCover/DotCoverContext.cs index e29c098e23..0f3cfd2e90 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverContext.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverContext.cs @@ -1,74 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.DotCover { - internal sealed class DotCoverContext : ICakeContext + internal sealed class DotCoverContext : CakeContextAdapter { - private readonly ICakeContext _context; - private readonly ICakeLog _log; private readonly DotCoverProcessRunner _runner; - public IFileSystem FileSystem - { - get { return _context.FileSystem; } - } - - public ICakeEnvironment Environment - { - get { return _context.Environment; } - } - - public IGlobber Globber - { - get { return _context.Globber; } - } + public override ICakeLog Log { get; } - public ICakeLog Log - { - get { return _log; } - } + public override IProcessRunner ProcessRunner => _runner; - public ICakeArguments Arguments - { - get { return _context.Arguments; } - } + public FilePath FilePath => _runner.FilePath; - public IProcessRunner ProcessRunner - { - get { return _runner; } - } - - public IRegistry Registry - { - get { return _context.Registry; } - } - - public IToolLocator Tools - { - get { return _context.Tools; } - } - - public FilePath FilePath - { - get { return _runner.FilePath; } - } - - public ProcessSettings Settings - { - get { return _runner.ProcessSettings; } - } + public ProcessSettings Settings => _runner.ProcessSettings; - public DotCoverContext(ICakeContext context) + public DotCoverContext(ICakeContext context) : base(context) { - _context = context; - _log = new NullLog(); + Log = new NullLog(); _runner = new DotCoverProcessRunner(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs new file mode 100644 index 0000000000..2e37d78c3a --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotCover +{ + /// + /// Contains settings used by . + /// + public abstract class DotCoverCoverageSettings : DotCoverSettings + { + private readonly HashSet _scope; + private readonly HashSet _filters; + private readonly HashSet _processFilters; + private readonly HashSet _attributeFilters; + private readonly HashSet _excludeAssemblies; + private readonly HashSet _excludeAttributes; + private readonly HashSet _excludeProcesses; + + /// + /// Gets or sets program working directory + /// This represents the --target-working-directory option for Cover command, /TargetWorkingDir for others. + /// + public DirectoryPath TargetWorkingDir { get; set; } + + /// + /// Gets the assemblies loaded in the specified scope into coverage results. + /// Ant-style patterns are supported here (e.g.ProjectFolder/**/*.dll) + /// This represents the /Scope option. + /// + public ISet Scope + { + get { return _scope; } + } + + /// + /// Gets the coverage filters using the following syntax: +:module=*;class=*;function=*; + /// Use -:myassembly to exclude an assembly from code coverage. + /// Asterisk wildcard (*) is supported here. + /// This represents the /Filters option. + /// + public ISet Filters + { + get { return _filters; } + } + + /// + /// Gets the attribute filters using the following syntax: filter1;filter2;... + /// Asterisk wildcard(*) is supported here + /// This represents the /AttributeFilters option. + /// + public ISet AttributeFilters + { + get { return _attributeFilters; } + } + + /// + /// Gets the coverage process filters using the following syntax: +:test.exe;program.exe*; + /// Use -:anexe to exclude an assembly from code coverage. + /// This represents the /ProcessFilters option. + /// + public ISet ProcessFilters + { + get { return _processFilters; } + } + + /// + /// Gets assembly names to exclude from analysis. Wildcards (*) allowed. + /// This represents the --exclude-assemblies option. + /// + public ISet ExcludeAssemblies + { + get { return _excludeAssemblies; } + } + + /// + /// Gets fully qualified attribute names to exclude from analysis. Wildcards (*) allowed. + /// Code marked with these attributes will be excluded from coverage. + /// This represents the --exclude-attributes option. + /// + public ISet ExcludeAttributes + { + get { return _excludeAttributes; } + } + + /// + /// Gets process names to ignore during analysis. Wildcards (*) allowed. + /// This represents the --exclude-processes option. + /// + public ISet ExcludeProcesses + { + get { return _excludeProcesses; } + } + + /// + /// Gets or sets a value indicating whether the default (automatically added) filters should be disabled + /// This represents the /DisableDefaultFilters option. + /// + public bool DisableDefaultFilters { get; set; } + + /// + /// Initializes a new instance of the class. + /// + protected DotCoverCoverageSettings() + { + _scope = new HashSet(StringComparer.Ordinal); + _filters = new HashSet(StringComparer.OrdinalIgnoreCase); + _attributeFilters = new HashSet(StringComparer.OrdinalIgnoreCase); + _processFilters = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeAttributes = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeProcesses = new HashSet(StringComparer.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs new file mode 100644 index 0000000000..b2b02b94b5 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Tools.DotCover +{ + /// + /// Contains extensions for . + /// + public static class DotCoverCoverageSettingsExtensions + { + /// + /// Adds the scope. + /// + /// The settings. + /// The scope. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithScope(this T settings, string scope) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.Scope.Add(scope); + return settings; + } + + /// + /// Adds the filter. + /// + /// The settings. + /// The filter. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithFilter(this T settings, string filter) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.Filters.Add(filter); + return settings; + } + + /// + /// Adds the attribute filter. + /// + /// The settings. + /// The filter. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithAttributeFilter(this T settings, string attributeFilter) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.AttributeFilters.Add(attributeFilter); + return settings; + } + + /// + /// Adds the filter. + /// + /// The settings. + /// The process filter. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithProcessFilter(this T settings, string filter) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ProcessFilters.Add(filter); + return settings; + } + + /// + /// Adds an assembly name to exclude from analysis. + /// + /// The settings. + /// The assembly name to exclude. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeAssembly(this T settings, string assemblyName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeAssemblies.Add(assemblyName); + return settings; + } + + /// + /// Adds a fully qualified attribute name to exclude from analysis. + /// + /// The settings. + /// The fully qualified attribute name. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeAttribute(this T settings, string attributeName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeAttributes.Add(attributeName); + return settings; + } + + /// + /// Adds a process name to ignore during analysis. + /// + /// The settings. + /// The process name to ignore. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeProcess(this T settings, string processName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeProcesses.Add(processName); + return settings; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs new file mode 100644 index 0000000000..030fe51e71 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotCover +{ + /// + /// DotCover Coverage tool. + /// + /// The settings type. + public abstract class DotCoverCoverageTool : DotCoverTool where TSettings : DotCoverCoverageSettings + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotCoverCoverageTool( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Get arguments from the target executable for Cover command (using new format). + /// + /// The context. + /// The action to run DotCover for. + /// The process arguments. + protected ProcessArgumentBuilder GetCoverTargetArguments(ICakeContext context, Action action) + { + // Run the tool using the interceptor. + var targetContext = InterceptAction(context, action); + + var builder = new ProcessArgumentBuilder(); + // The target application to call. + builder.AppendSwitch("--target-executable", targetContext.FilePath.FullPath.Quote()); + + // The arguments to the target application. + if (targetContext.Settings != null && targetContext.Settings.Arguments != null) + { + var arguments = targetContext.Settings.Arguments.Render(); + if (!string.IsNullOrWhiteSpace(arguments)) + { + arguments = arguments.Replace("\"", "\\\""); + builder.AppendSwitch("--target-arguments", arguments.Quote()); + } + } + + return builder; + } + + /// + /// Get arguments from the target executable. + /// + /// The context. + /// The action to run DotCover for. + /// The process arguments. + protected ProcessArgumentBuilder GetTargetArguments(ICakeContext context, Action action) + { + // Run the tool using the interceptor. + var targetContext = InterceptAction(context, action); + + var builder = new ProcessArgumentBuilder(); + + // The target application to call. + builder.AppendSwitch("/TargetExecutable", "=", targetContext.FilePath.FullPath.Quote()); + + // The arguments to the target application. + if (targetContext.Settings != null && targetContext.Settings.Arguments != null) + { + var arguments = targetContext.Settings.Arguments.Render(); + if (!string.IsNullOrWhiteSpace(arguments)) + { + arguments = arguments.Replace("\"", "\\\""); + builder.AppendSwitch("/TargetArguments", "=", arguments.Quote()); + } + } + + return builder; + } + + /// + /// Get arguments from coverage settings. + /// + /// The settings. + /// The process arguments. + protected ProcessArgumentBuilder GetCoverageArguments(DotCoverCoverageSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // TargetWorkingDir + if (settings.TargetWorkingDir != null) + { + builder.AppendSwitch("/TargetWorkingDir", "=", settings.TargetWorkingDir.MakeAbsolute(_environment).FullPath.Quote()); + } + + // Scope + if (settings.Scope.Count > 0) + { + var scope = string.Join(';', settings.Scope); + builder.AppendSwitch("/Scope", "=", scope.Quote()); + } + + // Filters + if (settings.Filters.Count > 0) + { + var filters = string.Join(';', settings.Filters); + builder.AppendSwitch("/Filters", "=", filters.Quote()); + } + + // AttributeFilters + if (settings.AttributeFilters.Count > 0) + { + var attributeFilters = string.Join(';', settings.AttributeFilters); + builder.AppendSwitch("/AttributeFilters", "=", attributeFilters.Quote()); + } + + // ProcessFilters + if (settings.ProcessFilters.Count > 0) + { + var processFilters = string.Join(';', settings.ProcessFilters); + builder.AppendSwitch("/ProcessFilters", "=", processFilters.Quote()); + } + + // DisableDefaultFilters + if (settings.DisableDefaultFilters) + { + builder.Append("/DisableDefaultFilters"); + } + + return builder; + } + + private static DotCoverContext InterceptAction( + ICakeContext context, + Action action) + { + var interceptor = new DotCoverContext(context); + action(interceptor); + + // Validate arguments. + if (interceptor.FilePath == null) + { + throw new CakeException("No tool was started."); + } + + return interceptor; + } + } +} diff --git a/src/Cake.Common/Tools/DotCover/DotCoverProcessRunner.cs b/src/Cake.Common/Tools/DotCover/DotCoverProcessRunner.cs index e3dafc0dea..ed350a5521 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverProcessRunner.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverProcessRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Core.IO; @@ -33,6 +34,11 @@ public int GetExitCode() return 0; } + public IEnumerable GetStandardError() + { + return Enumerable.Empty(); + } + public IEnumerable GetStandardOutput() { return Enumerable.Empty(); @@ -50,4 +56,4 @@ public IProcess Start(FilePath filePath, ProcessSettings settings) return new InterceptedProcess(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs b/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs new file mode 100644 index 0000000000..f7753a2446 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotCover +{ + /// + /// Represents the granularity for including covering tests in reports. + /// + public enum DotCoverReportScope + { + /// + /// No covering tests included. + /// + None, + + /// + /// Include covering tests at assembly level. + /// + Assembly, + + /// + /// Include covering tests at type level. + /// + Type, + + /// + /// Include covering tests at method level. + /// + Method, + + /// + /// Include covering tests at statement level. + /// + Statement + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverReportType.cs b/src/Cake.Common/Tools/DotCover/DotCoverReportType.cs index 6fb0922745..8f16ca8d04 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverReportType.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverReportType.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Diagnostics.CodeAnalysis; namespace Cake.Common.Tools.DotCover { /// - /// Represents DotCover ReportType + /// Represents DotCover ReportType. /// [SuppressMessage("ReSharper", "InconsistentNaming")] public enum DotCoverReportType @@ -36,4 +37,4 @@ public enum DotCoverReportType /// NDependXML, } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverSettings.cs b/src/Cake.Common/Tools/DotCover/DotCoverSettings.cs index 795c20101f..c1e67be183 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverSettings.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverSettings.cs @@ -1,8 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; + using Cake.Core.IO; using Cake.Core.Tooling; @@ -13,61 +12,17 @@ namespace Cake.Common.Tools.DotCover /// public abstract class DotCoverSettings : ToolSettings { - private readonly HashSet _scope; - private readonly HashSet _filters; - private readonly HashSet _attributeFilters; - - /// - /// Gets or sets program working directory - /// This represents the /TargetWorkingDir option. - /// - public DirectoryPath TargetWorkingDir { get; set; } - - /// - /// Gets the assemblies loaded in the specified scope into coverage results. - /// Ant-style patterns are supported here (e.g.ProjectFolder/**/*.dll) - /// This represents the /Scope option. - /// - public ISet Scope - { - get { return _scope; } - } - - /// - /// Gets the coverage filters using the following syntax: +:module=*;class=*;function=*; - /// Use -:myassembly to exclude an assembly from code coverage. - /// Asterisk wildcard (*) is supported here. - /// This represents the /Filters option. - /// - public ISet Filters - { - get { return _filters; } - } - - /// - /// Gets the attribute filters using the following syntax: filter1;filter2;... - /// Asterisk wildcard(*) is supported here - /// This represents the /AttributeFilters option. - /// - public ISet AttributeFilters - { - get { return _attributeFilters; } - } - /// - /// Gets or sets a value indicating whether the default (automatically added) filters should be disabled - /// This represents the /DisableDefaultFilters option. + /// Gets or sets a value that enables logging and specifies log file name + /// This represents the /LogFile option. /// - public bool DisableDefaultFilters { get; set; } + public FilePath LogFile { get; set; } /// - /// Initializes a new instance of the class. + /// Gets or sets a value that enables DotCover configuration file. + /// A configuration file is a reasonable alternative + /// to specifying all parameters in-line or having them in a batch file. /// - protected DotCoverSettings() - { - _scope = new HashSet(StringComparer.Ordinal); - _filters = new HashSet(StringComparer.OrdinalIgnoreCase); - _attributeFilters = new HashSet(StringComparer.OrdinalIgnoreCase); - } + public FilePath ConfigFile { get; set; } } } diff --git a/src/Cake.Common/Tools/DotCover/DotCoverSettingsExtensions.cs b/src/Cake.Common/Tools/DotCover/DotCoverSettingsExtensions.cs index c75bee1c1e..481100796c 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverSettingsExtensions.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverSettingsExtensions.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using Cake.Core.IO; namespace Cake.Common.Tools.DotCover { @@ -14,50 +16,15 @@ public static class DotCoverSettingsExtensions /// Adds the scope. /// /// The settings. - /// The scope. - /// The settings type, derived from - /// The same instance so that multiple calls can be chained. - public static T WithScope(this T settings, string scope) where T : DotCoverSettings - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.Scope.Add(scope); - return settings; - } - - /// - /// Adds the filter - /// - /// The settings. - /// The filter. - /// The settings type, derived from + /// The DotCover configuration file. + /// The settings type, derived from . /// The same instance so that multiple calls can be chained. - public static T WithFilter(this T settings, string filter) where T : DotCoverSettings + public static T WithConfigFile(this T settings, FilePath configFile) + where T : DotCoverSettings { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.Filters.Add(filter); - return settings; - } + ArgumentNullException.ThrowIfNull(settings); - /// - /// Adds the attribute filter - /// - /// The settings. - /// The filter. - /// The settings type, derived from - /// The same instance so that multiple calls can be chained. - public static T WithAttributeFilter(this T settings, string attributeFilter) where T : DotCoverSettings - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.AttributeFilters.Add(attributeFilter); + settings.ConfigFile = configFile; return settings; } } diff --git a/src/Cake.Common/Tools/DotCover/DotCoverTool.cs b/src/Cake.Common/Tools/DotCover/DotCoverTool.cs index cde8fceac5..1293f35111 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverTool.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverTool.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; @@ -9,12 +10,14 @@ namespace Cake.Common.Tools.DotCover { /// - /// Base class for all DotCover related tools + /// Base class for all DotCover related tools. /// - /// The settings type + /// The settings type. public abstract class DotCoverTool : Tool - where TSettings : ToolSettings + where TSettings : DotCoverSettings { + private readonly ICakeEnvironment _environment; + /// /// Initializes a new instance of the class. /// @@ -28,6 +31,7 @@ protected DotCoverTool( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { + _environment = environment; } /// @@ -45,7 +49,43 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "dotCover.exe" }; + return new[] { "dotCover.exe", "dotCover", "dotcover" }; + } + + /// + /// Get arguments from global settings. + /// + /// The settings. + /// The process arguments. + protected ProcessArgumentBuilder GetArguments(DotCoverSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // LogFile + if (settings.LogFile != null) + { + var logFilePath = settings.LogFile.MakeAbsolute(_environment); + builder.AppendSwitch("/LogFile", "=", logFilePath.FullPath.Quote()); + } + + return builder; + } + + /// + /// Get configuration full path from coverage settings. + /// + /// The settings. + /// The process arguments. + protected ProcessArgumentBuilder GetConfigurationFileArgument(DotCoverSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + if (settings.ConfigFile != null) + { + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Merge/DotCoverMergeSettings.cs b/src/Cake.Common/Tools/DotCover/Merge/DotCoverMergeSettings.cs new file mode 100644 index 0000000000..090de38cfa --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Merge/DotCoverMergeSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotCover.Merge +{ + /// + /// Contains settings used by . + /// + public sealed class DotCoverMergeSettings : DotCoverSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotCover/Merge/DotCoverMerger.cs b/src/Cake.Common/Tools/DotCover/Merge/DotCoverMerger.cs new file mode 100644 index 0000000000..36f05ede9e --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Merge/DotCoverMerger.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotCover.Merge +{ + /// + /// DotCover Merge merger. + /// + public sealed class DotCoverMerger : DotCoverTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotCoverMerger( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Runs DotCover Merge with the specified settings. + /// + /// The list of DotCover coverage snapshot files. + /// The merged output file. + /// The settings. + public void Merge( + IEnumerable sourceFiles, + FilePath outputFile, + DotCoverMergeSettings settings) + { + if (sourceFiles == null || !sourceFiles.Any()) + { + throw new ArgumentNullException("sourceFiles"); + } + ArgumentNullException.ThrowIfNull(outputFile); + ArgumentNullException.ThrowIfNull(settings); + + // Run the tool. + Run(settings, GetArguments(sourceFiles, outputFile, settings)); + } + + private ProcessArgumentBuilder GetArguments( + IEnumerable sourceFiles, + FilePath outputFile, + DotCoverMergeSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("Merge"); + + // Set configuration file if exists. + GetConfigurationFileArgument(settings).CopyTo(builder); + + // Set the Source files. + var source = string.Join(';', sourceFiles.Select(s => s.MakeAbsolute(_environment).FullPath)); + builder.AppendSwitch("/Source", "=", source.Quote()); + + // Set the Output file. + outputFile = outputFile.MakeAbsolute(_environment); + builder.AppendSwitch("/Output", "=", outputFile.FullPath.Quote()); + + // Get Global settings + GetArguments(settings).CopyTo(builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotCover/Report/DotCoverReportSettings.cs b/src/Cake.Common/Tools/DotCover/Report/DotCoverReportSettings.cs new file mode 100644 index 0000000000..0e04d50b72 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Report/DotCoverReportSettings.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotCover.Report +{ + /// + /// Contains settings used by . + /// + public sealed class DotCoverReportSettings : DotCoverSettings + { + /// + /// Gets or sets the type of the report. + /// This represents the /ReportType option. + /// The Default value is . + /// + public DotCoverReportType ReportType { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotCover/Report/DotCoverReporter.cs b/src/Cake.Common/Tools/DotCover/Report/DotCoverReporter.cs new file mode 100644 index 0000000000..870ed0a1a9 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Report/DotCoverReporter.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotCover.Report +{ + /// + /// DotCover Report reporter. + /// + public sealed class DotCoverReporter : DotCoverTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotCoverReporter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Runs DotCover Cover with the specified settings. + /// + /// The DotCover coverage snapshot file name. + /// The DotCover output file. + /// The settings. + public void Report( + FilePath sourceFile, + FilePath outputFile, + DotCoverReportSettings settings) + { + ArgumentNullException.ThrowIfNull(sourceFile); + ArgumentNullException.ThrowIfNull(outputFile); + ArgumentNullException.ThrowIfNull(settings); + + // Run the tool. + Run(settings, GetArguments(sourceFile, outputFile, settings)); + } + + private ProcessArgumentBuilder GetArguments( + FilePath sourceFile, + FilePath outputFile, + DotCoverReportSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("Report"); + + // Set configuration file if exists. + GetConfigurationFileArgument(settings).CopyTo(builder); + + // Set the Source file. + sourceFile = sourceFile.MakeAbsolute(_environment); + builder.AppendSwitch("/Source", "=", sourceFile.FullPath.Quote()); + + // Set the Output file. + outputFile = outputFile.MakeAbsolute(_environment); + builder.AppendSwitch("/Output", "=", outputFile.FullPath.Quote()); + + // Set the report type, don't include the default value + if (settings.ReportType != DotCoverReportType.XML) + { + builder.AppendSwitch("/ReportType", "=", settings.ReportType.ToString()); + } + + // Get Global settings + GetArguments(settings).CopyTo(builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Build/DotNetBuildSettings.cs b/src/Cake.Common/Tools/DotNet/Build/DotNetBuildSettings.cs new file mode 100644 index 0000000000..f817418c29 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Build/DotNetBuildSettings.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Build +{ + /// + /// Contains settings used by . + /// + public class DotNetBuildSettings : DotNetSettings + { + /// + /// Gets or sets the output directory. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the target runtime. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets the specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. + /// + public string VersionSuffix { get; set; } + + /// + /// Gets or sets a value indicating whether to mark the build as unsafe for incrementality. + /// This turns off incremental compilation and forces a clean rebuild of the project dependency graph. + /// + public bool NoIncremental { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore project to project references and only build the root project. + /// + public bool NoDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes build faster, but requires restore to be done before build is executed. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to display the startup banner or the copyright message. + /// + /// + /// Available since .NET Core 3.0 SDK. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets the specified NuGet package sources to use during the build. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Build/DotNetBuilder.cs b/src/Cake.Common/Tools/DotNet/Build/DotNetBuilder.cs new file mode 100644 index 0000000000..1b8f89399d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Build/DotNetBuilder.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Build +{ + /// + /// .NET project builder. + /// + public sealed class DotNetBuilder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetBuilder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Build the project using the specified path and settings. + /// + /// The target project path. + /// The settings. + public void Build(string project, DotNetBuildSettings settings) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetBuildSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("build"); + + // Specific path? + if (project != null) + { + builder.AppendQuoted(project); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.Append("--output"); + builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.Append("--framework"); + builder.Append(settings.Framework); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // Version suffix + if (!string.IsNullOrEmpty(settings.VersionSuffix)) + { + builder.Append("--version-suffix"); + builder.Append(settings.VersionSuffix); + } + + // No Incremental + if (settings.NoIncremental) + { + builder.Append("--no-incremental"); + } + + // No Dependencies + if (settings.NoDependencies) + { + builder.Append("--no-dependencies"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // No Logo + if (settings.NoLogo) + { + builder.Append("--nologo"); + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServer.cs b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServer.cs new file mode 100644 index 0000000000..6cff58b20d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServer.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.BuildServer +{ + /// + /// .NET Core project builder. + /// + public sealed class DotNetBuildServer : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetBuildServer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Build the project using the specified path and settings. + /// + /// The settings. + public void Shutdown(DotNetBuildServerShutdownSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetBuildServerShutdownSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("build-server"); + builder.Append("shutdown"); + + if (settings.MSBuild ?? false) + { + builder.Append("--msbuild"); + } + + if (settings.Razor ?? false) + { + builder.Append("--razor"); + } + + if (settings.VBCSCompiler ?? false) + { + builder.Append("--vbcscompiler"); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerSettings.cs b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerSettings.cs new file mode 100644 index 0000000000..f586090476 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.BuildServer +{ + /// + /// Contains settings used by . + /// + public abstract class DotNetBuildServerSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerShutdownSettings.cs b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerShutdownSettings.cs new file mode 100644 index 0000000000..8e67aab7a1 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/BuildServer/DotNetBuildServerShutdownSettings.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.BuildServer +{ + /// + /// Contains settings used by . + /// + public class DotNetBuildServerShutdownSettings : DotNetBuildServerSettings + { + /// + /// Gets or sets if to shuts down the MSBuild build server. + /// + public bool? MSBuild { get; set; } + + /// + /// Gets or sets if to shuts down the the Razor build server. + /// + public bool? Razor { get; set; } + + /// + /// Gets or sets if to shuts down the VB/C# compiler build server. + /// + public bool? VBCSCompiler { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Clean/DotNetCleanSettings.cs b/src/Cake.Common/Tools/DotNet/Clean/DotNetCleanSettings.cs new file mode 100644 index 0000000000..1b32775fb3 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Clean/DotNetCleanSettings.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Clean +{ + /// + /// Contains settings used by . + /// + public class DotNetCleanSettings : DotNetSettings + { + /// + /// Gets or sets the output directory. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets the specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets the target runtime. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets a value indicating whether to display the startup banner or the copyright message. + /// + /// + /// Available since .NET Core 3.0 SDK. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Clean/DotNetCleaner.cs b/src/Cake.Common/Tools/DotNet/Clean/DotNetCleaner.cs new file mode 100644 index 0000000000..1f6d21bb7e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Clean/DotNetCleaner.cs @@ -0,0 +1,102 @@ +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Clean +{ + /// + /// .NET Core project cleaner. + /// + public sealed class DotNetCleaner : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetCleaner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Cleans the project's output using the specified path and settings. + /// + /// The target project path. + /// The settings. + public void Clean(string project, DotNetCleanSettings settings) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetCleanSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("clean"); + + // Specific path? + if (project != null) + { + builder.AppendQuoted(project); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.Append("--output"); + builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.Append("--framework"); + builder.Append(settings.Framework); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // No Logo + if (settings.NoLogo) + { + builder.Append("--nologo"); + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Build.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Build.cs new file mode 100644 index 0000000000..8d965d8e33 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Build.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Build; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Build all projects. + /// + /// The context. + /// The projects path. + /// + /// + /// DotNetBuild("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Build")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Build")] + public static void DotNetBuild(this ICakeContext context, string project) + { + context.DotNetBuild(project, null); + } + + /// + /// Build all projects. + /// + /// The context. + /// The projects path. + /// The settings. + /// + /// + /// var settings = new DotNetBuildSettings + /// { + /// Framework = "netcoreapp2.0", + /// Configuration = "Debug", + /// OutputDirectory = "./artifacts/" + /// }; + /// + /// DotNetBuild("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Build")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Build")] + public static void DotNetBuild(this ICakeContext context, string project, DotNetBuildSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetBuildSettings(); + } + + var builder = new DotNetBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + builder.Build(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.BuildServer.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.BuildServer.cs new file mode 100644 index 0000000000..faca8bb438 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.BuildServer.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.BuildServer; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Shuts down build servers that are started from dotnet. + /// + /// The context. + /// + /// + /// DotNetBuildServerShutdown(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Build Server")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.BuildServer")] + public static void DotNetBuildServerShutdown(this ICakeContext context) + { + context.DotNetBuildServerShutdown(null); + } + + /// + /// Shuts down build servers that are started from dotnet. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetBuildServerShutdownSettings + /// { + /// MSBuild = true + /// }; + /// + /// DotNetBuildServerShutdown(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Build Server")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.BuildServer")] + public static void DotNetBuildServerShutdown(this ICakeContext context, DotNetBuildServerShutdownSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var buildServer = new DotNetBuildServer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + + buildServer.Shutdown(settings ?? new DotNetBuildServerShutdownSettings()); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Clean.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Clean.cs new file mode 100644 index 0000000000..09ddc3f9f0 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Clean.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Clean; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Cleans a project's output. + /// + /// The context. + /// The project's path. + /// + /// + /// DotNetClean("./src/project"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Clean")] + public static void DotNetClean(this ICakeContext context, string project) + { + context.DotNetClean(project, null); + } + + /// + /// Cleans a project's output. + /// + /// The context. + /// The projects path. + /// The settings. + /// + /// + /// var settings = new DotNetCleanSettings + /// { + /// Framework = "netcoreapp2.0", + /// Configuration = "Debug", + /// OutputDirectory = "./artifacts/" + /// }; + /// + /// DotNetClean("./src/project", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Clean")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Clean")] + public static void DotNetClean(this ICakeContext context, string project, DotNetCleanSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetCleanSettings(); + } + + var cleaner = new DotNetCleaner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + cleaner.Clean(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Execute.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Execute.cs new file mode 100644 index 0000000000..26f8fbe8f3 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Execute.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Execute; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Execute an assembly. + /// + /// The context. + /// The assembly path. + /// + /// + /// DotNetExecute("./bin/Debug/app.dll"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Execute")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Execute")] + public static void DotNetExecute(this ICakeContext context, FilePath assemblyPath) + { + context.DotNetExecute(assemblyPath, null); + } + + /// + /// Execute an assembly with arguments in the specific path. + /// + /// The context. + /// The assembly path. + /// The arguments. + /// + /// + /// DotNetExecute("./bin/Debug/app.dll", "--arg"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Execute")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Execute")] + public static void DotNetExecute(this ICakeContext context, FilePath assemblyPath, ProcessArgumentBuilder arguments) + { + context.DotNetExecute(assemblyPath, arguments, null); + } + + /// + /// Execute an assembly with arguments in the specific path with settings. + /// + /// The context. + /// The assembly path. + /// The arguments. + /// The settings. + /// + /// + /// var settings = new DotNetExecuteSettings + /// { + /// FrameworkVersion = "1.0.3" + /// }; + /// + /// DotNetExecute("./bin/Debug/app.dll", "--arg", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Execute")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Execute")] + public static void DotNetExecute(this ICakeContext context, FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetExecuteSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(assemblyPath); + + if (settings is null) + { + settings = new DotNetExecuteSettings(); + } + + var executor = new DotNetExecutor(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + executor.Execute(assemblyPath, arguments, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Format.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Format.cs new file mode 100644 index 0000000000..7e61f7a776 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Format.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Format; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Formats code to match editorconfig settings. + /// + /// The context. + /// The project or solution path. + /// + /// + /// DotNetFormat("./src/project"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormat(this ICakeContext context, string root) + { + context.DotNetFormat(root, null); + } + + /// + /// Formats code to match editorconfig settings. + /// + /// The context. + /// The project or solution path. + /// The settings. + /// + /// + /// var settings = new DotNetFormatSettings + /// { + /// NoRestore = true, + /// Include = "Program.cs Utility\Logging.cs", + /// Severity = DotNetFormatSeverity.Error + /// }; + /// + /// DotNetFormat("./src/project", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormat(this ICakeContext context, string root, DotNetFormatSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetFormatSettings(); + } + + var formatter = new DotNetFormatter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + formatter.Format(root, null, settings); + } + + /// + /// Format code to match editorconfig settings for whitespace. + /// + /// The context. + /// The project or solution path. + /// + /// + /// DotNetFormatWhitespace("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatWhitespace(this ICakeContext context, string root) + { + context.DotNetFormatWhitespace(root, null); + } + + /// + /// Format code to match editorconfig settings for whitespace. + /// + /// The context. + /// The project or solution path. + /// The settings. + /// + /// + /// var settings = new DotNetFormatSettings + /// { + /// NoRestore = true, + /// Include = "Program.cs Utility\Logging.cs" + /// }; + /// + /// DotNetFormatWhitespace("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatWhitespace(this ICakeContext context, string root, DotNetFormatSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetFormatSettings(); + } + + var formatter = new DotNetFormatter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + formatter.Format(root, "whitespace", settings); + } + + /// + /// Format code to match editorconfig settings for code style. + /// + /// The context. + /// The project or solution path. + /// + /// + /// DotNetFormatStyle("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatStyle(this ICakeContext context, string root) + { + context.DotNetFormatStyle(root, null); + } + + /// + /// Format code to match editorconfig settings for code style. + /// + /// The context. + /// The project or solution path. + /// The settings. + /// + /// + /// var settings = new DotNetFormatSettings + /// { + /// NoRestore = true, + /// Include = "Program.cs Utility\Logging.cs" + /// }; + /// + /// DotNetFormatStyle("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatStyle(this ICakeContext context, string root, DotNetFormatSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetFormatSettings(); + } + + var formatter = new DotNetFormatter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + formatter.Format(root, "style", settings); + } + + /// + /// Format code to match editorconfig settings for analyzers. + /// + /// The context. + /// The project or solution path. + /// + /// + /// DotNetFormatAnalyzers("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatAnalyzers(this ICakeContext context, string project) + { + context.DotNetFormatAnalyzers(project, null); + } + + /// + /// Format code to match editorconfig settings for analyzers. + /// + /// The context. + /// The project or solution path. + /// The settings. + /// + /// + /// var settings = new DotNetFormatSettings + /// { + /// NoRestore = true, + /// Include = "Program.cs Utility\Logging.cs" + /// }; + /// + /// DotNetFormatAnalyzers("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Format")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Format")] + public static void DotNetFormatAnalyzers(this ICakeContext context, string root, DotNetFormatSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetFormatSettings(); + } + + var formatter = new DotNetFormatter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + formatter.Format(root, "analyzers", settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.MSBuild.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.MSBuild.cs new file mode 100644 index 0000000000..448277fed9 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.MSBuild.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Builds the specified targets in a project file found in the current working directory. + /// + /// The context. + /// + /// + /// DotNetMSBuild(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context) + { + context.DotNetMSBuild((string)null, (DotNetMSBuildSettings)null); + } + + /// + /// Builds the specified targets in the project file. + /// + /// The context. + /// Project file or directory to search for project file. + /// + /// + /// DotNetMSBuild("foobar.proj"); + /// + /// + /// + /// If a directory is specified, MSBuild searches that directory for a project file. + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory) + { + if (string.IsNullOrWhiteSpace(projectOrDirectory)) + { + throw new ArgumentNullException(nameof(projectOrDirectory)); + } + + context.DotNetMSBuild(projectOrDirectory, null); + } + + /// + /// Builds the specified targets in a project file found in the current working directory. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSettings settings) + { + context.DotNetMSBuild(null, settings); + } + + /// + /// Builds the specified targets in a project file found in the current working directory. + /// + /// The context. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild(settings, + /// output => foreach (var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, DotNetMSBuildSettings settings, Action> standardOutputAction) + { + context.DotNetMSBuild(null, settings, standardOutputAction); + } + + /// + /// Builds the specified targets in the project file. + /// + /// The context. + /// Project file or directory to search for project file. + /// The settings. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild("foobar.proj", settings); + /// + /// + /// + /// If a project file is not specified, MSBuild searches the current working directory for a file that has a file + /// extension that ends in "proj" and uses that file. If a directory is specified, MSBuild searches that directory for a project file. + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory, DotNetMSBuildSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetMSBuildSettings(); + } + + var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + builder.Build(projectOrDirectory, settings, null); + } + + /// + /// Builds the specified targets in the project file. + /// + /// The context. + /// Project file or directory to search for project file. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var settings = new DotNetMSBuildSettings + /// { + /// NoLogo = true, + /// MaxCpuCount = -1 + /// }; + /// + /// DotNetMSBuild("foobar.proj", settings, + /// output => foreach (var line in output) outputBuilder.AppendLine(line)); + /// + /// + /// + /// If a project file is not specified, MSBuild searches the current working directory for a file that has a file + /// extension that ends in "proj" and uses that file. If a directory is specified, MSBuild searches that directory for a project file. + /// + [CakeMethodAlias] + [CakeAliasCategory("MSBuild")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.MSBuild")] + public static void DotNetMSBuild(this ICakeContext context, string projectOrDirectory, DotNetMSBuildSettings settings, Action> standardOutputAction) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetMSBuildSettings(); + } + + var builder = new DotNetMSBuildBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + builder.Build(projectOrDirectory, settings, standardOutputAction); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.NuGet.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.NuGet.cs new file mode 100644 index 0000000000..6610c4a145 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.NuGet.cs @@ -0,0 +1,464 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.NuGet.Delete; +using Cake.Common.Tools.DotNet.NuGet.Push; +using Cake.Common.Tools.DotNet.NuGet.Source; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Delete a NuGet Package from a server. + /// + /// The context. + /// + /// + /// DotNetNuGetDelete(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context) + { + context.DotNetNuGetDelete(null, null, null); + } + + /// + /// Deletes a package from nuget.org. + /// + /// The context. + /// Name of package to delete. + /// + /// + /// DotNetNuGetDelete("Microsoft.AspNetCore.Mvc"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context, string packageName) + { + context.DotNetNuGetDelete(packageName, null, null); + } + + /// + /// Deletes a specific version of a package from nuget.org. + /// + /// The context. + /// Name of package to delete. + /// Version of package to delete. + /// + /// + /// DotNetRestore("Microsoft.AspNetCore.Mvc", "1.0"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context, string packageName, string packageVersion) + { + context.DotNetNuGetDelete(packageName, packageVersion, null); + } + + /// + /// Deletes a package from a server. + /// + /// The context. + /// Name of package to delete. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetDeleteSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// NonInteractive = true + /// }; + /// + /// DotNetNuGetDelete("Microsoft.AspNetCore.Mvc", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context, string packageName, DotNetNuGetDeleteSettings settings) + { + context.DotNetNuGetDelete(packageName, null, settings); + } + + /// + /// Deletes a package from a server using the specified settings. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetDeleteSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// NonInteractive = true + /// }; + /// + /// DotNetNuGetDelete(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context, DotNetNuGetDeleteSettings settings) + { + context.DotNetNuGetDelete(null, null, settings); + } + + /// + /// Deletes a package from a server using the specified settings. + /// + /// The context. + /// Name of package to delete. + /// Version of package to delete. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetDeleteSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// NonInteractive = true + /// }; + /// + /// DotNetNuGetDelete("Microsoft.AspNetCore.Mvc", "1.0", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Delete")] + public static void DotNetNuGetDelete(this ICakeContext context, string packageName, string packageVersion, DotNetNuGetDeleteSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetNuGetDeleteSettings(); + } + + var nugetDeleter = new DotNetNuGetDeleter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + nugetDeleter.Delete(packageName, packageVersion, settings); + } + + /// + /// Pushes one or more packages to a server. + /// + /// The context. + /// of the package to push. + /// + /// + /// // With FilePath instance + /// var packageFilePath = GetFiles("*.nupkg").Single(); + /// DotNetNuGetPush(packageFilePath); + /// // With string parameter + /// DotNetNuGetPush("foo*.nupkg"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Push")] + public static void DotNetNuGetPush(this ICakeContext context, FilePath packageFilePath) + { + context.DotNetNuGetPush(packageFilePath, null); + } + + /// + /// Pushes one or more packages to a server using the specified settings. + /// + /// The context. + /// of the package to push. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetPushSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// ApiKey = "4003d786-cc37-4004-bfdf-c4f3e8ef9b3a" + /// }; + /// // With FilePath instance + /// var packageFilePath = GetFiles("foo*.nupkg").Single(); + /// DotNetNuGetPush(packageFilePath); + /// // With string parameter + /// DotNetNuGetPush("foo*.nupkg", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Push")] + public static void DotNetNuGetPush(this ICakeContext context, FilePath packageFilePath, DotNetNuGetPushSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetNuGetPushSettings(); + } + + var restorer = new DotNetNuGetPusher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + restorer.Push(packageFilePath?.FullPath, settings); + } + + /// + /// Add the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// UserName = "username", + /// Password = "password", + /// StorePasswordInClearText = true, + /// ValidAuthenticationTypes = "basic,negotiate" + /// }; + /// + /// DotNetNuGetAddSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetAddSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + sourcer.AddSource(name, settings); + } + + /// + /// Disable the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// + /// + /// DotNetNuGetDisableSource("example"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetDisableSource(this ICakeContext context, string name) + { + context.DotNetNuGetDisableSource(name, null); + } + + /// + /// Disable the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// ConfigFile = "NuGet.config" + /// }; + /// + /// DotNetNuGetDisableSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetDisableSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + sourcer.DisableSource(name, settings ?? new DotNetNuGetSourceSettings()); + } + + /// + /// Enable the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// + /// + /// DotNetNuGetEnableSource("example"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetEnableSource(this ICakeContext context, string name) + { + context.DotNetNuGetEnableSource(name, null); + } + + /// + /// Enable the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// ConfigFile = "NuGet.config" + /// }; + /// + /// DotNetNuGetEnableSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetEnableSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + sourcer.EnableSource(name, settings ?? new DotNetNuGetSourceSettings()); + } + + /// + /// Determines whether the specified NuGet source exists. + /// + /// The context. + /// The name of the source. + /// Whether the specified NuGet source exists. + /// + /// + /// var exists = DotNetNuGetHasSource("example"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static bool DotNetNuGetHasSource(this ICakeContext context, string name) + { + return context.DotNetNuGetHasSource(name, null); + } + + /// + /// Determines whether the specified NuGet source exists. + /// + /// The context. + /// The name of the source. + /// The settings. + /// Whether the specified NuGet source exists. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// ConfigFile = "NuGet.config" + /// }; + /// + /// var exists = DotNetNuGetHasSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static bool DotNetNuGetHasSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return sourcer.HasSource(name, settings ?? new DotNetNuGetSourceSettings()); + } + + /// + /// Remove the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// + /// + /// DotNetNuGetRemoveSource("example"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetRemoveSource(this ICakeContext context, string name) + { + context.DotNetNuGetRemoveSource(name, null); + } + + /// + /// Remove the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// ConfigFile = "NuGet.config" + /// }; + /// + /// DotNetNuGetRemoveSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetRemoveSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + sourcer.RemoveSource(name, settings ?? new DotNetNuGetSourceSettings()); + } + + /// + /// Update the specified NuGet source. + /// + /// The context. + /// The name of the source. + /// The settings. + /// + /// + /// var settings = new DotNetNuGetSourceSettings + /// { + /// Source = "https://www.example.com/nugetfeed", + /// UserName = "username", + /// Password = "password", + /// StorePasswordInClearText = true, + /// ValidAuthenticationTypes = "basic,negotiate" + /// }; + /// + /// DotNetNuGetUpdateSource("example", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("NuGet")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.NuGet.Source")] + public static void DotNetNuGetUpdateSource(this ICakeContext context, string name, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var sourcer = new DotNetNuGetSourcer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + sourcer.UpdateSource(name, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Pack.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Pack.cs new file mode 100644 index 0000000000..0e8667ca0c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Pack.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Pack; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Package all projects. + /// + /// The context. + /// The projects path. + /// + /// + /// DotNetPack("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Pack")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Pack")] + public static void DotNetPack(this ICakeContext context, string project) + { + context.DotNetPack(project, null); + } + + /// + /// Package all projects. + /// + /// The context. + /// The projects path. + /// The settings. + /// + /// + /// var settings = new DotNetPackSettings + /// { + /// Configuration = "Release", + /// OutputDirectory = "./artifacts/" + /// }; + /// + /// DotNetPack("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Pack")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Pack")] + public static void DotNetPack(this ICakeContext context, string project, DotNetPackSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetPackSettings(); + } + + var packer = new DotNetPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + packer.Pack(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Package.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Package.cs new file mode 100644 index 0000000000..cd7e115f27 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Package.cs @@ -0,0 +1,340 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Package.Add; +using Cake.Common.Tools.DotNet.Package.List; +using Cake.Common.Tools.DotNet.Package.Remove; +using Cake.Common.Tools.DotNet.Package.Search; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// + /// + /// DotNetAddPackage("Cake.FileHelper"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName) + { + context.DotNetAddPackage(packageName, null, null); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The target project file path. + /// + /// + /// DotNetAddPackage("Cake.FileHelper", "ToDo.csproj"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, string project) + { + context.DotNetAddPackage(packageName, project, null); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The settings. + /// + /// + /// var settings = new DotNetPackageAddSettings + /// { + /// NoRestore = true, + /// Version = "6.1.3" + /// }; + /// + /// DotNetAddPackage("Cake.FileHelper", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, DotNetPackageAddSettings settings) + { + context.DotNetAddPackage(packageName, null, settings); + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The context. + /// The package reference to add. + /// The target project file path. + /// The settings. + /// + /// + /// var settings = new DotNetPackageAddSettings + /// { + /// NoRestore = true, + /// Version = "6.1.3" + /// }; + /// + /// DotNetAddPackage("Cake.FileHelper", "ToDo.csproj", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Add")] + public static void DotNetAddPackage(this ICakeContext context, string packageName, string project, DotNetPackageAddSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetPackageAddSettings(); + } + + var adder = new DotNetPackageAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + adder.Add(packageName, project, settings); + } + + /// + /// Removes package reference from a project file. + /// + /// The context. + /// The package reference to remove. + /// + /// + /// DotNetRemovePackage("Cake.FileHelper"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Remove")] + public static void DotNetRemovePackage(this ICakeContext context, string packageName) + { + context.DotNetRemovePackage(packageName, null); + } + + /// + /// Removes package reference from a project file. + /// + /// The context. + /// The package reference to remove. + /// The target project file path. + /// + /// + /// DotNetRemovePackage("Cake.FileHelper", "ToDo.csproj"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Remove")] + public static void DotNetRemovePackage(this ICakeContext context, string packageName, string project) + { + context.DotNetRemovePackage(packageName, project, null); + } + + /// + /// Removes package reference from a project file. + /// + /// The context. + /// The package reference to remove. + /// The target project file path. + /// The settings. + /// + /// + /// DotNetRemovePackage( + /// "Cake.FileHelper", + /// "ToDo.csproj", + /// new DotNetPackageRemoveSettings { WorkingDirectory = "./src" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Remove")] + public static void DotNetRemovePackage(this ICakeContext context, string packageName, string project, DotNetPackageRemoveSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + settings ??= new DotNetPackageRemoveSettings(); + var remover = new DotNetPackageRemover(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + remover.Remove(packageName, project, settings); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The search term. + /// The settings. + /// List of packages with their version. + /// + /// + /// var packageList = DotNetPackageSearch("Cake", new DotNetPackageSearchSettings { + /// AllVersions = false, + /// Prerelease = false + /// }); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Search")] + public static IEnumerable DotNetSearchPackage(this ICakeContext context, string searchTerm, DotNetPackageSearchSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + var runner = new DotNetPackageSearcher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return runner.Search(searchTerm, settings); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The package Id. + /// List of packages with their version. + /// + /// + /// var packageList = DotNetPackageSearch("Cake", new DotNetPackageSearchSettings { + /// AllVersions = false, + /// Prerelease = false + /// }); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Search")] + public static IEnumerable DotNetSearchPackage(this ICakeContext context, string searchTerm) + { + ArgumentNullException.ThrowIfNull(context); + var runner = new DotNetPackageSearcher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return runner.Search(searchTerm, new DotNetPackageSearchSettings()); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The settings. + /// List of packages with their version. + /// + /// + /// var packageList = DotNetPackageSearch("Cake", new DotNetPackageSearchSettings { + /// AllVersions = false, + /// Prerelease = false + /// }); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.Search")] + public static IEnumerable DotNetSearchPackage(this ICakeContext context, DotNetPackageSearchSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + var runner = new DotNetPackageSearcher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return runner.Search(null, settings); + } + + /// + /// Lists the package references for a project or solution. + /// + /// The context. + /// The the package references. + /// + /// + /// DotNetPackageList output = DotNetListPackage(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.List")] + public static DotNetPackageList DotNetListPackage(this ICakeContext context) + { + return context.DotNetListPackage(null); + } + + /// + /// Lists the package references for a project or solution. + /// + /// The context. + /// The project or solution file to operate on. If not specified, the command searches the current directory for one. If more than one solution or project is found, an error is thrown. + /// The the package references. + /// + /// + /// DotNetPackageList output = DotNetListPackage("./src/MyProject/MyProject.csproj"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.List")] + public static DotNetPackageList DotNetListPackage(this ICakeContext context, string project) + { + return context.DotNetListPackage(project, null); + } + + /// + /// Lists the package references for a project or solution. + /// + /// The context. + /// The project or solution file to operate on. If not specified, the command searches the current directory for one. If more than one solution or project is found, an error is thrown. + /// The settings. + /// The the package references. + /// + /// + /// var settings = new DotNetPackageListSettings + /// { + /// Outdated = true + /// }; + /// + /// DotNetPackageList output = DotNetListPackage("./src/MyProject/MyProject.csproj", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Package")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Package.List")] + public static DotNetPackageList DotNetListPackage(this ICakeContext context, string project, DotNetPackageListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetPackageListSettings(); + } + + var lister = new DotNetPackageLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Publish.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Publish.cs new file mode 100644 index 0000000000..0236e824ff --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Publish.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Publish; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Publish all projects. + /// + /// The context. + /// The projects path. + /// + /// + /// DotNetPublish("./src/*"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Publish")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Publish")] + public static void DotNetPublish(this ICakeContext context, string project) + { + context.DotNetPublish(project, null); + } + + /// + /// Publish all projects. + /// + /// The context. + /// The projects path. + /// The settings. + /// + /// + /// var settings = new DotNetPublishSettings + /// { + /// Framework = "netcoreapp2.0", + /// Configuration = "Release", + /// OutputDirectory = "./artifacts/" + /// }; + /// + /// DotNetPublish("./src/*", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Publish")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Publish")] + public static void DotNetPublish(this ICakeContext context, string project, DotNetPublishSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetPublishSettings(); + } + + var publisher = new DotNetPublisher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + publisher.Publish(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Reference.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Reference.cs new file mode 100644 index 0000000000..88abefad88 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Reference.cs @@ -0,0 +1,299 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Reference.Add; +using Cake.Common.Tools.DotNet.Reference.List; +using Cake.Common.Tools.DotNet.Reference.Remove; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Adds project-to-project (P2P) references. + /// + /// The context. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetAddReference(GetFiles("./src/*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Add")] + public static void DotNetAddReference(this ICakeContext context, IEnumerable projectReferences) + { + context.DotNetAddReference(projectReferences, null); + } + + /// + /// Adds project-to-project (P2P) references. + /// + /// The context. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetReferenceAddSettings + /// { + /// Framework = "net8.0" + /// }; + /// + /// DotNetAddReference(GetFiles("./src/*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Add")] + public static void DotNetAddReference(this ICakeContext context, IEnumerable projectReferences, DotNetReferenceAddSettings settings) + { + context.DotNetAddReference(null, projectReferences, settings); + } + + /// + /// Adds project-to-project (P2P) references. + /// + /// The context. + /// The target project file path. If not specified, the command searches the current directory for one. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetAddReference("./app/app.csproj", GetFiles("./src/*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Add")] + public static void DotNetAddReference(this ICakeContext context, string project, IEnumerable projectReferences) + { + context.DotNetAddReference(project, projectReferences, null); + } + + /// + /// Adds project-to-project (P2P) references. + /// + /// The context. + /// The target project file path. If not specified, the command searches the current directory for one. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetReferenceAddSettings + /// { + /// Framework = "net8.0" + /// }; + /// + /// DotNetAddReference("./app/app.csproj", GetFiles("./src/*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Add")] + public static void DotNetAddReference(this ICakeContext context, string project, IEnumerable projectReferences, DotNetReferenceAddSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetReferenceAddSettings(); + } + + var adder = new DotNetReferenceAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + adder.Add(project, projectReferences, settings); + } + + /// + /// Removes project-to-project (P2P) references. + /// + /// The context. + /// Project-to-project (P2P) references to remove. You can specify one or multiple projects. Glob patterns are supported on Unix/Linux based terminals. + /// + /// + /// DotNetRemoveReference(GetFiles("./src/*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Remove")] + public static void DotNetRemoveReference(this ICakeContext context, IEnumerable projectReferences) + { + context.DotNetRemoveReference(projectReferences, null); + } + + /// + /// Removes project-to-project (P2P) references. + /// + /// The context. + /// Project-to-project (P2P) references to remove. You can specify one or multiple projects. Glob patterns are supported on Unix/Linux based terminals. + /// The settings. + /// + /// + /// var settings = new DotNetReferenceRemoveSettings + /// { + /// Framework = "net8.0" + /// }; + /// + /// DotNetRemoveReference(GetFiles("./src/*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Remove")] + public static void DotNetRemoveReference(this ICakeContext context, IEnumerable projectReferences, DotNetReferenceRemoveSettings settings) + { + context.DotNetRemoveReference(null, projectReferences, settings); + } + + /// + /// Removes project-to-project (P2P) references. + /// + /// The context. + /// Target project file. If not specified, the command searches the current directory for one. + /// Project-to-project (P2P) references to remove. You can specify one or multiple projects. Glob patterns are supported on Unix/Linux based terminals. + /// + /// + /// DotNetRemoveReference("./app/app.csproj", GetFiles("./src/*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Remove")] + public static void DotNetRemoveReference(this ICakeContext context, string project, IEnumerable projectReferences) + { + context.DotNetRemoveReference(project, projectReferences, null); + } + + /// + /// Removes project-to-project (P2P) references. + /// + /// The context. + /// Target project file. If not specified, the command searches the current directory for one. + /// Project-to-project (P2P) references to remove. You can specify one or multiple projects. Glob patterns are supported on Unix/Linux based terminals. + /// The settings. + /// + /// + /// var settings = new DotNetReferenceRemoveSettings + /// { + /// Framework = "net8.0" + /// }; + /// + /// DotNetRemoveReference("./app/app.csproj", GetFiles("./src/*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.Remove")] + public static void DotNetRemoveReference(this ICakeContext context, string project, IEnumerable projectReferences, DotNetReferenceRemoveSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetReferenceRemoveSettings(); + } + + var remover = new DotNetReferenceRemover(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + remover.Remove(project, projectReferences, settings); + } + + /// + /// Lists project-to-project references. + /// + /// The context. + /// The list of project-to-project references. + /// + /// + /// var references = DotNetListReference(); + /// + /// foreach (var reference in references) + /// { + /// Information(reference); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.List")] + public static IEnumerable DotNetListReference(this ICakeContext context) + { + return context.DotNetListReference(null); + } + + /// + /// Lists project-to-project references. + /// + /// The context. + /// The project file to operate on. If a file is not specified, the command will search the current directory for one. + /// The list of project-to-project references. + /// + /// + /// var references = DotNetListReference("./app/app.csproj"); + /// + /// foreach (var reference in references) + /// { + /// Information(reference); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.List")] + public static IEnumerable DotNetListReference(this ICakeContext context, string project) + { + return context.DotNetListReference(project, null); + } + + /// + /// Lists project-to-project references. + /// + /// The context. + /// The project file to operate on. If a file is not specified, the command will search the current directory for one. + /// The settings. + /// The list of project-to-project references. + /// + /// + /// var settings = new DotNetReferenceListSettings + /// { + /// Verbosity = DotNetVerbosity.Diagnostic + /// }; + /// + /// var references = DotNetListReference("./app/app.csproj", settings); + /// + /// foreach (var reference in references) + /// { + /// Information(reference); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Reference")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Reference.List")] + public static IEnumerable DotNetListReference(this ICakeContext context, string project, DotNetReferenceListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetReferenceListSettings(); + } + + var lister = new DotNetReferenceLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Restore.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Restore.cs new file mode 100644 index 0000000000..a5010c914b --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Restore.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Restore; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Restore all NuGet Packages. + /// + /// The context. + /// + /// + /// DotNetRestore(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Restore")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Restore")] + public static void DotNetRestore(this ICakeContext context) + { + context.DotNetRestore(null, null); + } + + /// + /// Restore all NuGet Packages in the specified path. + /// + /// The context. + /// Path to the project file to restore. + /// + /// + /// DotNetRestore("./src/MyProject/MyProject.csproj"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Restore")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Restore")] + public static void DotNetRestore(this ICakeContext context, string root) + { + context.DotNetRestore(root, null); + } + + /// + /// Restore all NuGet Packages with the settings. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetRestoreSettings + /// { + /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, + /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, + /// PackagesDirectory = "./packages", + /// DotNetVerbosity.Information, + /// DisableParallel = true, + /// InferRuntimes = new[] {"runtime1", "runtime2"} + /// }; + /// + /// DotNetRestore(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Restore")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Restore")] + public static void DotNetRestore(this ICakeContext context, DotNetRestoreSettings settings) + { + context.DotNetRestore(null, settings); + } + + /// + /// Restore all NuGet Packages in the specified path with settings. + /// + /// The context. + /// Path to the project file to restore. + /// The settings. + /// + /// + /// var settings = new DotNetRestoreSettings + /// { + /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, + /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, + /// PackagesDirectory = "./packages", + /// DotNetVerbosity.Information, + /// DisableParallel = true, + /// InferRuntimes = new[] {"runtime1", "runtime2"} + /// }; + /// + /// DotNetRestore("./src/MyProject/MyProject.csproj", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Restore")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Restore")] + public static void DotNetRestore(this ICakeContext context, string root, DotNetRestoreSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetRestoreSettings(); + } + + var restorer = new DotNetRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); + restorer.Restore(root, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Run.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Run.cs new file mode 100644 index 0000000000..9a4b5a1bc4 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Run.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Run; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Run all projects. + /// + /// The context. + /// + /// + /// DotNetRun(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Run")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Run")] + public static void DotNetRun(this ICakeContext context) + { + context.DotNetRun(null, null, null); + } + + /// + /// Run project. + /// + /// The context. + /// The project path. + /// + /// + /// DotNetRun("./src/Project"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Run")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Run")] + public static void DotNetRun(this ICakeContext context, string project) + { + context.DotNetRun(project, null, null); + } + + /// + /// Run project with path and arguments. + /// + /// The context. + /// The project path. + /// The arguments. + /// + /// + /// DotNetRun("./src/Project", "--args"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Run")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Run")] + public static void DotNetRun(this ICakeContext context, string project, ProcessArgumentBuilder arguments) + { + context.DotNetRun(project, arguments, null); + } + + /// + /// Run project with settings. + /// + /// The context. + /// The project path. + /// The arguments. + /// The settings. + /// + /// + /// var settings = new DotNetRunSettings + /// { + /// Framework = "netcoreapp2.0", + /// Configuration = "Release" + /// }; + /// + /// DotNetRun("./src/Project", "--args", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Run")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Run")] + public static void DotNetRun(this ICakeContext context, string project, ProcessArgumentBuilder arguments, DotNetRunSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetRunSettings(); + } + + var runner = new DotNetRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(project, arguments, settings); + } + + /// + /// Run project with settings. + /// + /// The context. + /// The project path. + /// The settings. + /// + /// + /// var settings = new DotNetRunSettings + /// { + /// Framework = "netcoreapp2.0", + /// Configuration = "Release" + /// }; + /// + /// DotNetRun("./src/Project", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Run")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Run")] + public static void DotNetRun(this ICakeContext context, string project, DotNetRunSettings settings) + { + context.DotNetRun(project, null, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.SDK.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.SDK.cs new file mode 100644 index 0000000000..10efcb6d70 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.SDK.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.SDKCheck; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Lists the latest available version of the .NET SDK and .NET Runtime. + /// + /// The context. + /// + /// + /// DotNetSDKCheck(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("SDK")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.SDKCheck")] + public static void DotNetSDKCheck(this ICakeContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var checker = new DotNetSDKChecker(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + checker.Check(); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Sln.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Sln.cs new file mode 100644 index 0000000000..d20378c317 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Sln.cs @@ -0,0 +1,272 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Sln.Add; +using Cake.Common.Tools.DotNet.Sln.List; +using Cake.Common.Tools.DotNet.Sln.Remove; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The list of projects. + /// + /// + /// var projects = DotNetSlnList(); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetSlnList(this ICakeContext context) + { + return context.DotNetSlnList(null); + } + + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The solution file to use. If this argument is omitted, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The list of projects. + /// + /// + /// var projects = DotNetSlnList("./app/app.sln"); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetSlnList(this ICakeContext context, FilePath solution) + { + return context.DotNetSlnList(solution, null); + } + + /// + /// Lists all projects in a solution file. + /// + /// The context. + /// The solution file to use. If this argument is omitted, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The settings. + /// The list of projects. + /// + /// + /// var settings = new DotNetSlnListSettings + /// { + /// Verbosity = DotNetVerbosity.Diagnostic + /// }; + /// + /// var projects = DotNetSlnList("./app/app.sln"); + /// + /// foreach (var project in projects) + /// { + /// Information(project); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.List")] + public static IEnumerable DotNetSlnList(this ICakeContext context, FilePath solution, DotNetSlnListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + settings ??= new DotNetSlnListSettings(); + + var lister = new DotNetSlnLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(solution, settings); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetSlnAdd(GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, IEnumerable projectPath) + { + context.DotNetSlnAdd(null, projectPath); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// + /// + /// DotNetSlnAdd("app.sln", GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, FilePath solution, IEnumerable projectPath) + { + context.DotNetSlnAdd(solution, projectPath, null); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetSlnAddSettings + /// { + /// SolutionFolder = "libs/math" + /// }; + /// + /// DotNetSlnAdd(GetFiles("./*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + context.DotNetSlnAdd(null, projectPath, settings); + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + /// + /// + /// var settings = new DotNetSlnAddSettings + /// { + /// SolutionFolder = "libs/math" + /// }; + /// + /// DotNetSlnAdd("app.sln", GetFiles("./*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Add")] + public static void DotNetSlnAdd(this ICakeContext context, FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetSlnAddSettings(); + } + + var adder = new DotNetSlnAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + adder.Add(solution, projectPath, settings); + } + + /// + /// Removes a project or multiple projects from the solution file. + /// + /// The context. + /// The path to the project or projects to remove from the solution. + /// + /// + /// DotNetSlnRemove(GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Remove")] + public static void DotNetSlnRemove(this ICakeContext context, IEnumerable projectPath) + { + context.DotNetSlnRemove(null, projectPath); + } + + /// + /// Removes a project or multiple projects from the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to remove from the solution. + /// + /// + /// DotNetSlnRemove("app.sln", GetFiles("./*.csproj")); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Remove")] + public static void DotNetSlnRemove(this ICakeContext context, FilePath solution, IEnumerable projectPath) + { + context.DotNetSlnRemove(solution, projectPath, null); + } + + /// + /// Removes a project or multiple projects from the solution file. + /// + /// The context. + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to remove from the solution. + /// The settings. + /// + /// + /// var settings = new DotNetSlnRemoveSettings + /// { + /// Verbosity = DotNetVerbosity.Diagnostic + /// }; + /// + /// DotNetSlnRemove("app.sln", GetFiles("./*.csproj"), settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Sln")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Sln.Remove")] + public static void DotNetSlnRemove(this ICakeContext context, FilePath solution, IEnumerable projectPath, DotNetSlnRemoveSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetSlnRemoveSettings(); + } + + var remover = new DotNetSlnRemover(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + remover.Remove(solution, projectPath, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Test.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Test.cs new file mode 100644 index 0000000000..971320ba1c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Test.cs @@ -0,0 +1,300 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.IO; +using Cake.Common.Tools.DotNet.Test; +using Cake.Common.Tools.DotNet.VSTest; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Test project. + /// + /// The context. + /// + /// + /// DotNetTest(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Test")] + public static void DotNetTest(this ICakeContext context) + { + context.DotNetTest(null, null, null); + } + + /// + /// Test project with path. + /// + /// The context. + /// The project path. + /// + /// Specify the path to the .csproj file in the test project. + /// + /// DotNetTest("./test/Project.Tests/Project.Tests.csproj"); + /// + /// You could also specify a task that runs multiple test projects. + /// Cake task: + /// + /// Task("Test") + /// .Does(() => + /// { + /// var projectFiles = GetFiles("./test/**/*.csproj"); + /// foreach (var file in projectFiles) + /// { + /// DotNetTest(file.FullPath); + /// } + /// }); + /// + /// If your test project is using project.json, the project parameter should just be the directory path. + /// + /// DotNetTest("./test/Project.Tests/"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Test")] + public static void DotNetTest(this ICakeContext context, string project) + { + context.DotNetTest(project, null, null); + } + + /// + /// Test project with settings. + /// + /// The context. + /// The project path. + /// The settings. + /// + /// + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// DotNetTest("./test/Project.Tests/Project.Tests.csproj", settings); + /// + /// You could also specify a task that runs multiple test projects. + /// Cake task: + /// + /// Task("Test") + /// .Does(() => + /// { + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// var projectFiles = GetFiles("./test/**/*.csproj"); + /// foreach (var file in projectFiles) + /// { + /// DotNetTest(file.FullPath, settings); + /// } + /// }); + /// + /// If your test project is using project.json, the project parameter should just be the directory path. + /// + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// DotNetTest("./test/Project.Tests/", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Test")] + public static void DotNetTest(this ICakeContext context, string project, DotNetTestSettings settings) + { + context.DotNetTest(project, null, settings); + } + + /// + /// Test project with settings. + /// + /// The context. + /// The project path. + /// The arguments. + /// The settings. + /// + /// + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// DotNetTest("./test/Project.Tests/Project.Tests.csproj", settings); + /// + /// You could also specify a task that runs multiple test projects. + /// Cake task: + /// + /// Task("Test") + /// .Does(() => + /// { + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// var projectFiles = GetFiles("./test/**/*.csproj"); + /// foreach (var file in projectFiles) + /// { + /// DotNetTest(file.FullPath, "MSTest.MapInconclusiveToFailed=true", settings); + /// } + /// }); + /// + /// If your test project is using project.json, the project parameter should just be the directory path. + /// + /// var settings = new DotNetTestSettings + /// { + /// Configuration = "Release" + /// }; + /// + /// DotNetTest("./test/Project.Tests/", "MSTest.MapInconclusiveToFailed=true", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Test")] + public static void DotNetTest(this ICakeContext context, string project, ProcessArgumentBuilder arguments, DotNetTestSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetTestSettings(); + } + + var tester = new DotNetTester(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + tester.Test(project, arguments, settings); + } + + /// + /// Test one or more projects specified by a path or glob pattern using the VS Test host runner. + /// + /// The context. + /// A path to the test file or glob for one or more test files. + /// + /// Specify the path to the .csproj file in the test project. + /// + /// DotNetVSTest("./test/Project.Tests/bin/Release/netcoreapp2.1/Project.Tests.dll"); + /// + /// You could also specify a glob pattern to run multiple test projects. + /// + /// DotNetVSTest("./**/bin/Release/netcoreapp2.1/*.Tests.dll"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.VSTest")] + public static void DotNetVSTest(this ICakeContext context, GlobPattern testFile) => context.DotNetVSTest(testFile, null); + + /// + /// Test one or more projects specified by a path or glob pattern with settings using the VS Test host runner. + /// + /// The context. + /// A path to the test file or glob for one or more test files. + /// The settings. + /// + /// Specify the path to the .csproj file in the test project. + /// + /// var settings = new DotNetVSTestSettings + /// { + /// Framework = "FrameworkCore10", + /// Platform = "x64" + /// }; + /// + /// DotNetTest("./test/Project.Tests/bin/Release/netcoreapp2.1/Project.Tests.dll", settings); + /// + /// You could also specify a glob pattern to run multiple test projects. + /// + /// var settings = new DotNetVSTestSettings + /// { + /// Framework = "FrameworkCore10", + /// Platform = "x64", + /// Parallel = true + /// }; + /// + /// DotNetVSTest("./**/bin/Release/netcoreapp2.1/*.Tests.dll", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.VSTest")] + public static void DotNetVSTest(this ICakeContext context, GlobPattern testFile, DotNetVSTestSettings settings) + { + var testFiles = context.GetFiles(testFile); + + context.DotNetVSTest(testFiles, settings); + } + + /// + /// Test one or more specified projects with settings using the VS Test host runner. + /// + /// The context. + /// The project paths to test. + /// The settings. + /// + /// + /// var settings = new DotNetVSTestSettings + /// { + /// Framework = "FrameworkCore10", + /// Platform = "x64" + /// }; + /// + /// DotNetVSTest(new[] { (FilePath)"./test/Project.Tests/bin/Release/netcoreapp2.1/Project.Tests.dll" }, settings); + /// + /// You could also specify a task that runs multiple test projects. + /// Cake task: + /// + /// Task("Test") + /// .Does(() => + /// { + /// var settings = new DotNetVSTestSettings + /// { + /// Framework = "FrameworkCore10", + /// Platform = "x64", + /// Parallel = true + /// }; + /// + /// var testFiles = GetFiles("./test/**/bin/Release/netcoreapp2.1/*.Test.dll"); + /// + /// DotNetVSTest(testFiles, settings); + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Test")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.VSTest")] + public static void DotNetVSTest(this ICakeContext context, IEnumerable testFiles, DotNetVSTestSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetVSTestSettings(); + } + + var tester = new DotNetVSTester(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + tester.Test(testFiles, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Tool.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Tool.cs new file mode 100644 index 0000000000..f41cf47e2a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Tool.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.Tool; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Execute an .NET Core Extensibility Tool. + /// + /// The context. + /// The command to execute. + /// + /// + /// DotNetTool("cake"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Tool")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Tool")] + public static void DotNetTool(this ICakeContext context, string command) + { + ArgumentNullException.ThrowIfNull(context); + + var arguments = new ProcessArgumentBuilder(); + var settings = new DotNetToolSettings(); + + context.DotNetTool(null, command, arguments, settings); + } + + /// + /// Execute an .NET Core Extensibility Tool. + /// + /// The context. + /// The command to execute. + /// The settings. + /// + /// + /// var settings = new DotNetToolSettings + /// { + /// DiagnosticOutput = true + /// }; + /// + /// DotNetTool("cake", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Tool")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Tool")] + public static void DotNetTool(this ICakeContext context, string command, DotNetToolSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var arguments = new ProcessArgumentBuilder(); + + context.DotNetTool(null, command, arguments, settings); + } + + /// + /// Execute an .NET Core Extensibility Tool. + /// + /// The context. + /// The project path. + /// The command to execute. + /// + /// + /// DotNetTool("./src/project", "xunit", "-xml report.xml"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Tool")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Tool")] + public static void DotNetTool(this ICakeContext context, FilePath projectPath, string command) + { + ArgumentNullException.ThrowIfNull(context); + + var arguments = new ProcessArgumentBuilder(); + var settings = new DotNetToolSettings(); + + context.DotNetTool(projectPath, command, arguments, settings); + } + + /// + /// Execute an .NET Core Extensibility Tool. + /// + /// The context. + /// The project path. + /// The command to execute. + /// The arguments. + /// + /// + /// DotNetTool("./src/project", "xunit", "-xml report.xml"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Tool")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Tool")] + public static void DotNetTool(this ICakeContext context, FilePath projectPath, string command, ProcessArgumentBuilder arguments) + { + ArgumentNullException.ThrowIfNull(context); + + var settings = new DotNetToolSettings(); + + context.DotNetTool(projectPath, command, arguments, settings); + } + + /// + /// Execute an .NET Core Extensibility Tool. + /// + /// The context. + /// The project path. + /// The command to execute. + /// The arguments. + /// The settings. + /// + /// + /// DotNetTool("./src/project", "xunit", "-xml report.xml"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Tool")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Tool")] + public static void DotNetTool(this ICakeContext context, FilePath projectPath, string command, ProcessArgumentBuilder arguments, DotNetToolSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var runner = new DotNetToolRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + + runner.Execute(projectPath, command, arguments, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.Workload.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.Workload.cs new file mode 100644 index 0000000000..d50a427cec --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.Workload.cs @@ -0,0 +1,456 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.Workload.Install; +using Cake.Common.Tools.DotNet.Workload.List; +using Cake.Common.Tools.DotNet.Workload.Repair; +using Cake.Common.Tools.DotNet.Workload.Restore; +using Cake.Common.Tools.DotNet.Workload.Search; +using Cake.Common.Tools.DotNet.Workload.Uninstall; +using Cake.Common.Tools.DotNet.Workload.Update; +using Cake.Core; +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + public static partial class DotNetAliases + { + /// + /// Lists available workloads. + /// + /// The context. + /// The list of available workloads. + /// + /// + /// var workloads = DotNetWorkloadSearch(); + /// + /// foreach (var workload in workloads) + /// { + /// Information($"Id: {workload.Id}, Description: {workload.Description}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Search")] + public static IEnumerable DotNetWorkloadSearch(this ICakeContext context) + { + return context.DotNetWorkloadSearch(null); + } + + /// + /// Lists available workloads by specifying all or part of the workload ID. + /// + /// The context. + /// The workload ID to search for, or part of it. + /// The list of available workloads. + /// + /// + /// var workloads = DotNetWorkloadSearch("maui"); + /// + /// foreach (var workload in workloads) + /// { + /// Information($"Id: {workload.Id}, Description: {workload.Description}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Search")] + public static IEnumerable DotNetWorkloadSearch(this ICakeContext context, string searchString) + { + return context.DotNetWorkloadSearch(searchString, null); + } + + /// + /// Lists available workloads by specifying all or part of the workload ID. + /// + /// The context. + /// The workload ID to search for, or part of it. + /// The settings. + /// The list of available workloads. + /// + /// + /// var settings = new DotNetWorkloadSearchSettings + /// { + /// DotNetVerbosity.Detailed + /// }; + /// + /// var workloads = DotNetWorkloadSearch("maui", settings); + /// + /// foreach (var workload in workloads) + /// { + /// Information($"Id: {workload.Id}, Description: {workload.Description}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Search")] + public static IEnumerable DotNetWorkloadSearch(this ICakeContext context, string searchString, DotNetWorkloadSearchSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetWorkloadSearchSettings(); + } + + var searcher = new DotNetWorkloadSearcher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return searcher.Search(searchString, settings); + } + + /// + /// Uninstalls a specified workload. + /// + /// The context. + /// The workload ID to uninstall. + /// + /// + /// DotNetWorkloadUninstall("maui"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Uninstall")] + public static void DotNetWorkloadUninstall(this ICakeContext context, string workloadId) + { + context.DotNetWorkloadUninstall(new string[] { workloadId }); + } + + /// + /// Uninstalls one or more workloads. + /// + /// The context. + /// The workload ID or multiple IDs to uninstall. + /// + /// + /// DotNetWorkloadUninstall(new string[] { "maui", "maui-desktop", "maui-mobile" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Uninstall")] + public static void DotNetWorkloadUninstall(this ICakeContext context, IEnumerable workloadIds) + { + ArgumentNullException.ThrowIfNull(context); + + var uninstaller = new DotNetWorkloadUninstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + uninstaller.Uninstall(workloadIds); + } + + /// + /// Installs a specified optional workload. + /// + /// The context. + /// The workload ID to install. + /// + /// + /// DotNetWorkloadInstall("maui"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, string workloadId) + { + context.DotNetWorkloadInstall(workloadId, null); + } + + /// + /// Installs a specified optional workload. + /// + /// The context. + /// The workload ID to install. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadInstallSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadInstall("maui", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, string workloadId, DotNetWorkloadInstallSettings settings) + { + context.DotNetWorkloadInstall(new string[] { workloadId }, settings); + } + + /// + /// Installs one or more optional workloads. + /// + /// The context. + /// The workload ID or multiple IDs to install. + /// + /// + /// DotNetWorkloadInstall(new string[] { "maui", "maui-desktop", "maui-mobile" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, IEnumerable workloadIds) + { + context.DotNetWorkloadInstall(workloadIds, null); + } + + /// + /// Installs one or more optional workloads. + /// + /// The context. + /// The workload ID or multiple IDs to install. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadInstallSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadInstall(new string[] { "maui", "maui-desktop", "maui-mobile" }, settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Install")] + public static void DotNetWorkloadInstall(this ICakeContext context, IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetWorkloadInstallSettings(); + } + + var installer = new DotNetWorkloadInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + installer.Install(workloadIds, settings); + } + + /// + /// Lists all installed workloads. + /// + /// The context. + /// The list of installed workloads. + /// + /// + /// var workloadIds = DotNetWorkloadList(); + /// + /// foreach (var workloadId in workloadIds) + /// { + /// Information($"Installed Workload Id: {workloadId}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.List")] + public static IEnumerable DotNetWorkloadList(this ICakeContext context) + { + return context.DotNetWorkloadList(null); + } + + /// + /// Lists all installed workloads. + /// + /// The context. + /// The settings. + /// The list of installed workloads. + /// + /// + /// var settings = new DotNetWorkloadListSettings + /// { + /// Verbosity = DotNetVerbosity.Detailed + /// }; + /// + /// var workloads = DotNetWorkloadList(settings); + /// + /// foreach (var workload in workloads) + /// { + /// Information($"Installed Workload Id: {workload.Id}\t Manifest Version: {workload.ManifestVersion}\t Installation Source: {workload.InstallationSource}"); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.List")] + public static IEnumerable DotNetWorkloadList(this ICakeContext context, DotNetWorkloadListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetWorkloadListSettings(); + } + + var lister = new DotNetWorkloadLister(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return lister.List(settings); + } + + /// + /// Repairs all workloads installations. + /// + /// The context. + /// + /// + /// DotNetWorkloadRepair(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Repair")] + public static void DotNetWorkloadRepair(this ICakeContext context) + { + context.DotNetWorkloadRepair(null); + } + + /// + /// Repairs all workloads installations. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadRepairSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadRepair(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Repair")] + public static void DotNetWorkloadRepair(this ICakeContext context, DotNetWorkloadRepairSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetWorkloadRepairSettings(); + } + + var repairer = new DotNetWorkloadRepairer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + repairer.Repair(settings); + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The context. + /// + /// + /// DotNetWorkloadUpdate(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Update")] + public static void DotNetWorkloadUpdate(this ICakeContext context) + { + context.DotNetWorkloadUpdate(null); + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The context. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadUpdateSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadUpdate(settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Update")] + public static void DotNetWorkloadUpdate(this ICakeContext context, DotNetWorkloadUpdateSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings == null) + { + settings = new DotNetWorkloadUpdateSettings(); + } + + var updater = new DotNetWorkloadUpdater(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + updater.Update(settings); + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The context. + /// The project or solution file to install workloads for. + /// + /// + /// DotNetWorkloadRestore("./src/project"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Restore")] + public static void DotNetWorkloadRestore(this ICakeContext context, string project) + { + context.DotNetWorkloadRestore(project, null); + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The context. + /// The project or solution file to install workloads for. + /// The settings. + /// + /// + /// var settings = new DotNetWorkloadRestoreSettings + /// { + /// IncludePreviews = true, + /// NoCache = true + /// }; + /// + /// DotNetWorkloadRestore("./src/project", settings); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Workload")] + [CakeNamespaceImport("Cake.Common.Tools.DotNet.Workload.Restore")] + public static void DotNetWorkloadRestore(this ICakeContext context, string project, DotNetWorkloadRestoreSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (settings is null) + { + settings = new DotNetWorkloadRestoreSettings(); + } + + var restorer = new DotNetWorkloadRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + restorer.Restore(project, settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetAliases.cs b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs new file mode 100644 index 0000000000..9655c8f907 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetAliases.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Annotations; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains functionality related to .NET CLI. + /// + /// In order to use the commands for this alias, the .NET CLI tools will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to install. + /// + /// + [CakeAliasCategory("DotNet")] + public static partial class DotNetAliases + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetRollForward.cs b/src/Cake.Common/Tools/DotNet/DotNetRollForward.cs new file mode 100644 index 0000000000..66c03dc589 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetRollForward.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains the roll forward policy to be used. + /// + public enum DotNetRollForward + { + /// + /// Roll forward to the lowest higher minor version, if requested minor version is missing. + /// + Minor, + + /// + /// Roll forward to the highest patch version. This disables minor version roll forward. + /// + LatestPatch, + + /// + /// Roll forward to lowest higher major version, and lowest minor version, if requested major version is missing. + /// + Major, + + /// + /// Roll forward to highest minor version, even if requested minor version is present. + /// + LatestMinor, + + /// + /// Roll forward to highest major and highest minor version, even if requested major is present. + /// + LatestMajor, + + /// + /// Don't roll forward. Only bind to specified version. + /// + Disable, + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetSettings.cs b/src/Cake.Common/Tools/DotNet/DotNetSettings.cs new file mode 100644 index 0000000000..7ad53356f2 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetSettings.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains common settings used by . + /// + public abstract class DotNetSettings : ToolSettings + { + /// + /// Gets or sets the verbosity of logging to use. + /// + public DotNetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets a value indicating whether to not enable diagnostic output. + /// + public bool DiagnosticOutput { get; set; } + + /// + /// Gets or sets the dotnet roll forward policy. + /// + public DotNetRollForward? RollForward { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetTool.cs b/src/Cake.Common/Tools/DotNet/DotNetTool.cs new file mode 100644 index 0000000000..5d473e01f3 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetTool.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Base class for all .NET Core related tools. + /// + /// The settings type. + public abstract class DotNetTool : Tool + where TSettings : DotNetSettings + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + protected DotNetTool( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return ".NET CLI"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "dotnet", "dotnet.exe" }; + } + + /// + /// Runs the dotnet cli command using the specified settings and arguments. + /// + /// The settings. + /// The arguments. + protected void RunCommand(TSettings settings, ProcessArgumentBuilder arguments) + { + // add arguments common to all commands last + AppendCommonArguments(arguments, settings); + + Run(settings, arguments, null, null); + } + + /// + /// Runs the dotnet cli command using the specified settings and arguments. + /// + /// The settings. + /// The arguments. + /// The processSettings. + protected void RunCommand(TSettings settings, ProcessArgumentBuilder arguments, ProcessSettings processSettings) + { + // add arguments common to all commands last + AppendCommonArguments(arguments, settings); + + Run(settings, arguments, processSettings, null); + } + + /// + /// Runs the dotnet cli command using the specified settings and arguments. + /// + /// The settings. + /// The arguments. + /// The processSettings. + /// If specified called after process exit. + protected void RunCommand(TSettings settings, ProcessArgumentBuilder arguments, ProcessSettings processSettings, Action postAction) + { + // add arguments common to all commands last + AppendCommonArguments(arguments, settings); + + Run(settings, arguments, processSettings, postAction); + } + + /// + /// Creates a and adds common commandline arguments. + /// + /// The settings. + /// Instance of . + protected ProcessArgumentBuilder CreateArgumentBuilder(TSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + if (settings.DiagnosticOutput) + { + builder.Append("--diagnostics"); + } + + return builder; + } + + /// + /// Adds common commandline arguments. + /// + /// Process argument builder to update. + /// The settings. + /// Returns updated with common commandline arguments. + private ProcessArgumentBuilder AppendCommonArguments(ProcessArgumentBuilder builder, TSettings settings) + { + // Verbosity + if (settings.Verbosity.HasValue) + { + builder.Append("--verbosity"); + builder.Append(settings.Verbosity.ToString().ToLower()); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/DotNetVerbosity.cs b/src/Cake.Common/Tools/DotNet/DotNetVerbosity.cs new file mode 100644 index 0000000000..4445ccbf17 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/DotNetVerbosity.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet +{ + /// + /// Contains the verbosity of logging to use. + /// + public enum DotNetVerbosity + { + /// + /// Quiet level. + /// + Quiet, + + /// + /// Minimal level. + /// + Minimal, + + /// + /// Normal level. + /// + Normal, + + /// + /// Detailed level. + /// + Detailed, + + /// + /// Diagnostic level. + /// + Diagnostic, + } +} diff --git a/src/Cake.Common/Tools/DotNet/Execute/DotNetExecuteSettings.cs b/src/Cake.Common/Tools/DotNet/Execute/DotNetExecuteSettings.cs new file mode 100644 index 0000000000..63824c7a80 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Execute/DotNetExecuteSettings.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Execute +{ + /// + /// Contains settings used by . + /// + public class DotNetExecuteSettings : DotNetSettings + { + /// + /// Gets or sets the version of the installed Shared Framework to use to run the application. + /// + public string FrameworkVersion { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Execute/DotNetExecutor.cs b/src/Cake.Common/Tools/DotNet/Execute/DotNetExecutor.cs new file mode 100644 index 0000000000..817a8ab880 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Execute/DotNetExecutor.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Execute +{ + /// + /// .NET assembly executor. + /// + public sealed class DotNetExecutor : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetExecutor( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Execute an assembly using arguments and settings. + /// + /// The assembly path. + /// The arguments. + /// The settings. + public void Execute(FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetExecuteSettings settings) + { + ArgumentNullException.ThrowIfNull(assemblyPath); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(assemblyPath, arguments, settings)); + } + + private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetExecuteSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + if (!string.IsNullOrWhiteSpace(settings.FrameworkVersion)) + { + builder.Append("--fx-version"); + builder.Append(settings.FrameworkVersion); + } + + builder.AppendQuoted(assemblyPath.MakeAbsolute(_environment).FullPath); + + if (!arguments.IsNullOrEmpty()) + { + arguments.CopyTo(builder); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSettings.cs b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSettings.cs new file mode 100644 index 0000000000..7bd4c4ed09 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSettings.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Format +{ + /// + /// Contains settings used by . + /// + public class DotNetFormatSettings : DotNetSettings + { + /// + /// Gets or sets a space-separated list of diagnostic IDs to use as a filter when fixing code style or third-party issues. + /// + public ICollection Diagnostics { get; set; } = new List(); + + /// + /// Gets or sets the minimum severity of diagnostics to fix. + /// + public DotNetFormatSeverity? Severity { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes build faster, but requires restore to be done before build is executed. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to verify that no formatting changes would be performed. + /// Terminates with a non zero exit code if any files would have been formatted. + /// + public bool VerifyNoChanges { get; set; } + + /// + /// Gets or sets a space-separated list of relative file or folder paths to include in formatting. + /// All files in the solution or project are formatted if empty. + /// + public ICollection Include { get; set; } = new List(); + + /// + /// Gets or sets a space-separated list of relative file or folder paths to exclude from formatting. The default is none. + /// + public ICollection Exclude { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether to format files generated by the SDK. + /// + public bool IncludeGenerated { get; set; } + + /// + /// Gets or sets a value indicating whether to log all project or solution load information to a binary log file. + /// + public FilePath BinaryLog { get; set; } + + /// + /// Gets or sets a path to a JSON report. + /// + public FilePath Report { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSeverity.cs b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSeverity.cs new file mode 100644 index 0000000000..ceb0a48f06 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatSeverity.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Format +{ + /// + /// Severity of dotnet format. + /// + public enum DotNetFormatSeverity + { + /// + /// Information + /// + Info, + + /// + /// Warning + /// + Warning, + + /// + /// Error + /// + Error + } +} diff --git a/src/Cake.Common/Tools/DotNet/Format/DotNetFormatter.cs b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatter.cs new file mode 100644 index 0000000000..7c9c15c169 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Format/DotNetFormatter.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Format +{ + /// + /// .NET project formatter. + /// + public sealed class DotNetFormatter : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetFormatter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Format the project or solution using the specified path and settings. + /// + /// The target project or solution path. + /// The sub command. + /// The settings. + public void Format(string root, string subcommand, DotNetFormatSettings settings) + { + ArgumentNullException.ThrowIfNull(root); + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(root, subcommand, settings)); + } + + private ProcessArgumentBuilder GetArguments(string root, string subcommand, DotNetFormatSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("format"); + + // Subcommand + if (!string.IsNullOrWhiteSpace(subcommand)) + { + builder.Append(subcommand); + } + + // Specific path? + if (root != null) + { + builder.AppendQuoted(root); + } + + // Diagnostics + if (settings.Diagnostics != null && settings.Diagnostics.Any()) + { + builder.AppendSwitch("--diagnostics", string.Join(' ', settings.Diagnostics)); + } + + // Severity + if (settings.Severity.HasValue) + { + builder.Append("--severity"); + builder.Append(GetSeverityValue(settings.Severity.Value)); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // Verify No Changes + if (settings.VerifyNoChanges) + { + builder.Append("--verify-no-changes"); + } + + // Include + if (settings.Include != null && settings.Include.Any()) + { + builder.AppendSwitch("--include", string.Join(' ', settings.Include)); + } + + // Exclude + if (settings.Exclude != null && settings.Exclude.Any()) + { + builder.AppendSwitch("--exclude", string.Join(' ', settings.Exclude)); + } + + // Include Generated + if (settings.IncludeGenerated) + { + builder.Append("--include-generated"); + } + + // Binary Log + if (settings.BinaryLog != null) + { + builder.AppendSwitchQuoted($"--binarylog", settings.BinaryLog.MakeAbsolute(_environment).FullPath); + } + + // Report + if (settings.Report != null) + { + builder.AppendSwitchQuoted($"--report", settings.Report.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + + private static string GetSeverityValue(DotNetFormatSeverity value) + { + return value switch + { + DotNetFormatSeverity.Info => "info", + DotNetFormatSeverity.Warning => "warn", + DotNetFormatSeverity.Error => "error", + _ => throw new InvalidOperationException($"Unknown severity value '{value}'"), + }; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs new file mode 100644 index 0000000000..21f639bd03 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildBuilder.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// .NET Core project builder. + /// + /// + /// Verbosity is passed as MSBuild /verbosity (not dotnet --verbosity) because dotnet msbuild forwards + /// arguments to MSBuild, which does not accept the dotnet CLI --verbosity form and fails with MSB1016. + /// + public sealed class DotNetMSBuildBuilder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetMSBuildBuilder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Build the project using the specified path and settings. + /// + /// The target project path. + /// The settings. + /// The action to invoke with the standard output. + /// + /// Calls Run directly so that dotnet --verbosity is not appended (it would cause MSB1016); + /// verbosity is passed as MSBuild /verbosity in the arguments instead. + /// + public void Build(string projectOrDirectory, DotNetMSBuildSettings settings, Action> standardOutputAction) + { + ArgumentNullException.ThrowIfNull(settings); + + var arguments = GetArguments(projectOrDirectory, settings); + Run( + settings, + arguments, + standardOutputAction == null ? null : new ProcessSettings { RedirectStandardOutput = true }, + standardOutputAction == null ? null : new Action(process => standardOutputAction(process.GetStandardOutput()))); + } + + private ProcessArgumentBuilder GetArguments(string projectOrDirectory, DotNetMSBuildSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("msbuild"); + + // add msbuild settings + builder.AppendMSBuildSettings(settings, _environment, false); + + // Specific path? + if (projectOrDirectory != null) + { + builder.AppendQuoted(projectOrDirectory); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs new file mode 100644 index 0000000000..404fc49643 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettings.cs @@ -0,0 +1,274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Common.Tools.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Contains settings used by . + /// + public class DotNetMSBuildSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to show detailed information at the end of the build log about the configurations that were built and how they were scheduled to nodes. + /// + public bool DetailedSummary { get; set; } + + /// + /// Gets or sets extensions to ignore when determining which project file to build. + /// + public ICollection IgnoreProjectExtensions { get; set; } + + /// + /// Gets or sets the maximum number of concurrent processes to use when building. + /// + /// + /// If you don't include this switch, the default value is 1. If you specifying a value that is zero or less, MSBuild will use up to the number of processors in the computer. + /// + public int? MaxCpuCount { get; set; } + + /// + /// Gets or sets a value indicating whether to exclude any MSBuild.rsp files automatically. + /// + public bool ExcludeAutoResponseFiles { get; set; } + + /// + /// Gets or sets a value indicating whether to display the startup banner and the copyright message. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets the default value of all the version numbers embedded in the build output. + /// + public string Version + { + get => GetPropertyValueOrDefault("Version"); + set => this.WithProperty("Version", value); + } + + /// + /// Gets or sets the base version number embedded in the build output. + /// + public string VersionPrefix + { + get => GetPropertyValueOrDefault("VersionPrefix"); + set => this.WithProperty("VersionPrefix", value); + } + + /// + /// Gets or sets the pre-release label of the version number embedded in the build output. + /// + public string VersionSuffix + { + get => GetPropertyValueOrDefault("VersionSuffix"); + set => this.WithProperty("VersionSuffix", value); + } + + /// + /// Gets or sets the file version number embedded in the build output. + /// + public string FileVersion + { + get => GetPropertyValueOrDefault("FileVersion"); + set => this.WithProperty("FileVersion", value); + } + + /// + /// Gets or sets the assembly version number embedded in the build output. + /// + public string AssemblyVersion + { + get => GetPropertyValueOrDefault("AssemblyVersion"); + set => this.WithProperty("AssemblyVersion", value); + } + + /// + /// Gets or sets the assembly informational version number embedded in the build output. + /// + public string InformationalVersion + { + get => GetPropertyValueOrDefault("InformationalVersion"); + set => this.WithProperty("InformationalVersion", value); + } + + /// + /// Gets or sets the version number of the NuGet package generated. + /// + public string PackageVersion + { + get => GetPropertyValueOrDefault("PackageVersion"); + set => this.WithProperty("PackageVersion", value); + } + + /// + /// Gets or sets the release notes of the NuGet package generated. + /// + public string PackageReleaseNotes + { + get => GetPropertyValueOrDefault("PackageReleaseNotes"); + set => this.WithProperty("PackageReleaseNotes", value); + } + + /// + /// Gets or sets a value indicating whether to normalize stored file paths used when producing deterministic builds. + /// + /// + /// For more information see https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/#deterministic-builds. + /// + public bool? ContinuousIntegrationBuild { get; set; } + + /// + /// Gets the project-level properties to set or override. + /// + public IDictionary> Properties { get; } + + /// + /// Gets the targets to build in the project. + /// + /// + /// If you specify any targets, they are run instead of any targets in the DefaultTargets attribute in the project file. + /// + public ICollection Targets { get; } + + /// + /// Gets the properties to retrieve. + /// + /// The properties to retrieve. + /// For more information, refer to Evaluate items and properties and display results of targets. + public HashSet GetProperties { get; } + + /// + /// Gets the items to retrieve. + /// + /// The items to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetItems { get; } + + /// + /// Gets the target results to retrieve. + /// + /// The target results to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetTargetResults { get; } + + /// + /// Gets or sets the version of the Toolset to use to build the project. + /// + public MSBuildVersion? ToolVersion { get; set; } + + /// + /// Gets or sets a value indicating whether to validate the project file and, if validation succeeds, build the project. + /// + public bool ValidateProjectFile { get; set; } + + /// + /// Gets the response files to use. + /// + /// + /// A response file is a text file that is used to insert command-line switches. For more information see https://docs.microsoft.com/en-gb/visualstudio/msbuild/msbuild-response-files. + /// + public ICollection ResponseFiles { get; } + + /// + /// Gets or sets a value indicating whether to log the build output of each MSBuild node to its own file. + /// + /// + /// The initial location for these files is the current directory. By default, the files are named "MSBuildNodeId.log". You can use the /fileLoggerParameters switch to specify the location of the files and other parameters for the fileLogger. + /// If you name a log file by using the /fileLoggerParameters switch, the distributed logger will use that name as a template and append the node ID to that name when creating a log file for each node. + /// + public bool DistributedFileLogger { get; set; } + + /// + /// Gets the distributed loggers to use. + /// + /// + /// A distributed logger consists of a central and forwarding logger. MSBuild will attach an instance of the forwarding logger to each secondary node. + /// For more information see https://msdn.microsoft.com/en-us/library/bb383987.aspx. + /// + public ICollection DistributedLoggers { get; } + + /// + /// Gets or sets the parameters for the console logger. + /// + public MSBuildLoggerSettings ConsoleLoggerSettings { get; set; } + + /// + /// Gets the file loggers to use. + /// + public ICollection FileLoggers { get; } + + /// + /// Gets or sets the binary logging options. + /// + public MSBuildBinaryLoggerSettings BinaryLogger { get; set; } + + /// + /// Gets the loggers to use to log events from MSBuild. + /// + public ICollection Loggers { get; } + + /// + /// Gets or sets a value indicating whether to disable the default console logger, and not log events to the console. + /// + public bool DisableConsoleLogger { get; set; } + + /// + /// Gets the warning codes to treats as errors. + /// + /// + /// When a warning is treated as an error the target will continue to execute as if it was a warning but the overall build will fail. + /// + public IList WarningCodesAsError { get; } + + /// + /// Gets or sets a value indicating how all warnings should be treated. + /// + public MSBuildTreatAllWarningsAs TreatAllWarningsAs { get; set; } + + /// + /// Gets the warning codes to treats as low importance messages. + /// + public IList WarningCodesAsMessage { get; } + + /// + /// Gets or sets a value indicating whether or not node reuse is used. + /// When you’re doing multiple builds in a row, this helps reduce your total build time, + /// by avoiding the start up costs of each MSBuild child node. + /// + public bool? NodeReuse { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public DotNetMSBuildSettings() + { + Properties = new Dictionary>(StringComparer.OrdinalIgnoreCase); + Targets = new List(); + GetProperties = new HashSet(StringComparer.OrdinalIgnoreCase); + GetItems = new HashSet(StringComparer.OrdinalIgnoreCase); + GetTargetResults = new HashSet(StringComparer.OrdinalIgnoreCase); + ResponseFiles = new List(); + DistributedLoggers = new List(); + FileLoggers = new List(); + Loggers = new List(); + WarningCodesAsError = new List(); + WarningCodesAsMessage = new List(); + IgnoreProjectExtensions = new List(); + } + + private string GetPropertyValueOrDefault(string propertyName, string @default = null) + { + if (!Properties.TryGetValue(propertyName, out var propertyValues)) + { + return @default; + } + + return string.Join(';', propertyValues); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs new file mode 100644 index 0000000000..19ca53e3fc --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/DotNetMSBuildSettingsExtensions.cs @@ -0,0 +1,633 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Common.Tools.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Contains functionality related to .NET MSBuild settings. + /// + public static class DotNetMSBuildSettingsExtensions + { + /// + /// Adds a MSBuild target to the configuration. + /// + /// The settings. + /// The MSBuild target. + /// The same instance so that multiple calls can be chained. + /// Ignores a target if already added. + public static DotNetMSBuildSettings WithTarget(this DotNetMSBuildSettings settings, string target) + { + EnsureSettings(settings); + + if (!settings.Targets.Contains(target)) + { + settings.Targets.Add(target); + } + + return settings; + } + + /// + /// Adds a property to the configuration. + /// + /// The settings. + /// The property name. + /// The property values. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithProperty(this DotNetMSBuildSettings settings, string name, params string[] values) + { + EnsureSettings(settings); + + ICollection currentValue; + + // try to get existing values of properties and add new property values + currentValue = settings.Properties.TryGetValue(name, out currentValue) && currentValue != null + ? currentValue.Concat(values).ToList() + : new List(values); + + settings.Properties[name] = currentValue; + + return settings; + } + + /// + /// Adds a property to retrieve the value. + /// + /// The settings. + /// The name of the property to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetProperty(this DotNetMSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetProperties.Add(name); + + return settings; + } + + /// + /// Adds a item to retrieve the value. + /// + /// The settings. + /// The name of the item to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetItem(this DotNetMSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetItems.Add(name); + + return settings; + } + + /// + /// Adds a target to retrieve the result. + /// + /// The settings. + /// The name of the target to retrieve the result. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithGetTargetResult(this DotNetMSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetTargetResults.Add(name); + + return settings; + } + + /// + /// Shows detailed information at the end of the build log about the configurations that were built and how they were scheduled to nodes. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings ShowDetailedSummary(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.DetailedSummary = true; + return settings; + } + + /// + /// Adds a extension to ignore when determining which project file to build. + /// + /// The settings. + /// The extension to ignore. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithIgnoredProjectExtension(this DotNetMSBuildSettings settings, string extension) + { + EnsureSettings(settings); + + settings.IgnoreProjectExtensions.Add(extension); + return settings; + } + + /// + /// Sets the maximum CPU count. Without this set MSBuild will compile projects in this solution one at a time. + /// + /// The settings. + /// The maximum CPU count. Set this value to zero to use as many MSBuild processes as available CPUs. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetMaxCpuCount(this DotNetMSBuildSettings settings, int? maxCpuCount) + { + EnsureSettings(settings); + + settings.MaxCpuCount = maxCpuCount.HasValue + ? Math.Max(0, maxCpuCount.Value) + : 0; + return settings; + } + + /// + /// Exclude any MSBuild.rsp files automatically. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings ExcludeAutoResponseFiles(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.ExcludeAutoResponseFiles = true; + return settings; + } + + /// + /// Hide the startup banner and the copyright message. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings HideLogo(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.NoLogo = true; + return settings; + } + + /// + /// Sets a value indicating whether to normalize stored file paths used when producing deterministic builds. + /// + /// + /// For more information see https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/#deterministic-builds. + /// + /// The settings. + /// A value indicating whether to normalize stored file paths used when producing deterministic builds. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetContinuousIntegrationBuild(this DotNetMSBuildSettings settings, bool? continuousIntegrationBuild = true) + { + EnsureSettings(settings); + + settings.ContinuousIntegrationBuild = continuousIntegrationBuild; + return settings; + } + + /// + /// Sets the version of the Toolset to use to build the project. + /// + /// The settings. + /// The version. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings UseToolVersion(this DotNetMSBuildSettings settings, MSBuildVersion version) + { + EnsureSettings(settings); + + settings.ToolVersion = version; + return settings; + } + + /// + /// Validate the project file and, if validation succeeds, build the project. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings ValidateProjectFile(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.ValidateProjectFile = true; + return settings; + } + + /// + /// Adds a response file to use. + /// + /// The settings. + /// The response file to add. + /// The same instance so that multiple calls can be chained. + /// + /// A response file is a text file that is used to insert command-line switches. For more information see https://docs.microsoft.com/en-gb/visualstudio/msbuild/msbuild-response-files. + /// + public static DotNetMSBuildSettings WithResponseFile(this DotNetMSBuildSettings settings, FilePath responseFile) + { + EnsureSettings(settings); + + settings.ResponseFiles.Add(responseFile); + return settings; + } + + /// + /// Log the build output of each MSBuild node to its own file. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings UseDistributedFileLogger(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.DistributedFileLogger = true; + return settings; + } + + /// + /// Adds a distributed loggers to use. + /// + /// The settings. + /// The response file to add. + /// The same instance so that multiple calls can be chained. + /// + /// A distributed logger consists of a central and forwarding logger. MSBuild will attach an instance of the forwarding logger to each secondary node. + /// For more information see https://msdn.microsoft.com/en-us/library/bb383987.aspx. + /// + public static DotNetMSBuildSettings WithDistributedLogger(this DotNetMSBuildSettings settings, MSBuildDistributedLogger logger) + { + EnsureSettings(settings); + + settings.DistributedLoggers.Add(logger); + return settings; + } + + /// + /// Sets the parameters for the console logger. + /// + /// The settings. + /// The console logger parameters to set. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetConsoleLoggerSettings(this DotNetMSBuildSettings settings, MSBuildLoggerSettings consoleLoggerParameters) + { + EnsureSettings(settings); + + settings.ConsoleLoggerSettings = consoleLoggerParameters ?? throw new ArgumentNullException(nameof(consoleLoggerParameters)); + + return settings; + } + + /// + /// Adds a file logger with all the default settings. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + /// + /// Each file logger will be declared in the order added. + /// The first file logger will match up to the /fl parameter. + /// The next nine (max) file loggers will match up to the /fl1 through /fl9 respectively. + /// + public static DotNetMSBuildSettings AddFileLogger(this DotNetMSBuildSettings settings) + { + return AddFileLogger(settings, new MSBuildFileLoggerSettings()); + } + + /// + /// Adds a file logger. + /// + /// The settings. + /// Parameters to be passed to the logger. + /// The same instance so that multiple calls can be chained. + /// + /// Each file logger will be declared in the order added. + /// The first file logger will match up to the /fl parameter. + /// The next nine (max) file loggers will match up to the /fl1 through /fl9 respectively. + /// + public static DotNetMSBuildSettings AddFileLogger(this DotNetMSBuildSettings settings, MSBuildFileLoggerSettings fileLoggerParameters) + { + EnsureSettings(settings); + + ArgumentNullException.ThrowIfNull(fileLoggerParameters); + + settings.FileLoggers.Add(fileLoggerParameters); + + return settings; + } + + /// + /// Enables the binary logger with all the default settings. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings EnableBinaryLogger(this DotNetMSBuildSettings settings) + { + return EnableBinaryLogger(settings, MSBuildBinaryLoggerImports.Unspecified); + } + + /// + /// Enables the binary logger with the specified imports and default file name. + /// + /// The settings. + /// The imports. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings EnableBinaryLogger(this DotNetMSBuildSettings settings, MSBuildBinaryLoggerImports imports) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.BinaryLogger = new MSBuildBinaryLoggerSettings + { + Enabled = true, + Imports = imports, + }; + + return settings; + } + + /// + /// Enables the binary logger with the specified log file name and no imports. + /// + /// The settings. + /// The log file name. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings EnableBinaryLogger(this DotNetMSBuildSettings settings, string fileName) + { + return EnableBinaryLogger(settings, fileName, MSBuildBinaryLoggerImports.Unspecified); + } + + /// + /// Enables the binary logger with the specified log file name and imports. + /// + /// The settings. + /// The log file name. + /// The imports. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings EnableBinaryLogger(this DotNetMSBuildSettings settings, string fileName, MSBuildBinaryLoggerImports imports) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.BinaryLogger = new MSBuildBinaryLoggerSettings + { + Enabled = true, + FileName = fileName, + Imports = imports, + }; + + return settings; + } + + /// + /// Adds a custom logger. + /// + /// The settings. + /// The assembly containing the logger. Should match the format {AssemblyName[,StrongName] | AssemblyFile}. + /// The class implementing the logger. Should match the format [PartialOrFullNamespace.]LoggerClassName. If the assembly contains only one logger, class does not need to be specified. + /// Parameters to be passed to the logger. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings WithLogger(this DotNetMSBuildSettings settings, string loggerAssembly, string loggerClass = null, string loggerParameters = null) + { + EnsureSettings(settings); + + if (string.IsNullOrWhiteSpace(loggerAssembly)) + { + throw new ArgumentException(nameof(loggerAssembly)); + } + + settings.Loggers.Add(new MSBuildLogger + { + Assembly = loggerAssembly, + Class = loggerClass, + Parameters = loggerParameters + }); + + return settings; + } + + /// + /// Disables the default console logger, and not log events to the console. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings DisableConsoleLogger(this DotNetMSBuildSettings settings) + { + EnsureSettings(settings); + + settings.DisableConsoleLogger = true; + + return settings; + } + + /// + /// Sets the warning code to treats as an error. + /// + /// The settings. + /// The warning code to treat as an error. + /// The same instance so that multiple calls can be chained. + /// + /// When a warning is treated as an error the target will continue to execute as if it was a warning but the overall build will fail. + /// + public static DotNetMSBuildSettings SetWarningCodeAsError(this DotNetMSBuildSettings settings, string warningCode) + { + EnsureSettings(settings); + + if (string.IsNullOrWhiteSpace(warningCode)) + { + throw new ArgumentException("Warning code cannot be null or empty", nameof(warningCode)); + } + + settings.WarningCodesAsError.Add(warningCode); + + return settings; + } + + /// + /// Sets the warning code to treats as a message. + /// + /// The settings. + /// The warning code to treat as a message. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetWarningCodeAsMessage(this DotNetMSBuildSettings settings, string warningCode) + { + EnsureSettings(settings); + + if (string.IsNullOrWhiteSpace(warningCode)) + { + throw new ArgumentException("Warning code cannot be null or empty", nameof(warningCode)); + } + + settings.WarningCodesAsMessage.Add(warningCode); + + return settings; + } + + /// + /// Sets how all warnings should be treated. + /// + /// The settings. + /// How all warning should be treated. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings TreatAllWarningsAs(this DotNetMSBuildSettings settings, MSBuildTreatAllWarningsAs behaviour) + { + EnsureSettings(settings); + + settings.TreatAllWarningsAs = behaviour; + + return settings; + } + + /// + /// Sets whether or not node reuse should be enabled. + /// + /// The settings. + /// true if node reuse should be enabled; otherwise false. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetNodeReuse(this DotNetMSBuildSettings settings, bool reuse) + { + EnsureSettings(settings); + + settings.NodeReuse = reuse; + return settings; + } + + /// + /// Sets the configuration. + /// + /// The settings. + /// The configuration. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetConfiguration(this DotNetMSBuildSettings settings, string configuration) + => settings.WithProperty("configuration", configuration); + + /// + /// Sets the version. + /// + /// The settings. + /// The version. + /// The same instance so that multiple calls can be chained. + /// + /// Version will override VersionPrefix and VersionSuffix if set. + /// This may also override version settings during packaging. + /// + public static DotNetMSBuildSettings SetVersion(this DotNetMSBuildSettings settings, string version) + => settings.WithProperty("Version", version); + + /// + /// Sets the file version. + /// + /// The settings. + /// The file version. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetFileVersion(this DotNetMSBuildSettings settings, string fileVersion) + => settings.WithProperty("FileVersion", fileVersion); + + /// + /// Sets the assembly version. + /// + /// The settings. + /// The assembly version. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetAssemblyVersion(this DotNetMSBuildSettings settings, string assemblyVersion) + => settings.WithProperty("AssemblyVersion", assemblyVersion); + + /// + /// Sets the informational version. + /// + /// The settings. + /// The informational version. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetInformationalVersion(this DotNetMSBuildSettings settings, string informationalVersion) + => settings.WithProperty("InformationalVersion", informationalVersion); + + /// + /// Sets the package version. + /// + /// The settings. + /// The package version. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetPackageVersion(this DotNetMSBuildSettings settings, string packageVersion) + => settings.WithProperty("PackageVersion", packageVersion); + + /// + /// Sets the package release notes. + /// + /// The settings. + /// The package release notes. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetPackageReleaseNotes(this DotNetMSBuildSettings settings, string packageReleaseNotes) + => settings.WithProperty("PackageReleaseNotes", packageReleaseNotes); + + /// + /// Suppress warning CS7035. + /// This is useful when using semantic versioning and either the file or informational version + /// doesn't match the recommended format. + /// The recommended format is: major.minor.build.revision where + /// each is an integer between 0 and 65534 (inclusive). + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SuppressVersionRecommendedFormatWarning(this DotNetMSBuildSettings settings) + => settings.WithProperty("nowarn", "7035"); + + /// + /// Sets the version prefix. + /// + /// The settings. + /// The version prefix. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetVersionPrefix(this DotNetMSBuildSettings settings, string versionPrefix) + => settings.WithProperty("VersionPrefix", versionPrefix); + + /// + /// Sets the version Suffix. + /// + /// The settings. + /// The version prefix. + /// The same instance so that multiple calls can be chained. + public static DotNetMSBuildSettings SetVersionSuffix(this DotNetMSBuildSettings settings, string versionSuffix) + => settings.WithProperty("VersionSuffix", versionSuffix); + + /// + /// Adds a framework to target. + /// + /// The settings. + /// The framework to target. + /// The same instance so that multiple calls can be chained. + /// + /// For list of target frameworks see https://docs.microsoft.com/en-us/dotnet/standard/frameworks. + /// + public static DotNetMSBuildSettings SetTargetFramework(this DotNetMSBuildSettings settings, string targetFramework) + => settings.WithProperty("TargetFrameworks", targetFramework); + + /// + /// Sets a target operating systems where the application or assembly will run. + /// + /// The settings. + /// The runtime id of the operating system. + /// The same instance so that multiple calls can be chained. + /// + /// For list of runtime ids see https://docs.microsoft.com/en-us/dotnet/core/rid-catalog. + /// + public static DotNetMSBuildSettings SetRuntime(this DotNetMSBuildSettings settings, string runtimeId) + => settings.WithProperty("RuntimeIdentifiers", runtimeId); + + private static void EnsureSettings(DotNetMSBuildSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs new file mode 100644 index 0000000000..65f77a5772 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildArgumentBuilderExtensions.cs @@ -0,0 +1,482 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Cake.Common.Tools.MSBuild; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Contains functionality related to MSBuild arguments. + /// + public static class MSBuildArgumentBuilderExtensions + { + /// + /// Adds MSBuild arguments. + /// + /// Argument builder. + /// MSBuild settings to add. + /// The environment. + /// Throws if 10 or more file loggers specified. + public static void AppendMSBuildSettings(this ProcessArgumentBuilder builder, DotNetMSBuildSettings settings, ICakeEnvironment environment) + => builder.AppendMSBuildSettings(settings, environment, true); + + /// + /// Adds MSBuild arguments. + /// + /// Argument builder. + /// MSBuild settings to add. + /// The environment. + /// The flag for if argument customization should be invoked. + /// Throws if 10 or more file loggers specified. + public static void AppendMSBuildSettings(this ProcessArgumentBuilder builder, DotNetMSBuildSettings settings, ICakeEnvironment environment, bool invokeArgumentCustomization) + { + ArgumentNullException.ThrowIfNull(builder); + + var msBuilder = new ProcessArgumentBuilder(); + + // Verbosity: use MSBuild /verbosity switch (dotnet msbuild does not support --verbosity; it forwards to MSBuild which expects /verbosity:x) + if (settings.Verbosity.HasValue) + { + msBuilder.AppendMSBuildSwitch("verbosity", GetMSBuildVerbosityValue(settings.Verbosity.Value)); + } + + // Got any targets? + if (settings.Targets.Any()) + { + if (settings.Targets.All(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Specify the name of the target", nameof(settings.Targets)); + } + + msBuilder.AppendMSBuildSwitch("target", string.Join(';', settings.Targets)); + } + + // Got any properties? + foreach (var property in settings.Properties) + { + if (property.Value == null || property.Value.All(value => value == null)) + { + throw new ArgumentException("A property must have at least one non-null value", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("property", $"{property.Key}={property.BuildMSBuildPropertyParameterString()}"); + } + + // Got any properties to retrieve? + foreach (var property in settings.GetProperties) + { + if (property == null || string.IsNullOrWhiteSpace(property)) + { + throw new ArgumentException("A property to retrieve must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getProperty", property); + } + + // Got any items to retrieve? + foreach (var item in settings.GetItems) + { + if (item == null || string.IsNullOrWhiteSpace(item)) + { + throw new ArgumentException("An item to retrieve must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getItem", item); + } + + // Got any target results to retrieve? + foreach (var target in settings.GetTargetResults) + { + if (target == null || string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("An target to retrieve results must have have non-empty name", nameof(settings.Properties)); + } + + msBuilder.AppendMSBuildSwitch("getTargetResult", target); + } + + // Set the maximum number of processors? + if (settings.MaxCpuCount.HasValue) + { + msBuilder.AppendMSBuildSwitchWithOptionalValue("maxcpucount", settings.MaxCpuCount.Value, maxCpuCount => maxCpuCount > 0); + } + + // use different version of msbuild? + if (settings.ToolVersion.HasValue) + { + msBuilder.AppendMSBuildSwitch("toolsversion", GetToolVersionValue(settings.ToolVersion.Value)); + } + + // configure console logger? + if (!settings.DisableConsoleLogger && settings.ConsoleLoggerSettings != null) + { + var arguments = GetLoggerSettings(settings.ConsoleLoggerSettings); + + if (arguments.Any()) + { + msBuilder.AppendMSBuildSwitch("consoleloggerparameters", arguments); + } + } + + // disable console logger? + if (settings.DisableConsoleLogger) + { + msBuilder.AppendMSBuildSwitch("noconsolelogger"); + } + + // Got any file loggers? + if (settings.FileLoggers.Any()) + { + if (settings.FileLoggers.Count >= 10) + { + throw new InvalidOperationException("Too Many FileLoggers"); + } + + var arguments = settings + .FileLoggers + .Select((logger, index) => GetLoggerArgument(index, logger, environment)) + .Where(arg => !string.IsNullOrEmpty(arg)); + + foreach (var argument in arguments) + { + msBuilder.Append(argument); + } + } + + // Use binary logging? + if (settings.BinaryLogger != null && settings.BinaryLogger.Enabled) + { + string binaryOptions = null; + if (!string.IsNullOrEmpty(settings.BinaryLogger.FileName)) + { + binaryOptions = settings.BinaryLogger.FileName.Quote(); + } + + if (settings.BinaryLogger.Imports != MSBuildBinaryLoggerImports.Unspecified) + { + if (!string.IsNullOrEmpty(binaryOptions)) + { + binaryOptions += ";"; + } + + binaryOptions = binaryOptions + "ProjectImports=" + settings.BinaryLogger.Imports; + } + + msBuilder.AppendMSBuildSwitchWithOptionalValueIfNotEmpty("binarylogger", binaryOptions); + } + + // Got any distributed loggers? + foreach (var distributedLogger in settings.DistributedLoggers) + { + msBuilder.AppendMSBuildSwitch("distributedlogger", $"{GetLoggerValue(distributedLogger.CentralLogger)}*{GetLoggerValue(distributedLogger.ForwardingLogger)}"); + } + + // use a file logger for each node? + if (settings.DistributedFileLogger) + { + msBuilder.AppendMSBuildSwitch("distributedfilelogger"); + } + + // Got any loggers? + foreach (var logger in settings.Loggers) + { + msBuilder.AppendMSBuildSwitch("logger", GetLoggerValue(logger)); + } + + var showWarningsAsError = settings.TreatAllWarningsAs == MSBuildTreatAllWarningsAs.Error || settings.WarningCodesAsError.Any(); + var showWarningsAsMessages = settings.TreatAllWarningsAs == MSBuildTreatAllWarningsAs.Message || settings.WarningCodesAsMessage.Any(); + + // Treat all or some warnings as errors? + if (showWarningsAsError) + { + msBuilder.AppendMSBuildSwitchWithOptionalValueIfNotEmpty("warnaserror", GetWarningCodes(settings.TreatAllWarningsAs == MSBuildTreatAllWarningsAs.Error, settings.WarningCodesAsError)); + } + + // Treat all or some warnings as messages? + if (showWarningsAsMessages) + { + msBuilder.AppendMSBuildSwitchWithOptionalValueIfNotEmpty("warnasmessage", GetWarningCodes(settings.TreatAllWarningsAs == MSBuildTreatAllWarningsAs.Message, settings.WarningCodesAsMessage)); + } + + // set project file extensions to ignore when searching for project file + if (settings.IgnoreProjectExtensions.Any()) + { + msBuilder.AppendMSBuildSwitch("ignoreprojectextensions", string.Join(',', settings.IgnoreProjectExtensions)); + } + + // detailed summary? + if (settings.DetailedSummary) + { + msBuilder.AppendMSBuildSwitch("detailedsummary"); + } + + // Include response files? + foreach (var responseFile in settings.ResponseFiles) + { + msBuilder.AppendSwitchQuoted("@", string.Empty, responseFile.MakeAbsolute(environment).FullPath); + } + + // exclude auto response files? + if (settings.ExcludeAutoResponseFiles) + { + msBuilder.AppendMSBuildSwitch("noautoresponse"); + } + + // don't output MSBuild logo? + if (settings.NoLogo) + { + msBuilder.AppendMSBuildSwitch("nologo"); + } + + // Set Continuous Integration Build? + if (settings.ContinuousIntegrationBuild.HasValue) + { + var continuousIntegrationBuild = settings.ContinuousIntegrationBuild.Value ? "true" : "false"; + msBuilder.AppendMSBuildSwitch("property", $"ContinuousIntegrationBuild={continuousIntegrationBuild}"); + } + + // Re-use of MSBuild nodes? + if (settings.NodeReuse.HasValue) + { + msBuilder.Append(string.Concat("/nodeReuse:", settings.NodeReuse.Value ? "true" : "false")); + } + + builder.AppendRange( + invokeArgumentCustomization + ? settings.ArgumentCustomization?.Invoke(msBuilder) ?? msBuilder + : msBuilder); + } + + private static string GetLoggerValue(MSBuildLogger logger) + { + if (string.IsNullOrWhiteSpace(logger.Assembly)) + { + throw new ArgumentNullException(nameof(logger.Assembly), "Assembly must be a strong name or file"); + } + + var argumentBuilder = new StringBuilder(); + if (!string.IsNullOrWhiteSpace(logger.Class)) + { + argumentBuilder.Append(logger.Class); + argumentBuilder.Append(','); + argumentBuilder.Append(logger.Assembly); + } + else + { + argumentBuilder.Append(logger.Assembly?.Quote()); + } + + if (!string.IsNullOrWhiteSpace(logger.Parameters)) + { + argumentBuilder.Append(';'); + argumentBuilder.Append(logger.Parameters); + } + + return argumentBuilder.ToString(); + } + + private static string GetLoggerArgument(int index, MSBuildFileLoggerSettings logger, ICakeEnvironment environment) + { + var parameters = GetLoggerSettings(logger, environment); + if (string.IsNullOrWhiteSpace(parameters)) + { + return string.Empty; + } + + var counter = index == 0 ? string.Empty : index.ToString(); + return $"/fileLogger{counter} /fileloggerparameters{counter}:{parameters}"; + } + + /// + /// Maps to MSBuild verbosity string (quiet, minimal, normal, detailed, diagnostic). + /// + private static string GetMSBuildVerbosityValue(DotNet.DotNetVerbosity verbosity) + => verbosity switch + { + DotNet.DotNetVerbosity.Quiet => "quiet", + DotNet.DotNetVerbosity.Minimal => "minimal", + DotNet.DotNetVerbosity.Normal => "normal", + DotNet.DotNetVerbosity.Detailed => "detailed", + DotNet.DotNetVerbosity.Diagnostic => "diagnostic", + _ => throw new ArgumentOutOfRangeException(nameof(verbosity), verbosity, "Invalid value"), + }; + + private static string GetToolVersionValue(MSBuildVersion toolVersion) + { + switch (toolVersion) + { + case MSBuildVersion.MSBuild20: + return "2.0"; + case MSBuildVersion.MSBuild35: + return "3.5"; + case MSBuildVersion.MSBuild4: + return "4.0"; + case MSBuildVersion.MSBuild12: + return "12.0"; + case MSBuildVersion.MSBuild14: + return "14.0"; + case MSBuildVersion.MSBuild15: + return "15.0"; + case MSBuildVersion.MSBuild16: + return "16.0"; + case MSBuildVersion.MSBuild17: + return "17.0"; + case MSBuildVersion.MSBuild18: + return "18.0"; + default: + throw new ArgumentOutOfRangeException(nameof(toolVersion), toolVersion, "Invalid value"); + } + } + + private static string GetLoggerSettings(MSBuildFileLoggerSettings loggerSettings, ICakeEnvironment environment) + { + var settings = new List(); + + var commonArguments = GetLoggerSettings(loggerSettings); + + if (commonArguments.Any()) + { + settings.Add(commonArguments); + } + + if (loggerSettings.LogFile != null) + { + var filePath = string.IsNullOrWhiteSpace(loggerSettings.LogFile) + ? string.Empty + : FilePath.FromString(loggerSettings.LogFile).MakeAbsolute(environment).ToString(); + + settings.Add($"LogFile=\"{filePath}\""); + } + + if (loggerSettings.AppendToLogFile) + { + settings.Add("Append"); + } + + if (!string.IsNullOrWhiteSpace(loggerSettings.FileEncoding)) + { + settings.Add($"Encoding={loggerSettings.FileEncoding}"); + } + + return string.Join(';', settings); + } + + private static string GetLoggerSettings(MSBuildLoggerSettings loggerSettings) + { + var settings = new List(); + + if (loggerSettings.PerformanceSummary) + { + settings.Add("PerformanceSummary"); + } + + if (loggerSettings.NoSummary) + { + settings.Add("NoSummary"); + } + + if (loggerSettings.SummaryOutputLevel != MSBuildLoggerOutputLevel.Default) + { + switch (loggerSettings.SummaryOutputLevel) + { + case MSBuildLoggerOutputLevel.WarningsOnly: + settings.Add("WarningsOnly"); + break; + case MSBuildLoggerOutputLevel.ErrorsOnly: + settings.Add("ErrorsOnly"); + break; + } + } + + if (loggerSettings.HideItemAndPropertyList) + { + settings.Add("NoItemAndPropertyList"); + } + + if (loggerSettings.ShowCommandLine) + { + settings.Add("ShowCommandLine"); + } + + if (loggerSettings.ShowTimestamp) + { + settings.Add("ShowTimestamp"); + } + + if (loggerSettings.ShowEventId) + { + settings.Add("ShowEventId"); + } + + if (loggerSettings.ForceNoAlign) + { + settings.Add("ForceNoAlign"); + } + + if (loggerSettings.ConsoleColorType == MSBuildConsoleColorType.Disabled) + { + settings.Add("DisableConsoleColor"); + } + + if (loggerSettings.ConsoleColorType == MSBuildConsoleColorType.ForceAnsi) + { + settings.Add("ForceConsoleColor"); + } + + if (loggerSettings.DisableMultiprocessorLogging) + { + settings.Add("DisableMPLogging"); + } + + if (loggerSettings.Verbosity.HasValue) + { + settings.Add($"Verbosity={loggerSettings.Verbosity}"); + } + + return string.Join(';', settings); + } + + private static string GetWarningCodes(bool shouldApplyToAllWarnings, IList warningCodes) + => shouldApplyToAllWarnings + ? null + : string.Join(';', warningCodes); + + private static void AppendMSBuildSwitch(this ProcessArgumentBuilder builder, string @switch) + => builder.Append($"/{@switch}"); + + private static void AppendMSBuildSwitch(this ProcessArgumentBuilder builder, string @switch, string value) + => builder.AppendSwitch($"/{@switch}", ":", value); + + private static void AppendMSBuildSwitchQuoted(this ProcessArgumentBuilder builder, string @switch, string value) + => builder.AppendSwitchQuoted($"/{@switch}", ":", value); + + private static void AppendMSBuildSwitchWithOptionalValueIfNotEmpty(this ProcessArgumentBuilder builder, string @switch, string value) + { + if (!string.IsNullOrWhiteSpace(value)) + { + builder.AppendMSBuildSwitch(@switch, value); + return; + } + + builder.AppendMSBuildSwitch(@switch); + } + + private static void AppendMSBuildSwitchWithOptionalValue(this ProcessArgumentBuilder builder, string @switch, T value, Func predicate) + { + if (predicate(value)) + { + builder.AppendMSBuildSwitch(@switch, value.ToString()); + return; + } + + builder.AppendMSBuildSwitch(@switch); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerImports.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerImports.cs new file mode 100644 index 0000000000..58a6633f7c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerImports.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// What files to include in the binary log. + /// + public enum MSBuildBinaryLoggerImports + { + /// Don't specify imports + Unspecified = 0, + + /// Do not collect project and imports files + None = 2, + + /// Embed in the binlog file + Embed = 3, + + /// Produce a separate .ProjectImports.zip + ZipFile = 4 + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerSettings.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerSettings.cs new file mode 100644 index 0000000000..388b7fd855 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildBinaryLoggerSettings.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// MSBuild binary logger settings used by . + /// + public class MSBuildBinaryLoggerSettings + { + /// + /// Gets or sets a value indicating whether binary logging should be enabled. + /// + public bool Enabled { get; set; } + + /// + /// Gets or sets the output filename. + /// + public string FileName { get; set; } + + /// + /// Gets or sets what source files should be included in the log. + /// + public MSBuildBinaryLoggerImports Imports { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildConsoleColorType.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildConsoleColorType.cs new file mode 100644 index 0000000000..e198476f3d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildConsoleColorType.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents how the console color should behave in a console. + /// + public enum MSBuildConsoleColorType + { + /// + /// Use the normal console color behaviour. + /// + Normal = 0, + + /// + /// Use the default console color for all logging messages. + /// + Disabled, + + /// + /// Use ANSI console colors even if console does not support it + /// + ForceAnsi + } +} diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildDistributedLogger.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildDistributedLogger.cs new file mode 100644 index 0000000000..026945dca8 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildDistributedLogger.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Common.Tools.MSBuild; + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents the Distributed Logging Model with a central logger and forwarding logger. + /// + public class MSBuildDistributedLogger + { + /// + /// Gets or sets the logger to use as the central logger. + /// + public MSBuildLogger CentralLogger { get; set; } + + /// + /// Gets or sets the logger to use as the forwarding logger. + /// + public MSBuildLogger ForwardingLogger { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildFileLoggerSettings.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildFileLoggerSettings.cs new file mode 100644 index 0000000000..7ca078b86d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildFileLoggerSettings.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents the settings for a file logger. + /// + public class MSBuildFileLoggerSettings : MSBuildLoggerSettings + { + /// + /// Gets or sets the path to the log file into which the build log is written. + /// + /// + /// An empty string will use msbuild.log, in the current directory. + /// + public string LogFile { get; set; } + + /// + /// Gets or sets a value indicating whether the build log is appended to the log file or overwrites it. + /// + public bool AppendToLogFile { get; set; } + + /// + /// Gets or sets the encoding for the file (for example, UTF-8, Unicode, or ASCII). + /// + public string FileEncoding { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerOutputLevel.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerOutputLevel.cs new file mode 100644 index 0000000000..ccb5b496d0 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerOutputLevel.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents the logging output level for a MSBuild logger. + /// + public enum MSBuildLoggerOutputLevel + { + /// + /// Show the error and warning summary at the end. + /// + Default = 0, + + /// + /// Show only warnings summary at the end. + /// + WarningsOnly, + + /// + /// Show only errors summary at the end. + /// + ErrorsOnly + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerSettings.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerSettings.cs new file mode 100644 index 0000000000..2639a4fbb2 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildLoggerSettings.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents the common settings for a logger. + /// + public class MSBuildLoggerSettings + { + /// + /// Gets or sets a value indicating whether to show the time that’s spent in tasks, targets, and projects. + /// + public bool PerformanceSummary { get; set; } + + /// + /// Gets or sets a value indicating whether to hide the error and warning summary at the end. + /// + public bool NoSummary { get; set; } + + /// + /// Gets or sets value that indicates the level of summary output at the end for the logger. + /// + /// Default is to show errors and summary. + public MSBuildLoggerOutputLevel SummaryOutputLevel { get; set; } + + /// + /// Gets or sets a value indicating whether to hide the list of items and properties that would appear at the start of each project build if the verbosity level is set to diagnostic. + /// + public bool HideItemAndPropertyList { get; set; } + + /// + /// Gets or sets a value indicating whether to show TaskCommandLineEvent messages. + /// + public bool ShowCommandLine { get; set; } + + /// + /// Gets or sets a value indicating whether to show the timestamp as a prefix to any message. + /// + public bool ShowTimestamp { get; set; } + + /// + /// Gets or sets a value indicating whether to show the event Id for each started event, finished event, and message. + /// + public bool ShowEventId { get; set; } + + /// + /// Gets or sets a value indicating whether to not align the text to the size of the console buffer. + /// + public bool ForceNoAlign { get; set; } + + /// + /// Gets or sets value that indicates the type of console color to use for all logging messages. + /// + public MSBuildConsoleColorType ConsoleColorType { get; set; } + + /// + /// Gets or sets a value indicating whether to disable the multiprocessor logging style of output when running in non-multiprocessor mode. + /// + public bool DisableMultiprocessorLogging { get; set; } + + /// + /// Gets or sets a value that overrides the /verbosity setting for this logger. + /// + public DotNetVerbosity? Verbosity { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildTreatAllWarningsAs.cs b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildTreatAllWarningsAs.cs new file mode 100644 index 0000000000..fa408f20be --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/MSBuild/MSBuildTreatAllWarningsAs.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.MSBuild +{ + /// + /// Represents how all warnings should be treated as by MSBuild. + /// + public enum MSBuildTreatAllWarningsAs + { + /// + /// Use the default MSBuild behaviour. + /// + Default = 0, + + /// + /// Treat all warnings as low importance messages. + /// + Message = 1, + + /// + /// Treat all warnings as errors. + /// + Error = 2 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleteSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleteSettings.cs new file mode 100644 index 0000000000..aafeec576e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleteSettings.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Delete +{ + /// + /// Contains settings used by . + /// + public class DotNetNuGetDeleteSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating the server URL. + /// + /// + /// Supported URLs for nuget.org include http://www.nuget.org, http://www.nuget.org/api/v3, + /// and http://www.nuget.org/api/v2/package. For private feeds, substitute the host name + /// (for example, %hostname%/api/v3). + /// + public string Source { get; set; } + + /// + /// Gets or sets a value indicating whether to append "api/v2/package" to the source URL. + /// + /// + /// Available since .NET Core 2.1 SDK. + /// + public bool NoServiceEndpoint { get; set; } + + /// + /// Gets or sets a value indicating whether to block and require manual action for operations like authentication. + /// + /// + /// Available since .NET Core 2.2 SDK. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to prompt for user input or confirmations. + /// + public bool NonInteractive { get; set; } + + /// + /// Gets or sets a value indicating the API key for the server. + /// + public string ApiKey { get; set; } + + /// + /// Gets or sets a value indicating whether to force command-line output in English. + /// + public bool ForceEnglishOutput { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleter.cs b/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleter.cs new file mode 100644 index 0000000000..fa931df6d5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Delete/DotNetNuGetDeleter.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.NuGet.Delete +{ + /// + /// .NET NuGet deleter. Deletes or unlists a package from the server. + /// + public sealed class DotNetNuGetDeleter : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetNuGetDeleter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Delete the NuGet package using the specified name, version and settings. + /// + /// The name of the target package. + /// Version of the package. + /// The settings. + public void Delete(string packageName, string version, DotNetNuGetDeleteSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(packageName, version, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageName, string packageVersion, DotNetNuGetDeleteSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget delete"); + + // Specific package and version? + if (!string.IsNullOrWhiteSpace(packageName)) + { + builder.Append(packageName); + + if (!string.IsNullOrWhiteSpace(packageVersion)) + { + builder.Append(packageVersion); + } + } + + // Source to delete package at + if (!string.IsNullOrWhiteSpace(settings.Source)) + { + builder.Append("--source"); + builder.AppendQuoted(settings.Source); + } + + // No service endpoint + if (settings.NoServiceEndpoint) + { + builder.Append("--no-service-endpoint"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // Non-Interactive + if (settings.NonInteractive) + { + builder.Append("--non-interactive"); + } + + // API Key + if (!string.IsNullOrEmpty(settings.ApiKey)) + { + builder.Append("--api-key"); + builder.AppendQuotedSecret(settings.ApiKey); + } + + // Force English Output + if (settings.ForceEnglishOutput) + { + builder.Append("--force-english-output"); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPushSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPushSettings.cs new file mode 100644 index 0000000000..8591fad9c8 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPushSettings.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Push +{ + /// + /// Contains settings used by . + /// + public class DotNetNuGetPushSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating the server URL. + /// + /// + /// This option is required unless DefaultPushSource config value is set in the NuGet config file. + /// + public string Source { get; set; } + + /// + /// Gets or sets a value indicating the symbol server URL. + /// + public string SymbolSource { get; set; } + + /// + /// Gets or sets a value indicating whether to append "api/v2/package" to the source URL. + /// + /// + /// Available since .NET Core 2.1 SDK. + /// + public bool NoServiceEndpoint { get; set; } + + /// + /// Gets or sets a value indicating whether to block and require manual action for operations like authentication. + /// + /// + /// Available since .NET Core 2.2 SDK. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating timeout for pushing to a server in seconds. + /// + /// Defaults to 300 seconds (5 minutes). Specifying 0 (zero seconds) applies the default value. + /// + /// + public int? Timeout { get; set; } + + /// + /// Gets or sets a value indicating the API key for the server. + /// + public string ApiKey { get; set; } + + /// + /// Gets or sets a value indicating the API key for the symbol server. + /// + public string SymbolApiKey { get; set; } + + /// + /// Gets or sets a value indicating whether buffering is disabled when pushing to an HTTP(S) server. + /// + /// + /// This decreases memory usage. + /// + public bool DisableBuffering { get; set; } + + /// + /// Gets or sets a value indicating whether symbols should be not be pushed if present. + /// + public bool IgnoreSymbols { get; set; } + + /// + /// Gets or sets a value indicating whether, when pushing multiple packages to an HTTP(S) server, + /// to treat any 409 Conflict response as a warning so that the push can continue. + /// + /// + /// Available since .NET Core 3.1 SDK. + /// + public bool SkipDuplicate { get; set; } + + /// + /// Gets or sets a value indicating whether to force command-line output in English. + /// + public bool ForceEnglishOutput { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPusher.cs b/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPusher.cs new file mode 100644 index 0000000000..4ecf6262ec --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Push/DotNetNuGetPusher.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.NuGet.Push +{ + /// + /// .NET NuGet pusher. Pushes a package and its symbols to the server. + /// + public sealed class DotNetNuGetPusher : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetNuGetPusher( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Push one or more NuGet package using the specified name, version and settings. + /// + /// The name of the target package. + /// The settings. + public void Push(string packageName, DotNetNuGetPushSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(packageName, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageName, DotNetNuGetPushSettings settings) + { + if (string.IsNullOrWhiteSpace(packageName)) + { + throw new ArgumentNullException(nameof(packageName)); + } + + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget push"); + + // Specific package + builder.AppendQuoted(packageName); + + // Where to push package to + if (!string.IsNullOrWhiteSpace(settings.Source)) + { + builder.Append("--source"); + builder.Append(settings.Source); + } + + // api key for source + if (!string.IsNullOrWhiteSpace(settings.ApiKey)) + { + builder.Append("--api-key"); + builder.AppendQuotedSecret(settings.ApiKey); + } + + // Where to push symbol package to + if (!string.IsNullOrWhiteSpace(settings.SymbolSource)) + { + builder.Append("--symbol-source"); + builder.Append(settings.SymbolSource); + } + + // api key for symbol source + if (!string.IsNullOrWhiteSpace(settings.SymbolApiKey)) + { + builder.Append("--symbol-api-key"); + builder.AppendQuotedSecret(settings.SymbolApiKey); + } + + // No service endpoint + if (settings.NoServiceEndpoint) + { + builder.Append("--no-service-endpoint"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // Timeout + if (settings.Timeout.HasValue) + { + builder.Append("--timeout"); + builder.Append(settings.Timeout.Value.ToString()); + } + + // Disable buffering + if (settings.DisableBuffering) + { + builder.Append("--disable-buffering"); + } + + // push symbol package? + if (settings.IgnoreSymbols) + { + builder.Append("--no-symbols"); + } + + // skip duplicate + if (settings.SkipDuplicate) + { + builder.Append("--skip-duplicate"); + } + + // Force English Output + if (settings.ForceEnglishOutput) + { + builder.Append("--force-english-output"); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetAddSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetAddSourceSettings.cs new file mode 100644 index 0000000000..2b7fd534ac --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetAddSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for adding new sources. + /// + public class DotNetNuGetAddSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetDisableSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetDisableSourceSettings.cs new file mode 100644 index 0000000000..be3d3bde68 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetDisableSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for disabling NuGet sources. + /// + public class DotNetNuGetDisableSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetEnableSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetEnableSourceSettings.cs new file mode 100644 index 0000000000..64741c6a77 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetEnableSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for enabling a NuGet source. + /// + public class DotNetNuGetEnableSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetHasSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetHasSourceSettings.cs new file mode 100644 index 0000000000..dc622b95d9 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetHasSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for checking if a NuGet source exists. + /// + public class DotNetNuGetHasSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetListSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetListSourceSettings.cs new file mode 100644 index 0000000000..4de2ed7201 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetListSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for listing NuGet sources. + /// + public class DotNetNuGetListSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetRemoveSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetRemoveSourceSettings.cs new file mode 100644 index 0000000000..f73cd4cfc7 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetRemoveSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for removing a NuGet source. + /// + public class DotNetNuGetRemoveSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourceSettings.cs new file mode 100644 index 0000000000..13c21aa8c3 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourceSettings.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by . + /// + public class DotNetNuGetSourceSettings : DotNetSettings + { + /// + /// Gets or sets the path to the package(s) source. + /// + public string Source { get; set; } + + /// + /// Gets or sets a value indicating whether this source contains sensitive data, i.e. authentication token in url. + /// + public bool IsSensitiveSource { get; set; } + + /// + /// Gets or sets the user name to be used when connecting to an authenticated source. + /// + public string UserName { get; set; } + + /// + /// Gets or sets the password to be used when connecting to an authenticated source. + /// + public string Password { get; set; } + + /// + /// Gets or sets a value indicating whether to enable storing portable package source credentials by disabling password encryption. + /// + public bool StorePasswordInClearText { get; set; } + + /// + /// Gets or sets the comma-separated list of valid authentication types for this source. + /// + /// + /// By default, all authentication types are valid. Example: basic,negotiate. + /// + public string ValidAuthenticationTypes { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// + public FilePath ConfigFile { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourcer.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourcer.cs new file mode 100644 index 0000000000..6bfdb7ab30 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetSourcer.cs @@ -0,0 +1,303 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// .NET NuGet sourcer. + /// + public sealed class DotNetNuGetSourcer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetNuGetSourcer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Add the specified NuGet source. + /// + /// The name of the source. + /// The settings. + public void AddSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + if (string.IsNullOrWhiteSpace(settings.Source)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(settings.Source)); + } + + RunCommand(settings, GetAddSourceArguments(name, settings)); + } + + /// + /// Disable the specified NuGet source. + /// + /// The name of the source. + /// The settings. + public void DisableSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetDisableSourceArguments(name, settings)); + } + + /// + /// Enable the specified NuGet source. + /// + /// The name of the source. + /// The settings. + public void EnableSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetEnableSourceArguments(name, settings)); + } + + /// + /// Determines whether the specified NuGet source exists. + /// + /// The name of the source. + /// The settings. + /// Whether the specified NuGet source exists. + public bool HasSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + var sources = ListSource("detailed", settings); + var matches = Regex.Matches(sources, @"\d+\.\s+(?.+?)\s+\[(?:Enabled|Disabled)\]", RegexOptions.IgnoreCase); + + return matches.Cast().Any(match => match.Groups["name"].Value.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Lists the NuGet sources. + /// + /// The output format. Accepts two values: detailed (the default) and short. + /// The settings. + /// The NuGet sources. + public string ListSource(string format, DotNetNuGetSourceSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + string output = null; + Run(settings, GetListSourceArguments(format, settings), new ProcessSettings { RedirectStandardOutput = true }, + process => output = string.Join(Environment.NewLine, process.GetStandardOutput())); + + return output; + } + + /// + /// Remove the specified NuGet source. + /// + /// The name of the source. + /// The settings. + public void RemoveSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetRemoveSourceArguments(name, settings)); + } + + /// + /// Update the specified NuGet source. + /// + /// The name of the source. + /// The settings. + public void UpdateSource(string name, DotNetNuGetSourceSettings settings) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetUpdateSourceArguments(name, settings)); + } + + private ProcessArgumentBuilder GetAddSourceArguments(string name, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget add source"); + if (settings.IsSensitiveSource) + { + builder.AppendQuotedSecret(settings.Source); + } + else + { + builder.AppendQuoted(settings.Source); + } + builder.Append("--name"); + builder.AppendQuoted(name); + if (!string.IsNullOrWhiteSpace(settings.UserName)) + { + builder.Append("--username"); + builder.AppendQuoted(settings.UserName); + } + if (!string.IsNullOrWhiteSpace(settings.Password)) + { + builder.Append("--password"); + builder.AppendQuotedSecret(settings.Password); + } + if (settings.StorePasswordInClearText) + { + builder.Append("--store-password-in-clear-text"); + } + if (!string.IsNullOrWhiteSpace(settings.ValidAuthenticationTypes)) + { + builder.Append("--valid-authentication-types"); + builder.AppendQuoted(settings.ValidAuthenticationTypes); + } + AddCommonArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetDisableSourceArguments(string name, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget disable source"); + builder.AppendQuoted(name); + AddCommonArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetEnableSourceArguments(string name, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget enable source"); + builder.AppendQuoted(name); + AddCommonArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetListSourceArguments(string format, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget list source"); + if (!string.IsNullOrWhiteSpace(format)) + { + builder.Append("--format"); + builder.AppendQuoted(format); + } + AddCommonArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetRemoveSourceArguments(string name, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget remove source"); + builder.AppendQuoted(name); + AddCommonArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetUpdateSourceArguments(string name, DotNetNuGetSourceSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("nuget update source"); + builder.AppendQuoted(name); + if (!string.IsNullOrWhiteSpace(settings.Source)) + { + builder.Append("--source"); + if (settings.IsSensitiveSource) + { + builder.AppendQuotedSecret(settings.Source); + } + else + { + builder.AppendQuoted(settings.Source); + } + } + if (!string.IsNullOrWhiteSpace(settings.UserName)) + { + builder.Append("--username"); + builder.AppendQuoted(settings.UserName); + } + if (!string.IsNullOrWhiteSpace(settings.Password)) + { + builder.Append("--password"); + builder.AppendQuotedSecret(settings.Password); + } + if (settings.StorePasswordInClearText) + { + builder.Append("--store-password-in-clear-text"); + } + if (!string.IsNullOrWhiteSpace(settings.ValidAuthenticationTypes)) + { + builder.Append("--valid-authentication-types"); + builder.AppendQuoted(settings.ValidAuthenticationTypes); + } + AddCommonArguments(settings, builder); + + return builder; + } + + private void AddCommonArguments(DotNetNuGetSourceSettings settings, ProcessArgumentBuilder builder) + { + if (settings.ConfigFile != null) + { + builder.Append("--configfile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetUpdateSourceSettings.cs b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetUpdateSourceSettings.cs new file mode 100644 index 0000000000..e2573e1803 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/NuGet/Source/DotNetNuGetUpdateSourceSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.NuGet.Source +{ + /// + /// Contains settings used by for updating a NuGet source. + /// + public class DotNetNuGetUpdateSourceSettings : DotNetNuGetSourceSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Pack/DotNetPackSettings.cs b/src/Cake.Common/Tools/DotNet/Pack/DotNetPackSettings.cs new file mode 100644 index 0000000000..fae1a24c07 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Pack/DotNetPackSettings.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Pack +{ + /// + /// Contains settings used by . + /// + public class DotNetPackSettings : DotNetSettings + { + /// + /// Gets or sets the output directory. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. + /// + public string VersionSuffix { get; set; } + + /// + /// Gets or sets a value indicating whether to not build project before packing. + /// + public bool NoBuild { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore project to project references and only build the root project. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes build faster, but requires restore to be done before build is executed. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to display the startup banner or the copyright message. + /// + /// + /// Available since .NET Core 3.0 SDK. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets a value indicating whether to generate the symbols package. + /// + public bool IncludeSymbols { get; set; } + + /// + /// Gets or sets the symbol package format. + /// + /// The symbol package format. + public string SymbolPackageFormat { get; set; } + + /// + /// Gets or sets a value indicating whether to includes the source files in the NuGet package. + /// The sources files are included in the src folder within the nupkg. + /// + public bool IncludeSource { get; set; } + + /// + /// Gets or sets a value indicating whether to set the serviceable flag in the package. + /// + /// + /// For more information, see https://aka.ms/nupkgservicing. + /// + public bool Serviceable { get; set; } + + /// + /// Gets or sets the target runtime. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets the specified NuGet package sources to use during the packing. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Pack/DotNetPacker.cs b/src/Cake.Common/Tools/DotNet/Pack/DotNetPacker.cs new file mode 100644 index 0000000000..5b0a66bd95 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Pack/DotNetPacker.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Pack +{ + /// + /// .NET project packer. + /// + public sealed class DotNetPacker : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPacker( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Pack the project using the specified path and settings. + /// + /// The target file path. + /// The settings. + public void Pack(string project, DotNetPackSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetPackSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("pack"); + + // Specific path? + if (project != null) + { + builder.AppendQuoted(project); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.Append("--output"); + builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // No build + if (settings.NoBuild) + { + builder.Append("--no-build"); + } + + // No Dependencies + if (settings.NoDependencies) + { + builder.Append("--no-dependencies"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // No Logo + if (settings.NoLogo) + { + builder.Append("--nologo"); + } + + // Include symbols + if (settings.IncludeSymbols) + { + builder.Append("--include-symbols"); + } + + // Symbol package format + if (!string.IsNullOrWhiteSpace(settings.SymbolPackageFormat)) + { + builder.Append(string.Concat("-p:SymbolPackageFormat=", settings.SymbolPackageFormat)); + } + + // Include source + if (settings.IncludeSource) + { + builder.Append("--include-source"); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // Version suffix + if (!string.IsNullOrEmpty(settings.VersionSuffix)) + { + builder.Append("--version-suffix"); + builder.Append(settings.VersionSuffix); + } + + // Serviceable + if (settings.Serviceable) + { + builder.Append("--serviceable"); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.cs b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.cs new file mode 100644 index 0000000000..cd52cc571e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAddSettings.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Package.Add +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetPackageAddSettings : DotNetSettings + { + /// + /// Gets or sets a specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes run faster, but requires restore to be done before run is executed. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets the directory path where to restore the packages. + /// The default package restore location is %userprofile%\.nuget\packages on Windows and ~/.nuget/packages on macOS and Linux. + /// + public DirectoryPath PackageDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to allow installation of prerelease packages. + /// Available since .NET Core 5 SDK. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use during the restore operation. + /// + public string Source { get; set; } + + /// + /// Gets or sets the version of the package to install. + /// If none specified, the latest will be used. + /// + public string Version { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs new file mode 100644 index 0000000000..99674273c7 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Add/DotNetPackageAdder.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Package.Add +{ + /// + /// .NET package adder. + /// + public sealed class DotNetPackageAdder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPackageAdder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Adds or updates a package reference in a project file. + /// + /// The package reference to add. + /// The target project file path. If not specified, the command searches the current directory for one. + /// The settings. + public void Add(string packageName, string project, DotNetPackageAddSettings settings) + { + ArgumentNullException.ThrowIfNull(packageName); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(packageName, project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageName, string project, DotNetPackageAddSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("add"); + + // Project path + if (project != null) + { + builder.AppendQuoted(project); + } + + // Package Name + builder.AppendSwitch("package", packageName); + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.AppendSwitch("--framework", settings.Framework); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // Package Directory + if (settings.PackageDirectory != null) + { + builder.Append("--package-directory"); + builder.AppendQuoted(settings.PackageDirectory.MakeAbsolute(_environment).FullPath); + } + + // Prerelease + if (settings.Prerelease) + { + builder.Append("--prerelease"); + } + + // Source + if (!string.IsNullOrEmpty(settings.Source)) + { + builder.AppendSwitchQuoted("--source", settings.Source); + } + + // Version + if (!string.IsNullOrEmpty(settings.Version)) + { + builder.AppendSwitchQuoted("--version", settings.Version); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageList.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageList.cs new file mode 100644 index 0000000000..64395c5844 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageList.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// An result as returned by . + /// + public sealed class DotNetPackageList + { + /// + /// Gets the output version. + /// + [JsonInclude] + public int Version { get; private set; } + + /// + /// Gets the specified parameters. + /// + [JsonInclude] + public string Parameters { get; private set; } + + /// + /// Gets the problems. + /// + [JsonInclude] + public IEnumerable Problems { get; private set; } + + /// + /// Gets the used sources. + /// + [JsonInclude] + public IEnumerable Sources { get; private set; } + + /// + /// Gets the projects. + /// + [JsonInclude] + public IEnumerable Projects { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListAlternativePackageItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListAlternativePackageItem.cs new file mode 100644 index 0000000000..ed1d291a94 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListAlternativePackageItem.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The alternative package information. + /// + public sealed class DotNetPackageListAlternativePackageItem + { + /// + /// Gets the alternative package id. + /// + [JsonInclude] + public string Id { get; private set; } + + /// + /// Gets the alternative package versions. + /// + [JsonInclude] + public string VersionRange { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListFrameworkItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListFrameworkItem.cs new file mode 100644 index 0000000000..90dc1babe2 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListFrameworkItem.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The framework information. + /// + public sealed class DotNetPackageListFrameworkItem + { + /// + /// Gets the framework name. + /// + [JsonInclude] + public string Framework { get; private set; } + + /// + /// Gets the top-level packages. + /// + [JsonInclude] + public IEnumerable TopLevelPackages { get; private set; } + + /// + /// Gets transitive packages. + /// + [JsonInclude] + public IEnumerable TransitivePackages { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListPackageItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListPackageItem.cs new file mode 100644 index 0000000000..f1c482c0e1 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListPackageItem.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The package information. + /// + public sealed class DotNetPackageListPackageItem + { + /// + /// Gets the package id. + /// + [JsonInclude] + public string Id { get; private set; } + + /// + /// Gets the requested version. + /// + [JsonInclude] + public string RequestedVersion { get; private set; } + + /// + /// Gets the resolved version. + /// + [JsonInclude] + public string ResolvedVersion { get; private set; } + + /// + /// Gets a value indicating whether the package is auto-referenced. + /// + [JsonInclude] + public string AutoReferenced { get; private set; } + + /// + /// Gets the latest version. + /// + [JsonInclude] + public string LatestVersion { get; private set; } + + /// + /// Gets the deprecation reasons. + /// + [JsonInclude] + public IEnumerable DeprecationReasons { get; private set; } + + /// + /// Gets the alternative package. + /// + [JsonInclude] + public DotNetPackageListAlternativePackageItem AlternativePackage { get; private set; } + + /// + /// Gets the vulnerabilities list. + /// + [JsonInclude] + public IEnumerable Vulnerabilities { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemItem.cs new file mode 100644 index 0000000000..38b30abc4c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemItem.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The problem information. + /// + public sealed class DotNetPackageListProblemItem + { + /// + /// Gets the problem level. + /// + [JsonInclude] + public DotNetPackageListProblemType? Level { get; private set; } + + /// + /// Gets the problem text. + /// + [JsonInclude] + public string Text { get; private set; } + + /// + /// Gets the project path. + /// + [JsonInclude] + public string Project { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemType.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemType.cs new file mode 100644 index 0000000000..1aebd7814c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProblemType.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The problem types. + /// + public enum DotNetPackageListProblemType + { + /// + /// Warning. + /// + Warning, + + /// + /// Error. + /// + Error + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProjectItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProjectItem.cs new file mode 100644 index 0000000000..a3a30ef713 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListProjectItem.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The project information. + /// + public sealed class DotNetPackageListProjectItem + { + /// + /// Gets the project path. + /// + [JsonInclude] + public string Path { get; private set; } + + /// + /// Gets the list of frameworks. + /// + [JsonInclude] + public IEnumerable Frameworks { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListSettings.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListSettings.cs new file mode 100644 index 0000000000..d73ad10dc8 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListSettings.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetPackageListSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// Requires the option. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to display packages that have been deprecated. + /// + public bool Deprecated { get; set; } + + /// + /// Gets or sets a value indicating whether to list packages that have newer versions available. + /// + public bool Outdated { get; set; } + + /// + /// Gets or sets a specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether to display only the packages with a matching major version number when searching for newer packages. + /// Requires the or option. + /// + public bool HighestMinor { get; set; } + + /// + /// Gets or sets a value indicating whether to display only the packages with a matching major and minor version numbers when searching for newer packages. + /// Requires the or option. + /// + public bool HighestPatch { get; set; } + + /// + /// Gets or sets a value indicating whether to list packages with prerelease versions when searching for newer packages. + /// Requires the or option. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets a value indicating whether to list transitive packages, in addition to the top-level packages. + /// When specifying this option, you get a list of packages that the top-level packages depend on. + /// + public bool Transitive { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. Available since .NET Core 3.0 SDK. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// Requires the or option. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether to list packages that have known vulnerabilities. + /// Cannot be combined with or options. + /// Nuget.org is the source of information about vulnerabilities. + /// + public bool Vulnerable { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListVulnerabilityItem.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListVulnerabilityItem.cs new file mode 100644 index 0000000000..b5109cc817 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageListVulnerabilityItem.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// The vulnerability information. + /// + public sealed class DotNetPackageListVulnerabilityItem + { + /// + /// Gets the severity level: Low, Moderate, High, Critical. + /// + [JsonInclude] + public string Severity { get; private set; } + + /// + /// Gets the URL of advisory. + /// + [JsonInclude] + public Uri AdvisoryUrl { get; private set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageLister.cs b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageLister.cs new file mode 100644 index 0000000000..232ac9329e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/List/DotNetPackageLister.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Text.Json; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Package.List +{ + /// + /// .NET package lister. + /// + public sealed class DotNetPackageLister : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPackageLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists the package references for a project or solution. + /// + /// The project or solution file to operate on. If not specified, the command searches the current directory for one. If more than one solution or project is found, an error is thrown. + /// The settings. + /// A task with the GitVersion results. + public DotNetPackageList List(string project, DotNetPackageListSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var output = string.Empty; + Run(settings, GetArguments(project, settings), new ProcessSettings { RedirectStandardOutput = true }, process => + { + output = string.Join('\n', process.GetStandardOutput()); + }); + + return JsonSerializer.Deserialize(output, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetPackageListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("list"); + + // Project path + if (project != null) + { + builder.AppendQuoted(project); + } + + builder.Append("package"); + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--config", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Deprecated + if (settings.Deprecated) + { + builder.Append("--deprecated"); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.AppendSwitch("--framework", settings.Framework); + } + + // Highest Minor + if (settings.HighestMinor) + { + builder.Append("--highest-minor"); + } + + // Highest Patch + if (settings.HighestPatch) + { + builder.Append("--highest-patch"); + } + + // Prerelease + if (settings.Prerelease) + { + builder.Append("--include-prerelease"); + } + + // Transitive + if (settings.Transitive) + { + builder.Append("--include-transitive"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // Outdated + if (settings.Outdated) + { + builder.Append("--outdated"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Vulnerable + if (settings.Vulnerable) + { + builder.Append("--vulnerable"); + } + + // Format + builder.Append("--format json"); + + // Version + builder.Append("--output-version 1"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemoveSettings.cs b/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemoveSettings.cs new file mode 100644 index 0000000000..31462c35ef --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemoveSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Package.Remove +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetPackageRemoveSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemover.cs b/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemover.cs new file mode 100644 index 0000000000..5556ae679c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Remove/DotNetPackageRemover.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Package.Remove +{ + /// + /// .NET package remover. + /// + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public sealed class DotNetPackageRemover( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : DotNetTool(fileSystem, environment, processRunner, tools) + { + /// + /// Removes package reference from a project file. + /// + /// The package reference to remove. + /// The target project file path. If not specified, the command searches the current directory for one. + public void Remove(string packageName, string project) + => Remove(packageName, project, new DotNetPackageRemoveSettings()); + + /// + /// Removes package reference from a project file. + /// + /// The package reference to remove. + /// The target project file path. If not specified, the command searches the current directory for one. + /// The settings. + public void Remove(string packageName, string project, DotNetPackageRemoveSettings settings) + { + ArgumentNullException.ThrowIfNull(packageName); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(packageName, project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageName, string project, DotNetPackageRemoveSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("remove"); + + // Project path + if (project != null) + { + builder.AppendQuoted(project); + } + + // Package Name + builder.AppendSwitch("package", packageName); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchItem.cs b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchItem.cs new file mode 100644 index 0000000000..197fe7460c --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchItem.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Package.Search +{ + /// + /// An item as returned by . + /// + public class DotNetPackageSearchItem + { + /// + /// Gets or sets the name of the NuGetListItem. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the NuGetListItem as string. + /// + public string Version { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchSettings.cs b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchSettings.cs new file mode 100644 index 0000000000..6cc8aacc35 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearchSettings.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Package.Search +{ + /// + /// Represents the settings for searching .NET packages. + /// + public class DotNetPackageSearchSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to allow prerelease packages to be shown. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets a value indicating whether an exact match is required. Causes and options to be ignored. + /// + public bool ExactMatch { get; set; } + + /// + /// Gets or sets the NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. + /// + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a list of package sources to search. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets the number of results to return. + /// + public int? Take { get; set; } + + /// + /// Gets or sets the number of results to skip, to allow pagination. + /// + public int? Skip { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearcher.cs b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearcher.cs new file mode 100644 index 0000000000..bd3371b8ab --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Package/Search/DotNetPackageSearcher.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Package.Search +{ + /// + /// .NET package searcher. + /// + public sealed class DotNetPackageSearcher : DotNetTool + { + private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); + + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPackageSearcher( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Searches for packages. + /// + /// The search term. + /// The search settings. + /// A collection of . + public IEnumerable Search(string searchTerm, DotNetPackageSearchSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + using var ms = new MemoryStream(); + RunCommand( + settings, + GetArguments(searchTerm, settings), + processSettings, + process => + { + using var sr = new StreamWriter(ms, leaveOpen: true); + foreach (var line in process.GetStandardOutput()) + { + sr.WriteLine(line); + } + }); + + return Parse(ms.GetBuffer().AsMemory(0, (int)ms.Length)).ToList(); + } + + private ProcessArgumentBuilder GetArguments(string searchTerm, DotNetPackageSearchSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("package search"); + + if (!string.IsNullOrEmpty(searchTerm)) + { + builder.AppendQuoted(searchTerm); + } + + if (settings.Prerelease) + { + builder.Append("--prerelease"); + } + + if (settings.ExactMatch) + { + builder.Append("--exact-match"); + } + + if (settings.Sources != null && settings.Sources.Count > 0) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + if (settings.ConfigFile != null) + { + builder.Append("--configfile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + if (settings.Take is { } take) + { + builder.Append("--take"); + builder.Append(take.ToString()); + } + + if (settings.Skip is { } skip) + { + builder.Append("--skip"); + builder.Append(skip.ToString()); + } + + builder.Append("--verbosity normal"); + + builder.Append("--format json"); + + return builder; + } + + private static IEnumerable Parse(ReadOnlyMemory json) + { + var result = JsonSerializer.Deserialize(json.Span, _jsonSerializerOptions); + + if (result is not null) + { + foreach (var searchResult in result.SearchResult) + { + foreach (var package in searchResult.Packages) + { + yield return new DotNetPackageSearchItem { Name = package.Id, Version = package.LatestVersion ?? package.Version }; + } + } + } + } + + private sealed class Result + { + public List SearchResult { get; set; } + } + + private sealed class SearchResult + { + public List Packages { get; set; } + } + + private sealed class Package + { + /// Gets or sets the package identifier. + public string Id { get; set; } + + /// Gets or sets the latest version. Used when not using --exact-match; one entry per package with latest version. + public string LatestVersion { get; set; } + + /// Gets or sets the version. Used when using --exact-match; one entry per package version (property name "version" in JSON). + public string Version { get; set; } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs new file mode 100644 index 0000000000..00e47aa990 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublishSettings.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Publish +{ + /// + /// Contains settings used by . + /// + public class DotNetPublishSettings : DotNetSettings + { + /// + /// Gets or sets the output directory. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the target runtime. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets a specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. + /// + public string VersionSuffix { get; set; } + + /// + /// Gets or sets a value indicating whether to not to build the project before publishing. + /// This makes build faster, but requires build to be done before publish is executed. + /// + /// + /// Requires .NET Core 2.1 or newer. + /// + public bool NoBuild { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore project to project references and only build the root project. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes build faster, but requires restore to be done before build is executed. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to display the startup banner or the copyright message. + /// + /// + /// Available since .NET Core 3.0 SDK. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets a value indicating whether to force all dependencies to be resolved even if the last restore was successful. This is equivalent to deleting project.assets.json. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool Force { get; set; } + + /// + /// Gets or sets a value indicating whether Publish the .NET Core runtime with your application so the runtime doesn't need to be installed on the target machine. Defaults to 'true' if a runtime identifier is specified. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool? SelfContained { get; set; } + + /// + /// Gets or sets a value indicating whether to package your app into a platform-specific single-file executable. + /// + /// + /// Requires .NET Core 3.x or newer. + /// + public bool? PublishSingleFile { get; set; } + + /// + /// Gets or sets a value indicating whether to reduce the size of apps by analyzing IL and trimming unused assemblies. + /// + /// + /// Requires .NET Core 3.x or newer. + /// + public bool? PublishTrimmed { get; set; } + + /// + /// Gets or sets a value as to whether tiered compilation quick JIT is enabled. + /// + /// + /// Requires .NET Core 3.x or newer. Tiered compilation is enabled by default in .NET Core 3. + /// Code generated by Quick JIT may run slower, allocate more memory, or use more stack space. + /// + public bool? TieredCompilationQuickJit { get; set; } + + /// + /// Gets or sets a value indicating whether tiered compilation is enabled. + /// + /// + /// Requires .NET Core 3.x or newer. Tiered compilation is enabled by default in .NET Core 3. + /// + public bool? TieredCompilation { get; set; } + + /// + /// Gets or sets a value indicating whether to compile your application assemblies as ReadyToRun (R2R) format. + /// + /// + /// Requires .NET Core 3.x or newer. Tiered compilation is enabled by default in .NET Core 3. + /// + public bool? PublishReadyToRun { get; set; } + + /// + /// Gets or sets a value indicating whether to show warnings emitted by ReadyToRun (R2R) compilation. + /// + /// + /// Requires .NET Core 3.x or newer. Tiered compilation is enabled by default in .NET Core 3. + /// + public bool? PublishReadyToRunShowWarnings { get; set; } + + /// + /// Gets or sets a value indicating whether to bundle native libraries when publishing a platform-specific single-file executable. + /// + /// + /// Requires .NET 5 or newer. + /// + public bool? IncludeNativeLibrariesForSelfExtract { get; set; } + + /// + /// Gets or sets a value indicating whether to bundle all content when publishing a platform-specific single-file executable. + /// This will extract all files before running the executable and preserves the original .NET Core single-file deployment behavior. + /// + /// + /// Requires .NET 5 or newer. + /// + public bool? IncludeAllContentForSelfExtract { get; set; } + + /// + /// Gets or sets a value indicating whether to enable compression on the embedded assemblies when publishing a single-file executable. + /// + /// + /// Requires .NET 6 or newer. + /// + public bool? EnableCompressionInSingleFile { get; set; } + + /// + /// Gets or sets the specified NuGet package sources to use during the restore. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets the target operating system (OS). + /// This is a shorthand syntax for setting the Runtime Identifier (RID), where the provided value is combined with the default RID. + /// If you use this option, don't use the -r|--runtime option. + /// + /// + /// Requires .NET 6 or newer. + /// + public string OS { get; set; } + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs new file mode 100644 index 0000000000..18421a333b --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Publish/DotNetPublisher.cs @@ -0,0 +1,280 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Publish +{ + /// + /// .NET project publisher. + /// + public sealed class DotNetPublisher : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetPublisher( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Publish the project using the specified path and settings. + /// + /// The target file path. + /// The settings. + public void Publish(string path, DotNetPublishSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(path, settings)); + } + + private ProcessArgumentBuilder GetArguments(string path, DotNetPublishSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("publish"); + + // Specific path? + if (path != null) + { + builder.AppendQuoted(path); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.Append("--output"); + builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.Append("--framework"); + builder.Append(settings.Framework); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // Version suffix + if (!string.IsNullOrEmpty(settings.VersionSuffix)) + { + builder.Append("--version-suffix"); + builder.Append(settings.VersionSuffix); + } + + // No Build + if (settings.NoBuild) + { + builder.Append("--no-build"); + } + + // No Dependencies + if (settings.NoDependencies) + { + builder.Append("--no-dependencies"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // No Logo + if (settings.NoLogo) + { + builder.Append("--nologo"); + } + + // Force + if (settings.Force) + { + builder.Append("--force"); + } + + // Self contained + if (settings.SelfContained.HasValue) + { + if (settings.SelfContained.Value) + { + builder.Append("--self-contained"); + } + else + { + builder.Append("--no-self-contained"); + } + } + + // publish single file + if (settings.PublishSingleFile.HasValue) + { + if (settings.PublishSingleFile.Value) + { + builder.Append("-p:PublishSingleFile=true"); + } + else + { + builder.Append("-p:PublishSingleFile=false"); + } + } + + // publish trimmed + if (settings.PublishTrimmed.HasValue) + { + if (settings.PublishTrimmed.Value) + { + builder.Append("-p:PublishTrimmed=true"); + } + else + { + builder.Append("-p:PublishTrimmed=false"); + } + } + + // Tiered Compilation Quick Jit + if (settings.TieredCompilationQuickJit.HasValue) + { + if (settings.TieredCompilationQuickJit.Value) + { + builder.Append("-p:TieredCompilationQuickJit=true"); + } + else + { + builder.Append("-p:TieredCompilationQuickJit=false"); + } + } + + // Tiered Compilation + if (settings.TieredCompilation.HasValue) + { + if (settings.TieredCompilation.Value) + { + builder.Append("-p:TieredCompilation=true"); + } + else + { + builder.Append("-p:TieredCompilation=false"); + } + } + + // Publish ReadyToRun + if (settings.PublishReadyToRun.HasValue) + { + if (settings.PublishReadyToRun.Value) + { + builder.Append("-p:PublishReadyToRun=true"); + } + else + { + builder.Append("-p:PublishReadyToRun=false"); + } + } + + // Publish ReadyToRunShowWarnings + if (settings.PublishReadyToRunShowWarnings.HasValue) + { + if (settings.PublishReadyToRunShowWarnings.Value) + { + builder.Append("-p:PublishReadyToRunShowWarnings=true"); + } + else + { + builder.Append("-p:PublishReadyToRunShowWarnings=false"); + } + } + + // Include Native Libraries For Self-Extract + if (settings.IncludeNativeLibrariesForSelfExtract.HasValue) + { + if (settings.IncludeNativeLibrariesForSelfExtract.Value) + { + builder.Append("-p:IncludeNativeLibrariesForSelfExtract=true"); + } + else + { + builder.Append("-p:IncludeNativeLibrariesForSelfExtract=false"); + } + } + + // Include All Content For Self-Extract + if (settings.IncludeAllContentForSelfExtract.HasValue) + { + if (settings.IncludeAllContentForSelfExtract.Value) + { + builder.Append("-p:IncludeAllContentForSelfExtract=true"); + } + else + { + builder.Append("-p:IncludeAllContentForSelfExtract=false"); + } + } + + // Enable compression on the embedded assemblies + if (settings.EnableCompressionInSingleFile.HasValue) + { + if (settings.EnableCompressionInSingleFile.Value) + { + builder.Append("-p:EnableCompressionInSingleFile=true"); + } + else + { + builder.Append("-p:EnableCompressionInSingleFile=false"); + } + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + // Os + if (!string.IsNullOrEmpty(settings.OS)) + { + builder.Append("--os"); + builder.Append(settings.OS); + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAddSettings.cs b/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAddSettings.cs new file mode 100644 index 0000000000..cd419a088d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAddSettings.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Reference.Add +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetReferenceAddSettings : DotNetSettings + { + /// + /// Gets or sets a specific framework. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. Available since .NET Core 3.0 SDK. + /// + public bool Interactive { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAdder.cs b/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAdder.cs new file mode 100644 index 0000000000..cf76a33452 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/Add/DotNetReferenceAdder.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Reference.Add +{ + /// + /// .NET reference adder. + /// + public sealed class DotNetReferenceAdder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetReferenceAdder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Adds project-to-project (P2P) references. + /// + /// The target project file path. If not specified, the command searches the current directory for one. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + public void Add(string project, IEnumerable projectReferences, DotNetReferenceAddSettings settings) + { + if (projectReferences == null || !projectReferences.Any()) + { + throw new ArgumentNullException(nameof(projectReferences)); + } + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, projectReferences, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, IEnumerable projectReferences, DotNetReferenceAddSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("add"); + + // Project path + if (!string.IsNullOrWhiteSpace(project)) + { + builder.AppendQuoted(project); + } + + // References + builder.Append("reference"); + foreach (var reference in projectReferences) + { + builder.AppendQuoted(reference.MakeAbsolute(_environment).FullPath); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.AppendSwitch("--framework", settings.Framework); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceListSettings.cs b/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceListSettings.cs new file mode 100644 index 0000000000..e2f6e1f2c4 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceListSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Reference.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetReferenceListSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceLister.cs b/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceLister.cs new file mode 100644 index 0000000000..633d53f3c7 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/List/DotNetReferenceLister.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Reference.List +{ + /// + /// .NET reference lister. + /// + public sealed class DotNetReferenceLister : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetReferenceLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists project-to-project references. + /// + /// The target project file path. If not specified, the command searches the current directory for one. + /// The settings. + /// The list of project-to-project references. + public IEnumerable List(string project, DotNetReferenceListSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(project, settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetReferenceListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("list"); + + // Project path + if (!string.IsNullOrWhiteSpace(project)) + { + builder.AppendQuoted(project); + } + + builder.Append("reference"); + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("There are no Project to Project references") == true) + { + yield break; + } + + if (line?.StartsWith("Project reference(s)") == true) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + break; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return trimmedLine; + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemoveSettings.cs b/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemoveSettings.cs new file mode 100644 index 0000000000..4d561c05fe --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemoveSettings.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Reference.Remove +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetReferenceRemoveSettings : DotNetSettings + { + /// + /// Gets or sets a specific framework. + /// + public string Framework { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemover.cs b/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemover.cs new file mode 100644 index 0000000000..b63e3be7f0 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Reference/Remove/DotNetReferenceRemover.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Reference.Remove +{ + /// + /// .NET reference remover. + /// + public sealed class DotNetReferenceRemover : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetReferenceRemover( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Removes project-to-project (P2P) references. + /// + /// The target project file path. If not specified, the command searches the current directory for one. + /// One or more project references to add. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + public void Remove(string project, IEnumerable projectReferences, DotNetReferenceRemoveSettings settings) + { + if (projectReferences == null || !projectReferences.Any()) + { + throw new ArgumentNullException(nameof(projectReferences)); + } + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, projectReferences, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, IEnumerable projectReferences, DotNetReferenceRemoveSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("remove"); + + // Project path + if (!string.IsNullOrWhiteSpace(project)) + { + builder.AppendQuoted(project); + } + + // References + builder.Append("reference"); + foreach (var reference in projectReferences) + { + builder.AppendQuoted(reference.MakeAbsolute(_environment).FullPath); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.AppendSwitch("--framework", settings.Framework); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs new file mode 100644 index 0000000000..733e3a1511 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestoreSettings.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Restore +{ + /// + /// Contains settings used by . + /// + public class DotNetRestoreSettings : DotNetSettings + { + /// + /// Gets or sets the specified NuGet package sources to use during the restore. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets the NuGet configuration file to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets the directory to install packages in. + /// + public DirectoryPath PackagesDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to disable restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to only warning failed sources if there are packages meeting version requirement. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets the target runtime to restore packages for. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets a value indicating whether to ignore project to project references and restore only the root project. + /// + public bool NoDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether to force all dependencies to be resolved even if the last restore was successful. + /// This is equivalent to deleting the project.assets.json file. + /// + /// Note: This flag was introduced with the .NET Core 2.x release. + /// + public bool Force { get; set; } + + /// + /// Gets or sets a value indicating whether to stop and wait for user input or action (for example to complete authentication). + /// + /// + /// Supported by .NET SDK version 2.1.400 and above. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to enable project lock file to be generated and used with restore. + /// + /// + /// Supported by .NET SDK version 2.1.500 and above. + /// + public bool UseLockFile { get; set; } + + /// + /// Gets or sets a value indicating whether to not allow updating project lock file. + /// + /// + /// When set to true, restore will fail if the lock file is out of sync. + /// Useful for CI builds when you do not want the build to continue if the package closure has changed than what is present in the lock file. + /// + /// Supported by .NET SDK version 2.1.500 and above. + /// + /// + public bool LockedMode { get; set; } + + /// + /// Gets or sets a value indicating output location where project lock file is written. + /// + /// + /// If not set, 'dotnet restore' defaults to 'PROJECT_ROOT\packages.lock.json'. + /// + /// Supported by .NET SDK version 2.1.500 and above. + /// + /// + public FilePath LockFilePath { get; set; } + + /// + /// Gets or sets a value indicating whether to force restore to reevaluate all dependencies even if a lock file already exists. + /// + /// + /// Supported by .NET SDK version 2.1.500 and above. + /// + public bool ForceEvaluate { get; set; } + + /// + /// Gets or sets a value indicating whether to compile your application assemblies as ReadyToRun (R2R) format. + /// + /// + /// In .NET 6, dotnet restore followed by dotnet publish -p:PublishReadyToRun=true --no-restore will fail with the NETSDK1095 error. + /// This is because the crossgen binary is now shipped as a separate NuGet package, and so needs to be part of the restore operation for publishing to succeed. + /// + /// Supported by .NET SDK version 6.0.100 and above. + /// + /// + public bool? PublishReadyToRun { get; set; } + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs new file mode 100644 index 0000000000..42b5ccb74e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Restore/DotNetRestorer.cs @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Restore +{ + /// + /// .NET project restorer. + /// + public sealed class DotNetRestorer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The cake log. + public DotNetRestorer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + ICakeLog log) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Restore the project using the specified path and settings. + /// + /// List of projects and project folders to restore. Each value can be: a path to a project.json or global.json file, or a folder to recursively search for project.json files. + /// The settings. + public void Restore(string root, DotNetRestoreSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(root, settings)); + } + + private ProcessArgumentBuilder GetArguments(string root, DotNetRestoreSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("restore"); + + // Specific root? + if (root != null) + { + builder.AppendQuoted(root); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Output directory + if (settings.PackagesDirectory != null) + { + builder.Append("--packages"); + builder.AppendQuoted(settings.PackagesDirectory.MakeAbsolute(_environment).FullPath); + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + // Config file + if (settings.ConfigFile != null) + { + builder.Append("--configfile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Ignore failed sources + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Disable parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore failed sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Ignore project to project references + if (settings.NoDependencies) + { + builder.Append("--no-dependencies"); + } + + // Force restore + if (settings.Force) + { + builder.Append("--force"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // Use lock file + if (settings.UseLockFile) + { + builder.Append("--use-lock-file"); + } + + // Locked mode + if (settings.LockedMode) + { + builder.Append("--locked-mode"); + } + + // Lock file path + if (settings.LockFilePath != null) + { + builder.AppendSwitchQuoted("--lock-file-path", " ", settings.LockFilePath.MakeAbsolute(_environment).FullPath); + } + + // Force Evaluate + if (settings.ForceEvaluate) + { + builder.Append("--force-evaluate"); + } + + // Publish ReadyToRun + if (settings.PublishReadyToRun.HasValue) + { + if (settings.PublishReadyToRun.Value) + { + builder.Append("-p:PublishReadyToRun=true"); + } + else + { + builder.Append("-p:PublishReadyToRun=false"); + } + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Run/DotNetRunSettings.cs b/src/Cake.Common/Tools/DotNet/Run/DotNetRunSettings.cs new file mode 100644 index 0000000000..a4d4101355 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Run/DotNetRunSettings.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; + +namespace Cake.Common.Tools.DotNet.Run +{ + /// + /// Contains settings used by . + /// + public class DotNetRunSettings : DotNetSettings + { + /// + /// Gets or sets a specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes run faster, but requires restore to be done before run is executed. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit build. + /// This makes run faster, but requires build to be done before run is executed. + /// + public bool NoBuild { get; set; } + + /// + /// Gets or sets the specified NuGet package sources to use during the run is executed. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets the target runtime. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/Run/DotNetRunner.cs b/src/Cake.Common/Tools/DotNet/Run/DotNetRunner.cs new file mode 100644 index 0000000000..e45bfd5143 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Run/DotNetRunner.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Run +{ + /// + /// .NET project runner. + /// + public sealed class DotNetRunner : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetRunner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Runs the project using the specified path with arguments and settings. + /// + /// The target project path. + /// The arguments. + /// The settings. + public void Run(string project, ProcessArgumentBuilder arguments, DotNetRunSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, arguments, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, ProcessArgumentBuilder arguments, DotNetRunSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("run"); + + // Specific path? + if (project != null) + { + builder.Append("--project"); + builder.AppendQuoted(project); + } + + // Framework + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.Append("--framework"); + builder.Append(settings.Framework); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // No Build + if (settings.NoBuild) + { + builder.Append("--no-build"); + } + + // Runtime + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + // Roll Forward Policy + if (!(settings.RollForward is null)) + { + builder.Append("--roll-forward"); + builder.Append(settings.RollForward.Value.ToString("F")); + } + + // MSBuild Settings + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + // Arguments + if (!arguments.IsNullOrEmpty()) + { + builder.Append("--"); + arguments.CopyTo(builder); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKCheckSettings.cs b/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKCheckSettings.cs new file mode 100644 index 0000000000..a326da9534 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKCheckSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.SDKCheck +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSDKCheckSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKChecker.cs b/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKChecker.cs new file mode 100644 index 0000000000..c130736457 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/SDKCheck/DotNetSDKChecker.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.SDKCheck +{ + /// + /// .NET SDK checker. + /// + public sealed class DotNetSDKChecker : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSDKChecker( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Lists the latest available version of the .NET SDK and .NET Runtime, for each feature band. + /// + public void Check() + { + var settings = new DotNetSDKCheckSettings(); + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetSDKCheckSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sdk check"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs new file mode 100644 index 0000000000..0896472f92 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAddSettings.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Sln.Add +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSlnAddSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to place the projects in the root of the solution, rather than creating a solution folder. + /// Can't be used with . + /// + public bool InRoot { get; set; } + + /// + /// Gets or sets the destination solution folder path to add the projects to. Can't be used with . + /// + public DirectoryPath SolutionFolder { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs new file mode 100644 index 0000000000..0a832897f5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Add/DotNetSlnAdder.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Sln.Add +{ + /// + /// .NET project adder. + /// + public sealed class DotNetSlnAdder : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSlnAdder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Adds one or more projects to the solution file. + /// + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to add to the solution. Glob patterns are supported on Unix/Linux-based systems. + /// The settings. + public void Add(FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + if (projectPath == null || !projectPath.Any()) + { + throw new ArgumentNullException(nameof(projectPath)); + } + ArgumentNullException.ThrowIfNull(settings); + if (settings.InRoot && settings.SolutionFolder != null) + { + throw new ArgumentException("InRoot and SolutionFolder cannot be used together."); + } + + RunCommand(settings, GetArguments(solution, projectPath, settings)); + } + + private ProcessArgumentBuilder GetArguments(FilePath solution, IEnumerable projectPath, DotNetSlnAddSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sln"); + + // Solution path + if (solution != null) + { + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); + } + + builder.Append("add"); + + // Solution folder + if (settings.SolutionFolder != null) + { + builder.AppendSwitchQuoted("--solution-folder", settings.SolutionFolder.MakeAbsolute(_environment).FullPath); + } + + // In root + if (settings.InRoot) + { + builder.Append("--in-root"); + } + + // Project path + foreach (var project in projectPath) + { + builder.AppendQuoted(project.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs new file mode 100644 index 0000000000..62a11de27f --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnListSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Sln.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSlnListSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs new file mode 100644 index 0000000000..57972992e2 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/List/DotNetSlnLister.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Sln.List +{ + /// + /// .NET projects lister. + /// + public sealed class DotNetSlnLister : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSlnLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists all projects in a solution file. + /// + /// The solution file to use. If not specified, the command searches the current directory for one. If it finds no solution file or multiple solution files, the command fails. + /// The settings. + /// The list of project-to-project references. + public IEnumerable List(FilePath solution, DotNetSlnListSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true, + EnvironmentVariables = new Dictionary(settings.EnvironmentVariables) + { + { "DOTNET_CLI_UI_LANGUAGE", "en" } + } + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(solution, settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(FilePath solution, DotNetSlnListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sln"); + + // Solution path + if (solution != null) + { + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); + } + + builder.Append("list"); + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("Project(s)") == true) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + break; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return trimmedLine; + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemoveSettings.cs b/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemoveSettings.cs new file mode 100644 index 0000000000..0ab6776ee1 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemoveSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Sln.Remove +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetSlnRemoveSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemover.cs b/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemover.cs new file mode 100644 index 0000000000..a481e1dd49 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Sln/Remove/DotNetSlnRemover.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Sln.Remove +{ + /// + /// .NET project remover. + /// + public sealed class DotNetSlnRemover : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetSlnRemover( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Removes a project or multiple projects from the solution file. + /// + /// The solution file to use. If it is unspecified, the command searches the current directory for one and fails if there are multiple solution files. + /// The path to the project or projects to remove from the solution. + /// The settings. + public void Remove(FilePath solution, IEnumerable projectPath, DotNetSlnRemoveSettings settings) + { + if (projectPath == null || !projectPath.Any()) + { + throw new ArgumentNullException(nameof(projectPath)); + } + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(solution, projectPath, settings)); + } + + private ProcessArgumentBuilder GetArguments(FilePath solution, IEnumerable projectPath, DotNetSlnRemoveSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("sln"); + + // Solution path + if (solution != null) + { + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); + } + + builder.Append("remove"); + + // Project path + foreach (var project in projectPath) + { + builder.AppendQuoted(project.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Test/DotNetTestPathType.cs b/src/Cake.Common/Tools/DotNet/Test/DotNetTestPathType.cs new file mode 100644 index 0000000000..10251522c5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Test/DotNetTestPathType.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Test +{ + /// + /// Represents the path type for .NET test command. + /// + public enum DotNetTestPathType + { + /// + /// No explicit path type specified (default behavior). + /// + None, + + /// + /// Automatically detect the path type based on the file extension. + /// + Auto, + + /// + /// Explicitly specify the path as a project file. + /// + Project, + + /// + /// Explicitly specify the path as a solution file. + /// + Solution + } +} diff --git a/src/Cake.Common/Tools/DotNet/Test/DotNetTestSettings.cs b/src/Cake.Common/Tools/DotNet/Test/DotNetTestSettings.cs new file mode 100644 index 0000000000..06cee57577 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Test/DotNetTestSettings.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Test +{ + /// + /// Contains settings used by . + /// + public class DotNetTestSettings : DotNetSettings + { + /// + /// Gets or sets the settings file to use when running tests. + /// + public FilePath Settings { get; set; } + + /// + /// Gets or sets the filter expression to filter out tests in the current project. + /// + /// + /// For more information on filtering support, see https://aka.ms/vstest-filtering. + /// + public string Filter { get; set; } + + /// + /// Gets or sets the path to use for the custom test adapter in the test run. + /// + public DirectoryPath TestAdapterPath { get; set; } + + /// + /// Gets or sets the loggers for test results. + /// + public ICollection Loggers { get; set; } = new List(); + + /// + /// Gets or sets the output directory. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the configuration under which to build. + /// + public string Configuration { get; set; } + + /// + /// Gets or sets the data collectors for the test run. + /// + public ICollection Collectors { get; set; } = new List(); + + /// + /// Gets or sets specific framework to compile. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether to not build the project before testing. + /// + public bool NoBuild { get; set; } + + /// + /// Gets or sets a value indicating whether to not do implicit NuGet package restore. + /// This makes build faster, but requires restore to be done before build is executed. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public bool NoRestore { get; set; } + + /// + /// Gets or sets a value indicating whether to run tests without displaying the Microsoft TestPlatform banner. + /// + /// + /// Available since .NET Core 3.0 SDK. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets a file to write diagnostic messages to. + /// + public FilePath DiagnosticFile { get; set; } + + /// + /// Gets or sets the results directory. This setting is only available from 2.0.0 upward. + /// + public DirectoryPath ResultsDirectory { get; set; } + + /// + /// Gets or sets the file path to write VSTest reports to. + /// + public FilePath VSTestReportPath { get; set; } + + /// + /// Gets or sets the target runtime to test for. This setting is only available from .NET Core 3.x upward. + /// + public string Runtime { get; set; } + + /// + /// Gets or sets a value indicating whether to run the tests in blame mode. This option is helpful in isolating a problematic test causing the test host to crash. + /// Outputs a 'Sequence.xml' file in the current directory that captures the order of execution of test before the crash. + /// + public bool Blame { get; set; } + + /// + /// Gets or sets the specified NuGet package sources to use during testing. + /// + /// + /// Requires .NET Core 2.x or newer. + /// + public ICollection Sources { get; set; } = new List(); + + /// + /// Gets or sets additional arguments to be passed to MSBuild. + /// + public DotNetMSBuildSettings MSBuildSettings { get; set; } + + /// + /// Gets or sets the path type for the test command. + /// When set to , the path type will be automatically detected based on the file extension. + /// When set to , the path will be treated as a project file. + /// When set to , the path will be treated as a solution file. + /// This is particularly useful for .NET 10+ where explicit --project or --solution parameters are required. + /// + public DotNetTestPathType PathType { get; set; } = DotNetTestPathType.None; + } +} diff --git a/src/Cake.Common/Tools/DotNet/Test/DotNetTester.cs b/src/Cake.Common/Tools/DotNet/Test/DotNetTester.cs new file mode 100644 index 0000000000..4796f8ee75 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Test/DotNetTester.cs @@ -0,0 +1,240 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Test +{ + /// + /// .NET project tester. + /// + public sealed class DotNetTester : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetTester( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Tests the project using the specified path with arguments and settings. + /// + /// The target project path. + /// The arguments. + /// The settings. + public void Test(string project, ProcessArgumentBuilder arguments, DotNetTestSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, arguments, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, ProcessArgumentBuilder arguments, DotNetTestSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("test"); + + // Specific path? + if (project != null) + { + // Handle path type for .NET 10+ compatibility + switch (DeterminePathType(project, settings.PathType)) + { + case DotNetTestPathType.Project: + builder.Append("--project"); + break; + case DotNetTestPathType.Solution: + builder.Append("--solution"); + break; + } + + builder.AppendQuoted(project); + } + + // Settings + if (settings.Settings != null) + { + builder.Append("--settings"); + builder.AppendQuoted(settings.Settings.MakeAbsolute(_environment).FullPath); + } + + // Filter + if (!string.IsNullOrWhiteSpace(settings.Filter)) + { + builder.Append("--filter"); + builder.AppendQuoted(settings.Filter); + } + + // Settings + if (settings.TestAdapterPath != null) + { + builder.Append("--test-adapter-path"); + builder.AppendQuoted(settings.TestAdapterPath.MakeAbsolute(_environment).FullPath); + } + + // Loggers + if (settings.Loggers != null) + { + foreach (var logger in settings.Loggers) + { + builder.Append("--logger"); + builder.AppendQuoted(logger); + } + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.Append("--output"); + builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Frameworks + if (!string.IsNullOrEmpty(settings.Framework)) + { + builder.Append("--framework"); + builder.Append(settings.Framework); + } + + // Configuration + if (!string.IsNullOrEmpty(settings.Configuration)) + { + builder.Append("--configuration"); + builder.Append(settings.Configuration); + } + + // Collectors + if (settings.Collectors != null) + { + foreach (var collector in settings.Collectors) + { + builder.Append("--collect"); + builder.AppendQuoted(collector); + } + } + + // Diagnostic file + if (settings.DiagnosticFile != null) + { + builder.Append("--diag"); + builder.AppendQuoted(settings.DiagnosticFile.MakeAbsolute(_environment).FullPath); + } + + // No Build + if (settings.NoBuild) + { + builder.Append("--no-build"); + } + + // No Restore + if (settings.NoRestore) + { + builder.Append("--no-restore"); + } + + // No Logo + if (settings.NoLogo) + { + builder.Append("--nologo"); + } + + if (settings.ResultsDirectory != null) + { + builder.Append("--results-directory"); + builder.AppendQuoted(settings.ResultsDirectory.MakeAbsolute(_environment).FullPath); + } + + if (settings.VSTestReportPath != null) + { + builder.AppendSwitchQuoted($"--logger trx;LogFileName", "=", settings.VSTestReportPath.MakeAbsolute(_environment).FullPath); + } + + if (!string.IsNullOrEmpty(settings.Runtime)) + { + builder.Append("--runtime"); + builder.Append(settings.Runtime); + } + + // Sources + if (settings.Sources != null) + { + foreach (var source in settings.Sources) + { + builder.Append("--source"); + builder.AppendQuoted(source); + } + } + + // Blame + if (settings.Blame) + { + builder.Append("--blame"); + } + + if (settings.MSBuildSettings != null) + { + builder.AppendMSBuildSettings(settings.MSBuildSettings, _environment); + } + + if (!arguments.IsNullOrEmpty()) + { + builder.Append("--"); + arguments.CopyTo(builder); + } + + return builder; + } + + /// + /// Determines the path type based on the project path and settings. + /// + /// The project path. + /// The configured path type. + /// The determined path type. + private static DotNetTestPathType DeterminePathType(string project, DotNetTestPathType pathType) + { + return pathType switch + { + DotNetTestPathType.None => DotNetTestPathType.None, + DotNetTestPathType.Project => DotNetTestPathType.Project, + DotNetTestPathType.Solution => DotNetTestPathType.Solution, + DotNetTestPathType.Auto => AutoDetectPathType(project), + _ => DotNetTestPathType.None + }; + } + + /// + /// Auto-detects the path type based on file extension. + /// + /// The project path. + /// The detected path type. + private static DotNetTestPathType AutoDetectPathType(string project) + { + var extension = FilePath.FromString(project).GetExtension().ToLowerInvariant(); + return extension switch + { + ".sln" or ".slnx" => DotNetTestPathType.Solution, + ".csproj" or ".vbproj" or ".fsproj" or ".vcxproj" => DotNetTestPathType.Project, + _ => DotNetTestPathType.None // Default to legacy behavior for unknown extensions + }; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Tool/DotNetToolRunner.cs b/src/Cake.Common/Tools/DotNet/Tool/DotNetToolRunner.cs new file mode 100644 index 0000000000..4228827182 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Tool/DotNetToolRunner.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Tool +{ + /// + /// .NET Extensibility Commands Runner. + /// + public sealed class DotNetToolRunner : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetToolRunner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Execute an assembly using arguments and settings. + /// + /// The target project path. + /// The command to execute. + /// The arguments. + /// The settings. + public void Execute(FilePath projectPath, string command, ProcessArgumentBuilder arguments, DotNetToolSettings settings) + { + if (string.IsNullOrWhiteSpace(command)) + { + throw new ArgumentNullException(nameof(command)); + } + + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + WorkingDirectory = settings.WorkingDirectory ?? projectPath?.GetDirectory() + }; + + RunCommand(settings, GetArguments(command, arguments, settings), processSettings); + } + + private ProcessArgumentBuilder GetArguments(string command, ProcessArgumentBuilder arguments, DotNetToolSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + // Appending quoted to cater for scenarios where the command passed is not a .NET CLI command, + // but the path of an application to run that contains whitespace + builder.AppendQuoted(command); + + // Arguments + if (!arguments.IsNullOrEmpty()) + { + arguments.CopyTo(builder); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Tool/DotNetToolSettings.cs b/src/Cake.Common/Tools/DotNet/Tool/DotNetToolSettings.cs new file mode 100644 index 0000000000..168a7ac584 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Tool/DotNetToolSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Tool +{ + /// + /// Contains settings used by . + /// + public class DotNetToolSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTestSettings.cs b/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTestSettings.cs new file mode 100644 index 0000000000..3571f91e7a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTestSettings.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Common.Tools.VSTest; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.VSTest +{ + /// + /// Contains settings used by . + /// + public class DotNetVSTestSettings : DotNetSettings + { + /// + /// Gets or sets the settings file to use when running tests. + /// + public FilePath Settings { get; set; } + + /// + /// Gets or sets the a list tests to run. + /// + public ICollection TestsToRun { get; set; } + + /// + /// Gets or sets the path to use for the custom test adapter in the test run. + /// + public DirectoryPath TestAdapterPath { get; set; } + + /// + /// Gets or sets the target platform architecture to be used for test execution. + /// + public VSTestPlatform Platform { get; set; } + + /// + /// Gets or sets specific .Net Framework version to be used for test execution. + /// + /// + /// Valid values are ".NETFramework,Version=v4.6", ".NETCoreApp,Version=v1.0" etc. + /// Other supported values are Framework35, Framework40, Framework45 and FrameworkCore10. + /// + public string Framework { get; set; } + + /// + /// Gets or sets a value indicating whether the tests should be executed in parallel. + /// + /// + /// By default up to all available cores on the machine may be used. The number of cores to use may be configured using a settings file. + /// + public bool Parallel { get; set; } + + /// + /// Gets or sets the filter expression to run test that match. + /// + /// + /// For more information on filtering support, see https://aka.ms/vstest-filtering. + /// + public string TestCaseFilter { get; set; } + + /// + /// Gets or sets a logger for test results. + /// + public string Logger { get; set; } + + /// + /// Gets or sets the Process Id of the Parent Process responsible for launching current process. + /// + public string ParentProcessId { get; set; } + + /// + /// Gets or sets the Port for socket connection and receiving the event messages. + /// + public int? Port { get; set; } + + /// + /// Gets or sets a file to write diagnostic messages to. + /// + public FilePath DiagnosticFile { get; set; } + + /// + /// Gets or sets the path to put the test results in. + /// + public DirectoryPath ResultsDirectory { get; set; } + + /// + /// Gets or sets a list of extra arguments that should be passed to adapter. + /// + public IDictionary Arguments { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public DotNetVSTestSettings() + { + TestsToRun = new List(); + Arguments = new Dictionary(); + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTester.cs b/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTester.cs new file mode 100644 index 0000000000..eb8bfcd7c9 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/VSTest/DotNetVSTester.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Common.Tools.VSTest; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.VSTest +{ + /// + /// .NET Core VSTest tester. + /// + public sealed class DotNetVSTester : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetVSTester( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Tests the project using the specified path with arguments and settings. + /// + /// A list of test files to run. + /// The settings. + public void Test(IEnumerable testFiles, DotNetVSTestSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + if (testFiles == null || !testFiles.Any()) + { + throw new ArgumentNullException(nameof(testFiles)); + } + + RunCommand(settings, GetArguments(testFiles, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable testFiles, DotNetVSTestSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("vstest"); + + // Specific path? + foreach (var testFile in testFiles) + { + builder.AppendQuoted(testFile.MakeAbsolute(_environment).FullPath); + } + + // Settings + if (settings.Settings != null) + { + builder.AppendSwitchQuoted("--Settings", ":", settings.Settings.MakeAbsolute(_environment).FullPath); + } + + // Tests to run + if (settings.TestsToRun.Any()) + { + builder.AppendSwitch("--Tests", ":", string.Join(',', settings.TestsToRun)); + } + + // Path to custom test adapter + if (settings.TestAdapterPath != null) + { + builder.AppendSwitchQuoted("--TestAdapterPath", ":", settings.TestAdapterPath.MakeAbsolute(_environment).FullPath); + } + + // Platform architecture to execute tests on + if (settings.Platform != VSTestPlatform.Default) + { + builder.AppendSwitch("--Platform", ":", settings.Platform.ToString()); + } + + // Target Framework + if (!string.IsNullOrWhiteSpace(settings.Framework)) + { + builder.AppendSwitch("--Framework", ":", settings.Framework); + } + + // Run tests in parallel? + if (settings.Parallel) + { + builder.Append("--Parallel"); + } + + // Test Case Filter + if (!string.IsNullOrWhiteSpace(settings.TestCaseFilter)) + { + builder.AppendSwitchQuoted("--TestCaseFilter", ":", settings.TestCaseFilter); + } + + // Logger + if (!string.IsNullOrWhiteSpace(settings.Logger)) + { + builder.AppendSwitchQuoted("--logger", ":", settings.Logger); + } + + // Parent Process Id? + if (!string.IsNullOrWhiteSpace(settings.ParentProcessId)) + { + builder.AppendSwitch("--ParentProcessId", ":", settings.ParentProcessId); + } + + // Port? + if (settings.Port.HasValue) + { + builder.AppendSwitch("--Port", ":", settings.Port.Value.ToString()); + } + + // Write to Diagnostic file? + if (settings.DiagnosticFile != null) + { + builder.AppendSwitchQuoted("--Diag", ":", settings.DiagnosticFile.MakeAbsolute(_environment).FullPath); + } + + // Path to output test results + if (settings.ResultsDirectory != null) + { + builder.AppendSwitchQuoted("--ResultsDirectory", ":", settings.ResultsDirectory.MakeAbsolute(_environment).FullPath); + } + + // Extra arguments + foreach (var argument in settings.Arguments) + { + builder.AppendSwitchQuoted(argument.Key, ":", argument.Value); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs new file mode 100644 index 0000000000..480b30cdbe --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstallSettings.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Install +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadInstallSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to skip updating the workload manifests. + /// The workload manifests define what assets and versions need to be installed for each workload. + /// + public bool SkipManifestUpdate { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs new file mode 100644 index 0000000000..dab9f33a9a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Install/DotNetWorkloadInstaller.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Install +{ + /// + /// .NET workloads installer. + /// + public sealed class DotNetWorkloadInstaller : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadInstaller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Lists the latest available version of the .NET SDK and .NET Runtime, for each feature band. + /// + /// The workload ID or multiple IDs to uninstall. + /// The settings. + public void Install(IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + if (workloadIds == null || !workloadIds.Any()) + { + throw new ArgumentNullException(nameof(workloadIds)); + } + + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(workloadIds, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable workloadIds, DotNetWorkloadInstallSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload install"); + + if (workloadIds != null && workloadIds.Any()) + { + builder.Append(string.Join(' ', workloadIds)); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Skip Manifest Update + if (settings.SkipManifestUpdate) + { + builder.Append("--skip-manifest-update"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs new file mode 100644 index 0000000000..b572100154 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListItem.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Workload.List +{ + /// + /// An item as returned by . + /// + public sealed class DotNetWorkloadListItem + { + /// + /// Initializes a new instance of the class. + /// + /// The workload Id. + /// The workload manifest version. + /// The workload installation source. + public DotNetWorkloadListItem(string id, string manifestVersion, string installationSource) + { + Id = id; + ManifestVersion = manifestVersion; + InstallationSource = installationSource; + } + + /// + /// Gets the workload ID. + /// + public string Id { get; } + + /// + /// Gets the manifest version of the workload as string. + /// + public string ManifestVersion { get; } + + /// + /// Gets the installation source of the workload as string. + /// + public string InstallationSource { get; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.cs new file mode 100644 index 0000000000..daef0e0ea5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadListSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Workload.List +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadListSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs new file mode 100644 index 0000000000..c85ea4048e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/List/DotNetWorkloadLister.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.List +{ + /// + /// .NET workloads lister. + /// + public sealed class DotNetWorkloadLister : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadLister( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Lists all installed workloads. + /// + /// The settings. + /// The list of installed workloads. + public IEnumerable List(DotNetWorkloadListSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadListSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload list"); + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + int manifestIndex = -1; + int sourceIndex = -1; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("Installed Workload Ids") == true + && (manifestIndex = line?.IndexOf("Manifest Version") ?? -1) > 22 + && (sourceIndex = line?.IndexOf("Installation Source") ?? -1) > 39) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + break; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return new DotNetWorkloadListItem( + string.Concat(trimmedLine.Take(manifestIndex)).TrimEnd(), + string.Concat(trimmedLine.Take(sourceIndex).Skip(manifestIndex)).TrimEnd(), + string.Concat(trimmedLine.Skip(sourceIndex))); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs new file mode 100644 index 0000000000..f78a0ed532 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairSettings.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Repair +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadRepairSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs new file mode 100644 index 0000000000..148e3b950e --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Repair/DotNetWorkloadRepairer.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Repair +{ + /// + /// .NET workloads installations repairer. + /// + public sealed class DotNetWorkloadRepairer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadRepairer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Repairs all workloads installations. + /// + /// The settings. + public void Repair(DotNetWorkloadRepairSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadRepairSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload repair"); + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs new file mode 100644 index 0000000000..54aaffdd9a --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestoreSettings.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Restore +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadRestoreSettings : DotNetSettings + { + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to skip updating the workload manifests. + /// The workload manifests define what assets and versions need to be installed for each workload. + /// + public bool SkipManifestUpdate { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs new file mode 100644 index 0000000000..bc6b7316c2 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Restore/DotNetWorkloadRestorer.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Restore +{ + /// + /// .NET workloads restorer. + /// + public sealed class DotNetWorkloadRestorer : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadRestorer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Installs workloads needed for a project or a solution. + /// + /// The target project or solution file path. + /// The settings. + public void Restore(string project, DotNetWorkloadRestoreSettings settings) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(project, settings)); + } + + private ProcessArgumentBuilder GetArguments(string project, DotNetWorkloadRestoreSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload restore"); + + // Specific path? + if (project != null) + { + builder.AppendQuoted(project); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Skip Manifest Update + if (settings.SkipManifestUpdate) + { + builder.Append("--skip-manifest-update"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkload.cs b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkload.cs new file mode 100644 index 0000000000..4ebd1583b1 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkload.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Workload.Search +{ + /// + /// Workload information. + /// + public class DotNetWorkload + { + /// + /// Initializes a new instance of the class. + /// + /// The workload Id. + /// The workload description. + public DotNetWorkload(string id, string description) + { + Id = id; + Description = description; + } + + /// + /// Gets the workload Id. + /// + public string Id { get; } + + /// + /// Gets the workload description. + /// + public string Description { get; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearchSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearchSettings.cs new file mode 100644 index 0000000000..d94c6ee90d --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearchSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Workload.Search +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadSearchSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearcher.cs b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearcher.cs new file mode 100644 index 0000000000..55a95c81a9 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Search/DotNetWorkloadSearcher.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Search +{ + /// + /// .NET workloads searcher. + /// + public sealed class DotNetWorkloadSearcher : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadSearcher( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Lists the latest available version of the .NET SDK and .NET Runtime, for each feature band. + /// + /// The workload ID to search for, or part of it. + /// The settings. + /// The list of available workloads. + public IEnumerable Search(string searchString, DotNetWorkloadSearchSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + RunCommand(settings, GetArguments(searchString, settings), processSettings, + process => result = process.GetStandardOutput()); + + return ParseResult(result).ToList(); + } + + private ProcessArgumentBuilder GetArguments(string searchString, DotNetWorkloadSearchSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload search"); + + if (!string.IsNullOrEmpty(searchString)) + { + builder.Append(searchString); + } + + return builder; + } + + private static IEnumerable ParseResult(IEnumerable result) + { + bool first = true; + int descriptionIndex = -1; + foreach (var line in result) + { + if (first) + { + if (line?.StartsWith("Workload ID") == true + && (descriptionIndex = line?.IndexOf("Description") ?? -1) > 11) + { + first = false; + } + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + var trimmedLine = line.Trim(); + + if (trimmedLine.Trim().All(c => c == '-')) + { + continue; + } + + yield return new DotNetWorkload( + string.Concat(trimmedLine.Take(descriptionIndex)).TrimEnd(), + string.Concat(trimmedLine.Skip(descriptionIndex))); + } + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.cs new file mode 100644 index 0000000000..f30a97d7a0 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstallSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotNet.Workload.Uninstall +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadUninstallSettings : DotNetSettings + { + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs new file mode 100644 index 0000000000..95f6ae79a5 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Uninstall/DotNetWorkloadUninstaller.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Uninstall +{ + /// + /// .NET workloads uninstaller. + /// + public sealed class DotNetWorkloadUninstaller : DotNetTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadUninstaller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Uninstalls one or more workloads. + /// + /// The workload ID or multiple IDs to uninstall. + public void Uninstall(IEnumerable workloadIds) + { + if (workloadIds == null || !workloadIds.Any()) + { + throw new ArgumentNullException(nameof(workloadIds)); + } + + var settings = new DotNetWorkloadUninstallSettings(); + RunCommand(settings, GetArguments(workloadIds, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable workloadIds, DotNetWorkloadUninstallSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload uninstall"); + + if (workloadIds != null && workloadIds.Any()) + { + builder.Append(string.Join(' ', workloadIds)); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs new file mode 100644 index 0000000000..b09e84e610 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdateSettings.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotNet.Workload.Update +{ + /// + /// Contains settings used by . + /// + public sealed class DotNetWorkloadUpdateSettings : DotNetSettings + { + /// + /// Gets or sets a value indicating whether to downloads advertising manifests but doesn't update any workloads. + /// + public bool AdvertisingManifestsOnly { get; set; } + + /// + /// Gets or sets the NuGet configuration file (nuget.config) to use. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to prevent restoring multiple projects in parallel. + /// + public bool DisableParallel { get; set; } + + /// + /// Gets or sets a value indicating whether to include workloads installed with previous SDK versions in the update. + /// + public bool FromPreviousSdk { get; set; } + + /// + /// Gets or sets a value indicating whether to treat package source failures as warnings. + /// + public bool IgnoreFailedSources { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease workload manifests. + /// + public bool IncludePreviews { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the command to stop and wait for user input or action. + /// For example, to complete authentication. + /// + public bool Interactive { get; set; } + + /// + /// Gets or sets a value indicating whether to do not cache packages and http requests. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets the URI of the NuGet package source to use. + /// This setting overrides all of the sources specified in the nuget.config files. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets the temporary directory used to download and extract NuGet packages (must be secure). + /// + public DirectoryPath TempDir { get; set; } + } +} diff --git a/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs new file mode 100644 index 0000000000..37884c8844 --- /dev/null +++ b/src/Cake.Common/Tools/DotNet/Workload/Update/DotNetWorkloadUpdater.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.DotNet.Workload.Update +{ + /// + /// .NET workloads updater. + /// + public sealed class DotNetWorkloadUpdater : DotNetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public DotNetWorkloadUpdater( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Updates all installed workloads to the newest available version. + /// + /// The settings. + public void Update(DotNetWorkloadUpdateSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + RunCommand(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(DotNetWorkloadUpdateSettings settings) + { + var builder = CreateArgumentBuilder(settings); + + builder.Append("workload update"); + + // Advertising Manifests Only + if (settings.AdvertisingManifestsOnly) + { + builder.Append("--advertising-manifests-only"); + } + + // Config File + if (settings.ConfigFile != null) + { + builder.AppendSwitchQuoted("--configfile", settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + // Disable Parallel + if (settings.DisableParallel) + { + builder.Append("--disable-parallel"); + } + + // From Previous SDK + if (settings.FromPreviousSdk) + { + builder.Append("--from-previous-sdk"); + } + + // Ignore Failed Sources + if (settings.IgnoreFailedSources) + { + builder.Append("--ignore-failed-sources"); + } + + // Include Previews + if (settings.IncludePreviews) + { + builder.Append("--include-previews"); + } + + // Interactive + if (settings.Interactive) + { + builder.Append("--interactive"); + } + + // No Cache + if (settings.NoCache) + { + builder.Append("--no-cache"); + } + + // Source + if (settings.Source != null && settings.Source.Any()) + { + foreach (var source in settings.Source) + { + builder.AppendSwitchQuoted("--source", source); + } + } + + // Temp Dir + if (settings.TempDir != null) + { + builder.AppendSwitchQuoted("--temp-dir", settings.TempDir.MakeAbsolute(_environment).FullPath); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/DotNetBuildAliases.cs b/src/Cake.Common/Tools/DotNetBuildAliases.cs deleted file mode 100644 index 86c64b87ca..0000000000 --- a/src/Cake.Common/Tools/DotNetBuildAliases.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Common.Tools.MSBuild; -using Cake.Common.Tools.XBuild; -using Cake.Core; -using Cake.Core.Annotations; -using Cake.Core.IO; - -namespace Cake.Common.Tools -{ - /// - /// Contains functionality to run either MSBuild on Windows or XBuild on Mac/Linux/Unix. - /// - [CakeAliasCategory("DotNetBuild")] - public static class DotNetBuildAliases - { - /// - /// Builds the specified solution using MSBuild or XBuild. - /// - /// - /// - /// DotNetBuild("./project/project.sln"); - /// - /// - /// The context. - /// The solution. - [CakeMethodAlias] - public static void DotNetBuild(this ICakeContext context, FilePath solution) - { - DotNetBuild(context, solution, settings => { }); - } - - /// - /// Builds the specified solution using MSBuild or XBuild. - /// - /// - /// - /// DotNetBuild("./project/project.sln", settings => - /// settings.SetConfiguration("Debug") - /// .SetVerbosity(Core.Diagnostics.Verbosity.Minimal) - /// .WithTarget("Build") - /// .WithProperty("TreatWarningsAsErrors","true")); - /// - /// - /// The context. - /// The solution. - /// The configurator. - [CakeMethodAlias] - public static void DotNetBuild(this ICakeContext context, FilePath solution, Action configurator) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (configurator == null) - { - throw new ArgumentNullException("configurator"); - } - - // Create the settings using the delegate. - var dotNetSettings = new DotNetBuildSettings(solution); - configurator(dotNetSettings); - - // Running on Mac/Linux/Unix? - if (context.Environment.IsUnix()) - { - // Use XBuild. - XBuildAliases.XBuild(context, solution, settings => - { - settings.Configuration = dotNetSettings.Configuration; - settings.Verbosity = dotNetSettings.Verbosity; - - foreach (var target in dotNetSettings.Targets) - { - settings.Targets.Add(target); - } - - foreach (var property in dotNetSettings.Properties) - { - settings.Properties.Add(property); - } - }); - } - else - { - // Use MSBuild. - MSBuildAliases.MSBuild(context, solution, settings => - { - settings.Configuration = dotNetSettings.Configuration; - settings.Verbosity = dotNetSettings.Verbosity; - - foreach (var target in dotNetSettings.Targets) - { - settings.Targets.Add(target); - } - - foreach (var property in dotNetSettings.Properties) - { - settings.Properties.Add(property); - } - }); - } - } - } -} diff --git a/src/Cake.Common/Tools/DotNetBuildSettings.cs b/src/Cake.Common/Tools/DotNetBuildSettings.cs deleted file mode 100644 index 4c48141cf6..0000000000 --- a/src/Cake.Common/Tools/DotNetBuildSettings.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using Cake.Core.Diagnostics; -using Cake.Core.IO; - -namespace Cake.Common.Tools -{ - /// - /// Contains settings used by the DotNetBuild alias. - /// - public sealed class DotNetBuildSettings - { - private readonly FilePath _solution; - private readonly HashSet _targets; - private readonly Dictionary> _properties; - - /// - /// Gets the solution path. - /// - /// The solution. - public FilePath Solution - { - get { return _solution; } - } - - /// - /// Gets the targets. - /// - /// The targets. - public ISet Targets - { - get { return _targets; } - } - - /// - /// Gets the properties. - /// - /// The properties. - public IDictionary> Properties - { - get { return _properties; } - } - - /// - /// Gets or sets the configuration. - /// - /// The configuration. - public string Configuration { get; set; } - - /// - /// Gets or sets the amount of information to display in the build log. - /// Each logger displays events based on the verbosity level that you set for that logger. - /// - /// The build log verbosity. - public Verbosity Verbosity { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The solution. - public DotNetBuildSettings(FilePath solution) - { - if (solution == null) - { - throw new ArgumentNullException("solution"); - } - - _solution = solution; - _targets = new HashSet(StringComparer.OrdinalIgnoreCase); - _properties = new Dictionary>(StringComparer.OrdinalIgnoreCase); - - Configuration = string.Empty; - Verbosity = Verbosity.Normal; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetBuildSettingsExtensions.cs b/src/Cake.Common/Tools/DotNetBuildSettingsExtensions.cs deleted file mode 100644 index 26f046663b..0000000000 --- a/src/Cake.Common/Tools/DotNetBuildSettingsExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Cake.Core.Diagnostics; - -namespace Cake.Common.Tools -{ - /// - /// Contains functionality related to .NET build settings. - /// - public static class DotNetBuildSettingsExtensions - { - /// - /// Adds a .NET build target to the configuration. - /// - /// The settings. - /// The .NET build target. - /// The same instance so that multiple calls can be chained. - public static DotNetBuildSettings WithTarget(this DotNetBuildSettings settings, string target) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.Targets.Add(target); - return settings; - } - - /// - /// Adds a property to the configuration. - /// - /// The settings. - /// The property name. - /// The property values. - /// The same instance so that multiple calls can be chained. - public static DotNetBuildSettings WithProperty(this DotNetBuildSettings settings, string name, params string[] values) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - IList currentValue; - currentValue = new List( - settings.Properties.TryGetValue(name, out currentValue) && currentValue != null - ? currentValue.Concat(values) - : values); - - settings.Properties[name] = currentValue; - - return settings; - } - - /// - /// Sets the configuration. - /// - /// The settings. - /// The configuration. - /// The same instance so that multiple calls can be chained. - public static DotNetBuildSettings SetConfiguration(this DotNetBuildSettings settings, string configuration) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.Configuration = configuration; - return settings; - } - - /// - /// Sets the build log verbosity. - /// - /// The settings. - /// The build log verbosity. - /// The same instance so that multiple calls can be chained. - public static DotNetBuildSettings SetVerbosity(this DotNetBuildSettings settings, Verbosity verbosity) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - settings.Verbosity = verbosity; - return settings; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuildSettings.cs b/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuildSettings.cs deleted file mode 100644 index 152f8ea1eb..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuildSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore.Build -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCoreBuildSettings : DotNetCoreSettings - { - /// - /// Gets or sets the directory in which to place temporary outputs. - /// - public DirectoryPath BuildBasePath { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets the target runtime. - /// - public string Runtime { get; set; } - - /// - /// Gets or sets the configuration under which to build. - /// - public string Configuration { get; set; } - - /// - /// Gets or sets the specific framework to compile. - /// - public string Framework { get; set; } - - /// - /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. - /// - public string VersionSuffix { get; set; } - - /// - /// Gets or sets a value indicating whether to print the incremental safety checks that prevent incremental compilation. - /// - public bool BuildProfile { get; set; } - - /// - /// Gets or sets a value indicating whether to mark the build as unsafe for incrementality. - /// This turns off incremental compilation and forces a clean rebuild of the project dependency graph. - /// - public bool NoIncremental { get; set; } - - /// - /// Gets or sets a value indicating whether to ignore project to project references and only build the root project. - /// - public bool NoDependencies { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuilder.cs b/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuilder.cs deleted file mode 100644 index dba75f3def..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Build/DotNetCoreBuilder.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Build -{ - /// - /// .NET Core project builder. - /// - public sealed class DotNetCoreBuilder : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCoreBuilder( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Build the project using the specified path and settings. - /// - /// The target project path. - /// The settings. - public void Build(string project, DotNetCoreBuildSettings settings) - { - if (project == null) - { - throw new ArgumentNullException("project"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(project, settings)); - } - - private ProcessArgumentBuilder GetArguments(string project, DotNetCoreBuildSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("build"); - - // Specific path? - if (project != null) - { - builder.Append(project); - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--output"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Temporary output directory - if (settings.BuildBasePath != null) - { - builder.Append("--build-base-path"); - builder.AppendQuoted(settings.BuildBasePath.MakeAbsolute(_environment).FullPath); - } - - // Runtime - if (!string.IsNullOrEmpty(settings.Runtime)) - { - builder.Append("--runtime"); - builder.Append(settings.Runtime); - } - - // Framework - if (!string.IsNullOrEmpty(settings.Framework)) - { - builder.Append("--framework"); - builder.Append(settings.Framework); - } - - // Configuration - if (!string.IsNullOrEmpty(settings.Configuration)) - { - builder.Append("--configuration"); - builder.Append(settings.Configuration); - } - - // Version suffix - if (!string.IsNullOrEmpty(settings.VersionSuffix)) - { - builder.Append("--version-suffix"); - builder.Append(settings.VersionSuffix); - } - - // Build Profile - if (settings.BuildProfile) - { - builder.Append("--build-profile"); - } - - // No Incremental - if (settings.NoIncremental) - { - builder.Append("--no-incremental"); - } - - // No Incremental - if (settings.NoDependencies) - { - builder.Append("--no-dependencies"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/DotNetCoreAliases.cs b/src/Cake.Common/Tools/DotNetCore/DotNetCoreAliases.cs deleted file mode 100644 index 4258abc43b..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/DotNetCoreAliases.cs +++ /dev/null @@ -1,536 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Common.Tools.DotNetCore.Build; -using Cake.Common.Tools.DotNetCore.Execute; -using Cake.Common.Tools.DotNetCore.Pack; -using Cake.Common.Tools.DotNetCore.Publish; -using Cake.Common.Tools.DotNetCore.Restore; -using Cake.Common.Tools.DotNetCore.Run; -using Cake.Common.Tools.DotNetCore.Test; -using Cake.Core; -using Cake.Core.Annotations; -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore -{ - /// - /// Contains functionality related to .NET Core CLI. - /// - /// In order to use the commands for this alias, the .Net Core CLI tools will need to be installed on the machine where - /// the Cake script is being executed. See this page for information - /// on how to install. - /// - /// - [CakeAliasCategory("DotNetCore")] - public static class DotNetCoreAliases - { - /// - /// Execute an assembly. - /// - /// The context. - /// The assembly path. - /// - /// - /// DotNetCoreExecute("./bin/Debug/app.dll"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Execute")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Execute")] - public static void DotNetCoreExecute(this ICakeContext context, FilePath assemblyPath) - { - context.DotNetCoreExecute(assemblyPath, null); - } - - /// - /// Execute an assembly with arguments in the specific path. - /// - /// The context. - /// The assembly path. - /// The arguments. - /// - /// - /// DotNetCoreExecute("./bin/Debug/app.dll", "--arg"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Execute")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Execute")] - public static void DotNetCoreExecute(this ICakeContext context, FilePath assemblyPath, ProcessArgumentBuilder arguments) - { - context.DotNetCoreExecute(assemblyPath, arguments, null); - } - - /// - /// Execute an assembly with arguments in the specific path with settings. - /// - /// The context. - /// The assembly path. - /// The arguments. - /// The settings. - /// - /// - /// var settings = new DotNetCoreSettings - /// { - /// Verbose = true - /// }; - /// - /// DotNetCoreExecute("./bin/Debug/app.dll", "--arg", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Execute")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Execute")] - public static void DotNetCoreExecute(this ICakeContext context, FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetCoreSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (assemblyPath == null) - { - throw new ArgumentNullException("assemblyPath"); - } - - if (settings == null) - { - settings = new DotNetCoreExecuteSettings(); - } - - var executor = new DotNetCoreExecutor(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - executor.Execute(assemblyPath, arguments, settings); - } - - /// - /// Restore all NuGet Packages. - /// - /// The context. - /// - /// - /// DotNetCoreRestore(); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Restore")] - public static void DotNetCoreRestore(this ICakeContext context) - { - context.DotNetCoreRestore(null, null); - } - - /// - /// Restore all NuGet Packages in the specified path. - /// - /// The context. - /// List of projects and project folders to restore. Each value can be: a path to a project.json or global.json file, or a folder to recursively search for project.json files. - /// - /// - /// DotNetCoreRestore("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Restore")] - public static void DotNetCoreRestore(this ICakeContext context, string root) - { - context.DotNetCoreRestore(root, null); - } - - /// - /// Restore all NuGet Packages with the settings. - /// - /// The context. - /// The settings. - /// - /// - /// var settings = new DotNetCoreRestoreSettings - /// { - /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, - /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, - /// Packages = "./packages", - /// Verbosity = Information, - /// DisableParallel = true, - /// Runtimes = new[] {"runtime1", "runtime2"} - /// }; - /// - /// DotNetCoreRestore(settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Restore")] - public static void DotNetCoreRestore(this ICakeContext context, DotNetCoreRestoreSettings settings) - { - context.DotNetCoreRestore(null, settings); - } - - /// - /// Restore all NuGet Packages in the specified path with settings. - /// - /// The context. - /// List of projects and project folders to restore. Each value can be: a path to a project.json or global.json file, or a folder to recursively search for project.json files. - /// The settings. - /// - /// - /// var settings = new DotNetCoreRestoreSettings - /// { - /// Sources = new[] {"https://www.example.com/nugetfeed", "https://www.example.com/nugetfeed2"}, - /// FallbackSources = new[] {"https://www.example.com/fallbacknugetfeed"}, - /// Packages = "./packages", - /// Verbosity = Information, - /// DisableParallel = true, - /// Runtimes = new[] {"runtime1", "runtime2"} - /// }; - /// - /// DotNetCoreRestore("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Restore")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Restore")] - public static void DotNetCoreRestore(this ICakeContext context, string root, DotNetCoreRestoreSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCoreRestoreSettings(); - } - - var restorer = new DotNetCoreRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - restorer.Restore(root, settings); - } - - /// - /// Build all projects. - /// - /// The context. - /// The projects path. - /// - /// - /// DotNetCoreBuild("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Build")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Build")] - public static void DotNetCoreBuild(this ICakeContext context, string project) - { - context.DotNetCoreBuild(project, null); - } - - /// - /// Build all projects. - /// - /// The context. - /// The projects path. - /// The settings. - /// - /// - /// var settings = new DotNetCoreBuildSettings - /// { - /// Frameworks = new[] { "net451", "dnxcore50" }, - /// Configuration = "Debug", - /// OutputDirectory = "./artifacts/" - /// }; - /// - /// DotNetCoreBuild("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Build")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Build")] - public static void DotNetCoreBuild(this ICakeContext context, string project, DotNetCoreBuildSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCoreBuildSettings(); - } - - var builder = new DotNetCoreBuilder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - builder.Build(project, settings); - } - - /// - /// Package all projects. - /// - /// The context. - /// The projects path. - /// - /// - /// DotNetCorePack("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Pack")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Pack")] - public static void DotNetCorePack(this ICakeContext context, string project) - { - context.DotNetCorePack(project, null); - } - - /// - /// Package all projects. - /// - /// The context. - /// The projects path. - /// The settings. - /// - /// - /// var settings = new DotNetCorePackSettings - /// { - /// Frameworks = new[] { "dnx451", "dnxcore50" }, - /// Configurations = new[] { "Debug", "Release" }, - /// OutputDirectory = "./artifacts/" - /// }; - /// - /// DotNetCorePack("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Pack")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Pack")] - public static void DotNetCorePack(this ICakeContext context, string project, DotNetCorePackSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCorePackSettings(); - } - - var packer = new DotNetCorePacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - packer.Pack(project, settings); - } - - /// - /// Run all projects. - /// - /// The context. - /// - /// - /// DotNetCoreRun(); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Run")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Run")] - public static void DotNetCoreRun(this ICakeContext context) - { - context.DotNetCoreRun(null, null, null); - } - - /// - /// Run project. - /// - /// The context. - /// The project path. - /// - /// - /// DotNetCoreRun("./src/Project"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Run")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Run")] - public static void DotNetCoreRun(this ICakeContext context, string project) - { - context.DotNetCoreRun(project, null, null); - } - - /// - /// Run project with path and arguments. - /// - /// The context. - /// The project path. - /// The arguments. - /// - /// - /// DotNetCoreRun("./src/Project", "--args"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Run")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Run")] - public static void DotNetCoreRun(this ICakeContext context, string project, ProcessArgumentBuilder arguments) - { - context.DotNetCoreRun(project, arguments, null); - } - - /// - /// Run project with settings. - /// - /// The context. - /// The project path. - /// The arguments. - /// The settings. - /// - /// - /// var settings = new DotNetCoreRunSettings - /// { - /// Framework = "dnxcore50", - /// Configuration = "Release" - /// }; - /// - /// DotNetCoreRun("./src/Project", "--args", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Run")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Run")] - public static void DotNetCoreRun(this ICakeContext context, string project, ProcessArgumentBuilder arguments, DotNetCoreRunSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCoreRunSettings(); - } - - var runner = new DotNetCoreRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(project, arguments, settings); - } - - /// - /// Publish all projects. - /// - /// The context. - /// The projects path. - /// - /// - /// DotNetCorePublish("./src/*"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Publish")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Publish")] - public static void DotNetCorePublish(this ICakeContext context, string project) - { - context.DotNetCorePublish(project, null); - } - - /// - /// Publish all projects. - /// - /// The context. - /// The projects path. - /// The settings. - /// - /// - /// var settings = new DotNetCorePublishSettings - /// { - /// Framework = "dnxcore50", - /// Configuration = "Release", - /// OutputDirectory = "./artifacts/" - /// }; - /// - /// DotNetCorePublish("./src/*", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Publish")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Publish")] - public static void DotNetCorePublish(this ICakeContext context, string project, DotNetCorePublishSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCorePublishSettings(); - } - - var publisher = new DotNetCorePublisher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - publisher.Publish(project, settings); - } - - /// - /// Test project. - /// - /// The context. - /// - /// - /// DotNetCoreTest(); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Test")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Test")] - public static void DotNetCoreTest(this ICakeContext context) - { - context.DotNetCoreTest(null, null); - } - - /// - /// Test project with path. - /// - /// The context. - /// The project path. - /// - /// - /// DotNetCoreTest("./src/Project"); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Test")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Test")] - public static void DotNetCoreTest(this ICakeContext context, string project) - { - context.DotNetCoreTest(project, null); - } - - /// - /// Test project with settings. - /// - /// The context. - /// The project path. - /// The settings. - /// - /// - /// var settings = new DotNetCoreRunSettings - /// { - /// Configuration = "Release" - /// }; - /// - /// DotNetCoreRun("./test/Project.Tests", settings); - /// - /// - [CakeMethodAlias] - [CakeAliasCategory("Test")] - [CakeNamespaceImport("Cake.Common.Tools.DotNetCore.Test")] - public static void DotNetCoreTest(this ICakeContext context, string project, DotNetCoreTestSettings settings) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - if (settings == null) - { - settings = new DotNetCoreTestSettings(); - } - - var tester = new DotNetCoreTester(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - tester.Test(project, settings); - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/DotNetCoreSettings.cs b/src/Cake.Common/Tools/DotNetCore/DotNetCoreSettings.cs deleted file mode 100644 index 8bd561bb1f..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/DotNetCoreSettings.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore -{ - /// - /// Contains common settings used by . - /// - public abstract class DotNetCoreSettings : ToolSettings - { - /// - /// Gets or sets a value indicating whether to not enable verbose output. - /// - public bool Verbose { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/DotNetCoreTool.cs b/src/Cake.Common/Tools/DotNetCore/DotNetCoreTool.cs deleted file mode 100644 index fdb46c7ec9..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/DotNetCoreTool.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore -{ - /// - /// Base class for all .NET Core related tools. - /// - /// The settings type. - public abstract class DotNetCoreTool : Tool - where TSettings : DotNetCoreSettings - { - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - protected DotNetCoreTool( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) - : base(fileSystem, environment, processRunner, tools) - { - } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return ".NET Core CLI"; - } - - /// - /// Gets the possible names of the tool executable. - /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "dotnet", "dotnet.exe" }; - } - - /// - /// Creates a and adds common commandline arguments. - /// - /// The settings. - /// Instance of . - protected ProcessArgumentBuilder CreateArgumentBuilder(TSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - if (settings.Verbose) - { - builder.Append("--verbose"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecuteSettings.cs b/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecuteSettings.cs deleted file mode 100644 index 7c5dcd84ca..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecuteSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.DotNetCore.Execute -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCoreExecuteSettings : DotNetCoreSettings - { - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecutor.cs b/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecutor.cs deleted file mode 100644 index 6214238847..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Execute/DotNetCoreExecutor.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Execute -{ - /// - /// .NET Core assembly executor. - /// - public sealed class DotNetCoreExecutor : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCoreExecutor( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Execute an assembly using arguments and settings. - /// - /// The assembly path. - /// The arguments. - /// The settings. - public void Execute(FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetCoreSettings settings) - { - if (assemblyPath == null) - { - throw new ArgumentNullException("assemblyPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(assemblyPath, arguments, settings)); - } - - private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, ProcessArgumentBuilder arguments, DotNetCoreSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - assemblyPath = assemblyPath.IsRelative ? assemblyPath.MakeAbsolute(_environment) : assemblyPath; - builder.Append(assemblyPath.FullPath); - - if (!arguments.IsNullOrEmpty()) - { - arguments.CopyTo(builder); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePackSettings.cs b/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePackSettings.cs deleted file mode 100644 index fcf123bb0d..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePackSettings.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore.Pack -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCorePackSettings : DotNetCoreSettings - { - /// - /// Gets or sets the directory in which to place temporary outputs. - /// - public DirectoryPath BuildBasePath { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets the configuration under which to build. - /// - public string Configuration { get; set; } - - /// - /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. - /// - public string VersionSuffix { get; set; } - - /// - /// Gets or sets a value indicating whether to not build project before packing. - /// - public bool NoBuild { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePacker.cs b/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePacker.cs deleted file mode 100644 index f9e67aaeb4..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Pack/DotNetCorePacker.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Pack -{ - /// - /// .NET Core project packer. - /// - public sealed class DotNetCorePacker : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCorePacker( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Pack the project using the specified path and settings. - /// - /// The target file path. - /// The settings. - public void Pack(string project, DotNetCorePackSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(project, settings)); - } - - private ProcessArgumentBuilder GetArguments(string project, DotNetCorePackSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("pack"); - - // Specific path? - if (project != null) - { - builder.Append(project); - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--output"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Build base path - if (settings.BuildBasePath != null) - { - builder.Append("--build-base-path"); - builder.AppendQuoted(settings.BuildBasePath.MakeAbsolute(_environment).FullPath); - } - - // No build - if (settings.NoBuild) - { - builder.Append("--no-build"); - } - - // Configuration - if (!string.IsNullOrEmpty(settings.Configuration)) - { - builder.Append("--configuration"); - builder.Append(settings.Configuration); - } - - // Version suffix - if (!string.IsNullOrEmpty(settings.VersionSuffix)) - { - builder.Append("--version-suffix"); - builder.Append(settings.VersionSuffix); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublishSettings.cs b/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublishSettings.cs deleted file mode 100644 index 2fee2acd61..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublishSettings.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore.Publish -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCorePublishSettings : DotNetCoreSettings - { - /// - /// Gets or sets the directory in which to place temporary outputs. - /// - public DirectoryPath BuildBasePath { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets the target runtime. - /// - public string Runtime { get; set; } - - /// - /// Gets or sets a specific framework to compile. - /// - public string Framework { get; set; } - - /// - /// Gets or sets the configuration under which to build. - /// - public string Configuration { get; set; } - - /// - /// Gets or sets the value that defines what `*` should be replaced with in version field in project.json. - /// - public string VersionSuffix { get; set; } - - /// - /// Gets or sets a value indicating whether to not build projects before publishing. - /// - public bool NoBuild { get; set; } - - /// - /// Gets or sets a value indicating whether to enable a temporary mechanism to include subdirectories - /// from native assets of dependency packages in output. - /// - public bool NativeSubDirectory { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublisher.cs b/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublisher.cs deleted file mode 100644 index fe2167c0cb..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Publish/DotNetCorePublisher.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Publish -{ - /// - /// .NET Core project runner. - /// - public sealed class DotNetCorePublisher : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCorePublisher( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Publish the project using the specified path and settings. - /// - /// The target file path. - /// The settings. - public void Publish(string path, DotNetCorePublishSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(path, settings)); - } - - private ProcessArgumentBuilder GetArguments(string path, DotNetCorePublishSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("publish"); - - // Specific path? - if (path != null) - { - builder.Append(path); - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--output"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Build base path - if (settings.BuildBasePath != null) - { - builder.Append("--build-base-path"); - builder.AppendQuoted(settings.BuildBasePath.MakeAbsolute(_environment).FullPath); - } - - // Runtime - if (!string.IsNullOrEmpty(settings.Runtime)) - { - builder.Append("--runtime"); - builder.Append(settings.Runtime); - } - - // Framework - if (!string.IsNullOrEmpty(settings.Framework)) - { - builder.Append("--framework"); - builder.Append(settings.Framework); - } - - // Configuration - if (!string.IsNullOrEmpty(settings.Configuration)) - { - builder.Append("--configuration"); - builder.Append(settings.Configuration); - } - - // Version suffix - if (!string.IsNullOrEmpty(settings.VersionSuffix)) - { - builder.Append("--version-suffix"); - builder.Append(settings.VersionSuffix); - } - - if (settings.NativeSubDirectory) - { - builder.Append("--native-subdirectory"); - } - - if (settings.NoBuild) - { - builder.Append("--no-build"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreSettings.cs b/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreSettings.cs deleted file mode 100644 index 0453bf0ae9..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreSettings.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore.Restore -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCoreRestoreSettings : DotNetCoreSettings - { - /// - /// Gets or sets the specified NuGet package sources to use during the restore. - /// - public ICollection Sources { get; set; } - - /// - /// Gets or sets the NuGet configuration file to use. - /// - public FilePath ConfigFile { get; set; } - - /// - /// Gets or sets the directory to install packages in. - /// - public DirectoryPath PackagesDirectory { get; set; } - - /// - /// Gets or sets a temporary option to allow NuGet to infer RIDs for legacy repositories. - /// - public ICollection InferRuntimes { get; set; } - - /// - /// Gets or sets the list of packages sources to use as a fallback. - /// - public ICollection FallbackSources { get; set; } - - /// - /// Gets or sets a value indicating whether to display any output. - /// - public bool Quiet { get; set; } - - /// - /// Gets or sets a value indicating whether to do not cache packages and http requests. - /// - public bool NoCache { get; set; } - - /// - /// Gets or sets a value indicating whether to disable restoring multiple projects in parallel. - /// - public bool DisableParallel { get; set; } - - /// - /// Gets or sets a value indicating whether to force the application to run using an invariant, English-based culture. - /// - public bool ForceEnglishOutput { get; set; } - - /// - /// Gets or sets a value indicating whether to only warning failed sources if there are packages meeting version requirement. - /// - public bool IgnoreFailedSources { get; set; } - - /// - /// Gets or sets the verbosity of logging to use. - /// - public DotNetCoreRestoreVerbosity? Verbosity { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreVerbosity.cs b/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreVerbosity.cs deleted file mode 100644 index 3484b14007..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestoreVerbosity.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.DotNetCore.Restore -{ - /// - /// Contains the verbosity of logging to use. Used by . - /// - public enum DotNetCoreRestoreVerbosity - { - /// - /// Error level. - /// - Error, - - /// - /// Warning level. - /// - Warning, - - /// - /// Information level. - /// - Information, - - /// - /// Verbose level. - /// - Verbose - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestorer.cs b/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestorer.cs deleted file mode 100644 index 3cf261f9bc..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Restore/DotNetCoreRestorer.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Restore -{ - /// - /// .NET Core project restorer. - /// - public sealed class DotNetCoreRestorer : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCoreRestorer( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Restore the project using the specified path and settings. - /// - /// List of projects and project folders to restore. Each value can be: a path to a project.json or global.json file, or a folder to recursively search for project.json files. - /// The settings. - public void Restore(string root, DotNetCoreRestoreSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(root, settings)); - } - - private ProcessArgumentBuilder GetArguments(string root, DotNetCoreRestoreSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("restore"); - - // Specific root? - if (root != null) - { - builder.Append(root); - } - - // Output directory - if (settings.PackagesDirectory != null) - { - builder.Append("--packages"); - builder.AppendQuoted(settings.PackagesDirectory.MakeAbsolute(_environment).FullPath); - } - - // Sources - if (settings.Sources != null) - { - foreach (var source in settings.Sources) - { - builder.Append("--source"); - builder.AppendQuoted(source); - } - } - - // List of fallback package sources - if (settings.FallbackSources != null) - { - foreach (var source in settings.FallbackSources) - { - builder.Append("--fallbacksource"); - builder.AppendQuoted(source); - } - } - - // Config file - if (settings.ConfigFile != null) - { - builder.Append("--configfile"); - builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); - } - - // List of runtime identifiers - if (settings.InferRuntimes != null) - { - foreach (var runtime in settings.InferRuntimes) - { - builder.Append("--infer-runtimes"); - builder.AppendQuoted(runtime); - } - } - - // Quiet - if (settings.Quiet) - { - builder.Append("--quiet"); - } - - // Ignore failed sources - if (settings.NoCache) - { - builder.Append("--no-cache"); - } - - // Disable parallel - if (settings.DisableParallel) - { - builder.Append("--disable-parallel"); - } - - // Ignore failed sources - if (settings.IgnoreFailedSources) - { - builder.Append("--ignore-failed-sources"); - } - - // Force english output - if (settings.ForceEnglishOutput) - { - builder.Append("--force-english-output"); - } - - // Verbosity - if (settings.Verbosity.HasValue) - { - builder.Append("--verbosity"); - builder.Append(settings.Verbosity.ToString()); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunSettings.cs b/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunSettings.cs deleted file mode 100644 index ba82d87932..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.DotNetCore.Run -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCoreRunSettings : DotNetCoreSettings - { - /// - /// Gets or sets a specific framework to compile. - /// - public string Framework { get; set; } - - /// - /// Gets or sets the configuration under which to build. - /// - public string Configuration { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunner.cs b/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunner.cs deleted file mode 100644 index 0f872d9713..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Run/DotNetCoreRunner.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Run -{ - /// - /// .NET Core project runner. - /// - public sealed class DotNetCoreRunner : DotNetCoreTool - { - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCoreRunner( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - } - - /// - /// Runs the project using the specified path with arguments and settings. - /// - /// The target project path. - /// The arguments. - /// The settings. - public void Run(string project, ProcessArgumentBuilder arguments, DotNetCoreRunSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(project, arguments, settings)); - } - - private ProcessArgumentBuilder GetArguments(string project, ProcessArgumentBuilder arguments, DotNetCoreRunSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("run"); - - // Specific path? - if (project != null) - { - builder.Append("--project"); - builder.AppendQuoted(project); - } - - // Framework - if (!string.IsNullOrEmpty(settings.Framework)) - { - builder.Append("--framework"); - builder.Append(settings.Framework); - } - - // Configuration - if (!string.IsNullOrEmpty(settings.Configuration)) - { - builder.Append("--configuration"); - builder.Append(settings.Configuration); - } - - // Arguments - if (!arguments.IsNullOrEmpty()) - { - builder.Append("--"); - arguments.CopyTo(builder); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTestSettings.cs b/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTestSettings.cs deleted file mode 100644 index 84231739ce..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTestSettings.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Cake.Core.IO; - -namespace Cake.Common.Tools.DotNetCore.Test -{ - /// - /// Contains settings used by . - /// - public sealed class DotNetCoreTestSettings : DotNetCoreSettings - { - /// - /// Gets or sets the directory in which to place temporary outputs. - /// - public DirectoryPath BuildBasePath { get; set; } - - /// - /// Gets or sets the output directory. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets the target runtime. - /// - public string Runtime { get; set; } - - /// - /// Gets or sets the configuration under which to build. - /// - public string Configuration { get; set; } - - /// - /// Gets or sets specific framework to compile. - /// - public string Framework { get; set; } - - /// - /// Gets or sets a value indicating whether to not build the project before testing. - /// - public bool NoBuild { get; set; } - } -} diff --git a/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTester.cs b/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTester.cs deleted file mode 100644 index c3cd5bd711..0000000000 --- a/src/Cake.Common/Tools/DotNetCore/Test/DotNetCoreTester.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.DotNetCore.Test -{ - /// - /// .NET Core project tester. - /// - public sealed class DotNetCoreTester : DotNetCoreTool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public DotNetCoreTester( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Tests the project using the specified path with arguments and settings. - /// - /// The target project path. - /// The settings. - public void Test(string project, DotNetCoreTestSettings settings) - { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(project, settings)); - } - - private ProcessArgumentBuilder GetArguments(string project, DotNetCoreTestSettings settings) - { - var builder = CreateArgumentBuilder(settings); - - builder.Append("test"); - - // Specific path? - if (project != null) - { - builder.Append(project); - } - - // Output directory - if (settings.OutputDirectory != null) - { - builder.Append("--output"); - builder.AppendQuoted(settings.OutputDirectory.MakeAbsolute(_environment).FullPath); - } - - // Temporary output directory - if (settings.BuildBasePath != null) - { - builder.Append("--build-base-path"); - builder.AppendQuoted(settings.BuildBasePath.MakeAbsolute(_environment).FullPath); - } - - // Runtime - if (!string.IsNullOrEmpty(settings.Runtime)) - { - builder.Append("--runtime"); - builder.Append(settings.Runtime); - } - - // Frameworks - if (!string.IsNullOrEmpty(settings.Framework)) - { - builder.Append("--framework"); - builder.Append(settings.Framework); - } - - // Configuration - if (!string.IsNullOrEmpty(settings.Configuration)) - { - builder.Append("--configuration"); - builder.Append(settings.Configuration); - } - - if (settings.NoBuild) - { - builder.Append("--no-build"); - } - - return builder; - } - } -} diff --git a/src/Cake.Common/Tools/DupFinder/DupFinderAliases.cs b/src/Cake.Common/Tools/DupFinder/DupFinderAliases.cs index 69016cab8b..e823863b30 100644 --- a/src/Cake.Common/Tools/DupFinder/DupFinderAliases.cs +++ b/src/Cake.Common/Tools/DupFinder/DupFinderAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.DupFinder /// Contains functionality related to ReSharper's dupFinder tool. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=JetBrains.ReSharper.CommandLineTools" /// @@ -123,10 +124,7 @@ public static void DupFinder(this ICakeContext context, IEnumerable fi [CakeAliasCategory("DupFinder")] public static void DupFinder(this ICakeContext context, IEnumerable files, DupFinderSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new DupFinderRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); runner.Run(files, settings); @@ -144,7 +142,7 @@ public static void DupFinder(this ICakeContext context, IEnumerable fi /// [CakeMethodAlias] [CakeAliasCategory("DupFinder")] - public static void DupFinder(this ICakeContext context, string pattern) + public static void DupFinder(this ICakeContext context, GlobPattern pattern) { DupFinder(context, pattern, new DupFinderSettings()); } @@ -168,17 +166,11 @@ public static void DupFinder(this ICakeContext context, string pattern) /// [CakeMethodAlias] [CakeAliasCategory("DupFinder")] - public static void DupFinder(this ICakeContext context, string pattern, DupFinderSettings settings) + public static void DupFinder(this ICakeContext context, GlobPattern pattern, DupFinderSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (pattern == null) - { - throw new ArgumentNullException("pattern"); - } + ArgumentNullException.ThrowIfNull(pattern); var sourceFiles = context.Globber.GetFiles(pattern).ToArray(); if (sourceFiles.Length == 0) @@ -205,13 +197,10 @@ public static void DupFinder(this ICakeContext context, string pattern, DupFinde [CakeAliasCategory("DupFinder")] public static void DupFinderFromConfig(this ICakeContext context, FilePath configFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new DupFinderRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); runner.RunFromConfig(configFile); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DupFinder/DupFinderRunner.cs b/src/Cake.Common/Tools/DupFinder/DupFinderRunner.cs index d935d548dd..782ca7c42e 100644 --- a/src/Cake.Common/Tools/DupFinder/DupFinderRunner.cs +++ b/src/Cake.Common/Tools/DupFinder/DupFinderRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -14,7 +15,7 @@ namespace Cake.Common.Tools.DupFinder { /// - /// DupFinder runner + /// DupFinder runner. /// public sealed class DupFinderRunner : Tool { @@ -49,22 +50,19 @@ public DupFinderRunner( /// The settings. public void Run(IEnumerable filePaths, DupFinderSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } + ArgumentNullException.ThrowIfNull(filePaths); Run(settings, GetArgument(settings, filePaths)); - if (settings.OutputFile != null) + if (settings.SkipOutputAnalysis || + settings.OutputFile == null) { - AnalyzeResultsFile(settings.OutputFile, settings.ThrowExceptionOnFindingDuplicates); + return; } + + AnalyzeResultsFile(settings.OutputFile, settings.ThrowExceptionOnFindingDuplicates); } /// @@ -73,10 +71,7 @@ public void Run(IEnumerable filePaths, DupFinderSettings settings) /// The config file. public void RunFromConfig(FilePath configFile) { - if (configFile == null) - { - throw new ArgumentNullException("configFile"); - } + ArgumentNullException.ThrowIfNull(configFile); Run(new DupFinderSettings(), GetConfigArgument(configFile)); } @@ -251,4 +246,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "dupfinder.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DupFinder/DupFinderSettings.cs b/src/Cake.Common/Tools/DupFinder/DupFinderSettings.cs index 124b21eab6..7e841e34ba 100644 --- a/src/Cake.Common/Tools/DupFinder/DupFinderSettings.cs +++ b/src/Cake.Common/Tools/DupFinder/DupFinderSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -66,7 +68,7 @@ public sealed class DupFinderSettings : ToolSettings /// /// Gets or sets MsBuild properties. /// - public Dictionary MsBuildProperties { get; set; } + public Dictionary MsBuildProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets or sets a value indicating whether to normalize type names to the last subtype. @@ -82,7 +84,7 @@ public sealed class DupFinderSettings : ToolSettings /// /// Gets or sets the location DupFinder should write its output. /// - /// The location DupFinder should write its output + /// The location DupFinder should write its output. public FilePath OutputFile { get; set; } /// @@ -96,8 +98,14 @@ public sealed class DupFinderSettings : ToolSettings public bool ShowText { get; set; } /// - /// Gets or sets a value indicating whether to throw an exception on finding duplicates + /// Gets or sets a value indicating whether to throw an exception on finding duplicates. /// public bool ThrowExceptionOnFindingDuplicates { get; set; } + + /// + /// Gets or sets a value indicating whether to skip analysis of the file + /// that was output by the command line tool or not. + /// + public bool SkipOutputAnalysis { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Fixie/FixieAliases.cs b/src/Cake.Common/Tools/Fixie/FixieAliases.cs index ec6501d8e1..25106c6d20 100644 --- a/src/Cake.Common/Tools/Fixie/FixieAliases.cs +++ b/src/Cake.Common/Tools/Fixie/FixieAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.Fixie /// Contains functionality related to running Fixie tests. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=Fixie" /// @@ -27,15 +28,17 @@ public static class FixieAliases /// /// Runs all Fixie tests in the assemblies matching the specified pattern. /// + /// + /// + /// Fixie("./src/UnitTests/*.dll"); + /// + /// /// The context. /// The pattern. [CakeMethodAlias] - public static void Fixie(this ICakeContext context, string pattern) + public static void Fixie(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -51,16 +54,20 @@ public static void Fixie(this ICakeContext context, string pattern) /// Runs all Fixie tests in the assemblies matching the specified pattern, /// using the specified settings. /// + /// + /// + /// Fixie("./src/UnitTests/*.dll", new FixieSettings { + /// NUnitXml = TestResult.xml + /// }); + /// + /// /// The context. /// The pattern. /// The settings. [CakeMethodAlias] - public static void Fixie(this ICakeContext context, string pattern, FixieSettings settings) + public static void Fixie(this ICakeContext context, GlobPattern pattern, FixieSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -75,15 +82,21 @@ public static void Fixie(this ICakeContext context, string pattern, FixieSetting /// /// Runs all Fixie tests in the specified assemblies. /// + /// + /// + /// var assemblies = new [] { + /// "UnitTests1.dll", + /// "UnitTests2.dll" + /// }; + /// Fixie(assemblies); + /// + /// /// The context. /// The assemblies. [CakeMethodAlias] public static void Fixie(this ICakeContext context, IEnumerable assemblies) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); Fixie(context, paths, new FixieSettings()); } @@ -91,6 +104,12 @@ public static void Fixie(this ICakeContext context, IEnumerable assembli /// /// Runs all Fixie tests in the specified assemblies. /// + /// + /// + /// var assemblies = GetFiles("./src/UnitTests/*.dll"); + /// Fixie(assemblies); + /// + /// /// The context. /// The assemblies. [CakeMethodAlias] @@ -103,16 +122,24 @@ public static void Fixie(this ICakeContext context, IEnumerable assemb /// Runs all Fixie tests in the specified assemblies, /// using the specified settings. /// + /// + /// + /// var assemblies = new [] { + /// "UnitTests1.dll", + /// "UnitTests2.dll" + /// }; + /// Fixie(assemblies, new FixieSettings { + /// NUnitXml = TestResult.xml + /// }); + /// + /// /// The context. /// The assemblies. /// The settings. [CakeMethodAlias] public static void Fixie(this ICakeContext context, IEnumerable assemblies, FixieSettings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); Fixie(context, paths, settings); } @@ -121,23 +148,25 @@ public static void Fixie(this ICakeContext context, IEnumerable assembli /// Runs all Fixie tests in the specified assemblies, /// using the specified settings. /// + /// + /// + /// var assemblies = GetFiles("./src/UnitTests/*.dll"); + /// Fixie(assemblies, new FixieSettings { + /// NUnitXml = TestResult.xml + /// }); + /// + /// /// The context. /// The assemblies. /// The settings. [CakeMethodAlias] public static void Fixie(this ICakeContext context, IEnumerable assemblies, FixieSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); var runner = new FixieRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblies, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Fixie/FixieRunner.cs b/src/Cake.Common/Tools/Fixie/FixieRunner.cs index 52745f6a30..a48b0029fc 100644 --- a/src/Cake.Common/Tools/Fixie/FixieRunner.cs +++ b/src/Cake.Common/Tools/Fixie/FixieRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -40,14 +41,8 @@ public FixieRunner( /// The settings. public void Run(IEnumerable assemblyPaths, FixieSettings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(assemblyPaths, settings)); } @@ -118,4 +113,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "Fixie.Console.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Fixie/FixieSettings.cs b/src/Cake.Common/Tools/Fixie/FixieSettings.cs index c87502878e..30ce025719 100644 --- a/src/Cake.Common/Tools/Fixie/FixieSettings.cs +++ b/src/Cake.Common/Tools/Fixie/FixieSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.IO; @@ -13,8 +14,6 @@ namespace Cake.Common.Tools.Fixie /// public sealed class FixieSettings : ToolSettings { - private readonly IDictionary> _options = new Dictionary>(StringComparer.OrdinalIgnoreCase); - /// /// Gets or sets the file to be used to output NUnit style of XML results. /// @@ -45,9 +44,6 @@ public sealed class FixieSettings : ToolSettings /// /// The collection of keys and values. /// - public IDictionary> Options - { - get { return _options; } - } + public IDictionary> Options { get; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Fixie/FixieSettingsExtensions.cs b/src/Cake.Common/Tools/Fixie/FixieSettingsExtensions.cs index 1801065852..0544a02fa2 100644 --- a/src/Cake.Common/Tools/Fixie/FixieSettingsExtensions.cs +++ b/src/Cake.Common/Tools/Fixie/FixieSettingsExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.Fixie { using System; @@ -21,10 +22,7 @@ public static class FixieSettingsExtensions /// The same instance so that multiple calls can be chained. public static FixieSettings WithOption(this FixieSettings settings, string name, params string[] values) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); IList currentValue; currentValue = new List(settings.Options.TryGetValue(name, out currentValue) && currentValue != null @@ -36,4 +34,4 @@ public static FixieSettings WithOption(this FixieSettings settings, string name, return settings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLink3Aliases.cs b/src/Cake.Common/Tools/GitLink/GitLink3Aliases.cs new file mode 100644 index 0000000000..d8d39ffff6 --- /dev/null +++ b/src/Cake.Common/Tools/GitLink/GitLink3Aliases.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.GitLink +{ + /// + /// Contains functionality related to GitLink version 3. + /// + /// In order to use the commands for this alias, include the following in your build.cake file to download and + /// install from nuget.org, or specify the ToolPath within the class: + /// + /// #tool "nuget:?package=gitlink" + /// + /// + /// + [CakeAliasCategory("GitLink v3")] + public static class GitLink3Aliases + { + /// + /// Update the pdb file to link all sources. + /// This will allow anyone to step through the source code while debugging without a symbol source server. + /// + /// The context. + /// The PDB File to analyze. + /// + /// + /// GitLink3("C:/temp/solution/bin/my.pdb"); + /// + /// + [CakeMethodAlias] + public static void GitLink3(this ICakeContext context, FilePath pdbFilePath) + { + GitLink3(context, pdbFilePath, new GitLink3Settings()); + } + + /// + /// Update the pdb file to link all sources. + /// This will allow anyone to step through the source code while debugging without a symbol source server. + /// + /// The context. + /// The PDB File to analyze. + /// The settings. + /// + /// + /// GitLink3("C:/temp/solution/bin/my.pdb", new GitLink3Settings { + /// RepositoryUrl = "http://mydomain.com", + /// ShaHash = "abcdef" + /// }); + /// + /// + [CakeMethodAlias] + public static void GitLink3(this ICakeContext context, FilePath pdbFilePath, GitLink3Settings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var runner = new GitLink3Runner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(pdbFilePath, settings); + } + + /// + /// Update the pdb files to link all sources. + /// This will allow anyone to step through the source code while debugging without a symbol source server. + /// + /// The context. + /// The PDB File collection to analyze. + /// + /// + /// GitLink3("C:/temp/solution/bin/**/*.pdb"); + /// + /// + [CakeMethodAlias] + public static void GitLink3(this ICakeContext context, IEnumerable pdbFiles) + { + GitLink3(context, pdbFiles, new GitLink3Settings()); + } + + /// + /// Update the pdb files to link all sources. + /// This will allow anyone to step through the source code while debugging without a symbol source server. + /// + /// The context. + /// The PDB File collection to analyze. + /// The settings. + /// + /// + /// GitLink3("C:/temp/solution/bin/**/*.pdb", new GitLink3Settings { + /// RepositoryUrl = "http://mydomain.com", + /// ShaHash = "abcdef" + /// }); + /// + /// + [CakeMethodAlias] + public static void GitLink3(this ICakeContext context, IEnumerable pdbFiles, GitLink3Settings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var runner = new GitLink3Runner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(pdbFiles, settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLink3Runner.cs b/src/Cake.Common/Tools/GitLink/GitLink3Runner.cs new file mode 100644 index 0000000000..9a2737f02c --- /dev/null +++ b/src/Cake.Common/Tools/GitLink/GitLink3Runner.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitLink +{ + /// + /// GitLink runner. + /// + public sealed class GitLink3Runner : Tool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public GitLink3Runner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Update pdb file to link all sources. + /// + /// The path to the pdb file to link. + /// The settings. + public void Run(FilePath pdbFile, GitLink3Settings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + ArgumentNullException.ThrowIfNull(pdbFile); + + Run(settings, GetArguments(pdbFile, settings)); + } + + /// + /// Update pdb files to link all sources. + /// + /// The file path collection for the pdb files to link. + /// The settings. + public void Run(IEnumerable pdbFiles, GitLink3Settings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + ArgumentNullException.ThrowIfNull(pdbFiles); + + foreach (var pdbFile in pdbFiles) + { + Run(settings, GetArguments(pdbFile, settings)); + } + } + + private ProcessArgumentBuilder GetArguments(FilePath pdbFilePath, GitLink3Settings settings) + { + var builder = new ProcessArgumentBuilder(); + + if (!string.IsNullOrWhiteSpace(settings.RepositoryUrl)) + { + builder.Append("-u"); + builder.AppendQuoted(settings.RepositoryUrl); + } + + if (!string.IsNullOrWhiteSpace(settings.ShaHash)) + { + builder.Append("--commit"); + builder.AppendQuoted(settings.ShaHash); + } + + if (settings.UsePowerShell) + { + builder.Append("-m"); + builder.Append("Powershell"); + } + + if (settings.SkipVerify) + { + builder.Append("-s"); + } + + if (settings.BaseDir != null) + { + builder.Append("--baseDir"); + builder.AppendQuoted(settings.BaseDir.MakeAbsolute(_environment).FullPath); + } + + builder.AppendQuoted(pdbFilePath.MakeAbsolute(_environment).FullPath); + + return builder; + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "GitLink"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "gitlink.exe" }; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLink3Settings.cs b/src/Cake.Common/Tools/GitLink/GitLink3Settings.cs new file mode 100644 index 0000000000..9f84c6e5db --- /dev/null +++ b/src/Cake.Common/Tools/GitLink/GitLink3Settings.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitLink +{ + /// + /// Contains settings used by . + /// + public sealed class GitLink3Settings : ToolSettings + { + /// + /// Gets or sets the Url to remote Git repository. + /// + public string RepositoryUrl { get; set; } + + /// + /// Gets or sets the SHA-1 hash of the Git commit to be used. + /// + public string ShaHash { get; set; } + + /// + /// Gets or sets the path to the root of the Git repository. + /// + public DirectoryPath BaseDir { get; set; } + + /// + /// Gets or sets a value indicating whether the Use PowerShell Command option should be enabled. + /// This option will use PowerShell instead of HTTP in SRCSRV to retrieve the source code. + /// + public bool UsePowerShell { get; set; } + + /// + /// Gets or sets a value indicating whether the Skip Verify option should be enabled. + /// This option indicates whether verification of all source files are available in source control. + /// + public bool SkipVerify { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLinkAliases.cs b/src/Cake.Common/Tools/GitLink/GitLinkAliases.cs index fb867a2d6a..4d79dbfee6 100644 --- a/src/Cake.Common/Tools/GitLink/GitLinkAliases.cs +++ b/src/Cake.Common/Tools/GitLink/GitLinkAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,13 +13,13 @@ namespace Cake.Common.Tools.GitLink /// Contains functionality related to GitLink. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// - /// #tool "nuget:?package=gitlink" + /// #tool "nuget:?package=gitlink&version=2.4.0" /// /// /// - [CakeAliasCategory("GitTools")] + [CakeAliasCategory("GitLink")] public static class GitLinkAliases { /// @@ -33,7 +34,6 @@ public static class GitLinkAliases /// /// [CakeMethodAlias] - [CakeAliasCategory("GitLink")] public static void GitLink(this ICakeContext context, DirectoryPath repositoryRootPath) { GitLink(context, repositoryRootPath, new GitLinkSettings()); @@ -56,16 +56,12 @@ public static void GitLink(this ICakeContext context, DirectoryPath repositoryRo /// /// [CakeMethodAlias] - [CakeAliasCategory("GitLink")] public static void GitLink(this ICakeContext context, DirectoryPath repositoryRootPath, GitLinkSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new GitLinkRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(repositoryRootPath, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLinkRunner.cs b/src/Cake.Common/Tools/GitLink/GitLinkRunner.cs index b712fb5f8b..16b81529e1 100644 --- a/src/Cake.Common/Tools/GitLink/GitLinkRunner.cs +++ b/src/Cake.Common/Tools/GitLink/GitLinkRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -10,7 +11,7 @@ namespace Cake.Common.Tools.GitLink { /// - /// GitLink runner + /// GitLink runner. /// public sealed class GitLinkRunner : Tool { @@ -39,15 +40,9 @@ public GitLinkRunner( /// The settings. public void Run(DirectoryPath repositoryRootPath, GitLinkSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - if (repositoryRootPath == null) - { - throw new ArgumentNullException("repositoryRootPath"); - } + ArgumentNullException.ThrowIfNull(repositoryRootPath); Run(settings, GetArguments(repositoryRootPath, settings)); } @@ -147,4 +142,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "gitlink.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitLink/GitLinkSettings.cs b/src/Cake.Common/Tools/GitLink/GitLinkSettings.cs index 935ee6b546..e1a8810485 100644 --- a/src/Cake.Common/Tools/GitLink/GitLinkSettings.cs +++ b/src/Cake.Common/Tools/GitLink/GitLinkSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.GitLink public sealed class GitLinkSettings : ToolSettings { /// - /// Gets or sets the Url to remote git repository. + /// Gets or sets the Url to remote Git repository. /// public string RepositoryUrl { get; set; } @@ -24,13 +25,13 @@ public sealed class GitLinkSettings : ToolSettings /// /// Gets or sets the name of the configuration. /// - /// Default is Release + /// Default is Release. public string Configuration { get; set; } /// /// Gets or sets the name of the platform. /// - /// Default is AnyCPU + /// Default is AnyCPU. public string Platform { get; set; } /// @@ -44,7 +45,7 @@ public sealed class GitLinkSettings : ToolSettings public FilePath LogFilePath { get; set; } /// - /// Gets or sets the SHA-1 hash of the git commit to be used. + /// Gets or sets the SHA-1 hash of the Git commit to be used. /// public string ShaHash { get; set; } @@ -73,4 +74,4 @@ public sealed class GitLinkSettings : ToolSettings /// public bool IsDebug { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAddAssetsSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAddAssetsSettings.cs index 06d5481d7d..8d9ca3e7f2 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAddAssetsSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAddAssetsSettings.cs @@ -1,24 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseManager.AddAssets { /// /// Contains settings used by . /// - public sealed class GitReleaseManagerAddAssetsSettings : ToolSettings + public sealed class GitReleaseManagerAddAssetsSettings : GitReleaseManagerSettings { - /// - /// Gets or sets the path on which GitReleaseManager should be executed. - /// - public DirectoryPath TargetDirectory { get; set; } - - /// - /// Gets or sets the path to the GitReleaseManager log file. - /// - public FilePath LogFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdder.cs b/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdder.cs index 0a4a5e62a5..e51bd435ed 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdder.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/AddAssets/GitReleaseManagerAssetsAdder.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -13,8 +14,6 @@ namespace Cake.Common.Tools.GitReleaseManager.AddAssets /// public sealed class GitReleaseManagerAssetsAdder : GitReleaseManagerTool { - private readonly ICakeEnvironment _environment; - /// /// Initializes a new instance of the class. /// @@ -28,7 +27,6 @@ public GitReleaseManagerAssetsAdder( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { - _environment = environment; } /// @@ -45,40 +43,78 @@ public void AddAssets(string userName, string password, string owner, string rep { if (string.IsNullOrWhiteSpace(userName)) { - throw new ArgumentNullException("userName"); + throw new ArgumentNullException(nameof(userName)); } if (string.IsNullOrWhiteSpace(password)) { - throw new ArgumentNullException("password"); + throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("repository"); + throw new ArgumentNullException(nameof(repository)); } if (string.IsNullOrWhiteSpace(tagName)) { - throw new ArgumentNullException("tagName"); + throw new ArgumentNullException(nameof(tagName)); } if (string.IsNullOrWhiteSpace(assets)) { - throw new ArgumentNullException("assets"); + throw new ArgumentNullException(nameof(assets)); } - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, tagName, assets, settings)); + } + + /// + /// Creates a Release using the specified and settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The tag name. + /// The assets to upload. + /// The settings. + public void AddAssets(string token, string owner, string repository, string tagName, string assets, GitReleaseManagerAddAssetsSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(token)); } - Run(settings, GetArguments(userName, password, owner, repository, tagName, assets, settings)); + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + if (string.IsNullOrWhiteSpace(tagName)) + { + throw new ArgumentNullException(nameof(tagName)); + } + + if (string.IsNullOrWhiteSpace(assets)) + { + throw new ArgumentNullException(nameof(assets)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, tagName, assets, settings)); } private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, string tagName, string assets, GitReleaseManagerAddAssetsSettings settings) @@ -93,6 +129,31 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-p"); builder.AppendQuotedSecret(password); + ParseCommonArguments(builder, owner, repository, tagName, assets); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, string tagName, string assets, GitReleaseManagerAddAssetsSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("addasset"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository, tagName, assets); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository, string tagName, string assets) + { builder.Append("-o"); builder.AppendQuoted(owner); @@ -104,22 +165,6 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-a"); builder.AppendQuoted(assets); - - // Target Directory - if (settings.TargetDirectory != null) - { - builder.Append("-d"); - builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); - } - - // Log File Path - if (settings.LogFilePath != null) - { - builder.Append("-l"); - builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); - } - - return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerCloseMilestoneSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerCloseMilestoneSettings.cs index 7a98a45daf..118ed1d196 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerCloseMilestoneSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerCloseMilestoneSettings.cs @@ -1,24 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseManager.Close { /// /// Contains settings used by . /// - public sealed class GitReleaseManagerCloseMilestoneSettings : ToolSettings + public sealed class GitReleaseManagerCloseMilestoneSettings : GitReleaseManagerSettings { - /// - /// Gets or sets the path on which GitReleaseManager should be executed. - /// - public DirectoryPath TargetDirectory { get; set; } - - /// - /// Gets or sets the path to the GitReleaseManager log file. - /// - public FilePath LogFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloser.cs b/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloser.cs index 2d9dfc5cbc..30a399b885 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloser.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Close/GitReleaseManagerMilestoneCloser.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -13,8 +14,6 @@ namespace Cake.Common.Tools.GitReleaseManager.Close /// public sealed class GitReleaseManagerMilestoneCloser : GitReleaseManagerTool { - private readonly ICakeEnvironment _environment; - /// /// Initializes a new instance of the class. /// @@ -28,11 +27,10 @@ public GitReleaseManagerMilestoneCloser( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { - _environment = environment; } /// - /// Creates a Release using the specified and settings. + /// Closes a milestone using the specified settings. /// /// The user name. /// The password. @@ -44,35 +42,67 @@ public void Close(string userName, string password, string owner, string reposit { if (string.IsNullOrWhiteSpace(userName)) { - throw new ArgumentNullException("userName"); + throw new ArgumentNullException(nameof(userName)); } if (string.IsNullOrWhiteSpace(password)) { - throw new ArgumentNullException("password"); + throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("repository"); + throw new ArgumentNullException(nameof(repository)); } if (string.IsNullOrWhiteSpace(milestone)) { - throw new ArgumentNullException("milestone"); + throw new ArgumentNullException(nameof(milestone)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, milestone, settings)); + } + + /// + /// Closes a milestone using the specified settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// The settings. + public void Close(string token, string owner, string repository, string milestone, GitReleaseManagerCloseMilestoneSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); } - if (settings == null) + if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(owner)); } - Run(settings, GetArguments(userName, password, owner, repository, milestone, settings)); + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + if (string.IsNullOrWhiteSpace(milestone)) + { + throw new ArgumentNullException(nameof(milestone)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, milestone, settings)); } private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, string milestone, GitReleaseManagerCloseMilestoneSettings settings) @@ -87,6 +117,31 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-p"); builder.AppendQuotedSecret(password); + ParseCommonArguments(builder, owner, repository, milestone); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, string milestone, GitReleaseManagerCloseMilestoneSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("close"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository, milestone); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository, string milestone) + { builder.Append("-o"); builder.AppendQuoted(owner); @@ -95,22 +150,6 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-m"); builder.AppendQuoted(milestone); - - // Target Directory - if (settings.TargetDirectory != null) - { - builder.Append("-d"); - builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); - } - - // Log File Path - if (settings.LogFilePath != null) - { - builder.Append("-l"); - builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); - } - - return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreateSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreateSettings.cs index 9746eb2dad..1d21e11737 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreateSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreateSettings.cs @@ -1,15 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseManager.Create { /// /// Contains settings used by . /// - public sealed class GitReleaseManagerCreateSettings : ToolSettings + public sealed class GitReleaseManagerCreateSettings : GitReleaseManagerSettings { /// /// Gets or sets the milestone to be used when creating the release. @@ -37,18 +37,8 @@ public sealed class GitReleaseManagerCreateSettings : ToolSettings public string Assets { get; set; } /// - /// Gets or sets the commit to tag. Can be a branch or SHA. Defaults to repository's default branch.. + /// Gets or sets the commit to tag. Can be a branch or SHA. Defaults to repository's default branch. /// public string TargetCommitish { get; set; } - - /// - /// Gets or sets the path on which GitReleaseManager should be executed. - /// - public DirectoryPath TargetDirectory { get; set; } - - /// - /// Gets or sets the path to the GitReleaseManager log file. - /// - public FilePath LogFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreator.cs b/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreator.cs index 617928b6c9..3e2a3fef1f 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreator.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Create/GitReleaseManagerCreator.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -32,7 +33,7 @@ public GitReleaseManagerCreator( } /// - /// Creates a Release using the specified and settings. + /// Creates a Release using the specified settings. /// /// The user name. /// The password. @@ -43,30 +44,56 @@ public void Create(string userName, string password, string owner, string reposi { if (string.IsNullOrWhiteSpace(userName)) { - throw new ArgumentNullException("userName"); + throw new ArgumentNullException(nameof(userName)); } if (string.IsNullOrWhiteSpace(password)) { - throw new ArgumentNullException("password"); + throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("repository"); + throw new ArgumentNullException(nameof(repository)); } - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, settings)); + } + + /// + /// Creates a Release using the specified settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The settings. + public void Create(string token, string owner, string repository, GitReleaseManagerCreateSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(token)); } - Run(settings, GetArguments(userName, password, owner, repository, settings)); + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, settings)); } private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, GitReleaseManagerCreateSettings settings) @@ -81,6 +108,31 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-p"); builder.AppendQuotedSecret(password); + ParseCommonArguments(builder, owner, repository, settings); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, GitReleaseManagerCreateSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("create"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository, settings); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository, GitReleaseManagerCreateSettings settings) + { builder.Append("-o"); builder.AppendQuoted(owner); @@ -127,22 +179,6 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-c"); builder.AppendQuoted(settings.TargetCommitish); } - - // Target Directory - if (settings.TargetDirectory != null) - { - builder.Append("-d"); - builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); - } - - // Log File Path - if (settings.LogFilePath != null) - { - builder.Append("-l"); - builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); - } - - return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscardSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscardSettings.cs new file mode 100644 index 0000000000..02ec332afb --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscardSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.GitReleaseManager.Discard +{ + /// + /// Contains settings used by . + /// + public sealed class GitReleaseManagerDiscardSettings : GitReleaseManagerSettings + { + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarder.cs b/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarder.cs new file mode 100644 index 0000000000..dfd007b100 --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Discard/GitReleaseManagerDiscarder.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitReleaseManager.Discard +{ + /// + /// The GitReleaseManager Release Discarder used to discard releases. + /// + public sealed class GitReleaseManagerDiscarder : GitReleaseManagerTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public GitReleaseManagerDiscarder( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Discards a Release using the specified settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// The settings. + public void Discard(string token, string owner, string repository, string milestone, GitReleaseManagerDiscardSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); + } + + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + if (string.IsNullOrWhiteSpace(milestone)) + { + throw new ArgumentNullException(nameof(milestone)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, milestone, settings)); + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, string milestone, GitReleaseManagerDiscardSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("discard"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + builder.Append("-o"); + builder.AppendQuoted(owner); + + builder.Append("-r"); + builder.AppendQuoted(repository); + + builder.Append("-m"); + builder.AppendQuoted(milestone); + + AddBaseArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExportSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExportSettings.cs index 4bf1043e15..39e48a8b37 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExportSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExportSettings.cs @@ -1,29 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseManager.Export { /// /// Contains settings used by . /// - public sealed class GitReleaseManagerExportSettings : ToolSettings + public sealed class GitReleaseManagerExportSettings : GitReleaseManagerSettings { /// /// Gets or sets the tag name to be used when exporting the release notes. /// public string TagName { get; set; } - - /// - /// Gets or sets the path on which GitReleaseManager should be executed. - /// - public DirectoryPath TargetDirectory { get; set; } - - /// - /// Gets or sets the path to the GitReleaseManager log file. - /// - public FilePath LogFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExporter.cs b/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExporter.cs index 7316e32310..f240d040ff 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExporter.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Export/GitReleaseManagerExporter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -44,35 +45,61 @@ public void Export(string userName, string password, string owner, string reposi { if (string.IsNullOrWhiteSpace(userName)) { - throw new ArgumentNullException("userName"); + throw new ArgumentNullException(nameof(userName)); } if (string.IsNullOrWhiteSpace(password)) { - throw new ArgumentNullException("password"); + throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("repository"); + throw new ArgumentNullException(nameof(repository)); + } + + ArgumentNullException.ThrowIfNull(fileOutputPath); + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, fileOutputPath, settings)); + } + + /// + /// Creates a Release using the specified and settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The output path. + /// The settings. + public void Export(string token, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); } - if (fileOutputPath == null) + if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("fileOutputPath"); + throw new ArgumentNullException(nameof(owner)); } - if (settings == null) + if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(repository)); } - Run(settings, GetArguments(userName, password, owner, repository, fileOutputPath, settings)); + ArgumentNullException.ThrowIfNull(fileOutputPath); + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, fileOutputPath, settings)); } private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) @@ -87,6 +114,31 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-p"); builder.AppendQuotedSecret(password); + ParseCommonArguments(builder, owner, repository, fileOutputPath, settings); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("export"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository, fileOutputPath, settings); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) + { builder.Append("-o"); builder.AppendQuoted(owner); @@ -101,22 +153,6 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-t"); builder.AppendQuoted(settings.TagName); } - - // Target Directory - if (settings.TargetDirectory != null) - { - builder.Append("-d"); - builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); - } - - // Log File Path - if (settings.LogFilePath != null) - { - builder.Append("-l"); - builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); - } - - return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerAliases.cs b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerAliases.cs index 5c247590c6..bb528fa024 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerAliases.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerAliases.cs @@ -1,11 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.GitReleaseManager.AddAssets; using Cake.Common.Tools.GitReleaseManager.Close; using Cake.Common.Tools.GitReleaseManager.Create; +using Cake.Common.Tools.GitReleaseManager.Discard; using Cake.Common.Tools.GitReleaseManager.Export; +using Cake.Common.Tools.GitReleaseManager.Label; +using Cake.Common.Tools.GitReleaseManager.Open; using Cake.Common.Tools.GitReleaseManager.Publish; using Cake.Core; using Cake.Core.Annotations; @@ -17,7 +21,7 @@ namespace Cake.Common.Tools.GitReleaseManager /// Contains functionality related to GitReleaseManager. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the appropriate settings class: + /// install from nuget.org, or specify the ToolPath within the appropriate settings class: /// /// #tool "nuget:?package=gitreleasemanager" /// @@ -30,40 +34,33 @@ public static class GitReleaseManagerAliases /// Creates a Package Release. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// /// - /// GitReleaseManagerCreate("user", "password", "owner", "repo"); - /// - /// - /// - /// - /// GitReleaseManagerCreate("user", "password", "owner", "repo"); + /// GitReleaseManagerCreate("token", "owner", "repo"); /// /// [CakeMethodAlias] [CakeAliasCategory("Create")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Create")] - public static void GitReleaseManagerCreate(this ICakeContext context, string userName, string password, string owner, string repository) + public static void GitReleaseManagerCreate(this ICakeContext context, string token, string owner, string repository) { - GitReleaseManagerCreate(context, userName, password, owner, repository, new GitReleaseManagerCreateSettings()); + GitReleaseManagerCreate(context, token, owner, repository, new GitReleaseManagerCreateSettings()); } /// /// Creates a Package Release using the specified settings. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The settings. /// /// - /// GitReleaseManagerCreate("user", "password", "owner", "repo", new GitReleaseManagerCreateSettings { + /// GitReleaseManagerCreate("token", "owner", "repo", new GitReleaseManagerCreateSettings { /// Milestone = "0.1.0", /// Prerelease = false, /// Assets = "c:/temp/asset1.txt,c:/temp/asset2.txt", @@ -75,7 +72,7 @@ public static void GitReleaseManagerCreate(this ICakeContext context, string use /// /// /// - /// GitReleaseManagerCreate("user", "password", "owner", "repo", new GitReleaseManagerCreateSettings { + /// GitReleaseManagerCreate("token", "owner", "repo", new GitReleaseManagerCreateSettings { /// Name = "0.1.0", /// InputFilePath = "c:/repo/releasenotes.md", /// Prerelease = false, @@ -89,46 +86,41 @@ public static void GitReleaseManagerCreate(this ICakeContext context, string use [CakeMethodAlias] [CakeAliasCategory("Create")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Create")] - public static void GitReleaseManagerCreate(this ICakeContext context, string userName, string password, string owner, string repository, GitReleaseManagerCreateSettings settings) + public static void GitReleaseManagerCreate(this ICakeContext context, string token, string owner, string repository, GitReleaseManagerCreateSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var creator = new GitReleaseManagerCreator(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - creator.Create(userName, password, owner, repository, settings); + creator.Create(token, owner, repository, settings); } /// /// Add Assets to an existing release. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The tag name. /// The assets. /// /// - /// GitReleaseManagerAddAssets("user", "password", "owner", "repo", "0.1.0", "c:/temp/asset1.txt,c:/temp/asset2.txt"); + /// GitReleaseManagerAddAssets("token", "owner", "repo", "0.1.0", "c:/temp/asset1.txt,c:/temp/asset2.txt"); /// /// [CakeMethodAlias] [CakeAliasCategory("AddAssets")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.AddAssets")] - public static void GitReleaseManagerAddAssets(this ICakeContext context, string userName, string password, string owner, string repository, string tagName, string assets) + public static void GitReleaseManagerAddAssets(this ICakeContext context, string token, string owner, string repository, string tagName, string assets) { - GitReleaseManagerAddAssets(context, userName, password, owner, repository, tagName, assets, new GitReleaseManagerAddAssetsSettings()); + GitReleaseManagerAddAssets(context, token, owner, repository, tagName, assets, new GitReleaseManagerAddAssetsSettings()); } /// /// Add Assets to an existing release using the specified settings. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The tag name. @@ -136,7 +128,7 @@ public static void GitReleaseManagerAddAssets(this ICakeContext context, string /// The settings. /// /// - /// GitReleaseManagerAddAssets("user", "password", "owner", "repo", "0.1.0", "c:/temp/asset1.txt,c:/temp/asset2.txt" new GitReleaseManagerAddAssetsSettings { + /// GitReleaseManagerAddAssets("token", "owner", "repo", "0.1.0", "c:/temp/asset1.txt,c:/temp/asset2.txt", new GitReleaseManagerAddAssetsSettings { /// TargetDirectory = "c:/repo", /// LogFilePath = "c:/temp/grm.log" /// }); @@ -145,52 +137,47 @@ public static void GitReleaseManagerAddAssets(this ICakeContext context, string [CakeMethodAlias] [CakeAliasCategory("AddAssets")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.AddAssets")] - public static void GitReleaseManagerAddAssets(this ICakeContext context, string userName, string password, string owner, string repository, string tagName, string assets, GitReleaseManagerAddAssetsSettings settings) + public static void GitReleaseManagerAddAssets(this ICakeContext context, string token, string owner, string repository, string tagName, string assets, GitReleaseManagerAddAssetsSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assetsAdder = new GitReleaseManagerAssetsAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - assetsAdder.AddAssets(userName, password, owner, repository, tagName, assets, settings); + assetsAdder.AddAssets(token, owner, repository, tagName, assets, settings); } /// /// Closes the milestone associated with a release. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The milestone. /// /// - /// GitReleaseManagerClose("user", "password", "owner", "repo", "0.1.0"); + /// GitReleaseManagerClose("token", "owner", "repo", "0.1.0"); /// /// [CakeMethodAlias] [CakeAliasCategory("Close")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Close")] - public static void GitReleaseManagerClose(this ICakeContext context, string userName, string password, string owner, string repository, string milestone) + public static void GitReleaseManagerClose(this ICakeContext context, string token, string owner, string repository, string milestone) { - GitReleaseManagerClose(context, userName, password, owner, repository, milestone, new GitReleaseManagerCloseMilestoneSettings()); + GitReleaseManagerClose(context, token, owner, repository, milestone, new GitReleaseManagerCloseMilestoneSettings()); } /// /// Closes the milestone associated with a release using the specified settings. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The milestone. /// The settings. /// /// - /// GitReleaseManagerClose("user", "password", "owner", "repo", "0.1.0", new GitReleaseManagerCloseMilestoneSettings { + /// GitReleaseManagerClose("token", "owner", "repo", "0.1.0", new GitReleaseManagerCloseMilestoneSettings { /// TargetDirectory = "c:/repo", /// LogFilePath = "c:/temp/grm.log" /// }); @@ -199,52 +186,47 @@ public static void GitReleaseManagerClose(this ICakeContext context, string user [CakeMethodAlias] [CakeAliasCategory("Close")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Close")] - public static void GitReleaseManagerClose(this ICakeContext context, string userName, string password, string owner, string repository, string milestone, GitReleaseManagerCloseMilestoneSettings settings) + public static void GitReleaseManagerClose(this ICakeContext context, string token, string owner, string repository, string milestone, GitReleaseManagerCloseMilestoneSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var milestoneCloser = new GitReleaseManagerMilestoneCloser(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - milestoneCloser.Close(userName, password, owner, repository, milestone, settings); + milestoneCloser.Close(token, owner, repository, milestone, settings); } /// /// Publishes the release. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The tag name. /// /// - /// GitReleaseManagerPublish("user", "password", "owner", "repo", "0.1.0"); + /// GitReleaseManagerPublish("token", "owner", "repo", "0.1.0"); /// /// [CakeMethodAlias] [CakeAliasCategory("Publish")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Publish")] - public static void GitReleaseManagerPublish(this ICakeContext context, string userName, string password, string owner, string repository, string tagName) + public static void GitReleaseManagerPublish(this ICakeContext context, string token, string owner, string repository, string tagName) { - GitReleaseManagerPublish(context, userName, password, owner, repository, tagName, new GitReleaseManagerPublishSettings()); + GitReleaseManagerPublish(context, token, owner, repository, tagName, new GitReleaseManagerPublishSettings()); } /// /// Publishes the release using the specified settings. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The tag name. /// The settings. /// /// - /// GitReleaseManagerPublish("user", "password", "owner", "repo", "0.1.0", new GitReleaseManagerPublishSettings { + /// GitReleaseManagerPublish("token", "owner", "repo", "0.1.0", new GitReleaseManagerPublishSettings { /// TargetDirectory = "c:/repo", /// LogFilePath = "c:/temp/grm.log" /// }); @@ -253,53 +235,48 @@ public static void GitReleaseManagerPublish(this ICakeContext context, string us [CakeMethodAlias] [CakeAliasCategory("Publish")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Publish")] - public static void GitReleaseManagerPublish(this ICakeContext context, string userName, string password, string owner, string repository, string tagName, GitReleaseManagerPublishSettings settings) + public static void GitReleaseManagerPublish(this ICakeContext context, string token, string owner, string repository, string tagName, GitReleaseManagerPublishSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var publisher = new GitReleaseManagerPublisher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - publisher.Publish(userName, password, owner, repository, tagName, settings); + publisher.Publish(token, owner, repository, tagName, settings); } /// /// Exports the release notes. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The output file path. /// /// - /// GitReleaseManagerExport("user", "password", "owner", "repo", "c:/temp/releasenotes.md") + /// GitReleaseManagerExport("token", "owner", "repo", "c:/temp/releasenotes.md") /// }); /// /// [CakeMethodAlias] [CakeAliasCategory("Export")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Export")] - public static void GitReleaseManagerExport(this ICakeContext context, string userName, string password, string owner, string repository, FilePath fileOutputPath) + public static void GitReleaseManagerExport(this ICakeContext context, string token, string owner, string repository, FilePath fileOutputPath) { - GitReleaseManagerExport(context, userName, password, owner, repository, fileOutputPath, new GitReleaseManagerExportSettings()); + GitReleaseManagerExport(context, token, owner, repository, fileOutputPath, new GitReleaseManagerExportSettings()); } /// /// Exports the release notes using the specified settings. /// /// The context. - /// The user name. - /// The password. + /// The token. /// The owner. /// The repository. /// The output file path. /// The settings. /// /// - /// GitReleaseManagerExport("user", "password", "owner", "repo", "c:/temp/releasenotes.md", new GitReleaseManagerExportSettings { + /// GitReleaseManagerExport("token", "owner", "repo", "c:/temp/releasenotes.md", new GitReleaseManagerExportSettings { /// TagName = "0.1.0", /// TargetDirectory = "c:/repo", /// LogFilePath = "c:/temp/grm.log" @@ -309,15 +286,164 @@ public static void GitReleaseManagerExport(this ICakeContext context, string use [CakeMethodAlias] [CakeAliasCategory("Export")] [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Export")] - public static void GitReleaseManagerExport(this ICakeContext context, string userName, string password, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) + public static void GitReleaseManagerExport(this ICakeContext context, string token, string owner, string repository, FilePath fileOutputPath, GitReleaseManagerExportSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var publisher = new GitReleaseManagerExporter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - publisher.Export(userName, password, owner, repository, fileOutputPath, settings); + publisher.Export(token, owner, repository, fileOutputPath, settings); + } + + /// + /// Deletes and creates labels. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// + /// + /// GitReleaseManagerLabel("token", "owner", "repo"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Label")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Label")] + public static void GitReleaseManagerLabel(this ICakeContext context, string token, string owner, string repository) + { + GitReleaseManagerLabel(context, token, owner, repository, new GitReleaseManagerLabelSettings()); + } + + /// + /// Deletes and creates labels using the specified settings. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// The settings. + /// + /// + /// GitReleaseManagerLabel("token", "owner", "repo", new GitReleaseManagerLabelSettings { + /// LogFilePath = "c:/temp/grm.log" + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Label")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Label")] + public static void GitReleaseManagerLabel(this ICakeContext context, string token, string owner, string repository, GitReleaseManagerLabelSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var labeller = new GitReleaseManagerLabeller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + labeller.Label(token, owner, repository, settings); + } + + /// + /// Opens the milestone associated with a release. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// + /// + /// GitReleaseManagerOpen("token", "owner", "repo", "0.1.0"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Open")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Open")] + public static void GitReleaseManagerOpen(this ICakeContext context, string token, string owner, string repository, string milestone) + { + GitReleaseManagerOpen(context, token, owner, repository, milestone, new GitReleaseManagerOpenMilestoneSettings()); + } + + /// + /// Opens the milestone associated with a release using the specified settings. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// The settings. + /// + /// + /// GitReleaseManagerOpen("token", "owner", "repo", "0.1.0", new GitReleaseManagerOpenMilestoneSettings { + /// TargetDirectory = "c:/repo", + /// LogFilePath = "c:/temp/grm.log" + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Open")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Open")] + public static void GitReleaseManagerOpen(this ICakeContext context, string token, string owner, string repository, string milestone, GitReleaseManagerOpenMilestoneSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var milestoneOpener = new GitReleaseManagerMilestoneOpener(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + milestoneOpener.Open(token, owner, repository, milestone, settings); + } + + /// + /// Discards a Release. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// + /// + /// GitReleaseManagerDiscard("token", "owner", "repo", "0.1.0"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Discard")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Discard")] + public static void GitReleaseManagerDiscard(this ICakeContext context, string token, string owner, string repository, string milestone) + { + GitReleaseManagerDiscard(context, token, owner, repository, milestone, new GitReleaseManagerDiscardSettings()); + } + + /// + /// Discards a Release using the specified settings. + /// + /// The context. + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// The settings. + /// + /// + /// GitReleaseManagerDiscard("token", "owner", "repo", "0.1.0", new GitReleaseManagerDiscardSettings { + /// TargetDirectory = "c:/repo", + /// LogFilePath = "c:/temp/grm.log" + /// }); + /// + /// + /// + /// + /// GitReleaseManagerDiscard("token", "owner", "repo", "0.1.0", new GitReleaseManagerDiscardSettings { + /// TargetDirectory = "c:/repo", + /// LogFilePath = "c:/temp/grm.log" + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Discard")] + [CakeNamespaceImport("Cake.Common.Tools.GitReleaseManager.Discard")] + public static void GitReleaseManagerDiscard(this ICakeContext context, string token, string owner, string repository, string milestone, GitReleaseManagerDiscardSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var discarder = new GitReleaseManagerDiscarder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + discarder.Discard(token, owner, repository, milestone, settings); } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerSettings.cs new file mode 100644 index 0000000000..8b88534428 --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerSettings.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitReleaseManager +{ + /// + /// Contains settings used by . + /// + public class GitReleaseManagerSettings : ToolSettings + { + /// + /// Gets or sets the path on which GitReleaseManager should be executed. + /// + public DirectoryPath TargetDirectory { get; set; } + + /// + /// Gets or sets the path to the GitReleaseManager log file. + /// + public FilePath LogFilePath { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use debug level logging. + /// + public bool Debug { get; set; } + + /// + /// Gets or sets a value indicating whether or not to use verbose level logging. + /// + public bool Verbose { get; set; } + + /// + /// Gets or sets a value indicating whether or not to show the GitReleaseManager logo during execution. + /// + public bool NoLogo { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerTool.cs b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerTool.cs index 3919c9f671..6d2bbc5072 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerTool.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/GitReleaseManagerTool.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; @@ -15,6 +16,8 @@ namespace Cake.Common.Tools.GitReleaseManager public abstract class GitReleaseManagerTool : Tool where TSettings : ToolSettings { + private readonly ICakeEnvironment _environment; + /// /// Initializes a new instance of the class. /// @@ -28,6 +31,7 @@ protected GitReleaseManagerTool( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { + _environment = environment; } /// @@ -45,7 +49,50 @@ protected sealed override string GetToolName() /// The tool executable name. protected sealed override IEnumerable GetToolExecutableNames() { - return new[] { "GitReleaseManager.exe", "gitreleasemanager.exe", "grm.exe" }; + return new[] { "GitReleaseManager.exe", "gitreleasemanager.exe", "grm.exe", "dotnet-gitreleasemanager", "dotnet-gitreleasemanager.exe" }; + } + + /// + /// Adds common arguments to the process builder. + /// + /// The settings. + /// The process argument builder. + /// The process argument builder. + protected ProcessArgumentBuilder AddBaseArguments(GitReleaseManagerSettings settings, ProcessArgumentBuilder builder) + { + // Target Directory + if (settings.TargetDirectory != null) + { + builder.Append("-d"); + builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); + } + + // Log File Path + if (settings.LogFilePath != null) + { + builder.Append("-l"); + builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); + } + + // Debug + if (settings.Debug) + { + builder.Append("--debug"); + } + + // Verbose + if (settings.Verbose) + { + builder.Append("--verbose"); + } + + // NoLogo + if (settings.NoLogo) + { + builder.Append("--no-logo"); + } + + return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabelSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabelSettings.cs new file mode 100644 index 0000000000..a1f9e6d063 --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabelSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.GitReleaseManager.Label +{ + /// + /// Contains settings used by . + /// + public class GitReleaseManagerLabelSettings : GitReleaseManagerSettings + { + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabeller.cs b/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabeller.cs new file mode 100644 index 0000000000..9f01df9f55 --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Label/GitReleaseManagerLabeller.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitReleaseManager.Label +{ + /// + /// The GitReleaseManager Label Creator used to delete and create labels. + /// + public sealed class GitReleaseManagerLabeller : GitReleaseManagerTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public GitReleaseManagerLabeller( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Deletes and creates labels using the specified and settings. + /// + /// The user name. + /// The password. + /// The owner. + /// The repository. + /// The settings. + public void Label(string userName, string password, string owner, string repository, GitReleaseManagerLabelSettings settings) + { + if (string.IsNullOrWhiteSpace(userName)) + { + throw new ArgumentNullException(nameof(userName)); + } + + if (string.IsNullOrWhiteSpace(password)) + { + throw new ArgumentNullException(nameof(password)); + } + + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, settings)); + } + + /// + /// Deletes and creates labels using the specified and settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The settings. + public void Label(string token, string owner, string repository, GitReleaseManagerLabelSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); + } + + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, settings)); + } + + private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, GitReleaseManagerLabelSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("label"); + + builder.Append("-u"); + builder.AppendQuoted(userName); + + builder.Append("-p"); + builder.AppendQuotedSecret(password); + + ParseCommonArguments(builder, owner, repository); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, GitReleaseManagerLabelSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("label"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository) + { + builder.Append("-o"); + builder.AppendQuoted(owner); + + builder.Append("-r"); + builder.AppendQuoted(repository); + } + } +} diff --git a/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpener.cs b/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpener.cs new file mode 100644 index 0000000000..873baa370a --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerMilestoneOpener.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.GitReleaseManager.Open +{ + /// + /// The GitReleaseManager Milestone Opener used to open milestones. + /// + public sealed class GitReleaseManagerMilestoneOpener : GitReleaseManagerTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public GitReleaseManagerMilestoneOpener( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Creates a Release using the specified settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The milestone. + /// The settings. + public void Open(string token, string owner, string repository, string milestone, GitReleaseManagerOpenMilestoneSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new ArgumentNullException(nameof(token)); + } + + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + if (string.IsNullOrWhiteSpace(milestone)) + { + throw new ArgumentNullException(nameof(milestone)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, milestone, settings)); + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, string milestone, GitReleaseManagerOpenMilestoneSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("open"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + builder.Append("-o"); + builder.AppendQuoted(owner); + + builder.Append("-r"); + builder.AppendQuoted(repository); + + builder.Append("-m"); + builder.AppendQuoted(milestone); + + AddBaseArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerOpenMilestoneSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerOpenMilestoneSettings.cs new file mode 100644 index 0000000000..e21b7b7e85 --- /dev/null +++ b/src/Cake.Common/Tools/GitReleaseManager/Open/GitReleaseManagerOpenMilestoneSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.GitReleaseManager.Open +{ + /// + /// Contains settings used by . + /// + public sealed class GitReleaseManagerOpenMilestoneSettings : GitReleaseManagerSettings + { + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublishSettings.cs b/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublishSettings.cs index ad170209c9..f199c4cec7 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublishSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublishSettings.cs @@ -1,24 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseManager.Publish { /// /// Contains settings used by . /// - public sealed class GitReleaseManagerPublishSettings : ToolSettings + public sealed class GitReleaseManagerPublishSettings : GitReleaseManagerSettings { - /// - /// Gets or sets the path on which GitReleaseManager should be executed. - /// - public DirectoryPath TargetDirectory { get; set; } - - /// - /// Gets or sets the path to the GitReleaseManager log file. - /// - public FilePath LogFilePath { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisher.cs b/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisher.cs index bec9191563..bd528427c7 100644 --- a/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisher.cs +++ b/src/Cake.Common/Tools/GitReleaseManager/Publish/GitReleaseManagerPublisher.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -13,8 +14,6 @@ namespace Cake.Common.Tools.GitReleaseManager.Publish /// public sealed class GitReleaseManagerPublisher : GitReleaseManagerTool { - private readonly ICakeEnvironment _environment; - /// /// Initializes a new instance of the class. /// @@ -28,7 +27,6 @@ public GitReleaseManagerPublisher( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { - _environment = environment; } /// @@ -44,35 +42,67 @@ public void Publish(string userName, string password, string owner, string repos { if (string.IsNullOrWhiteSpace(userName)) { - throw new ArgumentNullException("userName"); + throw new ArgumentNullException(nameof(userName)); } if (string.IsNullOrWhiteSpace(password)) { - throw new ArgumentNullException("password"); + throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrWhiteSpace(owner)) { - throw new ArgumentNullException("owner"); + throw new ArgumentNullException(nameof(owner)); } if (string.IsNullOrWhiteSpace(repository)) { - throw new ArgumentNullException("repository"); + throw new ArgumentNullException(nameof(repository)); } if (string.IsNullOrWhiteSpace(tagName)) { - throw new ArgumentNullException("tagName"); + throw new ArgumentNullException(nameof(tagName)); } - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(userName, password, owner, repository, tagName, settings)); + } + + /// + /// Creates a Release using the specified and settings. + /// + /// The token. + /// The owner. + /// The repository. + /// The tag name. + /// The settings. + public void Publish(string token, string owner, string repository, string tagName, GitReleaseManagerPublishSettings settings) + { + if (string.IsNullOrWhiteSpace(token)) { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(token)); } - Run(settings, GetArguments(userName, password, owner, repository, tagName, settings)); + if (string.IsNullOrWhiteSpace(owner)) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (string.IsNullOrWhiteSpace(repository)) + { + throw new ArgumentNullException(nameof(repository)); + } + + if (string.IsNullOrWhiteSpace(tagName)) + { + throw new ArgumentNullException(nameof(tagName)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(token, owner, repository, tagName, settings)); } private ProcessArgumentBuilder GetArguments(string userName, string password, string owner, string repository, string tagName, GitReleaseManagerPublishSettings settings) @@ -87,6 +117,31 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-p"); builder.AppendQuotedSecret(password); + ParseCommonArguments(builder, owner, repository, tagName); + + AddBaseArguments(settings, builder); + + return builder; + } + + private ProcessArgumentBuilder GetArguments(string token, string owner, string repository, string tagName, GitReleaseManagerPublishSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("publish"); + + builder.Append("--token"); + builder.AppendQuotedSecret(token); + + ParseCommonArguments(builder, owner, repository, tagName); + + AddBaseArguments(settings, builder); + + return builder; + } + + private void ParseCommonArguments(ProcessArgumentBuilder builder, string owner, string repository, string tagName) + { builder.Append("-o"); builder.AppendQuoted(owner); @@ -95,22 +150,6 @@ private ProcessArgumentBuilder GetArguments(string userName, string password, st builder.Append("-t"); builder.AppendQuoted(tagName); - - // Target Directory - if (settings.TargetDirectory != null) - { - builder.Append("-d"); - builder.AppendQuoted(settings.TargetDirectory.MakeAbsolute(_environment).FullPath); - } - - // Log File Path - if (settings.LogFilePath != null) - { - builder.Append("-l"); - builder.AppendQuoted(settings.LogFilePath.MakeAbsolute(_environment).FullPath); - } - - return builder; } } } diff --git a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesAliases.cs b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesAliases.cs index e58c929c9f..56f9d9b1f8 100644 --- a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesAliases.cs +++ b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.GitReleaseNotes /// Contains functionality related to GitReleaseNotes. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=GitReleaseNotes" /// @@ -52,13 +53,10 @@ public static class GitReleaseNotesAliases [CakeAliasCategory("GitReleaseNotes")] public static void GitReleaseNotes(this ICakeContext context, FilePath outputFile, GitReleaseNotesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new GitReleaseNotesRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(outputFile, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesIssueTracker.cs b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesIssueTracker.cs index 538e9b8a5f..34dbca77ba 100644 --- a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesIssueTracker.cs +++ b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesIssueTracker.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.GitReleaseNotes { /// @@ -28,4 +29,4 @@ public enum GitReleaseNotesIssueTracker /// YouTrack } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesRunner.cs b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesRunner.cs index bf1e4b2a4e..3fb5d60d1a 100644 --- a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesRunner.cs +++ b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -39,14 +40,8 @@ public GitReleaseNotesRunner( /// The settings. public void Run(FilePath outputFile, GitReleaseNotesSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - if (outputFile == null) - { - throw new ArgumentNullException("outputFile"); - } + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(outputFile); Run(settings, GetArguments(outputFile, settings)); } @@ -197,4 +192,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "GitReleaseNotes.exe", "grn.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesSettings.cs b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesSettings.cs index 076793828d..a6e9e9927a 100644 --- a/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesSettings.cs +++ b/src/Cake.Common/Tools/GitReleaseNotes/GitReleaseNotesSettings.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Cake.Core.IO; + using Cake.Core.Tooling; namespace Cake.Common.Tools.GitReleaseNotes @@ -40,25 +40,25 @@ public sealed class GitReleaseNotesSettings : ToolSettings /// /// Gets or sets the token to use when accessing repository. /// - /// To be used instead of username/password + /// To be used instead of username/password. public string RepoToken { get; set; } /// /// Gets or sets the Url to use when accessing repository. /// - /// To be used instead of username/password + /// To be used instead of username/password. public string RepoUrl { get; set; } /// /// Gets or sets the branch name to checkout any existing release notes file. /// - /// To be used instead of username/password + /// To be used instead of username/password. public string RepoBranch { get; set; } /// /// Gets or sets the Url to the Issue Tracker. /// - /// To be used instead of username/password + /// To be used instead of username/password. public string IssueTrackerUrl { get; set; } /// @@ -74,7 +74,7 @@ public sealed class GitReleaseNotesSettings : ToolSettings /// /// Gets or sets the token to use when accessing issue tracker. /// - /// To be used instead of username/password + /// To be used instead of username/password. public string IssueTrackerToken { get; set; } /// @@ -103,4 +103,4 @@ public sealed class GitReleaseNotesSettings : ToolSettings /// If not specified then only the defaults (bug, enhancement, feature) are included. public bool AllLabels { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersion.cs b/src/Cake.Common/Tools/GitVersion/GitVersion.cs index ac13b8b797..f52472acbf 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersion.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersion.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.GitVersion { /// - /// GitVersion information + /// GitVersion information. /// public sealed class GitVersion { @@ -38,10 +39,20 @@ public sealed class GitVersion /// public string PreReleaseLabel { get; set; } + /// + /// Gets or sets the pre-release label with dash. + /// + public string PreReleaseLabelWithDash { get; set; } + /// /// Gets or sets the pre-release number. /// - public string PreReleaseNumber { get; set; } + public int? PreReleaseNumber { get; set; } + + /// + /// Gets or sets the weighted pre-release number. + /// + public int? WeightedPreReleaseNumber { get; set; } /// /// Gets or sets the build metadata. @@ -83,6 +94,11 @@ public sealed class GitVersion /// public string AssemblySemVer { get; set; } + /// + /// Gets or sets the assembly semantic file version. + /// + public string AssemblySemFileVer { get; set; } + /// /// Gets or sets the full Semantic Version. /// @@ -99,33 +115,63 @@ public sealed class GitVersion public string BranchName { get; set; } /// - /// Gets or sets the git sha. + /// Gets or sets the escaped branch name. + /// + public string EscapedBranchName { get; set; } + + /// + /// Gets or sets the Git SHA. /// public string Sha { get; set; } /// - /// Gets or sets the nuget version for v2. + /// Gets or sets the shortened Git SHA. + /// + public string ShortSha { get; set; } + + /// + /// Gets or sets the NuGet version for v2. /// public string NuGetVersionV2 { get; set; } /// - /// Gets or sets the nuget version. + /// Gets or sets the NuGet version. /// public string NuGetVersion { get; set; } /// - /// Gets or sets the commits since version source + /// Gets or sets the NuGet pre-release tag for v2. + /// + public string NuGetPreReleaseTagV2 { get; set; } + + /// + /// Gets or sets the NuGet pre-release tag. + /// + public string NuGetPreReleaseTag { get; set; } + + /// + /// Gets or sets the version source sha. + /// + public string VersionSourceSha { get; set; } + + /// + /// Gets or sets the commits since version source. /// - public string CommitsSinceVersionSource { get; set; } + public int? CommitsSinceVersionSource { get; set; } /// - /// Gets or sets the commits since version source padded + /// Gets or sets the commits since version source padded. /// public string CommitsSinceVersionSourcePadded { get; set; } /// - /// Gets or sets the commit date + /// Gets or sets the number of uncommited changes. + /// + public int? UncommittedChanges { get; set; } + + /// + /// Gets or sets the commit date. /// public string CommitDate { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionAliases.cs b/src/Cake.Common/Tools/GitVersion/GitVersionAliases.cs index 4619a7bf5d..7364f4f6b7 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersionAliases.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersionAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -11,9 +12,9 @@ namespace Cake.Common.Tools.GitVersion /// Contains functionality related to GitVersion. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// - /// #tool "nuget:?package=GitVersion.CommandLine" + /// #tool "dotnet:?package=GitVersion.Tool" /// /// /// @@ -21,10 +22,10 @@ namespace Cake.Common.Tools.GitVersion public static class GitVersionAliases { /// - /// Retrives the GitVersion output. + /// Retrieves the GitVersion output. /// /// The context. - /// The git version info. + /// The Git version info. /// /// Update the assembly info files for the project. /// Cake task: @@ -39,7 +40,7 @@ public static class GitVersionAliases /// }); /// ]]> /// - /// Get the git version info for the project using a dynamic repository. + /// Get the Git version info for the project using a dynamic repository. /// Cake task: /// /// /// @@ -61,20 +62,17 @@ public static class GitVersionAliases [CakeMethodAlias] public static GitVersion GitVersion(this ICakeContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); return GitVersion(context, new GitVersionSettings()); } /// - /// Retrives the GitVersion output. + /// Retrieves the GitVersion output. /// /// The context. /// The GitVersion settings. - /// The git version info. + /// The Git version info. /// /// Update the assembly info files for the project. /// Cake task: @@ -89,7 +87,7 @@ public static GitVersion GitVersion(this ICakeContext context) /// }); /// ]]> /// - /// Get the git version info for the project using a dynamic repository. + /// Get the Git version info for the project using a dynamic repository. /// Cake task: /// /// /// @@ -111,13 +109,10 @@ public static GitVersion GitVersion(this ICakeContext context) [CakeMethodAlias] public static GitVersion GitVersion(this ICakeContext context, GitVersionSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var gitVersionRunner = new GitVersionRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); return gitVersionRunner.Run(settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs b/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs new file mode 100644 index 0000000000..ac75887ab8 --- /dev/null +++ b/src/Cake.Common/Tools/GitVersion/GitVersionInternal.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.GitVersion +{ + /// + /// Internal DTO for deserializing GitVersion JSON output. Property names match GitVersion CLI output (PascalCase). + /// + internal class GitVersionInternal + { + private GitVersion _gitVersion; + + internal GitVersion GitVersion => _gitVersion ?? (_gitVersion = new GitVersion()); + + [JsonPropertyName("Major")] + public string Major + { + get => ToString(GitVersion.Major); + set => GitVersion.Major = ToInt(value); + } + + [JsonPropertyName("Minor")] + public string Minor + { + get => ToString(GitVersion.Minor); + set => GitVersion.Minor = ToInt(value); + } + + [JsonPropertyName("Patch")] + public string Patch + { + get => ToString(GitVersion.Patch); + set => GitVersion.Patch = ToInt(value); + } + + [JsonPropertyName("PreReleaseTag")] + public string PreReleaseTag + { + get => GitVersion.PreReleaseTag; + set => GitVersion.PreReleaseTag = value; + } + + [JsonPropertyName("PreReleaseTagWithDash")] + public string PreReleaseTagWithDash + { + get => GitVersion.PreReleaseTagWithDash; + set => GitVersion.PreReleaseTagWithDash = value; + } + + [JsonPropertyName("PreReleaseLabel")] + public string PreReleaseLabel + { + get => GitVersion.PreReleaseLabel; + set => GitVersion.PreReleaseLabel = value; + } + + [JsonPropertyName("PreReleaseLabelWithDash")] + public string PreReleaseLabelWithDash + { + get => GitVersion.PreReleaseLabelWithDash; + set => GitVersion.PreReleaseLabelWithDash = value; + } + + [JsonPropertyName("PreReleaseNumber")] + public string PreReleaseNumber + { + get => ToString(GitVersion.PreReleaseNumber); + set => GitVersion.PreReleaseNumber = ToNullableInt(value); + } + + [JsonPropertyName("WeightedPreReleaseNumber")] + public string WeightedPreReleaseNumber + { + get => ToString(GitVersion.WeightedPreReleaseNumber); + set => GitVersion.WeightedPreReleaseNumber = ToNullableInt(value); + } + + [JsonPropertyName("BuildMetaData")] + public string BuildMetaData + { + get => GitVersion.BuildMetaData; + set => GitVersion.BuildMetaData = value; + } + + [JsonPropertyName("BuildMetaDataPadded")] + public string BuildMetaDataPadded + { + get => GitVersion.BuildMetaDataPadded; + set => GitVersion.BuildMetaDataPadded = value; + } + + [JsonPropertyName("FullBuildMetaData")] + public string FullBuildMetaData + { + get => GitVersion.FullBuildMetaData; + set => GitVersion.FullBuildMetaData = value; + } + + [JsonPropertyName("MajorMinorPatch")] + public string MajorMinorPatch + { + get => GitVersion.MajorMinorPatch; + set => GitVersion.MajorMinorPatch = value; + } + + [JsonPropertyName("SemVer")] + public string SemVer + { + get => GitVersion.SemVer; + set => GitVersion.SemVer = value; + } + + [JsonPropertyName("LegacySemVer")] + public string LegacySemVer + { + get => GitVersion.LegacySemVer; + set => GitVersion.LegacySemVer = value; + } + + [JsonPropertyName("LegacySemVerPadded")] + public string LegacySemVerPadded + { + get => GitVersion.LegacySemVerPadded; + set => GitVersion.LegacySemVerPadded = value; + } + + [JsonPropertyName("AssemblySemVer")] + public string AssemblySemVer + { + get => GitVersion.AssemblySemVer; + set => GitVersion.AssemblySemVer = value; + } + + [JsonPropertyName("AssemblySemFileVer")] + public string AssemblySemFileVer + { + get => GitVersion.AssemblySemFileVer; + set => GitVersion.AssemblySemFileVer = value; + } + + [JsonPropertyName("FullSemVer")] + public string FullSemVer + { + get => GitVersion.FullSemVer; + set => GitVersion.FullSemVer = value; + } + + [JsonPropertyName("InformationalVersion")] + public string InformationalVersion + { + get => GitVersion.InformationalVersion; + set => GitVersion.InformationalVersion = value; + } + + [JsonPropertyName("BranchName")] + public string BranchName + { + get => GitVersion.BranchName; + set => GitVersion.BranchName = value; + } + + [JsonPropertyName("EscapedBranchName")] + public string EscapedBranchName + { + get => GitVersion.EscapedBranchName; + set => GitVersion.EscapedBranchName = value; + } + + [JsonPropertyName("Sha")] + public string Sha + { + get => GitVersion.Sha; + set => GitVersion.Sha = value; + } + + [JsonPropertyName("ShortSha")] + public string ShortSha + { + get => GitVersion.ShortSha; + set => GitVersion.ShortSha = value; + } + + [JsonPropertyName("NuGetVersionV2")] + public string NuGetVersionV2 + { + get => GitVersion.NuGetVersionV2; + set => GitVersion.NuGetVersionV2 = value; + } + + [JsonPropertyName("NuGetVersion")] + public string NuGetVersion + { + get => GitVersion.NuGetVersion; + set => GitVersion.NuGetVersion = value; + } + + [JsonPropertyName("NuGetPreReleaseTagV2")] + public string NuGetPreReleaseTagV2 + { + get => GitVersion.NuGetPreReleaseTagV2; + set => GitVersion.NuGetPreReleaseTagV2 = value; + } + + [JsonPropertyName("NuGetPreReleaseTag")] + public string NuGetPreReleaseTag + { + get => GitVersion.NuGetPreReleaseTag; + set => GitVersion.NuGetPreReleaseTag = value; + } + + [JsonPropertyName("VersionSourceSha")] + public string VersionSourceSha + { + get => GitVersion.VersionSourceSha; + set => GitVersion.VersionSourceSha = value; + } + + [JsonPropertyName("CommitsSinceVersionSource")] + public string CommitsSinceVersionSource + { + get => ToString(GitVersion.CommitsSinceVersionSource); + set => GitVersion.CommitsSinceVersionSource = ToNullableInt(value); + } + + [JsonPropertyName("CommitsSinceVersionSourcePadded")] + public string CommitsSinceVersionSourcePadded + { + get => GitVersion.CommitsSinceVersionSourcePadded; + set => GitVersion.CommitsSinceVersionSourcePadded = value; + } + + [JsonPropertyName("UncommittedChanges")] + public string UncommittedChanges + { + get => ToString(GitVersion.UncommittedChanges); + set => GitVersion.UncommittedChanges = ToNullableInt(value); + } + + [JsonPropertyName("CommitDate")] + public string CommitDate + { + get => GitVersion.CommitDate; + set => GitVersion.CommitDate = value; + } + + private static int? ToNullableInt(string value) => int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, + out int numericValue) + ? numericValue + : null as int?; + + private static int ToInt(string value) => int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, + out int numericValue) + ? numericValue + : -1; + + private static string ToString(int value) => value.ToString(CultureInfo.InvariantCulture); + + private static string ToString(int? value) => value.HasValue + ? ToString(value.Value) + : null; + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionJsonContext.cs b/src/Cake.Common/Tools/GitVersion/GitVersionJsonContext.cs new file mode 100644 index 0000000000..17472e6553 --- /dev/null +++ b/src/Cake.Common/Tools/GitVersion/GitVersionJsonContext.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.GitVersion +{ + /// + /// Source-generated JSON serializer context for GitVersion types. + /// Uses static options that include so CLI number/string values deserialize to string properties. + /// + [JsonSerializable(typeof(GitVersionInternal))] + internal partial class GitVersionJsonContext : JsonSerializerContext + { + /// + /// Gets the static serializer options used for GitVersion JSON (includes ). + /// + public static JsonSerializerOptions SerializerOptions { get; } = new () + { + Converters = { new JsonStringOrNumberConverter() } + }; + + /// + /// Gets the context instance with options that allow number-or-string for string properties (uses ). + /// Use this instead of when deserializing GitVersion CLI output. + /// + public static GitVersionJsonContext DefaultWithConverter { get; } = new (SerializerOptions); + } +} diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionLegacyCompat.cs b/src/Cake.Common/Tools/GitVersion/GitVersionLegacyCompat.cs new file mode 100644 index 0000000000..20104840c6 --- /dev/null +++ b/src/Cake.Common/Tools/GitVersion/GitVersionLegacyCompat.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; + +namespace Cake.Common.Tools.GitVersion +{ + /// + /// Best-effort population of legacy GitVersion properties removed in GitVersion 6. + /// When the tool omits these fields (e.g. GitVersion 6+), they are computed from + /// the properties that are still present so existing Cake scripts keep working. + /// + internal static class GitVersionLegacyCompat + { + private const int DefaultPadding = 4; + + /// + /// Populates legacy properties on the given instance when they are null. + /// Uses the same formulas as GitVersion 5 where possible (e.g. LegacySemVerPadded, NuGetVersion). + /// + /// The GitVersion instance to fill; not modified if null. + public static void PopulateLegacyProperties(GitVersion version) + { + if (version == null) + { + return; + } + + var majorMinorPatch = version.MajorMinorPatch ?? string.Empty; + + if (string.IsNullOrEmpty(version.CommitsSinceVersionSourcePadded) && version.CommitsSinceVersionSource.HasValue) + { + version.CommitsSinceVersionSourcePadded = version.CommitsSinceVersionSource.Value.ToString("D" + DefaultPadding, CultureInfo.InvariantCulture); + } + + if (string.IsNullOrEmpty(version.BuildMetaDataPadded)) + { + version.BuildMetaDataPadded = version.CommitsSinceVersionSource.HasValue + ? version.CommitsSinceVersionSource.Value.ToString("D" + DefaultPadding, CultureInfo.InvariantCulture) + : (version.BuildMetaData ?? string.Empty); + } + + if (string.IsNullOrEmpty(version.LegacySemVer)) + { + version.LegacySemVer = GetLegacySemVer(majorMinorPatch, version.PreReleaseLabelWithDash, version.PreReleaseNumber); + } + + if (string.IsNullOrEmpty(version.LegacySemVerPadded)) + { + version.LegacySemVerPadded = GetLegacySemVerPadded(majorMinorPatch, version.PreReleaseLabelWithDash, version.PreReleaseNumber); + } + + if (string.IsNullOrEmpty(version.NuGetVersionV2) || string.IsNullOrEmpty(version.NuGetVersion)) + { + var nuGetVersion = version.LegacySemVerPadded ?? version.SemVer ?? majorMinorPatch; + if (string.IsNullOrEmpty(version.NuGetVersionV2)) + { + version.NuGetVersionV2 = nuGetVersion.ToLowerInvariant(); + } + + if (string.IsNullOrEmpty(version.NuGetVersion)) + { + version.NuGetVersion = version.NuGetVersionV2; + } + } + + if (string.IsNullOrEmpty(version.NuGetPreReleaseTagV2) || string.IsNullOrEmpty(version.NuGetPreReleaseTag)) + { + var nuGetPreRelease = (version.PreReleaseLabelWithDash ?? string.Empty).ToLowerInvariant(); + if (string.IsNullOrEmpty(version.NuGetPreReleaseTagV2)) + { + version.NuGetPreReleaseTagV2 = nuGetPreRelease; + } + + if (string.IsNullOrEmpty(version.NuGetPreReleaseTag)) + { + version.NuGetPreReleaseTag = nuGetPreRelease; + } + } + } + + /// + /// Legacy semantic version: {MajorMinorPatch}-{PreReleaseLabel}{PreReleaseNumber} (no dot between label and number). + /// + private static string GetLegacySemVer(string majorMinorPatch, string preReleaseLabelWithDash, int? preReleaseNumber) + { + if (string.IsNullOrEmpty(majorMinorPatch)) + { + return string.Empty; + } + + if (string.IsNullOrEmpty(preReleaseLabelWithDash) || !preReleaseNumber.HasValue) + { + return majorMinorPatch; + } + + return majorMinorPatch + preReleaseLabelWithDash + preReleaseNumber.Value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Legacy semantic version with padded pre-release number (4 digits). + /// Format: {MajorMinorPatch}-{PreReleaseLabel}{PreReleaseNumber:D4}. + /// Example: 6.1.0-alpha0041. + /// + private static string GetLegacySemVerPadded(string majorMinorPatch, string preReleaseLabelWithDash, int? preReleaseNumber) + { + if (string.IsNullOrEmpty(majorMinorPatch)) + { + return string.Empty; + } + + if (string.IsNullOrEmpty(preReleaseLabelWithDash) || !preReleaseNumber.HasValue) + { + return majorMinorPatch; + } + + return majorMinorPatch + preReleaseLabelWithDash + preReleaseNumber.Value.ToString("D" + DefaultPadding, CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionOutput.cs b/src/Cake.Common/Tools/GitVersion/GitVersionOutput.cs index 172b2df922..be253b554b 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersionOutput.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersionOutput.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.GitVersion { /// - /// The git version output type. + /// The Git version output type. /// public enum GitVersionOutput { @@ -14,8 +15,13 @@ public enum GitVersionOutput Json, /// - /// Outputs to the stdout in a way usuable by a detected buildserver. + /// Outputs to the stdout in a way usable by a detected build server. + /// + BuildServer, + + /// + /// Outputs to a file, as specified in the OutputFile parameter. /// - BuildServer + File } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionRunner.cs b/src/Cake.Common/Tools/GitVersion/GitVersionRunner.cs index e51d6800cf..49d3ab3cf4 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersionRunner.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersionRunner.cs @@ -1,11 +1,12 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Json; -using System.Text; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -45,24 +46,28 @@ public GitVersionRunner( /// A task with the GitVersion results. public GitVersion Run(GitVersionSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (settings.OutputType != GitVersionOutput.BuildServer) { - var jsonString = string.Empty; - - Run(settings, GetArguments(settings), new ProcessSettings { RedirectStandardOutput = true }, - process => jsonString = string.Join("\n", process.GetStandardOutput())); - - var jsonSerializer = new DataContractJsonSerializer(typeof(GitVersion)); - - using (var jsonStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString))) + var output = string.Empty; + Run(settings, GetArguments(settings), new ProcessSettings { RedirectStandardOutput = true }, process => { - return jsonSerializer.ReadObject(jsonStream) as GitVersion; - } + output = string.Join('\n', process.GetStandardOutput()); + if (_log.Verbosity < Verbosity.Diagnostic) + { + var errors = Regex.Matches(output, @"( *ERROR:? [^\n]*)\n([^\n]*)").Cast() + .SelectMany(match => new[] { match.Groups[1].Value, match.Groups[2].Value }); + foreach (var error in errors) + { + _log.Error(error); + } + } + }); + + var result = JsonSerializer.Deserialize(output, GitVersionJsonContext.DefaultWithConverter.GitVersionInternal)?.GitVersion; + GitVersionLegacyCompat.PopulateLegacyProperties(result); + return result; } Run(settings, GetArguments(settings)); @@ -76,37 +81,47 @@ private ProcessArgumentBuilder GetArguments(GitVersionSettings settings) if (settings.OutputType.HasValue) { + builder.Append("-output"); + switch (settings.OutputType.Value) { case GitVersionOutput.Json: - builder.Append("/output"); builder.Append("json"); break; case GitVersionOutput.BuildServer: - builder.Append("/output"); builder.Append("buildserver"); + break; + case GitVersionOutput.File: + builder.Append("file"); + + if (settings.OutputFile != null) + { + builder.Append("-outputfile"); + builder.AppendQuoted(settings.OutputFile.FullPath); + } + break; } } if (!string.IsNullOrWhiteSpace(settings.ShowVariable)) { - builder.Append("/showvariable"); + builder.Append("-showvariable"); builder.Append(settings.ShowVariable); } if (!string.IsNullOrWhiteSpace(settings.UserName)) { - builder.Append("/u"); + builder.Append("-u"); builder.AppendQuoted(settings.UserName); - builder.Append("/p"); + builder.Append("-p"); builder.AppendQuotedSecret(settings.Password); } if (settings.UpdateAssemblyInfo) { - builder.Append("/updateassemblyinfo"); + builder.Append("-updateassemblyinfo"); if (settings.UpdateAssemblyInfoFilePath != null) { @@ -116,17 +131,17 @@ private ProcessArgumentBuilder GetArguments(GitVersionSettings settings) if (settings.RepositoryPath != null) { - builder.Append("/targetpath"); + builder.Append("-targetpath"); builder.AppendQuoted(settings.RepositoryPath.FullPath); } else if (!string.IsNullOrWhiteSpace(settings.Url)) { - builder.Append("/url"); + builder.Append("-url"); builder.AppendQuoted(settings.Url); if (!string.IsNullOrWhiteSpace(settings.Branch)) { - builder.Append("/b"); + builder.Append("-b"); builder.Append(settings.Branch); } else @@ -136,23 +151,113 @@ private ProcessArgumentBuilder GetArguments(GitVersionSettings settings) if (!string.IsNullOrWhiteSpace(settings.Commit)) { - builder.Append("/c"); + builder.Append("-c"); builder.AppendQuoted(settings.Commit); } if (settings.DynamicRepositoryPath != null) { - builder.Append("/dynamicRepoLocation"); + builder.Append("-dynamicRepoLocation"); builder.AppendQuoted(settings.DynamicRepositoryPath.FullPath); } } if (settings.LogFilePath != null) { - builder.Append("/l"); + builder.Append("-l"); builder.AppendQuoted(settings.LogFilePath.FullPath); } + if (settings.ConfigFile != null) + { + builder.Append("-config"); + builder.AppendQuoted(settings.ConfigFile.FullPath); + } + + if (settings.NoFetch) + { + builder.Append("-nofetch"); + } + + if (settings.NoCache) + { + builder.Append("-nocache"); + } + + if (settings.NoNormalize) + { + builder.Append("-nonormalize"); + } + + if (settings.Diag) + { + builder.Append("-diag"); + } + + if (settings.UpdateProjectFiles) + { + builder.Append("-updateprojectfiles"); + } + + if (settings.EnsureAssemblyInfo) + { + builder.Append("-ensureassemblyinfo"); + } + + if (settings.UpdateWixVersionFile) + { + builder.Append("-updatewixversionfile"); + } + + if (settings.Verbosity.HasValue) + { + switch (settings.Verbosity.Value) + { + case GitVersionVerbosity.Quiet: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Quiet)); + break; + case GitVersionVerbosity.Diagnostic: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Diagnostic)); + break; + case GitVersionVerbosity.Verbose: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Verbose)); + break; + case GitVersionVerbosity.Normal: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Normal)); + break; + case GitVersionVerbosity.Minimal: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Minimal)); + break; + } + } + else + { + switch (_log.Verbosity) + { + case Verbosity.Quiet: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Quiet)); + break; + case Verbosity.Diagnostic: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Diagnostic)); + break; + case Verbosity.Verbose: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Verbose)); + break; + case Verbosity.Minimal: + builder.Append("-verbosity"); + builder.Append(nameof(Verbosity.Minimal)); + break; + } + } + return builder; } @@ -171,7 +276,7 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "GitVersion.exe" }; + return new[] { "GitVersion.exe", "dotnet-gitversion", "dotnet-gitversion.exe" }; } } } diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionSettings.cs b/src/Cake.Common/Tools/GitVersion/GitVersionSettings.cs index 19778082ba..0793e08afc 100644 --- a/src/Cake.Common/Tools/GitVersion/GitVersionSettings.cs +++ b/src/Cake.Common/Tools/GitVersion/GitVersionSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.GitVersion public sealed class GitVersionSettings : ToolSettings { /// - /// Gets or sets the path for the git repository to use. + /// Gets or sets the path for the Git repository to use. /// public DirectoryPath RepositoryPath { get; set; } @@ -22,15 +23,43 @@ public sealed class GitVersionSettings : ToolSettings public GitVersionOutput? OutputType { get; set; } /// - /// Gets or sets a value indicating whether to update all the assemblyinfo files. + /// Gets or sets the path to a file to store the asserted GitVersion numbers in JSON format. + /// + public FilePath OutputFile { get; set; } + + /// + /// Gets or sets the path to config file. + /// + /// Defaults to GitVersion.yml. + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to update all the AssemblyInfo files. /// public bool UpdateAssemblyInfo { get; set; } /// - /// Gets or sets whether to update all the assemblyinfo files. + /// Gets or sets whether to update all the AssemblyInfo files. /// public FilePath UpdateAssemblyInfoFilePath { get; set; } + /// + /// Gets or sets a value indicating whether to recursively search for all project files (.csproj/.vbproj/.fsproj) files in the git repo and update them. + /// + /// This is only compatible with the newer Sdk projects. + public bool UpdateProjectFiles { get; set; } + + /// + /// Gets or sets a value indicating whether if the assembly info file specified with /updateassemblyinfo or /updateassemblyinfofilename is not found, it be created with these attributes: AssemblyFileVersion, AssemblyVersion and AssemblyInformationalVersion. + /// + /// Supports writing version info for: C#, F#, VB. + public bool EnsureAssemblyInfo { get; set; } + + /// + /// Gets or sets a value indicating whether all the GitVersion variables are written to 'GitVersion_WixVersion.wxi'. The variables can then be referenced in other WiX project files for versioning. + /// + public bool UpdateWixVersionFile { get; set; } + /// /// Gets or sets whether to only show a specific variable. /// @@ -47,7 +76,7 @@ public sealed class GitVersionSettings : ToolSettings public string Password { get; set; } /// - /// Gets or sets the git url to use if using dynamic repositories. + /// Gets or sets the Git URL to use if using dynamic repositories. /// public string Url { get; set; } @@ -61,6 +90,28 @@ public sealed class GitVersionSettings : ToolSettings /// public string Commit { get; set; } + /// + /// Gets or sets a value indicating whether to fetch repository information from remote when calculating version. + /// + /// If your CI server clones the entire repository you can set this to 'true' to prevent GitVersion attempting any remote repository fetching. + public bool NoFetch { get; set; } + + /// + /// Gets or sets a value indicating whether to bypass the cached GitVersion result. Result will not be written to the cache. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether to disable normalize step on a build server. + /// + public bool NoNormalize { get; set; } + + /// + /// Gets or sets a value indicating whether to run GitVersion with additional diagnostic information. + /// + /// Requires git.exe to be installed. + public bool Diag { get; set; } + /// /// Gets or sets the dynamic repository path. Defaults to %TEMP%. /// @@ -70,5 +121,10 @@ public sealed class GitVersionSettings : ToolSettings /// Gets or sets the path to the log file. /// public FilePath LogFilePath { get; set; } + + /// + /// Gets or sets the logging verbosity. + /// + public GitVersionVerbosity? Verbosity { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/GitVersion/GitVersionVerbosity.cs b/src/Cake.Common/Tools/GitVersion/GitVersionVerbosity.cs new file mode 100644 index 0000000000..f746ecea69 --- /dev/null +++ b/src/Cake.Common/Tools/GitVersion/GitVersionVerbosity.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.GitVersion +{ + /// + /// The GitVersion verbosity. Default is . + /// + public enum GitVersionVerbosity + { + /// + /// Quiet verbosity. + /// + Quiet = 0, + + /// + /// Minimal verbosity. + /// + Minimal = 1, + + /// + /// Normal verbosity (Default). + /// + Normal = 2, + + /// + /// Verbose verbosity. + /// + Verbose = 3, + + /// + /// Diagnostic verbosity. + /// + Diagnostic = 4, + } +} diff --git a/src/Cake.Common/Tools/GitVersion/JsonStringOrNumberConverter.cs b/src/Cake.Common/Tools/GitVersion/JsonStringOrNumberConverter.cs new file mode 100644 index 0000000000..e9a512bb6a --- /dev/null +++ b/src/Cake.Common/Tools/GitVersion/JsonStringOrNumberConverter.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Cake.Common.Tools.GitVersion +{ + /// + /// Allows deserializing JSON number or string values into a property. + /// GitVersion CLI may output numeric values (e.g. Major: 6) or strings; this converter accepts both. + /// + internal sealed class JsonStringOrNumberConverter : JsonConverter + { + /// + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.TryGetInt64(out var n) ? n.ToString(CultureInfo.InvariantCulture) : reader.GetDouble().ToString(CultureInfo.InvariantCulture), + JsonTokenType.Null => null, + _ => throw new JsonException($"Unexpected token {reader.TokenType} when reading string.") + }; + } + + /// + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value); + } + } + } +} diff --git a/src/Cake.Common/Tools/ILMerge/ILMergeAliases.cs b/src/Cake.Common/Tools/ILMerge/ILMergeAliases.cs index ae91199d51..5a8a71c063 100644 --- a/src/Cake.Common/Tools/ILMerge/ILMergeAliases.cs +++ b/src/Cake.Common/Tools/ILMerge/ILMergeAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -13,7 +14,7 @@ namespace Cake.Common.Tools.ILMerge /// Contains functionality related to ILMerge. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=ilmerge" /// @@ -39,10 +40,7 @@ public static class ILMergeAliases public static void ILMerge(this ICakeContext context, FilePath outputFile, FilePath primaryAssembly, IEnumerable assemblyPaths) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var merger = new ILMergeRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); merger.Merge(outputFile, primaryAssembly, assemblyPaths); @@ -70,13 +68,10 @@ public static void ILMerge(this ICakeContext context, FilePath outputFile, FileP public static void ILMerge(this ICakeContext context, FilePath outputFile, FilePath primaryAssembly, IEnumerable assemblyPaths, ILMergeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var merger = new ILMergeRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); merger.Merge(outputFile, primaryAssembly, assemblyPaths, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILMerge/ILMergeRunner.cs b/src/Cake.Common/Tools/ILMerge/ILMergeRunner.cs index 82ed68a76b..0de65d8134 100644 --- a/src/Cake.Common/Tools/ILMerge/ILMergeRunner.cs +++ b/src/Cake.Common/Tools/ILMerge/ILMergeRunner.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Globalization; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -42,18 +44,9 @@ public ILMergeRunner( public void Merge(FilePath outputAssemblyPath, FilePath primaryAssemblyPath, IEnumerable assemblyPaths, ILMergeSettings settings = null) { - if (outputAssemblyPath == null) - { - throw new ArgumentNullException("outputAssemblyPath"); - } - if (primaryAssemblyPath == null) - { - throw new ArgumentNullException("primaryAssemblyPath"); - } - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } + ArgumentNullException.ThrowIfNull(outputAssemblyPath); + ArgumentNullException.ThrowIfNull(primaryAssemblyPath); + ArgumentNullException.ThrowIfNull(assemblyPaths); settings = settings ?? new ILMergeSettings(); @@ -83,24 +76,140 @@ private ProcessArgumentBuilder GetArguments(FilePath outputAssemblyPath, FilePath primaryAssemblyFilePath, IEnumerable assemblyPaths, ILMergeSettings settings) { var builder = new ProcessArgumentBuilder(); + const string separator = ":"; - builder.Append(GetOutputParameter(outputAssemblyPath.MakeAbsolute(_environment))); + if (settings.SearchDirectories != null) + { + foreach (var searchDirectory in settings.SearchDirectories) + { + var directoryPath = searchDirectory.MakeAbsolute(_environment); + builder.AppendSwitchQuoted("/lib", separator, directoryPath.FullPath); + } + } + + if (settings.Log && settings.LogFile == null) + { + builder.Append("/log"); + } + + if (settings.LogFile != null) + { + var logFilePath = settings.LogFile.MakeAbsolute(_environment); + builder.AppendSwitchQuoted("/log", separator, logFilePath.FullPath); + } + + if (settings.KeyFile != null) + { + var keyFilePath = settings.KeyFile.MakeAbsolute(_environment); + builder.AppendSwitchQuoted("/keyfile", separator, keyFilePath.FullPath); + } + + if (!string.IsNullOrEmpty(settings.KeyContainer)) + { + builder.AppendSwitchQuoted("/keycontainer", separator, settings.KeyContainer); + } + + if ((settings.KeyFile != null || !string.IsNullOrEmpty(settings.KeyContainer)) && settings.DelaySign) + { + builder.Append("/delaysign"); + } + + if (settings.Internalize) + { + builder.Append("/internalize"); + } if (settings.TargetKind != TargetKind.Default) { builder.Append(GetTargetKindParameter(settings)); } + if (settings.Closed) + { + builder.Append("/closed"); + } + + if (settings.NDebug) + { + builder.Append("/ndebug"); + } + + if (!string.IsNullOrEmpty(settings.Version)) + { + builder.AppendSwitch("/ver", separator, settings.Version); + } + + if (settings.CopyAttributes) + { + builder.Append("/copyattrs"); + + if (settings.AllowMultiple) + { + builder.Append("/allowMultiple"); + } + + if (settings.KeepFirst) + { + builder.Append("/keepFirst"); + } + } + + if (settings.XmlDocumentation) + { + builder.Append("/xmldocs"); + } + + if (settings.AttributeFile != null) + { + var attributeFilePath = settings.AttributeFile.MakeAbsolute(_environment); + builder.AppendSwitchQuoted("/attr", separator, attributeFilePath.FullPath); + } + if (settings.TargetPlatform != null) { builder.Append(GetTargetPlatformParameter(settings)); } - if (settings.Internalize) + if (settings.UseFullPublicKeyForReferences) { - builder.Append("/internalize"); + builder.Append("/useFullPublicKeyForReferences"); + } + + if (settings.Wildcards) + { + builder.Append("/wildcards"); + } + + if (settings.ZeroPeKind) + { + builder.Append("/zeroPeKind"); + } + + if (settings.AllowDuplicateTypes && settings.DuplicateTypes == null) + { + builder.Append("/allowDup"); + } + + if (settings.DuplicateTypes != null) + { + foreach (var duplicateType in settings.DuplicateTypes) + { + builder.AppendSwitch("/allowDup", separator, duplicateType); + } } + if (settings.Union) + { + builder.Append("/union"); + } + + if (settings.Align.HasValue) + { + builder.AppendSwitch("/align", separator, settings.Align.Value.ToString(CultureInfo.InvariantCulture)); + } + + builder.Append(GetOutputParameter(outputAssemblyPath.MakeAbsolute(_environment))); + // Add primary assembly. builder.AppendQuoted(primaryAssemblyFilePath.MakeAbsolute(_environment).FullPath); @@ -132,7 +241,7 @@ private static string GetTargetPlatformParameter(ILMergeSettings settings) { result.Add(settings.TargetPlatform.Path.FullPath.Quote()); } - return string.Concat("/targetPlatform:", string.Join(",", result)); + return string.Concat("/targetPlatform:", string.Join(',', result)); } private static string GetTargetPlatformString(TargetPlatformVersion version) @@ -167,4 +276,4 @@ private static string GetTargetKindName(TargetKind kind) } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILMerge/ILMergeSettings.cs b/src/Cake.Common/Tools/ILMerge/ILMergeSettings.cs index b01a59b1b4..e8fd9f7241 100644 --- a/src/Cake.Common/Tools/ILMerge/ILMergeSettings.cs +++ b/src/Cake.Common/Tools/ILMerge/ILMergeSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Core.IO; using Cake.Core.Tooling; namespace Cake.Common.Tools.ILMerge @@ -32,6 +34,190 @@ public sealed class ILMergeSettings : ToolSettings /// The target platform. public TargetPlatform TargetPlatform { get; set; } + /// + /// Gets or sets a value indicating whether the target assembly will be + /// delay signed. + /// + /// + /// true if target assembly will be delay signed; otherwise, false. + /// + /// This can be set only in conjunction with the option. + public bool DelaySign { get; set; } + + /// + /// Gets or sets a value indicating whether the "transitive closure" of the + /// input assemblies is computed and added to the list of input assemblies. + /// + /// + /// true if the "transitive closure" of the input assemblies is computed + /// and added to the list of input assemblies; otherwise, false. + /// + /// + /// An assembly is considered part of the transitive closure if it is + /// referenced, either directly or indirectly, from one of the originally + /// specified input assemblies and it has an external reference to one of + /// the input assemblies, or one of the assemblies that has such a reference. + /// + public bool Closed { get; set; } + + /// + /// Gets or sets a value indicating whether a .pdb file for the output assembly + /// is generated and merges into it any .pdb files found for input assemblies. + /// + /// + /// true if pdb file is generated for output assembly; otherwise, false. + /// + public bool NDebug { get; set; } + + /// + /// Gets or sets a value indicating whether the assembly level attributes of + /// each input assembly are copied over into the target assembly. + /// + /// + /// true if the assembly level attributes are copied to target + /// assembly; otherwise, false. + /// + public bool CopyAttributes { get; set; } + + /// + /// Gets or sets a value indicating whether you want to allow duplicates (for + /// those attributes whose type specifies "AllowMultiple" in their definition). + /// + /// + /// true if duplicates are allowed; otherwise, false. + /// + /// This can be set only in conjunction with the option. + public bool AllowMultiple { get; set; } + + /// + /// Gets or sets a value indicating whether the first attribute that is found is kept. + /// + /// + /// true if the first attribute that is found is kept; otherwise, false. + /// + /// This can be set only in conjunction with the option. + public bool KeepFirst { get; set; } + + /// + /// Gets or sets a value indicating whether XML documentation files are merged + /// to produce an XML documentation file for the target assembly. + /// + /// + /// true if XML documentation files are merged; otherwise, false. + /// + public bool XmlDocumentation { get; set; } + + /// + /// Gets or sets a value indicating whether external assembly references + /// in the manifest of the target assembly will use full public keys or + /// public key tokens. + /// + /// + /// true when full public keys should be used; otherwise, false. + /// + public bool UseFullPublicKeyForReferences { get; set; } + + /// + /// Gets or sets a value indicating whether any wild cards in file names + /// are expanded and all matching files will be used as input. + /// + /// + /// true if wildcards in file names are expanded; otherwise, false. + /// + public bool Wildcards { get; set; } + + /// + /// Gets or sets a value indicating whether an assembly's PeKind flag (this + /// is the value of the field listed as .corflags in the Manifest) is zero + /// it will be treated as if it was ILonly. + /// + /// + /// true when assembly's PeKind flag is zero; otherwise, false. + /// + public bool ZeroPeKind { get; set; } + + /// + /// Gets or sets a value indicating whether types with the same name are all + /// merged into a single type in the target assembly. + /// + /// + /// true if types with the same name are merged into a single type in + /// the target assembly; otherwise, false. + /// + /// Cannot be specified at the same time as . + public bool Union { get; set; } + + /// + /// Gets or sets a value that controls the file alignment used for the target assembly. + /// + public int? Align { get; set; } + + /// + /// Gets or sets the path and filename to an attribute assembly, an assembly that + /// will be used to get all of the assembly-level attributes such as Culture, + /// Version, etc. + /// + public FilePath AttributeFile { get; set; } + + /// + /// Gets or sets the version. When this has a non-null value, then the target assembly will be given its + /// value as the version number of the assembly. + /// + /// + /// The version must be a valid assembly version as defined by the attribute + /// AssemblyVersion in the System.Reflection namespace. + /// + public string Version { get; set; } + + /// + /// Gets or sets a value indicating whether log messages are written. + /// + /// + /// If Log is true, but is null, then log messages are written to + /// Console.Out. + /// + public bool Log { get; set; } + + /// + /// Gets or sets the path to the file where log messages should be written to. + /// + public FilePath LogFile { get; set; } + + /// + /// Gets or sets the path to a .snk file. The target assembly will be signed with + /// its contents and will then have a strong name. + /// + public FilePath KeyFile { get; set; } + + /// + /// Gets or sets the name of the container to use when signing the target assembly. + /// + public string KeyContainer { get; set; } + + /// + /// Gets or sets the directories to be used to search for input assemblies. + /// + public DirectoryPath[] SearchDirectories { get; set; } + + /// + /// Gets or sets a value indicating whether to allow the user to allow all + /// public types to be renamed when they are duplicates. + /// + /// + /// true if all public types should be allowed to be renamed; otherwise false. + /// + /// + /// Use to allow fine grain control over exactly + /// which types are allowed to be renamed. + /// + public bool AllowDuplicateTypes { get; set; } + + /// + /// Gets or sets a list of public types which are allowed to be renamed when duplicates + /// exist. + /// + public string[] DuplicateTypes { get; set; } + /// /// Initializes a new instance of the class. /// @@ -41,4 +227,4 @@ public ILMergeSettings() TargetKind = TargetKind.Default; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILMerge/TargetKind.cs b/src/Cake.Common/Tools/ILMerge/TargetKind.cs index f47c210cd6..337d043d18 100644 --- a/src/Cake.Common/Tools/ILMerge/TargetKind.cs +++ b/src/Cake.Common/Tools/ILMerge/TargetKind.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.ILMerge { /// @@ -28,4 +29,4 @@ public enum TargetKind /// WinExe } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILMerge/TargetPlatform.cs b/src/Cake.Common/Tools/ILMerge/TargetPlatform.cs index 9567212df1..ad18f477b4 100644 --- a/src/Cake.Common/Tools/ILMerge/TargetPlatform.cs +++ b/src/Cake.Common/Tools/ILMerge/TargetPlatform.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; @@ -11,16 +12,13 @@ namespace Cake.Common.Tools.ILMerge /// public sealed class TargetPlatform { - private readonly DirectoryPath _path; - private readonly TargetPlatformVersion _platform; - /// /// Initializes a new instance of the class. /// /// The .NET framework target version. public TargetPlatform(TargetPlatformVersion platform) { - _platform = platform; + Platform = platform; } /// @@ -30,28 +28,19 @@ public TargetPlatform(TargetPlatformVersion platform) /// The directory where mscorlib.dll can be found. public TargetPlatform(TargetPlatformVersion platform, DirectoryPath path) { - if (path == null) - { - throw new ArgumentNullException("path"); - } - _platform = platform; - _path = path; + ArgumentNullException.ThrowIfNull(path); + Platform = platform; + Path = path; } /// /// Gets the .NET framework target version. /// - public TargetPlatformVersion Platform - { - get { return _platform; } - } + public TargetPlatformVersion Platform { get; } /// /// Gets the directory where mscorlib.dll can be found. /// - public DirectoryPath Path - { - get { return _path; } - } + public DirectoryPath Path { get; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILMerge/TargetPlatformVersion.cs b/src/Cake.Common/Tools/ILMerge/TargetPlatformVersion.cs index c7dc0537ec..186cdcb8d9 100644 --- a/src/Cake.Common/Tools/ILMerge/TargetPlatformVersion.cs +++ b/src/Cake.Common/Tools/ILMerge/TargetPlatformVersion.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Diagnostics.CodeAnalysis; namespace Cake.Common.Tools.ILMerge { /// - /// Represents the .NET Framework for the target assembly + /// Represents the .NET Framework for the target assembly. /// [SuppressMessage("ReSharper", "InconsistentNaming")] public enum TargetPlatformVersion @@ -31,4 +32,4 @@ public enum TargetPlatformVersion /// v4 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILRepack/ILRepackAliases.cs b/src/Cake.Common/Tools/ILRepack/ILRepackAliases.cs index a56c8b014f..932a86a6cc 100644 --- a/src/Cake.Common/Tools/ILRepack/ILRepackAliases.cs +++ b/src/Cake.Common/Tools/ILRepack/ILRepackAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -13,7 +14,7 @@ namespace Cake.Common.Tools.ILRepack /// Contains functionality related to ILRepack. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=ILRepack" /// @@ -42,10 +43,7 @@ public static void ILRepack( FilePath primaryAssembly, IEnumerable assemblyPaths) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var merger = new ILRepackRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); merger.Merge(outputFile, primaryAssembly, assemblyPaths); @@ -77,13 +75,10 @@ public static void ILRepack( IEnumerable assemblyPaths, ILRepackSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var merger = new ILRepackRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); merger.Merge(outputFile, primaryAssembly, assemblyPaths, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILRepack/ILRepackRunner.cs b/src/Cake.Common/Tools/ILRepack/ILRepackRunner.cs index 84a2792ed1..1534d8feb6 100644 --- a/src/Cake.Common/Tools/ILRepack/ILRepackRunner.cs +++ b/src/Cake.Common/Tools/ILRepack/ILRepackRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -40,18 +41,9 @@ public ILRepackRunner(IFileSystem fileSystem, ICakeEnvironment environment, IPro public void Merge(FilePath outputAssemblyPath, FilePath primaryAssemblyPath, IEnumerable assemblyPaths, ILRepackSettings settings = null) { - if (outputAssemblyPath == null) - { - throw new ArgumentNullException("outputAssemblyPath"); - } - if (primaryAssemblyPath == null) - { - throw new ArgumentNullException("primaryAssemblyPath"); - } - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } + ArgumentNullException.ThrowIfNull(outputAssemblyPath); + ArgumentNullException.ThrowIfNull(primaryAssemblyPath); + ArgumentNullException.ThrowIfNull(assemblyPaths); settings = settings ?? new ILRepackSettings(); @@ -247,4 +239,4 @@ private static string GetTargetKindName(ILMerge.TargetKind kind) } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ILRepack/ILRepackSettings.cs b/src/Cake.Common/Tools/ILRepack/ILRepackSettings.cs index a5c1195d6b..50cbdd5858 100644 --- a/src/Cake.Common/Tools/ILRepack/ILRepackSettings.cs +++ b/src/Cake.Common/Tools/ILRepack/ILRepackSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Common.Tools.ILMerge; @@ -48,13 +49,13 @@ public sealed class ILRepackSettings : ToolSettings */ /// - /// Gets or sets a keyfile to sign the output assembly + /// Gets or sets a keyfile to sign the output assembly. /// /// The keyfile. public FilePath Keyfile { get; set; } /// - /// Gets or sets a file to enable logging to (no logging if null or empty) + /// Gets or sets a file to enable logging to (no logging if null or empty). /// /// The log file. public string Log { get; set; } @@ -66,61 +67,61 @@ public sealed class ILRepackSettings : ToolSettings public Version Version { get; set; } /// - /// Gets or sets a value indicating whether to merge types with identical names into one + /// Gets or sets a value indicating whether to merge types with identical names into one. /// /// true if types with identical names should be merged into one; otherwise, false. public bool Union { get; set; } /// - /// Gets or sets a value indicating whether to disable symbol file generation + /// Gets or sets a value indicating whether to disable symbol file generation. /// /// true if debug symbols should be disabled; otherwise, false. public bool NDebug { get; set; } /// - /// Gets or sets a value indicating whether to copy assembly attributes (by default only the primary assembly attributes are copied) + /// Gets or sets a value indicating whether to copy assembly attributes (by default only the primary assembly attributes are copied). /// /// true if assembly attributes should be copied; otherwise, false. public bool CopyAttrs { get; set; } /// - /// Gets or sets the assembly file to take attributes from + /// Gets or sets the assembly file to take attributes from. /// /// The assembly file to take attributes from. public FilePath Attr { get; set; } /// - /// Gets or sets a value indicating whether to allow multiple attributes (if type allows) + /// Gets or sets a value indicating whether to allow multiple attributes (if type allows). /// /// true if multiple attributes should be allowed; otherwise, false. public bool AllowMultiple { get; set; } /// - /// Gets or sets the specify target assembly kind (library, exe, winexe supported, default is same as first assembly) + /// Gets or sets the specify target assembly kind (library, exe, winexe supported, default is same as first assembly). /// /// The kind of the target assembly to create. public TargetKind TargetKind { get; set; } /// - /// Gets or sets the target platform (v1, v1.1, v2, v4 supported) + /// Gets or sets the target platform (v1, v1.1, v2, v4 supported). /// /// The target platform. public TargetPlatformVersion? TargetPlatform { get; set; } /// - /// Gets or sets a value indicating whether to merge XML documentation as well + /// Gets or sets a value indicating whether to merge XML documentation as well. /// /// true if xml documents should be merged; otherwise, false. public bool XmlDocs { get; set; } /// - /// Gets or sets the paths to search directories for referenced assemblies (can be specified multiple times) + /// Gets or sets the paths to search directories for referenced assemblies (can be specified multiple times). /// /// The libs. - public List Libs { get; set; } + public List Libs { get; set; } = new List(); /// - /// Gets or sets a value indicating whether to set all types but the ones from the first assembly 'internal' + /// Gets or sets a value indicating whether to set all types but the ones from the first assembly 'internal'. /// /// /// true if types in assemblies other than the primary assembly should @@ -129,51 +130,51 @@ public sealed class ILRepackSettings : ToolSettings public bool Internalize { get; set; } /// - /// Gets or sets a value indicating whether to set the key, but don't sign the assembly + /// Gets or sets a value indicating whether to set the key, but don't sign the assembly. /// /// true if assembly should be delay signed; otherwise, false. public bool DelaySign { get; set; } /// - /// Gets or sets the specified type for being duplicated in input assemblies + /// Gets or sets the specified type for being duplicated in input assemblies. /// /// The type to allow duplication of. public string AllowDup { get; set; } /// - /// Gets or sets a value indicating whether to duplicate resources in output assembly (by default they're ignored) + /// Gets or sets a value indicating whether to duplicate resources in output assembly (by default they're ignored). /// /// true if duplicate resources should be allowed in the output assembly; otherwise, false. public bool AllowDuplicateResources { get; set; } /// - /// Gets or sets a value indicating whether to allow assemblies with Zero PeKind (but obviously only IL will get merged) + /// Gets or sets a value indicating whether to allow assemblies with Zero PeKind (but obviously only IL will get merged). /// /// true if assemblies with Zero PeKind should be allowed; otherwise, false. public bool ZeroPeKind { get; set; } /// - /// Gets or sets a value indicating whether to allow (and resolve) file wildcards (e.g. `*`.dll) in input assemblies + /// Gets or sets a value indicating whether to allow (and resolve) file wildcards (e.g. `*`.dll) in input assemblies. /// /// true if file wildcards should be allowed in input assembly paths; otherwise, false. public bool Wildcards { get; set; } /// - /// Gets or sets a value indicating whether to use as many CPUs as possible to merge the assemblies + /// Gets or sets a value indicating whether to use as many CPUs as possible to merge the assemblies. /// /// true if merging should use as many CPUs as possible in parallel; otherwise, false. public bool Parallel { get; set; } /// - /// Gets or sets a value indicating whether to pause execution once completed (good for debugging) + /// Gets or sets a value indicating whether to pause execution once completed (good for debugging). /// /// true if execution should pause once completed; otherwise, false. public bool Pause { get; set; } /// - /// Gets or sets a value indicating whether to show more logs + /// Gets or sets a value indicating whether to show more logs. /// /// true if more logs should be output during execution; otherwise, false. public bool Verbose { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InnoSetup/InnoSetupAliases.cs b/src/Cake.Common/Tools/InnoSetup/InnoSetupAliases.cs new file mode 100644 index 0000000000..f60d582e40 --- /dev/null +++ b/src/Cake.Common/Tools/InnoSetup/InnoSetupAliases.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.InnoSetup +{ + /// + /// Contains functionality related to Inno Setup. + /// + /// In order to use the commands for this alias, Inno Setup will need to be installed on the machine where + /// the Cake script is being executed. See this page for information + /// on how to download/install. + /// + /// + [CakeAliasCategory("Inno Setup")] + public static class InnoSetupAliases + { + /// + /// Compiles the given Inno Setup script using the default settings. + /// + /// The context. + /// The path to the .iss script file to compile. + /// + /// + /// InnoSetup("./src/Cake.iss"); + /// + /// + [CakeMethodAlias] + public static void InnoSetup(this ICakeContext context, FilePath scriptFile) + { + InnoSetup(context, scriptFile, new InnoSetupSettings()); + } + + /// + /// Compiles the given Inno Setup script using the given . + /// + /// The context. + /// The path to the .iss script file to compile. + /// The to use. + /// + /// + /// InnoSetup("./src/Cake.iss", new InnoSetupSettings { + /// OutputDirectory = outputDirectory + /// }); + /// + /// + [CakeMethodAlias] + public static void InnoSetup(this ICakeContext context, FilePath scriptFile, InnoSetupSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(scriptFile); + ArgumentNullException.ThrowIfNull(settings); + + var runner = new InnoSetupRunner(context.FileSystem, context.Registry, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(scriptFile, settings); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InnoSetup/InnoSetupQuietMode.cs b/src/Cake.Common/Tools/InnoSetup/InnoSetupQuietMode.cs new file mode 100644 index 0000000000..4bb0babf2b --- /dev/null +++ b/src/Cake.Common/Tools/InnoSetup/InnoSetupQuietMode.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.InnoSetup +{ + /// + /// Represents the possible quiet modes when compiling an Inno Setup script. + /// + public enum InnoSetupQuietMode + { + /// + /// Quiet mode disabled. This is the default value. + /// + Off, + + /// + /// Quiet mode. Only error messages are printed. + /// + Quiet, + + /// + /// Quiet mode with progress. Same as quiet mode, but also displays progress. + /// + QuietWithProgress + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InnoSetup/InnoSetupRunner.cs b/src/Cake.Common/Tools/InnoSetup/InnoSetupRunner.cs new file mode 100644 index 0000000000..207c490381 --- /dev/null +++ b/src/Cake.Common/Tools/InnoSetup/InnoSetupRunner.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.IO.Arguments; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.InnoSetup +{ + /// + /// The runner which executes Inno Setup. + /// + public sealed class InnoSetupRunner : Tool + { + private readonly IRegistry _registry; + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The registry. + /// The environment. + /// The process runner. + /// The tool locator. + public InnoSetupRunner( + IFileSystem fileSystem, + IRegistry registry, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _registry = registry; + _environment = environment; + } + + /// + /// Runs iscc.exe with the specified script files and settings. + /// + /// The script file (.iss) to compile. + /// The settings. + public void Run(FilePath scriptFile, InnoSetupSettings settings) + { + ArgumentNullException.ThrowIfNull(scriptFile); + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetArguments(scriptFile, settings)); + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "InnoSetup"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "iscc.exe" }; + } + + /// + /// Gets alternative file paths which the tool may exist in. + /// + /// The settings. + /// The default tool path. + protected override IEnumerable GetAlternativeToolPaths(InnoSetupSettings settings) + { + foreach (var keyPath in GetAlternativeRegistryKeyPathsForVersion(settings.Version)) + { + using (var innoSetupKey = _registry.LocalMachine.OpenKey(keyPath)) + { + var installationPath = innoSetupKey?.GetValue("InstallLocation") as string; + if (!string.IsNullOrEmpty(installationPath)) + { + var directory = new DirectoryPath(installationPath); + var isccPath = directory.CombineWithFilePath("iscc.exe"); + return new[] { isccPath }; + } + } + } + + return base.GetAlternativeToolPaths(settings); + } + + private IEnumerable GetAlternativeRegistryKeyPathsForVersion(InnoSetupVersion? version) + { + if (version != null) + { + return new[] { GetRegistryKeyPathForVersion(version.Value) }; + } + + var versionsToConsider = new[] + { + InnoSetupVersion.InnoSetup6, + InnoSetupVersion.InnoSetup5 + }; + return versionsToConsider.Select(GetRegistryKeyPathForVersion); + } + + private string GetRegistryKeyPathForVersion(InnoSetupVersion version) + { + // On 64-bit Windows, the registry key for Inno Setup will be accessible under Wow6432Node + var softwareKeyPath = _environment.Platform.Is64Bit ? @"SOFTWARE\Wow6432Node\" : @"SOFTWARE\"; + switch (version) + { + case InnoSetupVersion.InnoSetup6: + return $@"{softwareKeyPath}Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1"; + case InnoSetupVersion.InnoSetup5: + return $@"{softwareKeyPath}Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 5_is1"; + default: + throw new ArgumentOutOfRangeException(nameof(version), version, "Missing switch case"); + } + } + + private ProcessArgumentBuilder GetArguments(FilePath scriptFile, InnoSetupSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // Defines (/Ddefine[=value] + if (settings.Defines != null) + { + foreach (var item in settings.Defines) + { + builder.Append(string.IsNullOrEmpty(item.Value) + ? string.Format(CultureInfo.InvariantCulture, "/D{0}", item.Key) + : string.Format(CultureInfo.InvariantCulture, "/D{0}={1}", item.Key, + new QuotedArgument(new TextArgument(item.Value)))); + } + } + + // Enable output + if (settings.EnableOutput.HasValue) + { + builder.Append(settings.EnableOutput.Value ? "/O+" : "/O-"); + } + + // Output directory + if (settings.OutputDirectory != null) + { + builder.AppendSwitchQuoted("/O", string.Empty, settings.OutputDirectory.MakeAbsolute(_environment).FullPath); + } + + // Output base file name + if (!string.IsNullOrEmpty(settings.OutputBaseFilename)) + { + builder.AppendSwitchQuoted("/F", string.Empty, settings.OutputBaseFilename); + } + + // Quiet mode + switch (settings.QuietMode) + { + case InnoSetupQuietMode.Off: + break; + case InnoSetupQuietMode.Quiet: + builder.Append("/Q"); + break; + case InnoSetupQuietMode.QuietWithProgress: + builder.Append("/Qp"); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + // Quoted Script file + builder.AppendQuoted(scriptFile.MakeAbsolute(_environment).FullPath); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InnoSetup/InnoSetupSettings.cs b/src/Cake.Common/Tools/InnoSetup/InnoSetupSettings.cs new file mode 100644 index 0000000000..00256a7bbc --- /dev/null +++ b/src/Cake.Common/Tools/InnoSetup/InnoSetupSettings.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.InnoSetup +{ + /// + /// Contains settings used by the . + /// + public sealed class InnoSetupSettings : ToolSettings + { + /// + /// Gets or sets the script compiler defines. Emulates #define public preprocessor directive. + /// + public IDictionary Defines { get; set; } + + /// + /// Gets or sets whether or not the compiler should generate output (/O+ and /O- command line options, + /// overrides the script's Output attribute). + /// + public bool? EnableOutput { get; set; } + + /// + /// Gets or sets the output directory (/O<path> command line option, overrides the script's OutputDir + /// attribute). + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the output base file name (/F<filename> command line option, overrides the script's + /// OutputBaseFilename attribute). + /// + public string OutputBaseFilename { get; set; } + + /// + /// Gets or sets the script compiler's quiet mode (/Q and /Qp command line options). + /// + public InnoSetupQuietMode QuietMode { get; set; } + + /// + /// Gets or sets the version of Inno Setup to be used with this command. By default the highest installed version of Inno Setup is used. + /// + public InnoSetupVersion? Version { get; set; } + + /// + /// Initializes a new instance of the class with the default settings. + /// + public InnoSetupSettings() + { + Defines = new Dictionary(); + } + } +} diff --git a/src/Cake.Common/Tools/InnoSetup/InnoSetupVersion.cs b/src/Cake.Common/Tools/InnoSetup/InnoSetupVersion.cs new file mode 100644 index 0000000000..35a3f26852 --- /dev/null +++ b/src/Cake.Common/Tools/InnoSetup/InnoSetupVersion.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.InnoSetup +{ + /// + /// Represents the supported major versions of Inno Setup. + /// + public enum InnoSetupVersion + { + /// Version 5.x + InnoSetup5 = 5, + + /// Version 6.x + InnoSetup6 = 6 + } +} diff --git a/src/Cake.Common/Tools/InspectCode/InspectCodeAliases.cs b/src/Cake.Common/Tools/InspectCode/InspectCodeAliases.cs index e967a7cc17..6eff796682 100644 --- a/src/Cake.Common/Tools/InspectCode/InspectCodeAliases.cs +++ b/src/Cake.Common/Tools/InspectCode/InspectCodeAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.InspectCode /// Contains functionality related to ReSharper's InspectCode tool. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=JetBrains.ReSharper.CommandLineTools" /// @@ -22,7 +23,7 @@ namespace Cake.Common.Tools.InspectCode public static class InspectCodeAliases { /// - /// Analyses the specified solution with Resharper's InspectCode. + /// Analyses the specified solution with ReSharper's InspectCode. /// /// The context. /// The solution. @@ -39,7 +40,7 @@ public static void InspectCode(this ICakeContext context, FilePath solution) } /// - /// Analyses the specified solution with Resharper's InspectCode, + /// Analyses the specified solution with ReSharper's InspectCode, /// using the specified settings. /// /// The context. @@ -55,11 +56,11 @@ public static void InspectCode(this ICakeContext context, FilePath solution) /// msBuildProperties.Add("platform", "AnyCPU"); /// /// InspectCode("./MySolution.sln", new InspectCodeSettings { - /// SolutionWideAnalysis = true; - /// Profile = "./MySolution.sln.DotSettings"; - /// MsBuildProperties = msBuildProperties; - /// OutputFile = resharperReportsDirectory + File("inspectcode-output.xml"); - /// ThrowExceptionOnFindingViolations = true; + /// SolutionWideAnalysis = true, + /// Profile = "./MySolution.sln.DotSettings", + /// MsBuildProperties = msBuildProperties, + /// OutputFile = resharperReportsDirectory + File("inspectcode-output.xml"), + /// ThrowExceptionOnFindingViolations = true /// }); /// /// @@ -67,10 +68,7 @@ public static void InspectCode(this ICakeContext context, FilePath solution) [CakeAliasCategory("InspectCode")] public static void InspectCode(this ICakeContext context, FilePath solution, InspectCodeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new InspectCodeRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); runner.Run(solution, settings); @@ -90,13 +88,10 @@ public static void InspectCode(this ICakeContext context, FilePath solution, Ins [CakeAliasCategory("InspectCode")] public static void InspectCodeFromConfig(this ICakeContext context, FilePath configFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new InspectCodeRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Log); runner.RunFromConfig(configFile); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InspectCode/InspectCodeRunner.cs b/src/Cake.Common/Tools/InspectCode/InspectCodeRunner.cs index df82a7d0f8..820dc6a246 100644 --- a/src/Cake.Common/Tools/InspectCode/InspectCodeRunner.cs +++ b/src/Cake.Common/Tools/InspectCode/InspectCodeRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -14,7 +15,7 @@ namespace Cake.Common.Tools.InspectCode { /// - /// InspectCode runner + /// InspectCode runner. /// public sealed class InspectCodeRunner : Tool { @@ -49,21 +50,18 @@ public InspectCodeRunner( /// The settings. public void Run(FilePath solution, InspectCodeSettings settings) { - if (solution == null) - { - throw new ArgumentNullException("solution"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(solution); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(settings, solution)); - if (settings.OutputFile != null) + if (settings.SkipOutputAnalysis || + settings.OutputFile == null) { - AnalyseResultsFile(settings.OutputFile, settings.ThrowExceptionOnFindingViolations); + return; } + + AnalyseResultsFile(settings.OutputFile, settings.ThrowExceptionOnFindingViolations); } /// @@ -72,10 +70,7 @@ public void Run(FilePath solution, InspectCodeSettings settings) /// The config file. public void RunFromConfig(FilePath configFile) { - if (configFile == null) - { - throw new ArgumentNullException("configFile"); - } + ArgumentNullException.ThrowIfNull(configFile); Run(new InspectCodeSettings(), GetConfigArgument(configFile)); } @@ -194,6 +189,21 @@ private ProcessArgumentBuilder GetArguments(InspectCodeSettings settings, FilePa builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "/profile={0}", settings.Profile.MakeAbsolute(_environment).FullPath)); } + if (settings.Verbosity != null) + { + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "/verbosity={0}", settings.Verbosity.ToString().ToUpper(CultureInfo.InvariantCulture))); + } + + if (settings.Severity != null) + { + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "/severity={0}", settings.Severity.ToString().ToUpper(CultureInfo.InvariantCulture))); + } + + if (settings.Build.HasValue) + { + builder.Append(settings.Build.Value ? "--build" : "--no-build"); + } + builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); return builder; @@ -211,10 +221,17 @@ protected override string GetToolName() /// /// Gets the possible names of the tool executable. /// + /// The settings. /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() + protected override IEnumerable GetToolExecutableNames(InspectCodeSettings settings) { - return new[] { "inspectcode.exe" }; + return new[] { settings != null && settings.UseX86Tool ? "inspectcode.x86.exe" : "inspectcode.exe" }; } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() => GetToolExecutableNames(null); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InspectCode/InspectCodeSettings.cs b/src/Cake.Common/Tools/InspectCode/InspectCodeSettings.cs index 0774c97b5a..0b3329cdc6 100644 --- a/src/Cake.Common/Tools/InspectCode/InspectCodeSettings.cs +++ b/src/Cake.Common/Tools/InspectCode/InspectCodeSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -16,14 +18,14 @@ public sealed class InspectCodeSettings : ToolSettings Not (yet) supported options: - /toolset MsBuild toolset version. Highest available is used by default. Example: /toolset=12.0. - /dumpIssuesTypes (/it) Dump issues types (default: False). - - /targets-for-references MSBuild targets. These targets will be executed to get referenced assemblies of projects.. - - /targets-for-items MSBuild targets. These targets will be executed to get other items (e.g. Compile item) of projects.. + - /targets-for-references MSBuild targets. These targets will be executed to get referenced assemblies of projects. + - /targets-for-items MSBuild targets. These targets will be executed to get other items (e.g. Compile item) of projects. */ /// /// Gets or sets the location InspectCode should write its output. /// - /// The location that InspectCode should write its output + /// The location that InspectCode should write its output. public FilePath OutputFile { get; set; } /// @@ -37,7 +39,7 @@ public sealed class InspectCodeSettings : ToolSettings /// /// Gets or sets a value indicating whether disable solution-wide analysis should be forced. - /// Default value is false + /// Default value is false. /// /// /// true if solution-wide analysis should be disabled by force; otherwise, fault. @@ -54,11 +56,11 @@ public sealed class InspectCodeSettings : ToolSettings /// /// Gets or sets MSBuild properties. /// - /// The MSBuild properties to override - public Dictionary MsBuildProperties { get; set; } + /// The MSBuild properties to override. + public Dictionary MsBuildProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// - /// Gets or sets a list of Resharper extensions which will be used. + /// Gets or sets a list of ReSharper extensions which will be used. /// public string[] Extensions { get; set; } @@ -94,8 +96,56 @@ public sealed class InspectCodeSettings : ToolSettings public FilePath Profile { get; set; } /// - /// Gets or sets a value indicating whether to throw an exception on finding violations + /// Gets or sets a value indicating whether to throw an exception on finding violations. /// public bool ThrowExceptionOnFindingViolations { get; set; } + + /// + /// Gets or sets a value indicating whether to use x86 tool. + /// + public bool UseX86Tool { get; set; } + + /// + /// Gets or sets the verbosity level of the log messages. + /// + public InspectCodeVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the minimal severity of issues to report. + /// + public InspectCodeSeverity? Severity { get; set; } + + /// + /// Gets or sets a value indicating whether to skip analysis of the file + /// that was output by the command line tool or not. + /// + public bool SkipOutputAnalysis { get; set; } + + /// + /// Gets or sets a value indicating whether to build or not-build the + /// sources before running the tool. Setting this value is only valid + /// for InspectCode version 2021.2.0 and later. + /// + /// + /// + /// Setting this property to true will result in passing the '--build' option. + /// + /// + /// Setting this property to false will result in passing the '--no-build' option. + /// + /// + /// Setting this property to null will result in no changes to the options. This is the default + /// and the only valid setting for versions before 2021.2. + /// + /// + /// + /// + /// + /// Starting from version 2021.2, InspectCode builds the target solution before starting the analysis + /// to make sure it only finds relevant code issues. + /// To explicitly accept the new behavior and suppress this warning, use the '--build' option. + /// To match the behavior in previous versions and skip the build, use '--no-build'. + /// + public bool? Build { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InspectCode/InspectCodeSeverity.cs b/src/Cake.Common/Tools/InspectCode/InspectCodeSeverity.cs new file mode 100644 index 0000000000..e2c58d7127 --- /dev/null +++ b/src/Cake.Common/Tools/InspectCode/InspectCodeSeverity.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.InspectCode +{ + /// + /// Represents InspectCode's minimal severity report. + /// + public enum InspectCodeSeverity + { + /// + /// Severity: INFO. + /// + Info = 1, + + /// + /// Severity: HINT. + /// + Hint = 2, + + /// + /// Severity: SUGGESTION. + /// + Suggestion = 3, + + /// + /// Severity: WARNING. + /// + Warning = 4, + + /// + /// Severity: ERROR. + /// + Error = 5 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InspectCode/InspectCodeVerbosity.cs b/src/Cake.Common/Tools/InspectCode/InspectCodeVerbosity.cs new file mode 100644 index 0000000000..aa491edfbb --- /dev/null +++ b/src/Cake.Common/Tools/InspectCode/InspectCodeVerbosity.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.InspectCode +{ + /// + /// Represents InspectCode's logging verbosity. + /// + public enum InspectCodeVerbosity + { + /// + /// Verbosity: OFF. + /// + Off = 1, + + /// + /// Verbosity: FATAL. + /// + Fatal = 2, + + /// + /// Verbosity: ERROR. + /// + Error = 3, + + /// + /// Verbosity: WARN. + /// + Warn = 4, + + /// + /// Verbosity: INFO. + /// + Info = 5, + + /// + /// Verbosity: VERBOSE. + /// + Verbose = 6, + + /// + /// Verbosity: TRACE. + /// + Trace = 7 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/InspectCode/SettingsLayer.cs b/src/Cake.Common/Tools/InspectCode/SettingsLayer.cs index a725b77bce..c4f7039539 100644 --- a/src/Cake.Common/Tools/InspectCode/SettingsLayer.cs +++ b/src/Cake.Common/Tools/InspectCode/SettingsLayer.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.InspectCode { /// - /// Represents Resharper's settings layers. + /// Represents ReSharper's settings layers. /// public enum SettingsLayer { @@ -38,4 +39,4 @@ public enum SettingsLayer /// ProjectPersonal = 6 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs b/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs index 956b184295..dc0c0d4fa1 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildAliases.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using Cake.Core; using Cake.Core.Annotations; using Cake.Core.IO; @@ -19,10 +21,10 @@ namespace Cake.Common.Tools.MSBuild public static class MSBuildAliases { /// - /// Builds the specified solution using MSBuild. + /// Builds the specified solution or MsBuild project file using MSBuild. /// /// The context. - /// The solution. + /// The solution or MsBuild project file to build. /// /// /// MSBuild("./src/Cake.sln"); @@ -31,14 +33,32 @@ public static class MSBuildAliases [CakeMethodAlias] public static void MSBuild(this ICakeContext context, FilePath solution) { - MSBuild(context, solution, settings => { }); + MSBuild(context, solution, (MSBuildSettings settings) => { }); } /// - /// Builds the specified solution using MSBuild. + /// Builds the specified solution or MsBuild project file using MSBuild. /// /// The context. - /// The solution to build. + /// The solution or MsBuild project file to build. + /// The action to invoke with the standard output. + /// + /// + /// MSBuild("./src/Cake.sln", + /// output => foreach (var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, Action> standardOutputAction) + { + MSBuild(context, solution, settings => { }, standardOutputAction); + } + + /// + /// Builds the specified solution or MsBuild project file using MSBuild. + /// + /// The context. + /// The solution or MsBuild project file to build. /// The settings configurator. /// /// @@ -53,14 +73,8 @@ public static void MSBuild(this ICakeContext context, FilePath solution) [CakeMethodAlias] public static void MSBuild(this ICakeContext context, FilePath solution, Action configurator) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (configurator == null) - { - throw new ArgumentNullException("configurator"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(configurator); var settings = new MSBuildSettings(); configurator(settings); @@ -69,11 +83,44 @@ public static void MSBuild(this ICakeContext context, FilePath solution, Action< MSBuild(context, solution, settings); } + /// + /// Builds the specified solution or MsBuild project file using MSBuild. + /// + /// The context. + /// The solution or MsBuild project file to build. + /// The settings configurator. + /// The action to invoke with the standard output. + /// + /// + /// var outputBuilder = new StringBuilder(); + /// MSBuild("./src/Cake.sln", configurator => + /// configurator.SetConfiguration("Debug") + /// .SetVerbosity(Verbosity.Minimal) + /// .UseToolVersion(MSBuildToolVersion.VS2015) + /// .SetMSBuildPlatform(MSBuildPlatform.x86) + /// .SetPlatformTarget(PlatformTarget.MSIL), + /// output => foreach (var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, Action configurator, Action> standardOutputAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(configurator); + ArgumentNullException.ThrowIfNull(standardOutputAction); + + var settings = new MSBuildSettings(); + configurator(settings); + + // Perform the build. + MSBuild(context, solution, settings, standardOutputAction); + } + /// /// Builds the specified solution using MSBuild. /// /// The context. - /// The solution to build. + /// The solution or MsBuild project file to build. /// The settings. /// /// @@ -88,17 +135,41 @@ public static void MSBuild(this ICakeContext context, FilePath solution, Action< [CakeMethodAlias] public static void MSBuild(this ICakeContext context, FilePath solution, MSBuildSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(settings); + + var runner = new MSBuildRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(solution, settings, null); + } + + /// + /// Builds the specified solution or MsBuild project file using MSBuild. + /// + /// The context. + /// The solution to build. + /// The settings. + /// The action to invoke with the standard output. + /// + /// + /// var outputBuilder = new StringBuilder(); + /// MSBuild("./src/Cake.sln", new MSBuildSettings { + /// Verbosity = Verbosity.Minimal, + /// ToolVersion = MSBuildToolVersion.VS2015, + /// Configuration = "Release", + /// PlatformTarget = PlatformTarget.MSIL + /// }, + /// output => foreach (var line in output) outputBuilder.AppendLine(line)); + /// + /// + [CakeMethodAlias] + public static void MSBuild(this ICakeContext context, FilePath solution, MSBuildSettings settings, Action> standardOutputAction) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(standardOutputAction); var runner = new MSBuildRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(solution, settings); + runner.Run(solution, settings, standardOutputAction); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogImports.cs b/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogImports.cs new file mode 100644 index 0000000000..e70daad54a --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogImports.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// What files to include in the binary log. + /// + public enum MSBuildBinaryLogImports + { + /// Don't specify imports + Unspecified = 0, + + /// Do not collect project and imports files + None = 2, + + /// Embed in the binlog file + Embed = 3, + + /// Produce a separate .ProjectImports.zip + ZipFile = 4 + } +} diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogSettings.cs b/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogSettings.cs new file mode 100644 index 0000000000..c38f0d28a7 --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildBinaryLogSettings.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// MSBuild binary logging settings used by . + /// + public class MSBuildBinaryLogSettings + { + /// + /// Gets or sets a value indicating whether binary logging should be enabled. + /// + public bool Enabled { get; set; } + + /// + /// Gets or sets the output filename. + /// + public string FileName { get; set; } + + /// + /// Gets or sets what source files should be included in the log. + /// + public MSBuildBinaryLogImports Imports { get; set; } + } +} diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildFileLogger.cs b/src/Cake.Common/Tools/MSBuild/MSBuildFileLogger.cs new file mode 100644 index 0000000000..3d21fca691 --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildFileLogger.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// Contains settings for specifying a MSBuild file logger. + /// + public class MSBuildFileLogger + { + /// + /// Initializes a new instance of the class. + /// + public MSBuildFileLogger() + { + } + + /// + /// Gets or sets a value indicating whether PerformanceSummary will Show the time that’s spent in tasks, targets, and projects. + /// + public bool PerformanceSummaryEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether Summary will Show the error and warning summary at the end. + /// + public bool SummaryDisabled { get; set; } + + /// + /// Gets or sets show ErrorsOnly, WarningsOnly, or All. + /// + public MSBuildFileLoggerOutput MSBuildFileLoggerOutput { get; set; } + + /// + /// Gets or sets a value indicating whether NoItemAndPropertyList will be set to Don't show the list of items and properties that would appear at the start of each project build if the verbosity level is set to diagnostic. + /// + public bool HideVerboseItemAndPropertyList { get; set; } + + /// + /// Gets or sets a value indicating whether ShowCommandLine. Show TaskCommandLineEvent messages. + /// + public bool ShowCommandLine { get; set; } + + /// + /// Gets or sets a value indicating whether ShowTimestamp. Show the timestamp as a prefix to any message. + /// + public bool ShowTimestamp { get; set; } + + /// + /// Gets or sets a value indicating whether ShowEventId. Show the event ID for each started event, finished event, and message. + /// + public bool ShowEventId { get; set; } + + /// + /// Gets or sets Verbosity. Override the /verbosity setting for this logger. + /// Specify the following verbosity levels: q[uiet], m[inimal], n[ormal], v[erbose] (detailed), and diag[nostic]. + /// + public Verbosity? Verbosity { get; set; } + + /// + /// Gets or sets LogFile. The path to the log file into which the build log is written. + /// An empty string will use msbuild.log. + /// + public FilePath LogFile { get; set; } + + /// + /// Gets or sets a value indicating whether the build log is appended to the log file or overwrites it. When true, the build log is appended to the log file. + /// + public bool AppendToLogFile { get; set; } + + /// + /// Gets or sets Specifies the encoding for the file (for example, UTF-8, Unicode, or ASCII). + /// + public string Encoding { get; set; } + + /// + /// Process the file logger config and return parameters as a string. + /// + /// The environment. + /// The parameters separated by semi-colons. + public string GetParameters(ICakeEnvironment environment) + { + var parameters = new List(); + parameters.Add(LogFile != null ? $"logfile={LogFile.MakeAbsolute(environment).FullPath.Quote()}" : null); + parameters.Add(!string.IsNullOrWhiteSpace(Encoding) ? $"Encoding={Encoding}" : null); + parameters.Add(AppendToLogFile ? "Append" : null); + parameters.Add(PerformanceSummaryEnabled ? "PerformanceSummary" : null); + parameters.Add(SummaryDisabled ? "NoSummary" : null); + parameters.Add(MSBuildFileLoggerOutput == MSBuildFileLoggerOutput.ErrorsOnly ? "ErrorsOnly" : null); + parameters.Add(MSBuildFileLoggerOutput == MSBuildFileLoggerOutput.WarningsOnly ? "WarningsOnly" : null); + parameters.Add(HideVerboseItemAndPropertyList ? "NoItemAndPropertyList" : null); + parameters.Add(ShowCommandLine ? "ShowCommandLine" : null); + parameters.Add(ShowTimestamp ? "ShowTimestamp" : null); + parameters.Add(ShowEventId ? "ShowEventId" : null); + parameters.Add(Verbosity != null ? $"Verbosity={Verbosity.Value.GetMSBuildVerbosityName()}" : null); + + return string.Join(';', parameters.Where(p => p != null)); + } + } +} diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildFileLoggerOutput.cs b/src/Cake.Common/Tools/MSBuild/MSBuildFileLoggerOutput.cs new file mode 100644 index 0000000000..07788b8038 --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildFileLoggerOutput.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// The type of file logger output to generate. + /// + public enum MSBuildFileLoggerOutput + { + /// + /// Show errors and warnings. + /// + All = 0, + + /// + /// Show errors only. + /// + ErrorsOnly = 1, + + /// + /// Show warnings only. + /// + WarningsOnly = 2, + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildLogger.cs b/src/Cake.Common/Tools/MSBuild/MSBuildLogger.cs new file mode 100644 index 0000000000..659b4ec96f --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildLogger.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// Contains settings for specifying a MSBuild logger. + /// + public sealed class MSBuildLogger + { + /// + /// Gets or sets the assembly containing the logger. Should match the format {AssemblyName[,StrongName] | AssemblyFile}. + /// + public string Assembly { get; set; } + + /// + /// Gets or sets the class implementing the logger. Should match the format [PartialOrFullNamespace.]LoggerClassName + /// If the assembly contains only one logger, class does not need to be specified. + /// + public string Class { get; set; } + + /// + /// Gets or sets the parameters to be passed to the logger. + /// + public string Parameters { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildPlatform.cs b/src/Cake.Common/Tools/MSBuild/MSBuildPlatform.cs index 748398ef22..bf58d94dcc 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildPlatform.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildPlatform.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.MSBuild { /// @@ -25,4 +26,4 @@ public enum MSBuildPlatform // ReSharper disable once InconsistentNaming x64 = 2 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs b/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs new file mode 100644 index 0000000000..8bf965153d --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildPropertyExtensions.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text; + +namespace Cake.Common.Tools.MSBuild +{ + internal static class MSBuildPropertyExtensions + { + private static readonly IReadOnlyDictionary _escapeLookup = new Dictionary + { + { ';', "%3B" }, + { ',', "%2C" }, + { ' ', "%20" }, + { '\r', "%0D" }, + { '\n', "%0A" } + }; + + private static readonly HashSet _propertiesNotEscapeSemicolons = new HashSet + { + "DefineConstants", + "ExcludeFilesFromDeployment" + }; + + internal static string BuildMSBuildPropertyParameterString(this KeyValuePair property) + where TValue : ICollection + { + var propertyParameterString = new StringBuilder(); + var last = property.Value.Count - 1; + var index = 0; + + var escapeSemicolons = property.Key.AllowEscapeSemicolon(); + foreach (var parameter in property.Value) + { + if (string.IsNullOrEmpty(parameter)) + { + index++; + continue; + } + + propertyParameterString.Append(parameter.EscapeMSBuildPropertySpecialCharacters(escapeSemicolons)); + propertyParameterString.Append(index != last ? ";" : null); + + index++; + } + + return propertyParameterString.ToString(); + } + + private static string EscapeMSBuildPropertySpecialCharacters(this string value, bool escapeSemicolons) + { + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + + var escapedBuilder = new StringBuilder(); + foreach (var c in value) + { + if ((!escapeSemicolons && c.Equals(';')) || !_escapeLookup.TryGetValue(c, out var newChar)) + { + escapedBuilder.Append(c); + } + else + { + escapedBuilder.Append(newChar); + } + } + + return escapedBuilder.ToString(); + } + + private static bool AllowEscapeSemicolon(this string propertyName) + { + return !_propertiesNotEscapeSemicolons.Contains(propertyName); + } + } +} diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildResolver.cs b/src/Cake.Common/Tools/MSBuild/MSBuildResolver.cs index 6cad25d47a..59904d1006 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildResolver.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildResolver.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -9,11 +10,52 @@ namespace Cake.Common.Tools.MSBuild { internal static class MSBuildResolver { - public static FilePath GetMSBuildPath(IFileSystem fileSystem, ICakeEnvironment environment, MSBuildToolVersion version, MSBuildPlatform buildPlatform) + public static FilePath GetMSBuildPath(IFileSystem fileSystem, ICakeEnvironment environment, MSBuildPlatform buildPlatform, MSBuildSettings settings) { - var binPath = version == MSBuildToolVersion.Default - ? GetHighestAvailableMSBuildVersion(fileSystem, environment, buildPlatform) - : GetMSBuildPath(environment, (MSBuildVersion)version, buildPlatform); + if (environment.Platform.Family == PlatformFamily.OSX) + { + var macMSBuildPath = new FilePath("/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild"); + + if (fileSystem.Exist(macMSBuildPath)) + { + return macMSBuildPath; + } + + var brewMSBuildPath = new FilePath("/usr/local/bin/msbuild"); + + if (fileSystem.Exist(brewMSBuildPath)) + { + return brewMSBuildPath; + } + + throw new CakeException("Could not resolve MSBuild."); + } + else if (environment.Platform.Family == PlatformFamily.Linux) + { + var linuxMSBuildPath = new FilePath("/usr/bin/msbuild"); + + if (fileSystem.Exist(linuxMSBuildPath)) + { + return linuxMSBuildPath; + } + + throw new CakeException("Could not resolve MSBuild."); + } + else if (environment.Platform.Family == PlatformFamily.FreeBSD) + { + var freebsdMSBuildPath = new FilePath("/usr/local/bin/msbuild"); + + if (fileSystem.Exist(freebsdMSBuildPath)) + { + return freebsdMSBuildPath; + } + + throw new CakeException("Could not resolve MSBuild."); + } + + var binPath = settings.ToolVersion == MSBuildToolVersion.Default + ? GetHighestAvailableMSBuildVersion(fileSystem, environment, buildPlatform, settings.AllowPreviewVersion) + : GetMSBuildPath(fileSystem, environment, (MSBuildVersion)settings.ToolVersion, buildPlatform, settings.CustomVersion, settings.AllowPreviewVersion); if (binPath == null) { @@ -24,20 +66,24 @@ public static FilePath GetMSBuildPath(IFileSystem fileSystem, ICakeEnvironment e return binPath.CombineWithFilePath("MSBuild.exe"); } - private static DirectoryPath GetHighestAvailableMSBuildVersion(IFileSystem fileSystem, ICakeEnvironment environment, MSBuildPlatform buildPlatform) + private static DirectoryPath GetHighestAvailableMSBuildVersion(IFileSystem fileSystem, ICakeEnvironment environment, MSBuildPlatform buildPlatform, bool allowPreview) { var versions = new[] { + MSBuildVersion.MSBuild18, + MSBuildVersion.MSBuild17, + MSBuildVersion.MSBuild16, + MSBuildVersion.MSBuild15, MSBuildVersion.MSBuild14, MSBuildVersion.MSBuild12, MSBuildVersion.MSBuild4, MSBuildVersion.MSBuild35, - MSBuildVersion.MSBuild20 + MSBuildVersion.MSBuild20, }; foreach (var version in versions) { - var path = GetMSBuildPath(environment, version, buildPlatform); + var path = GetMSBuildPath(fileSystem, environment, version, buildPlatform, null, allowPreview); if (fileSystem.Exist(path)) { return path; @@ -46,20 +92,42 @@ private static DirectoryPath GetHighestAvailableMSBuildVersion(IFileSystem fileS return null; } - private static DirectoryPath GetMSBuildPath(ICakeEnvironment environment, MSBuildVersion version, MSBuildPlatform buildPlatform) + private static DirectoryPath GetMSBuildPath( + IFileSystem fileSystem, + ICakeEnvironment environment, + MSBuildVersion version, + MSBuildPlatform buildPlatform, + string customVersion, + bool allowPreview) { switch (version) { + case MSBuildVersion.MSBuild18: + return GetVisualStudio2026Path(fileSystem, environment, buildPlatform, allowPreview); + case MSBuildVersion.MSBuild17: + return GetVisualStudio2022Path(fileSystem, environment, buildPlatform, allowPreview); + case MSBuildVersion.MSBuild16: + return GetVisualStudio2019Path(fileSystem, environment, buildPlatform, allowPreview); + case MSBuildVersion.MSBuild15: + return GetVisualStudio2017Path(fileSystem, environment, buildPlatform, allowPreview); case MSBuildVersion.MSBuild14: return GetVisualStudioPath(environment, buildPlatform, "14.0"); case MSBuildVersion.MSBuild12: return GetVisualStudioPath(environment, buildPlatform, "12.0"); + case MSBuildVersion.MSBuildCustomVS: + return GetVisualStudioPath(environment, buildPlatform, customVersion); case MSBuildVersion.MSBuild4: return GetFrameworkPath(environment, buildPlatform, "v4.0.30319"); case MSBuildVersion.MSBuild35: return GetFrameworkPath(environment, buildPlatform, "v3.5"); case MSBuildVersion.MSBuild20: return GetFrameworkPath(environment, buildPlatform, "v2.0.50727"); + case MSBuildVersion.MSBuildNETCustom: + if (!customVersion.Contains("v")) + { + customVersion = "v" + customVersion; + } + return GetFrameworkPath(environment, buildPlatform, customVersion); default: return null; } @@ -72,7 +140,7 @@ private static DirectoryPath GetVisualStudioPath(ICakeEnvironment environment, M var binPath = programFilesPath.Combine(string.Concat("MSBuild/", version, "/Bin")); if (buildPlatform == MSBuildPlatform.Automatic) { - if (environment.Is64BitOperativeSystem()) + if (environment.Platform.Is64Bit) { binPath = binPath.Combine("amd64"); } @@ -84,6 +152,124 @@ private static DirectoryPath GetVisualStudioPath(ICakeEnvironment environment, M return binPath; } + private static DirectoryPath GetVisualStudio2017Path(IFileSystem fileSystem, ICakeEnvironment environment, + MSBuildPlatform buildPlatform, bool allowPreviewVersion) + { + foreach (var edition in allowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + // Get the bin path. + var binPath = VisualStudio.GetYearAndEditionRootPath(environment, "2017", edition).Combine("MSBuild/15.0/Bin"); + if (fileSystem.Exist(binPath)) + { + if (buildPlatform == MSBuildPlatform.Automatic) + { + if (environment.Platform.Is64Bit) + { + binPath = binPath.Combine("amd64"); + } + } + if (buildPlatform == MSBuildPlatform.x64) + { + binPath = binPath.Combine("amd64"); + } + return binPath; + } + } + return VisualStudio.GetYearAndEditionRootPath(environment, "2017", "Professional").Combine("MSBuild/15.0/Bin"); + } + + private static DirectoryPath GetVisualStudio2019Path(IFileSystem fileSystem, ICakeEnvironment environment, + MSBuildPlatform buildPlatform, bool allowPreviewVersion) + { + foreach (var edition in allowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + // Get the bin path. + var binPath = VisualStudio.GetYearAndEditionRootPath(environment, "2019", edition).Combine("MSBuild/Current/Bin"); + if (fileSystem.Exist(binPath)) + { + if (buildPlatform == MSBuildPlatform.Automatic) + { + if (environment.Platform.Is64Bit) + { + binPath = binPath.Combine("amd64"); + } + } + if (buildPlatform == MSBuildPlatform.x64) + { + binPath = binPath.Combine("amd64"); + } + return binPath; + } + } + return VisualStudio.GetYearAndEditionRootPath(environment, "2019", "Professional").Combine("MSBuild/Current/Bin"); + } + + private static DirectoryPath GetVisualStudio2022Path(IFileSystem fileSystem, ICakeEnvironment environment, + MSBuildPlatform buildPlatform, bool allowPreviewVersion) + { + foreach (var edition in allowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + // Get the bin path. + var binPath = VisualStudio.GetYearAndEditionRootPath(environment, "2022", edition).Combine("MSBuild/Current/Bin"); + if (fileSystem.Exist(binPath)) + { + if (buildPlatform == MSBuildPlatform.Automatic) + { + if (environment.Platform.Is64Bit) + { + binPath = binPath.Combine("amd64"); + } + } + + if (buildPlatform == MSBuildPlatform.x64) + { + binPath = binPath.Combine("amd64"); + } + + return binPath; + } + } + + return VisualStudio.GetYearAndEditionRootPath(environment, "2022", "Professional").Combine("MSBuild/Current/Bin"); + } + + private static DirectoryPath GetVisualStudio2026Path(IFileSystem fileSystem, ICakeEnvironment environment, + MSBuildPlatform buildPlatform, bool allowPreviewVersion) + { + foreach (var edition in allowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + // Get the bin path. + var binPath = VisualStudio.GetYearAndEditionRootPath(environment, "18", edition).Combine("MSBuild/Current/Bin"); + if (fileSystem.Exist(binPath)) + { + if (buildPlatform == MSBuildPlatform.Automatic) + { + if (environment.Platform.Is64Bit) + { + binPath = binPath.Combine("amd64"); + } + } + + if (buildPlatform == MSBuildPlatform.x64) + { + binPath = binPath.Combine("amd64"); + } + + return binPath; + } + } + + return VisualStudio.GetYearAndEditionRootPath(environment, "18", "Professional").Combine("MSBuild/Current/Bin"); + } + private static DirectoryPath GetFrameworkPath(ICakeEnvironment environment, MSBuildPlatform buildPlatform, string version) { // Get the Microsoft .NET folder. @@ -93,7 +279,7 @@ private static DirectoryPath GetFrameworkPath(ICakeEnvironment environment, MSBu if (buildPlatform == MSBuildPlatform.Automatic) { // Get the framework folder. - var is64Bit = environment.Is64BitOperativeSystem(); + var is64Bit = environment.Platform.Is64Bit; var frameWorkFolder = is64Bit ? netFolder.Combine("Framework64") : netFolder.Combine("Framework"); return frameWorkFolder.Combine(version); } diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs index 0091bd93a4..4dc988d13b 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildRunner.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text; using Cake.Core; -using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Tooling; @@ -40,22 +41,48 @@ public MSBuildRunner( /// /// Runs MSBuild with the specified settings. /// - /// The solution to build. + /// The solution or MsBuild project file to build. /// The settings. - public void Run(FilePath solution, MSBuildSettings settings) + /// The action to invoke with the standard output. + public void Run(FilePath solution, MSBuildSettings settings, Action> standardOutputAction) { - Run(settings, GetArguments(solution, settings)); + Run( + settings, + GetArguments(solution, settings), + standardOutputAction == null ? null : new ProcessSettings { RedirectStandardOutput = true }, + standardOutputAction == null ? null : new Action(process => standardOutputAction(process.GetStandardOutput()))); } - private ProcessArgumentBuilder GetArguments(FilePath solution, MSBuildSettings settings) + private ProcessArgumentBuilder GetArguments(FilePath projectFile, MSBuildSettings settings) { var builder = new ProcessArgumentBuilder(); - // Set the maximum number of processors. - builder.Append(settings.MaxCpuCount > 0 ? string.Concat("/m:", settings.MaxCpuCount) : "/m"); + // Set the maximum number of processors? + if (settings.MaxCpuCount != null) + { + builder.Append(settings.MaxCpuCount > 0 ? string.Concat("/m:", settings.MaxCpuCount) : "/m"); + } + + // Set the detailed summary flag. + if (settings.DetailedSummary.GetValueOrDefault()) + { + builder.Append("/ds"); + } + + // Set the no console logger flag. + if (settings.NoConsoleLogger.GetValueOrDefault()) + { + builder.Append("/noconlog"); + } + + // Set the no logo flag. + if (settings.NoLogo.GetValueOrDefault()) + { + builder.Append("/nologo"); + } // Set the verbosity. - builder.Append(string.Format(CultureInfo.InvariantCulture, "/v:{0}", GetVerbosityName(settings.Verbosity))); + builder.Append(string.Format(CultureInfo.InvariantCulture, "/v:{0}", settings.Verbosity.GetMSBuildVerbosityName())); if (settings.NodeReuse != null) { @@ -73,10 +100,29 @@ private ProcessArgumentBuilder GetArguments(FilePath solution, MSBuildSettings s if (settings.PlatformTarget.HasValue) { var platform = settings.PlatformTarget.Value; - bool isSolution = string.Equals(solution.GetExtension(), ".sln", StringComparison.OrdinalIgnoreCase); + bool isSolution = string.Equals(projectFile.GetExtension(), ".sln", StringComparison.OrdinalIgnoreCase); builder.Append(string.Concat("/p:Platform=", GetPlatformName(platform, isSolution))); } + // Set include symbols? + if (settings.IncludeSymbols.HasValue) + { + builder.Append(string.Concat("/p:IncludeSymbols=", settings.IncludeSymbols.Value ? "true" : "false")); + } + + // Set symbol package format? + if (!string.IsNullOrWhiteSpace(settings.SymbolPackageFormat)) + { + builder.Append(string.Concat("/p:SymbolPackageFormat=", settings.SymbolPackageFormat)); + } + + // Set Continuous Integration Build? + if (settings.ContinuousIntegrationBuild.HasValue) + { + var continuousIntegrationBuild = settings.ContinuousIntegrationBuild.Value ? "true" : "false"; + builder.Append(string.Concat("/p:ContinuousIntegrationBuild=", continuousIntegrationBuild)); + } + // Got any properties? if (settings.Properties.Count > 0) { @@ -89,21 +135,175 @@ private ProcessArgumentBuilder GetArguments(FilePath solution, MSBuildSettings s // Got any targets? if (settings.Targets.Count > 0) { - var targets = string.Join(";", settings.Targets); + var targets = string.Join(';', settings.Targets); builder.Append(string.Concat("/target:", targets)); } else { - // Use default target. - builder.Append("/target:Build"); + // Should use implicit target? + if (!settings.NoImplicitTarget.GetValueOrDefault()) + { + // Use default target. + builder.Append("/target:Build"); + } + } + + // Got any properties to retrieve? + if (settings.GetProperties.Count > 0) + { + foreach (var property in GetGetPropertiesArguments(settings.GetProperties)) + { + builder.Append(property); + } + } + + // Got any items to retrieve? + if (settings.GetItems.Count > 0) + { + foreach (var property in GetGetItemsArguments(settings.GetItems)) + { + builder.Append(property); + } + } + + // Got any target results to retrieve? + if (settings.GetTargetResults.Count > 0) + { + foreach (var property in GetGetTargetResultsArguments(settings.GetTargetResults)) + { + builder.Append(property); + } + } + + if (settings.Loggers.Count > 0) + { + foreach (var logger in settings.Loggers) + { + var argument = GetLoggerArgument(logger); + builder.Append(argument); + } + } + + // Got any file loggers? + if (settings.FileLoggers.Count > 0) + { + var arguments = settings.FileLoggers.Select((logger, index) => GetLoggerArgument(index, logger)); + + foreach (var argument in arguments) + { + builder.Append(argument); + } + } + + // Use binary logging? + if (settings.BinaryLogger != null && settings.BinaryLogger.Enabled) + { + string binaryOptions = null; + if (!string.IsNullOrEmpty(settings.BinaryLogger.FileName)) + { + binaryOptions = settings.BinaryLogger.FileName.Quote(); + } + + if (settings.BinaryLogger.Imports != MSBuildBinaryLogImports.Unspecified) + { + if (!string.IsNullOrEmpty(binaryOptions)) + { + binaryOptions += ";"; + } + + binaryOptions = binaryOptions + "ProjectImports=" + settings.BinaryLogger.Imports; + } + + if (string.IsNullOrEmpty(binaryOptions)) + { + builder.Append("/bl"); + } + else + { + builder.Append("/bl:" + binaryOptions); + } + } + + // Treat errors as warnings? + if (settings.WarningsAsErrorCodes.Any()) + { + var codes = string.Join(';', settings.WarningsAsErrorCodes); + builder.Append($"/warnaserror:{codes.Quote()}"); + } + else if (settings.WarningsAsError) + { + builder.Append("/warnaserror"); + } + + // Any warnings to NOT treat as errors? + if (settings.WarningsAsMessageCodes.Any()) + { + var codes = string.Join(';', settings.WarningsAsMessageCodes); + builder.Append($"/warnasmessage:{codes.Quote()}"); + } + + // Invoke restore target before any other target? + if (settings.Restore) + { + builder.Append("/restore"); + } + + // Set restore locked mode? + if (settings.RestoreLockedMode.HasValue) + { + builder.Append(string.Concat("/p:RestoreLockedMode=", settings.RestoreLockedMode.Value ? "true" : "false")); + } + + // Got any console logger parameters? + if (settings.ConsoleLoggerParameters.Count > 0) + { + var argument = "/clp:" + string.Join(';', settings.ConsoleLoggerParameters); + builder.Append(argument); } // Add the solution as the last parameter. - builder.AppendQuoted(solution.MakeAbsolute(_environment).FullPath); + builder.AppendQuoted(projectFile.MakeAbsolute(_environment).FullPath); return builder; } + private string GetLoggerArgument(int index, MSBuildFileLogger logger) + { + if (index >= 10) + { + throw new InvalidOperationException("Too Many FileLoggers"); + } + + var counter = index == 0 ? string.Empty : index.ToString(); + var argument = $"/fl{counter}"; + + var parameters = logger.GetParameters(_environment); + if (!string.IsNullOrWhiteSpace(parameters)) + { + argument = $"{argument} /flp{counter}:{parameters}"; + } + return argument; + } + + private static string GetLoggerArgument(MSBuildLogger logger) + { + var argumentBuilder = new StringBuilder("/logger:"); + if (!string.IsNullOrWhiteSpace(logger.Class)) + { + argumentBuilder.Append(logger.Class); + argumentBuilder.Append(','); + } + + argumentBuilder.Append(logger.Assembly.Quote()); + + if (!string.IsNullOrWhiteSpace(logger.Parameters)) + { + argumentBuilder.Append(';'); + argumentBuilder.Append(logger.Parameters); + } + return argumentBuilder.ToString(); + } + private static string GetPlatformName(PlatformTarget platform, bool isSolution) { switch (platform) @@ -117,39 +317,43 @@ private static string GetPlatformName(PlatformTarget platform, bool isSolution) return "x64"; case PlatformTarget.ARM: return "arm"; + case PlatformTarget.ARM64: + return "arm64"; case PlatformTarget.Win32: return "Win32"; default: - throw new ArgumentOutOfRangeException("platform", platform, "Invalid platform"); + throw new ArgumentOutOfRangeException(nameof(platform), platform, "Invalid platform"); + } + } + private static IEnumerable GetPropertyArguments(IDictionary> properties) + { + foreach (var property in properties) + { + yield return string.Concat("/p:", property.Key, "=", property.BuildMSBuildPropertyParameterString()); } } - private static string GetVerbosityName(Verbosity verbosity) + private static IEnumerable GetGetPropertiesArguments(HashSet getProperties) { - switch (verbosity) - { - case Verbosity.Quiet: - return "quiet"; - case Verbosity.Minimal: - return "minimal"; - case Verbosity.Normal: - return "normal"; - case Verbosity.Verbose: - return "detailed"; - case Verbosity.Diagnostic: - return "diagnostic"; - } - throw new CakeException("Encountered unknown MSBuild build log verbosity."); + foreach (var getProperty in getProperties) + { + yield return string.Concat("/getProperty:", getProperty); + } } - private static IEnumerable GetPropertyArguments(IDictionary> properties) + private static IEnumerable GetGetItemsArguments(HashSet getItems) { - foreach (var propertyKey in properties.Keys) + foreach (var getItem in getItems) { - foreach (var propertyValue in properties[propertyKey]) - { - yield return string.Concat("/p:", propertyKey, "=", propertyValue); - } + yield return string.Concat("/getItem:", getItem); + } + } + + private static IEnumerable GetGetTargetResultsArguments(HashSet getTargetResults) + { + foreach (var getTargetResult in getTargetResults) + { + yield return string.Concat("/getTargetResult:", getTargetResult); } } @@ -172,18 +376,15 @@ protected override IEnumerable GetToolExecutableNames() } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. protected override IEnumerable GetAlternativeToolPaths(MSBuildSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - MSBuildPlatform buildPlatform = settings.MSBuildPlatform; + var buildPlatform = settings.MSBuildPlatform; // If we haven't explicitly set an MSBuild target then use the Platform Target if (buildPlatform == MSBuildPlatform.Automatic) @@ -199,7 +400,7 @@ protected override IEnumerable GetAlternativeToolPaths(MSBuildSettings } } - var path = MSBuildResolver.GetMSBuildPath(_fileSystem, _environment, settings.ToolVersion, buildPlatform); + var path = MSBuildResolver.GetMSBuildPath(_fileSystem, _environment, buildPlatform, settings); if (path != null) { return new[] { path }; @@ -208,4 +409,4 @@ protected override IEnumerable GetAlternativeToolPaths(MSBuildSettings return Enumerable.Empty(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs index 23ee65d9d7..ee84e872d0 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildSettings.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Linq; using Cake.Core.Diagnostics; using Cake.Core.Tooling; @@ -15,24 +17,48 @@ public sealed class MSBuildSettings : ToolSettings { private readonly HashSet _targets; private readonly Dictionary> _properties; + private readonly HashSet _getProperties; + private readonly HashSet _getItems; + private readonly HashSet _getTargetResults; + private readonly List _loggers; + private readonly List _fileLoggers; + private readonly HashSet _warningsAsErrorCodes; + private readonly HashSet _warningsAsMessageCodes; + private readonly HashSet _consoleLoggerParameters; + internal string CustomVersion { get; set; } /// /// Gets the targets. /// /// The targets. - public ISet Targets - { - get { return _targets; } - } + public ISet Targets => _targets; /// /// Gets the properties. /// /// The properties. - public IDictionary> Properties - { - get { return _properties; } - } + public IDictionary> Properties => _properties; + + /// + /// Gets the properties to retrieve. + /// + /// The properties to retrieve. + /// For more information, refer to Evaluate items and properties and display results of targets. + public HashSet GetProperties => _getProperties; + + /// + /// Gets the items to retrieve. + /// + /// The items to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetItems => _getItems; + + /// + /// Gets the target results to retrieve. + /// + /// The target results to retrieve. + /// For more information, refer to Evaluate items and items and display results of targets. + public HashSet GetTargetResults => _getTargetResults; /// /// Gets or sets the platform target. @@ -46,6 +72,32 @@ public IDictionary> Properties /// The MSBuild platform. public MSBuildPlatform MSBuildPlatform { get; set; } + /// + /// Gets or sets the MSBuild target. + /// + /// The MSBuild target. + public string Target + { + get => string.Join(';', Targets); + set + { + Targets.Clear(); + + if (string.IsNullOrWhiteSpace(value)) + { + return; + } + + foreach (var target in value + .Split(';') + .Select(target => target.Trim()) + .Where(target => !string.IsNullOrEmpty(target))) + { + Targets.Add(target); + } + } + } + /// /// Gets or sets the tool version. /// @@ -60,9 +112,12 @@ public IDictionary> Properties /// /// Gets or sets the maximum CPU count. + /// If this value is zero, MSBuild will use as many processes as + /// there are available CPUs to build the project. If not set + /// MSBuild compile projects in this solution one at a time. /// /// The maximum CPU count. - public int MaxCpuCount { get; set; } + public int? MaxCpuCount { get; set; } /// /// Gets or sets whether or not node reuse is used. @@ -71,6 +126,113 @@ public IDictionary> Properties /// public bool? NodeReuse { get; set; } + /// + /// Gets or sets whether or not detailed summary is created. + /// Shows detailed information at the end of the build + /// about the configurations built and how they were + /// scheduled to nodes. + /// + public bool? DetailedSummary { get; set; } + + /// + /// Gets or sets whether or not information is logged to the console. + /// Disable the default console logger and do not log events + /// to the console. + /// + public bool? NoConsoleLogger { get; set; } + + /// + /// Gets or sets a value indicating whether to show copyright information at the start of the program. + /// + public bool? NoLogo { get; set; } + + /// + /// Gets or sets the default value of all the version numbers embedded in the build output. + /// + public string Version + { + get => GetPropertyValueOrDefault("Version"); + set => this.WithProperty("Version", value); + } + + /// + /// Gets or sets the base version number embedded in the build output. + /// + public string VersionPrefix + { + get => GetPropertyValueOrDefault("VersionPrefix"); + set => this.WithProperty("VersionPrefix", value); + } + + /// + /// Gets or sets the pre-release label of the version number embedded in the build output. + /// + public string VersionSuffix + { + get => GetPropertyValueOrDefault("VersionSuffix"); + set => this.WithProperty("VersionSuffix", value); + } + + /// + /// Gets or sets the file version number embedded in the build output. + /// + public string FileVersion + { + get => GetPropertyValueOrDefault("FileVersion"); + set => this.WithProperty("FileVersion", value); + } + + /// + /// Gets or sets the assembly version number embedded in the build output. + /// + public string AssemblyVersion + { + get => GetPropertyValueOrDefault("AssemblyVersion"); + set => this.WithProperty("AssemblyVersion", value); + } + + /// + /// Gets or sets the assembly informational version number embedded in the build output. + /// + public string InformationalVersion + { + get => GetPropertyValueOrDefault("InformationalVersion"); + set => this.WithProperty("InformationalVersion", value); + } + + /// + /// Gets or sets the version number of the NuGet package generated. + /// + public string PackageVersion + { + get => GetPropertyValueOrDefault("PackageVersion"); + set => this.WithProperty("PackageVersion", value); + } + + /// + /// Gets or sets the release notes of the NuGet package generated. + /// + public string PackageReleaseNotes + { + get => GetPropertyValueOrDefault("PackageReleaseNotes"); + set => this.WithProperty("PackageReleaseNotes", value); + } + + /// + /// Gets or sets a value indicating whether to normalize stored file paths used when producing deterministic builds. + /// + /// + /// For more information see https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/#deterministic-builds. + /// + public bool? ContinuousIntegrationBuild { get; set; } + + /// + /// Gets or sets a value indicating whether implicit target should be passed to MSBuild. + /// If set to true, no targets will be specified. + /// If set to false, and no targets specified, Build target will be passed by default. + /// + public bool? NoImplicitTarget { get; set; } + /// /// Gets or sets the amount of information to display in the build log. /// Each logger displays events based on the verbosity level that you set for that logger. @@ -78,6 +240,78 @@ public IDictionary> Properties /// The build log verbosity. public Verbosity Verbosity { get; set; } + /// + /// Gets or sets a value indicating whether a symbol package should be created. + /// + public bool? IncludeSymbols { get; set; } + + /// + /// Gets or sets the symbol package format. + /// + /// The symbol package format. + public string SymbolPackageFormat { get; set; } + + /// + /// Gets the loggers. + /// + public ICollection Loggers => _loggers; + + /// + /// Gets the file loggers. + /// + public ICollection FileLoggers => _fileLoggers; + + /// + /// Gets or sets the binary logging options. + /// + public MSBuildBinaryLogSettings BinaryLogger { get; set; } + + /// + /// Gets or sets a value indicating whether warnings should be treated as errors. + /// Treats all warnings as errors unless has specific codes specified. + /// + public bool WarningsAsError { get; set; } + + /// + /// Gets the warning codes to treat as errors. + /// If any specified will implicitly be treated as true. + /// + public ISet WarningsAsErrorCodes => _warningsAsErrorCodes; + + /// + /// Gets the warning codes to NOT treat as errors. + /// + /// Only available MSBuild version 15 (VS2017) and newer. + public ISet WarningsAsMessageCodes => _warningsAsMessageCodes; + + /// + /// Gets or sets a value indicating whether the Restore target should be run before any other targets. + /// This setting will pass the /restore option down to MSBuild. + /// Use this setting when working with the new csproj format. + /// + public bool Restore { get; set; } + + /// + /// Gets or sets a value indicating whether or not to lock the package dependency graph while + /// restoring, using the packages.lock.json file. + /// This setting is available with at least Visual Studio 2017 version 15.9 and above or NET SDK version 2.1.500 and above. + /// + public bool? RestoreLockedMode { get; set; } + + /// + /// Gets the console logger parameters. + /// + public ISet ConsoleLoggerParameters => _consoleLoggerParameters; + + /// + /// Gets or sets a value indicating whether tools from a preview edition of Visual Studio should be used. + /// + /// If set to true, MSBuildTools from a Preview edition + /// (e.g. Visual Studio 2022 Preview) will be considered to be used. + /// + /// + public bool AllowPreviewVersion { get; set; } = false; + /// /// Initializes a new instance of the class. /// @@ -85,11 +319,30 @@ public MSBuildSettings() { _targets = new HashSet(StringComparer.OrdinalIgnoreCase); _properties = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _getProperties = new HashSet(StringComparer.OrdinalIgnoreCase); + _getItems = new HashSet(StringComparer.OrdinalIgnoreCase); + _getTargetResults = new HashSet(StringComparer.OrdinalIgnoreCase); + _loggers = new List(); + _fileLoggers = new List(); + _warningsAsErrorCodes = new HashSet(StringComparer.OrdinalIgnoreCase); + _warningsAsMessageCodes = new HashSet(StringComparer.OrdinalIgnoreCase); + _consoleLoggerParameters = new HashSet(StringComparer.OrdinalIgnoreCase); ToolVersion = MSBuildToolVersion.Default; Configuration = string.Empty; Verbosity = Verbosity.Normal; MSBuildPlatform = MSBuildPlatform.Automatic; } + + private string GetPropertyValueOrDefault(string propertyName, string @default = null) + { + if (!Properties.TryGetValue(propertyName, out var propertyValues)) + { + return @default; + } + + var propertyValue = string.Join(';', propertyValues); + return propertyValue; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs b/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs index f1cd5ee31f..1dc9ca046b 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Cake.Core.Diagnostics; namespace Cake.Common.Tools.MSBuild @@ -21,10 +23,7 @@ public static class MSBuildSettingsExtensions /// The same instance so that multiple calls can be chained. public static MSBuildSettings WithTarget(this MSBuildSettings settings, string target) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Targets.Add(target); return settings; } @@ -37,14 +36,88 @@ public static MSBuildSettings WithTarget(this MSBuildSettings settings, string t /// The same instance so that multiple calls can be chained. public static MSBuildSettings UseToolVersion(this MSBuildSettings settings, MSBuildToolVersion version) { - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + settings.ToolVersion = version; + return settings; + } + + /// + /// Sets the tool version. + /// + /// The settings. + /// The string version. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings UseToolVersion(this MSBuildSettings settings, string version) + { + ArgumentNullException.ThrowIfNull(settings); + + // check to see if the version is null/empty or is not a num + if (string.IsNullOrEmpty(version)) { - throw new ArgumentNullException("settings"); + throw new ArgumentException(nameof(version)); } - settings.ToolVersion = version; + var sanitizeVersion = version.Replace(" ", string.Empty); + var mSBuildToolVersion = GetMSBuildToolVersionFromString(sanitizeVersion.ToUpper()); + if (mSBuildToolVersion == MSBuildToolVersion.NETCustom || mSBuildToolVersion == MSBuildToolVersion.VSCustom) + { + settings.CustomVersion = version; + } + settings.ToolVersion = mSBuildToolVersion; return settings; } + /// + /// Helper that gets the MSBuildToolVersion from the version string. + /// + /// The string version. + /// The matched MSBuildToolVersion enum . + private static MSBuildToolVersion GetMSBuildToolVersionFromString(string version) + { + switch (version) + { + case "2": + case "2.0": + case "NET20": return MSBuildToolVersion.NET20; + case "3": + case "3.0": + case "NET30": return MSBuildToolVersion.NET30; + case "3.5": + case "NET35": return MSBuildToolVersion.NET35; + case "4": + case "4.0": + case "NET40": return MSBuildToolVersion.NET40; + case "4.5": + case "NET45": return MSBuildToolVersion.NET45; + case "4.5.1": + case "NET451": return MSBuildToolVersion.NET451; + case "4.6": + case "NET46": return MSBuildToolVersion.NET46; + case "4.5.2": + case "NET452": return MSBuildToolVersion.NET452; + case "2005": + case "VS2005": return MSBuildToolVersion.VS2005; + case "2008": + case "VS2008": return MSBuildToolVersion.VS2008; + case "2010": + case "VS2010": return MSBuildToolVersion.VS2010; + case "2011": + case "VS2011": return MSBuildToolVersion.VS2011; + case "2012": + case "VS2012": return MSBuildToolVersion.VS2011; + case "2013": + case "VS2013": return MSBuildToolVersion.VS2013; + case "2015": + case "VS2015": return MSBuildToolVersion.VS2015; + case "2017": + case "VS2017": return MSBuildToolVersion.VS2017; + case "2019": + case "VS2019": return MSBuildToolVersion.VS2019; + case string vs when vs.Contains("VS") || Regex.Match(vs, @"\d{4}").Success: return MSBuildToolVersion.VSCustom; + case string dotNet when Regex.Match(dotNet, @"^[0-9,.]*$").Success: return MSBuildToolVersion.NETCustom; + default: return MSBuildToolVersion.Default; + } + } + /// /// Sets the platform target. /// @@ -53,14 +126,25 @@ public static MSBuildSettings UseToolVersion(this MSBuildSettings settings, MSBu /// The same instance so that multiple calls can be chained. public static MSBuildSettings SetPlatformTarget(this MSBuildSettings settings, PlatformTarget target) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.PlatformTarget = target; return settings; } + /// + /// Sets the platform target. + /// + /// The settings. + /// The target. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetPlatformTarget(this MSBuildSettings settings, string target) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.WithProperty("Platform", target); + return settings; + } + /// /// Sets the MSBuild platform. /// @@ -69,10 +153,7 @@ public static MSBuildSettings SetPlatformTarget(this MSBuildSettings settings, P /// The same instance so that multiple calls can be chained. public static MSBuildSettings SetMSBuildPlatform(this MSBuildSettings settings, MSBuildPlatform platform) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.MSBuildPlatform = platform; return settings; } @@ -86,10 +167,7 @@ public static MSBuildSettings SetMSBuildPlatform(this MSBuildSettings settings, /// The same instance so that multiple calls can be chained. public static MSBuildSettings WithProperty(this MSBuildSettings settings, string name, params string[] values) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); IList currentValue; currentValue = new List( @@ -102,6 +180,66 @@ public static MSBuildSettings WithProperty(this MSBuildSettings settings, string return settings; } + /// + /// Adds a property to retrieve the value. + /// + /// The settings. + /// The name of the property to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetProperty(this MSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetProperties.Add(name); + + return settings; + } + + /// + /// Adds a item to retrieve the value. + /// + /// The settings. + /// The name of the item to retrieve the value. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetItem(this MSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetItems.Add(name); + + return settings; + } + + /// + /// Adds a target to retrieve the result. + /// + /// The settings. + /// The name of the target to retrieve the result. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithGetTargetResult(this MSBuildSettings settings, string name) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException(nameof(name)); + } + + settings.GetTargetResults.Add(name); + + return settings; + } + /// /// Sets the configuration. /// @@ -110,27 +248,28 @@ public static MSBuildSettings WithProperty(this MSBuildSettings settings, string /// The same instance so that multiple calls can be chained. public static MSBuildSettings SetConfiguration(this MSBuildSettings settings, string configuration) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Configuration = configuration; return settings; } /// - /// Sets the maximum CPU count. + /// Sets the maximum CPU count. Without this set MSBuild will compile projects in this solution one at a time. /// /// The settings. - /// The maximum CPU count. + /// The maximum CPU count. Set this value to zero to use as many MSBuild processes as available CPUs. /// The same instance so that multiple calls can be chained. - public static MSBuildSettings SetMaxCpuCount(this MSBuildSettings settings, int maxCpuCount) + public static MSBuildSettings SetMaxCpuCount(this MSBuildSettings settings, int? maxCpuCount) { - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + if (maxCpuCount.HasValue) { - throw new ArgumentNullException("settings"); + settings.MaxCpuCount = Math.Max(0, maxCpuCount.Value); + } + else + { + settings.MaxCpuCount = null; } - settings.MaxCpuCount = Math.Max(0, maxCpuCount); return settings; } @@ -142,14 +281,182 @@ public static MSBuildSettings SetMaxCpuCount(this MSBuildSettings settings, int /// The same instance so that multiple calls can be chained. public static MSBuildSettings SetNodeReuse(this MSBuildSettings settings, bool reuse) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.NodeReuse = reuse; return settings; } + /// + /// Sets whether or not detailed summary should be enabled. + /// + /// The settings. + /// true if detailed summary should be enabled; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetDetailedSummary(this MSBuildSettings settings, bool detailedSummary) + { + ArgumentNullException.ThrowIfNull(settings); + settings.DetailedSummary = detailedSummary; + return settings; + } + + /// + /// Sets whether or not no console logging should be enabled. + /// + /// The settings. + /// true if no console log should be enabled; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetNoConsoleLogger(this MSBuildSettings settings, bool noConsoleLog) + { + ArgumentNullException.ThrowIfNull(settings); + settings.NoConsoleLogger = noConsoleLog; + return settings; + } + + /// + /// Sets whether or not copyright information at the start of the program should be shown. + /// + /// The settings. + /// true if no copyright information at the start of the program should be shown; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetNoLogo(this MSBuildSettings settings, bool noLogo) + { + ArgumentNullException.ThrowIfNull(settings); + settings.NoLogo = noLogo; + return settings; + } + + /// + /// Sets the version. + /// + /// The settings. + /// The version. + /// The same instance so that multiple calls can be chained. + /// + /// Version will override VersionPrefix and VersionSuffix if set. + /// This may also override version settings during packaging. + /// + public static MSBuildSettings SetVersion(this MSBuildSettings settings, string version) + => settings.WithProperty("Version", version); + + /// + /// Sets the version prefix. + /// + /// The settings. + /// The version prefix. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetVersionPrefix(this MSBuildSettings settings, string versionPrefix) + => settings.WithProperty("VersionPrefix", versionPrefix); + + /// + /// Sets the version suffix. + /// + /// The settings. + /// The version suffix. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetVersionSuffix(this MSBuildSettings settings, string versionSuffix) + => settings.WithProperty("VersionSuffix", versionSuffix); + + /// + /// Sets the file version. + /// + /// The settings. + /// The file version. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetFileVersion(this MSBuildSettings settings, string fileVersion) + => settings.WithProperty("FileVersion", fileVersion); + + /// + /// Sets the assembly version. + /// + /// The settings. + /// The assembly version. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetAssemblyVersion(this MSBuildSettings settings, string assemblyVersion) + => settings.WithProperty("AssemblyVersion", assemblyVersion); + + /// + /// Sets the informational version. + /// + /// The settings. + /// The informational version. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetInformationalVersion(this MSBuildSettings settings, string informationalVersion) + => settings.WithProperty("InformationalVersion", informationalVersion); + + /// + /// Sets the package version. + /// + /// The settings. + /// The package version. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetPackageVersion(this MSBuildSettings settings, string packageVersion) + => settings.WithProperty("PackageVersion", packageVersion); + + /// + /// Sets the package release notes. + /// + /// The settings. + /// The package release notes. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetPackageReleaseNotes(this MSBuildSettings settings, string packageReleaseNotes) + => settings.WithProperty("PackageReleaseNotes", packageReleaseNotes); + + /// + /// Sets a value indicating whether to normalize stored file paths used when producing deterministic builds. + /// + /// + /// For more information see https://devblogs.microsoft.com/dotnet/producing-packages-with-source-link/#deterministic-builds. + /// + /// The settings. + /// A value indicating whether to normalize stored file paths used when producing deterministic builds. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetContinuousIntegrationBuild(this MSBuildSettings settings, bool? continuousIntegrationBuild = true) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.ContinuousIntegrationBuild = continuousIntegrationBuild; + return settings; + } + + /// + /// Sets whether or not any targets should be passed to MSBuild. + /// + /// The settings. + /// true if no implicit target should be passed to MSBuild; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetNoImplicitTarget(this MSBuildSettings settings, bool noImplicitTarget) + { + ArgumentNullException.ThrowIfNull(settings); + settings.NoImplicitTarget = noImplicitTarget; + return settings; + } + + /// + /// Sets whether or not a symbol package should be created. + /// + /// The settings. + /// true if a symbol package should be created; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetIncludeSymbols(this MSBuildSettings settings, bool includeSymbols) + { + ArgumentNullException.ThrowIfNull(settings); + settings.IncludeSymbols = includeSymbols; + return settings; + } + + /// + /// Sets the symbol package format. + /// + /// The settings. + /// The symbol package format. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetSymbolPackageFormat(this MSBuildSettings settings, string symbolPackageFormat) + { + ArgumentNullException.ThrowIfNull(settings); + settings.SymbolPackageFormat = symbolPackageFormat; + return settings; + } + /// /// Sets the build log verbosity. /// @@ -158,12 +465,206 @@ public static MSBuildSettings SetNodeReuse(this MSBuildSettings settings, bool r /// The same instance so that multiple calls can be chained. public static MSBuildSettings SetVerbosity(this MSBuildSettings settings, Verbosity verbosity) { - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + settings.Verbosity = verbosity; + return settings; + } + + /// + /// Adds a custom logger. + /// + /// The settings. + /// The assembly containing the logger. Should match the format {AssemblyName[,StrongName] | AssemblyFile}. + /// The class implementing the logger. Should match the format [PartialOrFullNamespace.]LoggerClassName. If the assembly contains only one logger, class does not need to be specified. + /// Parameters to be passed to the logger. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithLogger(this MSBuildSettings settings, string loggerAssembly, string loggerClass = null, string loggerParameters = null) + { + ArgumentNullException.ThrowIfNull(settings); + if (string.IsNullOrWhiteSpace(loggerAssembly)) { - throw new ArgumentNullException("settings"); + throw new ArgumentException(nameof(loggerAssembly)); } - settings.Verbosity = verbosity; + settings.Loggers.Add(new MSBuildLogger + { + Assembly = loggerAssembly, + Class = loggerClass, + Parameters = loggerParameters + }); + return settings; + } + + /// + /// Adds a file logger with all the default settings. + /// Each file logger will be declared in the order added. + /// The first file logger will match up to the /fl parameter. + /// The next nine (max) file loggers will match up to the /fl1 through /fl9 respectively. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings AddFileLogger(this MSBuildSettings settings) + { + return AddFileLogger(settings, new MSBuildFileLogger()); + } + + /// + /// Adds a file logger. + /// Each file logger will be declared in the order added. + /// The first file logger will match up to the /fl parameter. + /// The next nine (max) file loggers will match up to the /fl1 through /fl9 respectively. + /// + /// The settings. + /// Parameters to be passed to the logger. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings AddFileLogger(this MSBuildSettings settings, MSBuildFileLogger fileLoggerParameters) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(fileLoggerParameters); + settings.FileLoggers.Add(fileLoggerParameters); + + return settings; + } + + /// + /// Enables the binary logger with all the default settings. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings EnableBinaryLogger(this MSBuildSettings settings) + { + return EnableBinaryLogger(settings, MSBuildBinaryLogImports.Unspecified); + } + + /// + /// Enables the binary logger with the specified imports and default file name. + /// + /// The settings. + /// The imports. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings EnableBinaryLogger(this MSBuildSettings settings, MSBuildBinaryLogImports imports) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.BinaryLogger = new MSBuildBinaryLogSettings + { + Enabled = true, + Imports = imports, + }; + + return settings; + } + + /// + /// Enables the binary logger with the specified log file name and no imports. + /// + /// The settings. + /// The log file name. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings EnableBinaryLogger(this MSBuildSettings settings, string fileName) + { + return EnableBinaryLogger(settings, fileName, MSBuildBinaryLogImports.Unspecified); + } + + /// + /// Enables the binary logger with the specified log file name and imports. + /// + /// The settings. + /// The log file name. + /// The imports. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings EnableBinaryLogger(this MSBuildSettings settings, string fileName, MSBuildBinaryLogImports imports) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.BinaryLogger = new MSBuildBinaryLogSettings + { + Enabled = true, + FileName = fileName, + Imports = imports, + }; + + return settings; + } + + /// + /// Treat warnings as errors, if no codes specified all errors will be treated as errors. + /// + /// The settings. + /// Only treat specified warning codes as errors. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithWarningsAsError(this MSBuildSettings settings, params string[] codes) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.WarningsAsError = true; + + foreach (var code in codes) + { + settings.WarningsAsErrorCodes.Add(code); + } + + return settings; + } + + /// + /// Warnings to not treat as errors. + /// + /// The settings. + /// Warning codes to not treat as errors. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithWarningsAsMessage(this MSBuildSettings settings, params string[] codes) + { + ArgumentNullException.ThrowIfNull(settings); + + foreach (var code in codes) + { + settings.WarningsAsMessageCodes.Add(code); + } + + return settings; + } + + /// + /// Invoke the Restore target before any other target. + /// + /// The setting. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithRestore(this MSBuildSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.Restore = true; + + return settings; + } + + /// + /// Sets whether or not to lock the package dependency graph while restoring, using the packages.lock.json file. + /// This setting is available with atleast Visual Studio 2017 version 15.9 and above or NET SDK version 2.1.500 and above. + /// + /// The settings. + /// true if locked mode restore should be enabled; otherwise false. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings SetRestoreLockedMode(this MSBuildSettings settings, bool restoreLockedMode) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.RestoreLockedMode = restoreLockedMode; + return settings; + } + + /// + /// Adds a console logger parameter. + /// + /// The settings. + /// The console logger parameter. + /// The same instance so that multiple calls can be chained. + public static MSBuildSettings WithConsoleLoggerParameter(this MSBuildSettings settings, string parameter) + { + ArgumentNullException.ThrowIfNull(settings); + + settings.ConsoleLoggerParameters.Add(parameter); return settings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildToolVersion.cs b/src/Cake.Common/Tools/MSBuild/MSBuildToolVersion.cs index 15ce3a98c7..914aac723e 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildToolVersion.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildToolVersion.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.MSBuild { /// @@ -87,5 +88,35 @@ public enum MSBuildToolVersion /// MSBuild tool version: .NET 4.6 /// NET46 = 5, + + /// + /// MSBuild tool version: Visual Studio 2017 + /// + VS2017 = 6, + + /// + /// MSBuild tool version: Visual Studio 2019 + /// + VS2019 = 7, + + /// + /// Custom Visual Studio build + /// + VSCustom = 8, + + /// + /// Custom Visual Studio build + /// + NETCustom = 9, + + /// + /// MSBuild tool version: Visual Studio 2022 + /// + VS2022 = 10, + + /// + /// MSBuild tool version: Visual Studio 2026 + /// + VS2026 = 11 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildVerbosityExtensions.cs b/src/Cake.Common/Tools/MSBuild/MSBuildVerbosityExtensions.cs new file mode 100644 index 0000000000..4f985f1570 --- /dev/null +++ b/src/Cake.Common/Tools/MSBuild/MSBuildVerbosityExtensions.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.Diagnostics; + +namespace Cake.Common.Tools.MSBuild +{ + /// + /// Contains functionality related to MSBuild verbosity. + /// + public static class MSBuildVerbosityExtensions + { + /// + /// Gets the MSBuild verbosity from . + /// + /// The verbosity. + /// MSBuild verbosity string. + public static string GetMSBuildVerbosityName(this Verbosity verbosity) + { + switch (verbosity) + { + case Verbosity.Quiet: + return "quiet"; + case Verbosity.Minimal: + return "minimal"; + case Verbosity.Normal: + return "normal"; + case Verbosity.Verbose: + return "detailed"; + case Verbosity.Diagnostic: + return "diagnostic"; + default: + throw new CakeException("Encountered unknown MSBuild build log verbosity."); + } + } + + /// + /// Gets the MSBuild from string value. + /// + /// The verbosity string value. + /// MSBuild enumeration. + /// Valid values are 'quiet', 'minimal', 'normal', 'detailed' and 'diagnostic'. + public static Verbosity GetMSBuildVerbosity(this string verbosity) + { + switch (verbosity?.ToLower()) + { + case "quiet": + return Verbosity.Quiet; + case "minimal": + return Verbosity.Minimal; + case "normal": + return Verbosity.Normal; + case "detailed": + return Verbosity.Verbose; + case "diagnostic": + return Verbosity.Diagnostic; + default: + throw new CakeException($"Encountered unknown MSBuild build log verbosity '{verbosity}'. Valid values are 'quiet', 'minimal', 'normal', 'detailed' and 'diagnostic'."); + } + } + } +} diff --git a/src/Cake.Common/Tools/MSBuild/MSBuildVersion.cs b/src/Cake.Common/Tools/MSBuild/MSBuildVersion.cs index 5066007846..de846ffea3 100644 --- a/src/Cake.Common/Tools/MSBuild/MSBuildVersion.cs +++ b/src/Cake.Common/Tools/MSBuild/MSBuildVersion.cs @@ -1,14 +1,49 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.MSBuild { - internal enum MSBuildVersion + /// + /// Represents a MSBuild version. + /// + public enum MSBuildVersion { + /// Version 2.0 MSBuild20 = 1, + + /// Version 3.5 MSBuild35 = 2, + + /// Version 4.0 MSBuild4 = 3, + + /// Version 12.0 MSBuild12 = 4, - MSBuild14 = 5 + + /// Version 14.0 + MSBuild14 = 5, + + /// Version 15.0 + MSBuild15 = 6, + + /// Version 16.0 + MSBuild16 = 7, + + /// + /// Custom VS Version + /// + MSBuildCustomVS = 8, + + /// + /// Custom .NET Version + /// + MSBuildNETCustom = 9, + + /// Version 17.0 + MSBuild17 = 10, + + /// Version 18.0 + MSBuild18 = 11 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSBuild/PlatformTarget.cs b/src/Cake.Common/Tools/MSBuild/PlatformTarget.cs index ef9bbce9c7..9bbdffb154 100644 --- a/src/Cake.Common/Tools/MSBuild/PlatformTarget.cs +++ b/src/Cake.Common/Tools/MSBuild/PlatformTarget.cs @@ -1,6 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.MSBuild { /// @@ -34,6 +35,30 @@ public enum PlatformTarget /// /// Platform target: Win32 /// - Win32 = 4 + Win32 = 4, + + /// + /// Platform target: ARM64 + /// + // ReSharper disable once InconsistentNaming + ARM64 = 5, + + /// + /// Platform target: ARMv6 + /// + // ReSharper disable once InconsistentNaming + ARMv6 = 6, + + /// + /// Platform target: ARMv7 + /// + // ReSharper disable once InconsistentNaming + ARMv7 = 7, + + /// + /// Platform target: ARMv7s + /// + // ReSharper disable once InconsistentNaming + ARMv7s = 8, } } diff --git a/src/Cake.Common/Tools/MSTest/MSTestAliases.cs b/src/Cake.Common/Tools/MSTest/MSTestAliases.cs index 6c5a8699f1..dce1da8402 100644 --- a/src/Cake.Common/Tools/MSTest/MSTestAliases.cs +++ b/src/Cake.Common/Tools/MSTest/MSTestAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +17,7 @@ namespace Cake.Common.Tools.MSTest /// /// In order to use the commands for this alias, MSTest will need to be installed on the machine where /// the Cake script is being executed. This is typically achieved by having either Visual Studio installed, or by - /// using the Micrsoft Build Tools, for example, for 2015. + /// using the Microsoft Build Tools, for example, for 2015. /// /// [CakeAliasCategory("MSTest")] @@ -33,12 +34,9 @@ public static class MSTestAliases /// The context. /// The pattern. [CakeMethodAlias] - public static void MSTest(this ICakeContext context, string pattern) + public static void MSTest(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -62,12 +60,9 @@ public static void MSTest(this ICakeContext context, string pattern) /// The pattern. /// The settings. [CakeMethodAlias] - public static void MSTest(this ICakeContext context, string pattern, MSTestSettings settings) + public static void MSTest(this ICakeContext context, GlobPattern pattern, MSTestSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -111,17 +106,11 @@ public static void MSTest(this ICakeContext context, IEnumerable assem [CakeMethodAlias] public static void MSTest(this ICakeContext context, IEnumerable assemblyPaths, MSTestSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblyPaths); var runner = new MSTestRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblyPaths, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSTest/MSTestRunner.cs b/src/Cake.Common/Tools/MSTest/MSTestRunner.cs index 2cd660a8f8..f68f43f406 100644 --- a/src/Cake.Common/Tools/MSTest/MSTestRunner.cs +++ b/src/Cake.Common/Tools/MSTest/MSTestRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -42,14 +43,8 @@ public MSTestRunner( /// The settings. public void Run(IEnumerable assemblyPaths, MSTestSettings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(assemblyPaths, settings)); } @@ -69,6 +64,11 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append(string.Concat("/category:", settings.Category.Quote())); } + if (!string.IsNullOrEmpty(settings.ResultsFile)) + { + builder.Append(string.Concat("/resultsfile:", settings.ResultsFile.Quote())); + } + if (settings.NoIsolation) { builder.Append("/noisolation"); @@ -102,15 +102,30 @@ protected override IEnumerable GetToolExecutableNames() } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. protected override IEnumerable GetAlternativeToolPaths(MSTestSettings settings) { - foreach (var version in new[] { "14.0", "12.0", "11.0", "10.0" }) + var vsRootRelativeToolPath = FilePath.FromString("Common7/IDE/mstest.exe"); + foreach (var year in VisualStudio.Versions.TwentySeventeenAndLater) { - var path = GetToolPath(version); + foreach (var edition in settings.AllowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + var path = VisualStudio.GetYearAndEditionToolPath(_environment, year, edition, vsRootRelativeToolPath); + if (_fileSystem.Exist(path)) + { + yield return path; + } + } + } + + foreach (var version in VisualStudio.Versions.TenToFourteen) + { + var path = VisualStudio.GetVersionNumberToolPath(_environment, version, vsRootRelativeToolPath); if (_fileSystem.Exist(path)) { yield return path; @@ -127,13 +142,6 @@ protected override IEnumerable GetAlternativeToolPaths(MSTestSettings } } - private FilePath GetToolPath(string version) - { - var programFiles = _environment.GetSpecialPath(SpecialPath.ProgramFilesX86); - var root = programFiles.Combine(string.Concat("Microsoft Visual Studio ", version, "/Common7/IDE")); - return root.CombineWithFilePath("mstest.exe"); - } - private FilePath GetCommonToolPath(string environmentVariable) { var visualStudioCommonToolsPath = _environment.GetEnvironmentVariable(environmentVariable); @@ -147,4 +155,4 @@ private FilePath GetCommonToolPath(string environmentVariable) return root.CombineWithFilePath("mstest.exe"); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSTest/MSTestSettings.cs b/src/Cake.Common/Tools/MSTest/MSTestSettings.cs index ff7c1ec324..9ea17208b8 100644 --- a/src/Cake.Common/Tools/MSTest/MSTestSettings.cs +++ b/src/Cake.Common/Tools/MSTest/MSTestSettings.cs @@ -28,6 +28,12 @@ public sealed class MSTestSettings : ToolSettings /// public string Category { get; set; } + /// + /// Gets or sets the filepath for a named resulting test file. + /// MSTest.exe flag /resultsfile. + /// + public string ResultsFile { get; set; } + /// /// Gets or sets the test settings file to pass to MSTest.exe flag /testsettings. /// @@ -40,5 +46,14 @@ public MSTestSettings() { NoIsolation = true; } + + /// + /// Gets or sets a value indicating whether tools from a preview edition of Visual Studio should be used. + /// + /// If set to true, MSTest from a Preview edition + /// (e.g. Visual Studio 2022 Preview) will be considered to be used. + /// + /// + public bool AllowPreviewVersion { get; set; } = false; } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSpec/MSpecAliases.cs b/src/Cake.Common/Tools/MSpec/MSpecAliases.cs new file mode 100644 index 0000000000..1b6ab3e7d2 --- /dev/null +++ b/src/Cake.Common/Tools/MSpec/MSpecAliases.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +namespace Cake.Common.Tools.MSpec +{ + /// + /// Contains functionality related to running Machine.Specifications tests. + /// + /// In order to use the commands for this alias, include the following in your build.cake file to download and + /// install from nuget.org, or specify the ToolPath within the class: + /// + /// #tool "nuget:?package=Machine.Specifications.Runner.Console" + /// + /// + /// + [CakeAliasCategory("MSpec")] + public static class MSpecAliases + { + /// + /// Runs all MSpec tests in the assemblies matching the specified pattern. + /// + /// The context. + /// The pattern. + /// + /// + /// MSpec("./src/**/bin/Release/*.Tests.dll"); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, GlobPattern pattern) + { + ArgumentNullException.ThrowIfNull(context); + + var assemblies = context.Globber.GetFiles(pattern).ToArray(); + if (assemblies.Length == 0) + { + context.Log.Verbose("The provided pattern did not match any files."); + return; + } + + MSpec(context, assemblies, new MSpecSettings()); + } + + /// + /// Runs all MSpec tests in the assemblies matching the specified pattern. + /// + /// The context. + /// The pattern. + /// The settings. + /// + /// + /// MSpec("./src/**/bin/Release/*.Tests.dll", + /// new MSpecSettings { + /// Parallelism = ParallelismOption.All, + /// HtmlReport = true, + /// NoAppDomain = true, + /// OutputDirectory = "./build" + /// }); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, GlobPattern pattern, MSpecSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var assemblies = context.Globber.GetFiles(pattern).ToArray(); + if (assemblies.Length == 0) + { + context.Log.Verbose("The provided pattern did not match any files."); + return; + } + + MSpec(context, assemblies, settings); + } + + /// + /// Runs all MSpec tests in the specified assemblies. + /// + /// The context. + /// The assemblies. + /// + /// + /// MSpec(new []{ + /// "./src/Cake.Common.Tests/bin/Release/Cake.Common.Tests.dll", + /// "./src/Cake.Core.Tests/bin/Release/Cake.Core.Tests.dll", + /// "./src/Cake.NuGet.Tests/bin/Release/Cake.NuGet.Tests.dll", + /// "./src/Cake.Tests/bin/Release/Cake.Tests.dll" + /// }); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, IEnumerable assemblies) + { + ArgumentNullException.ThrowIfNull(assemblies); + var paths = assemblies.Select(p => new FilePath(p)); + MSpec(context, paths, new MSpecSettings()); + } + + /// + /// Runs all MSpec tests in the specified assemblies. + /// + /// The context. + /// The assemblies. + /// + /// + /// var testAssemblies = GetFiles("./src/**/bin/Release/*.Tests.dll"); + /// MSpec(testAssemblies); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, IEnumerable assemblies) + { + MSpec(context, assemblies, new MSpecSettings()); + } + + /// + /// Runs all MSpec tests in the specified assemblies. + /// + /// The context. + /// The assemblies. + /// The settings. + /// + /// + /// MSpec(new []{ + /// "./src/Cake.Common.Tests/bin/Release/Cake.Common.Tests.dll", + /// "./src/Cake.Core.Tests/bin/Release/Cake.Core.Tests.dll", + /// "./src/Cake.NuGet.Tests/bin/Release/Cake.NuGet.Tests.dll", + /// "./src/Cake.Tests/bin/Release/Cake.Tests.dll" + /// }, + /// new MSpecSettings { + /// Parallelism = ParallelismOption.All, + /// HtmlReport = true, + /// NoAppDomain = true, + /// OutputDirectory = "./build" + /// }); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, IEnumerable assemblies, MSpecSettings settings) + { + ArgumentNullException.ThrowIfNull(assemblies); + var paths = assemblies.Select(p => new FilePath(p)); + MSpec(context, paths, settings); + } + + /// + /// Runs all MSpec tests in the specified assemblies. + /// + /// The context. + /// The assemblies. + /// The settings. + /// + /// + /// var testAssemblies = GetFiles("./src/**/bin/Release/*.Tests.dll"); + /// MSpec(testAssemblies, + /// new MSpecSettings { + /// Parallelism = ParallelismOption.All, + /// HtmlReport = true, + /// NoAppDomain = true, + /// OutputDirectory = "./build" + /// }); + /// + /// + [CakeMethodAlias] + public static void MSpec(this ICakeContext context, IEnumerable assemblies, MSpecSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); + + var runner = new MSpecRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(assemblies, settings); + } + } +} diff --git a/src/Cake.Common/Tools/MSpec/MSpecRunner.cs b/src/Cake.Common/Tools/MSpec/MSpecRunner.cs new file mode 100644 index 0000000000..fd70fa5b35 --- /dev/null +++ b/src/Cake.Common/Tools/MSpec/MSpecRunner.cs @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.MSpec +{ + /// + /// The MSpec unit test runner. + /// + public sealed class MSpecRunner : Tool + { + private readonly ICakeEnvironment _environment; + private bool _useX86; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public MSpecRunner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Runs the tests in the specified assemblies, using the specified settings. + /// + /// The assembly paths. + /// The settings. + public void Run(IEnumerable assemblyPaths, MSpecSettings settings) + { + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); + + if (string.IsNullOrWhiteSpace(settings.OutputDirectory?.FullPath)) + { + if (settings.HtmlReport) + { + throw new CakeException("Cannot generate HTML report when no output directory has been set."); + } + + if (settings.XmlReport) + { + throw new CakeException("Cannot generate XML report when no output directory has been set."); + } + } + + _useX86 = settings.UseX86; + + var paths = assemblyPaths as FilePath[] ?? assemblyPaths.ToArray(); + + Run(settings, GetArguments(paths, settings)); + } + + private ProcessArgumentBuilder GetArguments(IReadOnlyList assemblyPaths, MSpecSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + FilterTests(settings, builder); + + GeneralSettings(settings, builder); + + BuildServerSettings(settings, builder); + + ReportSettings(assemblyPaths, settings, builder); + + AssembliesToTest(assemblyPaths, builder); + + return builder; + } + + private static void GeneralSettings(MSpecSettings settings, ProcessArgumentBuilder builder) + { + if (settings.TimeInfo) + { + builder.Append("-t"); + } + + if (settings.Silent) + { + builder.Append("-s"); + } + + if (settings.Progress) + { + builder.Append("-p"); + } + + if (settings.NoColor) + { + builder.Append("-c"); + } + + if (settings.Wait) + { + builder.Append("-w"); + } + } + + private static void BuildServerSettings(MSpecSettings settings, ProcessArgumentBuilder builder) + { + if (settings.TeamCity) + { + builder.Append("--teamcity"); + } + + if (settings.NoTeamCity) + { + builder.Append("--no-teamcity-autodetect"); + } + + if (settings.AppVeyor) + { + builder.Append("--appveyor"); + } + + if (settings.NoAppVeyor) + { + builder.Append("--no-appveyor-autodetect"); + } + } + + private void FilterTests(MSpecSettings settings, ProcessArgumentBuilder builder) + { + if (settings.Filters != null) + { + builder.Append("-f"); + builder.AppendQuoted(settings.Filters.MakeAbsolute(_environment).FullPath); + } + + if (!string.IsNullOrWhiteSpace(settings.Include)) + { + builder.Append("-i"); + builder.AppendQuoted(settings.Include); + } + + if (!string.IsNullOrWhiteSpace(settings.Exclude)) + { + builder.Append("-x"); + builder.AppendQuoted(settings.Exclude); + } + } + + private void AssembliesToTest(IReadOnlyList assemblyPaths, ProcessArgumentBuilder builder) + { + foreach (var assemblyPath in assemblyPaths) + { + builder.AppendQuoted(assemblyPath.MakeAbsolute(_environment).FullPath); + } + } + + private void ReportSettings(IReadOnlyList assemblyPaths, MSpecSettings settings, ProcessArgumentBuilder builder) + { + if (settings.HtmlReport) + { + var reportFileName = MSpecRunnerUtilities.GetReportFileName(assemblyPaths, settings); + var assemblyFilename = reportFileName.AppendExtension(".html"); + var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); + + builder.Append("--html"); + builder.AppendQuoted(outputPath.FullPath); + } + + if (settings.XmlReport) + { + var reportFileName = MSpecRunnerUtilities.GetReportFileName(assemblyPaths, settings); + var assemblyFilename = reportFileName.AppendExtension(".xml"); + var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); + + builder.Append("--xml"); + builder.AppendQuoted(outputPath.FullPath); + } + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "MSpec"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return _useX86 ? new[] { "mspec-x86-clr4.exe" } : new[] { "mspec-clr4.exe" }; + } + } +} diff --git a/src/Cake.Common/Tools/MSpec/MSpecRunnerUtilities.cs b/src/Cake.Common/Tools/MSpec/MSpecRunnerUtilities.cs new file mode 100644 index 0000000000..9816257a33 --- /dev/null +++ b/src/Cake.Common/Tools/MSpec/MSpecRunnerUtilities.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.MSpec +{ + internal static class MSpecRunnerUtilities + { + internal static FilePath GetReportFileName(IReadOnlyList assemblyPaths, MSpecSettings settings) + { + if (string.IsNullOrEmpty(settings.ReportName)) + { + return assemblyPaths.Count == 1 + ? assemblyPaths[0].GetFilename() + : new FilePath("TestResults"); + } + else + { + return settings.ReportName; + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/MSpec/MspecSettings.cs b/src/Cake.Common/Tools/MSpec/MspecSettings.cs new file mode 100644 index 0000000000..d3451fe8ae --- /dev/null +++ b/src/Cake.Common/Tools/MSpec/MspecSettings.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.MSpec +{ + /// + /// Contains settings used by . + /// + public sealed class MSpecSettings : ToolSettings + { + /// + /// Gets or sets the path to the filter file specifying contexts to execute(full type name, one per line). Takes precedence over + /// tags. + /// + public FilePath Filters { get; set; } + + /// + /// Gets or sets a value indicating whether reporting for TeamCity CI integration(also auto - detected). + /// + /// + /// true to turn on TeamCity service messages; otherwise, false. + /// + public bool TeamCity { get; set; } + + /// + /// Gets or sets a value indicating whether to suppress colored console output. + /// + /// + /// true disable color output; otherwise, false. + /// + public bool NoColor { get; set; } + + /// + /// Gets or sets a value indicating whether an XML report should be generated. + /// + /// + /// true if an XML report should be generated; otherwise, false. + /// + public bool XmlReport { get; set; } + + /// + /// Gets or sets a value indicating whether an HTML report should be generated. + /// + /// + /// true if an HTML report should be generated; otherwise, false. + /// + public bool HtmlReport { get; set; } + + /// + /// Gets or sets the name that should be used for the HTML and XML reports. + /// + /// The custom report name. + public string ReportName { get; set; } + + /// + /// Gets or sets Executes all specifications in contexts with these comma delimited tags. Ex. - i "foo,bar,foo_bar". + /// + public string Include { get; set; } + + /// + /// Gets or sets Exclude specifications in contexts with these comma delimited tags. Ex. - x "foo,bar,foo_bar". + /// + public string Exclude { get; set; } + + /// + /// Gets or sets a value indicating whether to show time-related information in HTML output. + /// + public bool TimeInfo { get; set; } + + /// + /// Gets or sets a value indicating whether to suppress progress output(print fatal errors, failures and summary). + /// + public bool Silent { get; set; } + + /// + /// Gets or sets a value indicating whether to print dotted progress output. + /// + public bool Progress { get; set; } + + /// + /// Gets or sets a value indicating whether to wait 15 seconds for debugger to be attached. + /// + public bool Wait { get; set; } + + /// + /// Gets or sets a value indicating whether to disable TeamCity autodetection. + /// + public bool NoTeamCity { get; set; } + + /// + /// Gets or sets a value indicating whether to enable reporting for AppVeyor CI integration (also auto-detected). + /// + public bool AppVeyor { get; set; } + + /// + /// Gets or sets a value indicating whether to disable AppVeyor autodetection. + /// + public bool NoAppVeyor { get; set; } + + /// + /// Gets or sets output directory for reports. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to use X86. + /// + public bool UseX86 { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NSIS/MakeNSISRunner.cs b/src/Cake.Common/Tools/NSIS/MakeNSISRunner.cs index a50a9e400b..88dd74b543 100644 --- a/src/Cake.Common/Tools/NSIS/MakeNSISRunner.cs +++ b/src/Cake.Common/Tools/NSIS/MakeNSISRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -41,14 +42,8 @@ public MakeNSISRunner( /// The settings. public void Run(FilePath scriptFile, MakeNSISSettings settings) { - if (scriptFile == null) - { - throw new ArgumentNullException("scriptFile"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(scriptFile); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(scriptFile, settings)); } @@ -101,4 +96,4 @@ private ProcessArgumentBuilder GetArguments(FilePath scriptFile, MakeNSISSetting return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NSIS/MakeNSISSettings.cs b/src/Cake.Common/Tools/NSIS/MakeNSISSettings.cs index 4948a55d69..bb4f9aa850 100644 --- a/src/Cake.Common/Tools/NSIS/MakeNSISSettings.cs +++ b/src/Cake.Common/Tools/NSIS/MakeNSISSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.Tooling; @@ -15,7 +17,7 @@ public sealed class MakeNSISSettings : ToolSettings /// /// Gets or sets the script compiler defines. /// - public IDictionary Defines { get; set; } + public IDictionary Defines { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets or sets a value indicating whether to disable current directory change to that of the script file. @@ -27,4 +29,4 @@ public sealed class MakeNSISSettings : ToolSettings /// public bool NoConfig { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NSIS/NSISAliases.cs b/src/Cake.Common/Tools/NSIS/NSISAliases.cs index 854134f74a..3f96e14667 100644 --- a/src/Cake.Common/Tools/NSIS/NSISAliases.cs +++ b/src/Cake.Common/Tools/NSIS/NSISAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -25,6 +26,11 @@ public static class NSISAliases /// /// The context. /// The path to the .nsi script file to compile. + /// + /// + /// MakeNSIS("./src/Cake.nsi"); + /// + /// [CakeMethodAlias] [CakeAliasCategory("MakeNSIS")] // ReSharper disable once InconsistentNaming @@ -39,23 +45,24 @@ public static void MakeNSIS(this ICakeContext context, FilePath scriptFile) /// The context. /// The path to the .nsi script file to compile. /// The to use. + /// + /// + /// MakeNSIS("./src/Cake.nsi", new MakeNSISSettings { + /// NoConfig = true + /// }); + /// + /// [CakeMethodAlias] [CakeAliasCategory("MakeNSIS")] // ReSharper disable once InconsistentNaming public static void MakeNSIS(this ICakeContext context, FilePath scriptFile, MakeNSISSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (scriptFile == null) - { - throw new ArgumentNullException("scriptFile"); - } + ArgumentNullException.ThrowIfNull(scriptFile); var runner = new MakeNSISRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(scriptFile, settings ?? new MakeNSISSettings()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3Aliases.cs b/src/Cake.Common/Tools/NUnit/NUnit3Aliases.cs index af239117d3..8c5b6c9f5f 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3Aliases.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3Aliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.NUnit /// Contains functionality related to running NUnit v2 and v3 unit tests. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=NUnit.ConsoleRunner" /// @@ -35,12 +36,9 @@ public static class NUnit3Aliases /// /// [CakeMethodAlias] - public static void NUnit3(this ICakeContext context, string pattern) + public static void NUnit3(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -67,12 +65,9 @@ public static void NUnit3(this ICakeContext context, string pattern) /// /// [CakeMethodAlias] - public static void NUnit3(this ICakeContext context, string pattern, NUnit3Settings settings) + public static void NUnit3(this ICakeContext context, GlobPattern pattern, NUnit3Settings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -97,10 +92,7 @@ public static void NUnit3(this ICakeContext context, string pattern, NUnit3Setti [CakeMethodAlias] public static void NUnit3(this ICakeContext context, IEnumerable assemblies) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); NUnit3(context, paths, new NUnit3Settings()); } @@ -139,10 +131,7 @@ public static void NUnit3(this ICakeContext context, IEnumerable assem [CakeMethodAlias] public static void NUnit3(this ICakeContext context, IEnumerable assemblies, NUnit3Settings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); NUnit3(context, paths, settings); } @@ -165,17 +154,11 @@ public static void NUnit3(this ICakeContext context, IEnumerable assembl [CakeMethodAlias] public static void NUnit3(this ICakeContext context, IEnumerable assemblies, NUnit3Settings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); var runner = new NUnit3Runner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblies, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3AppDomainUsage.cs b/src/Cake.Common/Tools/NUnit/NUnit3AppDomainUsage.cs index f08de25545..17798c15bb 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3AppDomainUsage.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3AppDomainUsage.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NUnit { /// @@ -31,4 +32,4 @@ public enum NUnit3AppDomainUsage /// Multiple } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3Labels.cs b/src/Cake.Common/Tools/NUnit/NUnit3Labels.cs index 36de6ac366..84bc14c6c4 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3Labels.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3Labels.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NUnit { /// @@ -21,6 +22,16 @@ public enum NUnit3Labels /// /// Outputs labels for all tests. /// - All + All, + + /// + /// Outputs labels at the start of every test. + /// + Before, + + /// + /// Outputs labels at the end of every test. + /// + After } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3ProcessOption.cs b/src/Cake.Common/Tools/NUnit/NUnit3ProcessOption.cs index 46356559b2..40896628c7 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3ProcessOption.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3ProcessOption.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NUnit { /// @@ -23,4 +24,4 @@ public enum NUnit3ProcessOption /// InProcess } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3Result.cs b/src/Cake.Common/Tools/NUnit/NUnit3Result.cs new file mode 100644 index 0000000000..211a5f2b1e --- /dev/null +++ b/src/Cake.Common/Tools/NUnit/NUnit3Result.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; + +namespace Cake.Common.Tools.NUnit +{ + /// + /// Contains information for the results that should be exported. + /// + public sealed class NUnit3Result + { + /// + /// Gets or sets the name of the XML result file. + /// + /// + /// The name of the XML result file. Defaults to TestResult.xml. + /// + public FilePath FileName { get; set; } + + /// + /// Gets or sets the format that the results should be in. must be set to + /// have any effect. Specify nunit2 to output the results in NUnit 2 xml format. + /// nunit3 may be specified for NUnit 3 format, however this is the default. Additional + /// formats may be supported in the future, check the NUnit documentation. + /// + /// + /// The format of the result file. Defaults to nunit3. + /// + public string Format { get; set; } + + /// + /// Gets or sets the file name of an XSL transform that will be applied to the results. + /// + /// + /// The name of an XSLT file that will be applied to the results. + /// + public FilePath Transform { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NUnit/NUnit3Runner.cs b/src/Cake.Common/Tools/NUnit/NUnit3Runner.cs index 8f6fd64e04..6f0615b374 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3Runner.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3Runner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.NUnit { /// - /// The NUnit unit test runner. + /// The NUnit3 unit test runner. /// public sealed class NUnit3Runner : Tool { @@ -41,14 +42,8 @@ public NUnit3Runner( /// The settings. public void Run(IEnumerable assemblyPaths, NUnit3Settings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(assemblyPaths, settings)); } @@ -98,6 +93,11 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append("--stoponerror"); } + if (settings.SkipNonTestAssemblies) + { + builder.Append("--skipnontestassemblies"); + } + if (settings.Work != null) { builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--work={0}", settings.Work.MakeAbsolute(_environment).FullPath)); @@ -108,34 +108,27 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--out={0}", settings.OutputFile.MakeAbsolute(_environment).FullPath)); } - if (settings.ErrorOutputFile != null) - { - builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--err={0}", settings.ErrorOutputFile.MakeAbsolute(_environment).FullPath)); - } - - if (settings.Full) - { - builder.Append("--full"); - } - - if (settings.Results != null && settings.NoResults) + if (HasResults(settings) && settings.NoResults) { throw new ArgumentException( GetToolName() + ": You can't specify both a results file and set NoResults to true."); } - if (settings.Results != null) + if (HasResults(settings)) { - var results = new StringBuilder(settings.Results.MakeAbsolute(_environment).FullPath); - if (settings.ResultFormat != null) - { - results.AppendFormat(";format={0}", settings.ResultFormat); - } - if (settings.ResultTransform != null) + foreach (var result in settings.Results) { - results.AppendFormat(";transform={0}", settings.ResultTransform.MakeAbsolute(_environment).FullPath); + var resultString = new StringBuilder(result.FileName.MakeAbsolute(_environment).FullPath); + if (result.Format != null) + { + resultString.AppendFormat(";format={0}", result.Format); + } + if (result.Transform != null) + { + resultString.AppendFormat(";transform={0}", result.Transform.MakeAbsolute(_environment).FullPath); + } + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--result={0}", resultString)); } - builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--result={0}", results)); } else if (settings.NoResults) { @@ -162,11 +155,6 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append("--nocolor"); } - if (settings.Verbose) - { - builder.Append("--verbose"); - } - if (settings.Configuration != null) { builder.AppendQuoted("--config=" + settings.Configuration); @@ -208,9 +196,40 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append("--domain=" + settings.AppDomainUsage); } + if (settings.TraceLevel.HasValue) + { + builder.Append("--trace=" + settings.TraceLevel.Value.GetArgumentValue()); + } + + if (settings.ConfigFile != null) + { + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--configfile={0}", settings.ConfigFile.MakeAbsolute(_environment).FullPath)); + } + + if (settings.Params != null && settings.Params.Count > 0) + { + foreach (var param in settings.Params) + { + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--params={0}={1}", param.Key, param.Value)); + } + } + + if (settings.TestParams != null && settings.TestParams.Count > 0) + { + foreach (var testParam in settings.TestParams) + { + builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "--testparam:{0}={1}", testParam.Key, testParam.Value)); + } + } + return builder; } + private bool HasResults(NUnit3Settings settings) + { + return settings.Results != null && settings.Results.Count > 0; + } + /// /// Gets the name of the tool. /// @@ -228,5 +247,49 @@ protected override IEnumerable GetToolExecutableNames() { return new[] { "nunit3-console.exe" }; } + + /// + /// Customized NUnit 3 exit code handling. + /// Throws on non-zero exit code. + /// + /// The process exit code. + protected override void ProcessExitCode(int exitCode) + { + string error; + + if (exitCode <= 0) + { + switch (exitCode) + { + case 0: + return; + case -1: + error = "Invalid argument"; + break; + case -2: + error = "Invalid assembly"; + break; + case -4: + error = "Invalid test fixture"; + break; + case -5: + error = "Unload error"; + break; + case -100: + error = "Unexpected error"; + break; + default: + error = "Unrecognised error"; + break; + } + } + else + { + error = string.Format(CultureInfo.InvariantCulture, "{0} test(s) failed", exitCode); + } + + const string message = "{0}: {1} (exit code {2})."; + throw new CakeException(string.Format(CultureInfo.InvariantCulture, message, GetToolName(), error, exitCode)); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnit3Settings.cs b/src/Cake.Common/Tools/NUnit/NUnit3Settings.cs index 3bc1169ab3..c96545e271 100644 --- a/src/Cake.Common/Tools/NUnit/NUnit3Settings.cs +++ b/src/Cake.Common/Tools/NUnit/NUnit3Settings.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -36,6 +39,7 @@ public sealed class NUnit3Settings : ToolSettings /// describing exactly which tests to use. Examples of usage are: /// --where:cat==Data /// --where "method =~ /DataTest*/ && cat = Slow" + /// See https://github.com/nunit/docs/wiki/Test-Selection-Language. /// public string Where { get; set; } @@ -76,6 +80,16 @@ public sealed class NUnit3Settings : ToolSettings /// public bool StopOnError { get; set; } + /// + /// Gets or sets a value indicating whether execution of the test run should + /// skip any non-test assemblies specified, without error. + /// + /// + /// true if execution of the test run should skip any non-test assemblies specified, without error; + /// otherwise, false. + /// + public bool SkipNonTestAssemblies { get; set; } + /// /// Gets or sets the directory to use for output files. If /// not specified, defaults to the current directory. @@ -92,46 +106,10 @@ public sealed class NUnit3Settings : ToolSettings public FilePath OutputFile { get; set; } /// - /// Gets or sets the location that NUnit should write test error output. - /// - /// The location that NUnit should write test error output. - public FilePath ErrorOutputFile { get; set; } - - /// - /// Gets or sets a value indicating whether to print full report of all test results. - /// - /// - /// true if a full report of test results should be printed; - /// otherwise, false. - /// - public bool Full { get; set; } - - /// - /// Gets or sets the name of the XML result file. + /// Gets or sets the results that should be saved. /// - /// - /// The name of the XML result file. Defaults to TestResult.xml. - /// - public FilePath Results { get; set; } - - /// - /// Gets or sets the format that the results should be in. Results must be set to - /// have any effect. Specify nunit2 to output the results in NUnit 2 xml format. - /// nunit3 may be specified for NUnit 3 format, however this is the default. Additional - /// formats may be supported in the future, check the NUnit documentation. - /// - /// - /// The format of the result file. Defaults to nunit3. - /// - public string ResultFormat { get; set; } - - /// - /// Gets or sets the file name of an XSL transform that will be applied to the results. - /// - /// - /// The name of an XSLT file that will be applied to the results. - /// - public FilePath ResultTransform { get; set; } + /// The package owners. + public ICollection Results { get; set; } = new List(); /// /// Gets or sets a value indicating whether to generate the XML result file. @@ -145,8 +123,9 @@ public sealed class NUnit3Settings : ToolSettings /// Gets or sets a value specifying whether to write test case names to the output. /// /// - /// On to write labels for tests that are run or All to write labels - /// for all tests. + /// On to write labels for tests that are run,All to write labels + /// for all tests,Before to write labels at the start of every test + /// ,or After to write labels at the end of every test. /// public NUnit3Labels Labels { get; set; } @@ -174,14 +153,6 @@ public sealed class NUnit3Settings : ToolSettings /// public bool NoColor { get; set; } - /// - /// Gets or sets a value indicating whether to show additional information as the tests run. - /// - /// - /// true shows additional information as the tests run; otherwise, false. - /// - public bool Verbose { get; set; } - /// /// Gets or sets the name of a project configuration to load (e.g.:Debug). /// This selects the configuration within the NUnit project file. @@ -248,5 +219,41 @@ public sealed class NUnit3Settings : ToolSettings /// The maximum number of test assembly agents to run at one time. /// public int? Agents { get; set; } + + /// + /// Gets or sets a value indicating the path to an alternative app.config file to load. + /// + /// The location that NUnit should load an alternative app.config file from. + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets the parameters that should be passed to the runner. + /// + /// + /// List of parameters (key/value) which are passed to the runner. + /// + public IDictionary Params { get; set; } = + // “Case-sensitive.” https://github.com/nunit/docs/wiki/Console-Command-Line#options + new Dictionary(StringComparer.Ordinal); + + /// + /// Gets or sets the test parameters that should be passed to the runner. + /// + /// + /// List of test parameters (key/value) which are passed to the runner. + /// + public IDictionary TestParams { get; set; } = + // “Case-sensitive.” https://github.com/nunit/docs/wiki/Console-Command-Line#options + new Dictionary(StringComparer.Ordinal); + + /// + /// Gets or sets the level of detail at which the runner should write to its internal trace log. + /// Corresponds to the -trace=LEVEL command line argument. + /// If null, no argument will be specified. + /// + /// + /// The trace level. + /// + public NUnitInternalTraceLevel? TraceLevel { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnitAliases.cs b/src/Cake.Common/Tools/NUnit/NUnitAliases.cs index 1b47631efb..fad2e81d8a 100644 --- a/src/Cake.Common/Tools/NUnit/NUnitAliases.cs +++ b/src/Cake.Common/Tools/NUnit/NUnitAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.NUnit /// Contains functionality related to running NUnit tests. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=NUnit.Runners&version=2.6.4" /// @@ -35,12 +36,9 @@ public static class NUnitAliases /// The context. /// The pattern. [CakeMethodAlias] - public static void NUnit(this ICakeContext context, string pattern) + public static void NUnit(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -68,12 +66,9 @@ public static void NUnit(this ICakeContext context, string pattern) /// The pattern. /// The settings. [CakeMethodAlias] - public static void NUnit(this ICakeContext context, string pattern, NUnitSettings settings) + public static void NUnit(this ICakeContext context, GlobPattern pattern, NUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -102,10 +97,7 @@ public static void NUnit(this ICakeContext context, string pattern, NUnitSetting [CakeMethodAlias] public static void NUnit(this ICakeContext context, IEnumerable assemblies) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); NUnit(context, paths, new NUnitSettings()); } @@ -149,10 +141,7 @@ public static void NUnit(this ICakeContext context, IEnumerable assemb [CakeMethodAlias] public static void NUnit(this ICakeContext context, IEnumerable assemblies, NUnitSettings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); NUnit(context, paths, settings); } @@ -176,17 +165,11 @@ public static void NUnit(this ICakeContext context, IEnumerable assembli [CakeMethodAlias] public static void NUnit(this ICakeContext context, IEnumerable assemblies, NUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); var runner = new NUnitRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblies, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnitAppDomainUsage.cs b/src/Cake.Common/Tools/NUnit/NUnitAppDomainUsage.cs index b4ade932e8..bf30b20503 100644 --- a/src/Cake.Common/Tools/NUnit/NUnitAppDomainUsage.cs +++ b/src/Cake.Common/Tools/NUnit/NUnitAppDomainUsage.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NUnit { /// @@ -24,4 +25,4 @@ public enum NUnitAppDomainUsage /// Single } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevel.cs b/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevel.cs new file mode 100644 index 0000000000..eb88c6e611 --- /dev/null +++ b/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevel.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NUnit +{ + /// + /// Represents the level of detail at which NUnit should set internal tracing. + /// + public enum NUnitInternalTraceLevel + { + /// + /// Do not display any trace messages + /// + Off = 0, + + /// + /// Display Error messages only + /// + Error = 1, + + /// + /// Display Warning level and higher messages + /// + Warning = 2, + + /// + /// Display informational and higher messages + /// + Info = 3, + + /// + /// Display debug messages and higher - i.e. all messages + /// + Debug = 4, + + /// + /// Display debug messages and higher - i.e. all messages + /// + Verbose = Debug + } +} diff --git a/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevelExtensions.cs b/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevelExtensions.cs new file mode 100644 index 0000000000..bfb0126ce4 --- /dev/null +++ b/src/Cake.Common/Tools/NUnit/NUnitInternalTraceLevelExtensions.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Tools.NUnit +{ + /// + /// Contains extension methods for . + /// + public static class NUnitInternalTraceLevelExtensions + { + /// + /// Gets the LEVEL value for the --trace command line argument for the given . + /// + /// The value for which to get the representation. + /// Returns the appropriate representation for the given value. + public static string GetArgumentValue(this NUnitInternalTraceLevel level) + { + string result; + switch (level) + { + case NUnitInternalTraceLevel.Debug: + result = "verbose"; + break; + default: + result = Enum.GetName(level)?.ToLowerInvariant(); + break; + } + + return result ?? throw new ArgumentOutOfRangeException(nameof(level), level, "Unexpected value was encountered."); + } + } +} diff --git a/src/Cake.Common/Tools/NUnit/NUnitProcessOption.cs b/src/Cake.Common/Tools/NUnit/NUnitProcessOption.cs index 6cbb17fbd4..8503e3fe93 100644 --- a/src/Cake.Common/Tools/NUnit/NUnitProcessOption.cs +++ b/src/Cake.Common/Tools/NUnit/NUnitProcessOption.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NUnit { /// @@ -23,4 +24,4 @@ public enum NUnitProcessOption /// Multiple } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnitRunner.cs b/src/Cake.Common/Tools/NUnit/NUnitRunner.cs index 560335b9b5..e54d2392e9 100644 --- a/src/Cake.Common/Tools/NUnit/NUnitRunner.cs +++ b/src/Cake.Common/Tools/NUnit/NUnitRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -16,6 +17,7 @@ namespace Cake.Common.Tools.NUnit public sealed class NUnitRunner : Tool { private readonly ICakeEnvironment _environment; + private bool _x86; /// /// Initializes a new instance of the class. @@ -40,14 +42,10 @@ public NUnitRunner( /// The settings. public void Run(IEnumerable assemblyPaths, NUnitSettings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); + + _x86 = settings.X86; Run(settings, GetArguments(assemblyPaths, settings)); } @@ -108,6 +106,11 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append("-trace:" + settings.Trace); } + if (settings.Labels) + { + builder.Append("-labels"); + } + if (settings.OutputFile != null) { builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "-output:{0}", settings.OutputFile.MakeAbsolute(_environment).FullPath)); @@ -170,7 +173,53 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "nunit-console.exe" }; + if (!_x86) + { + return new[] { "nunit-console.exe" }; + } + + return new[] { "nunit-console-x86.exe" }; + } + + /// + /// Customized NUnit exit code handling. + /// Throws on non-zero exit code. + /// + /// The process exit code. + protected override void ProcessExitCode(int exitCode) + { + string error; + + if (exitCode <= 0) + { + switch (exitCode) + { + case 0: + return; + case -1: + error = "Invalid argument"; + break; + case -2: + error = "File not found"; + break; + case -3: + error = "Test fixture not found"; + break; + case -100: + error = "Unexpected error"; + break; + default: + error = "Unrecognised error"; + break; + } + } + else + { + error = string.Format(CultureInfo.InvariantCulture, "{0} test(s) failed", exitCode); + } + + const string message = "{0}: {1} (exit code {2})."; + throw new CakeException(string.Format(CultureInfo.InvariantCulture, message, GetToolName(), error, exitCode)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NUnit/NUnitSettings.cs b/src/Cake.Common/Tools/NUnit/NUnitSettings.cs index 0cb142d677..e22f8dde2f 100644 --- a/src/Cake.Common/Tools/NUnit/NUnitSettings.cs +++ b/src/Cake.Common/Tools/NUnit/NUnitSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -119,13 +120,13 @@ public NUnitSettings() /// /// Gets or sets a value indicating how NUnit should load tests in processes. - /// The Default value is + /// The Default value is . /// public NUnitProcessOption Process { get; set; } /// /// Gets or sets a value indicating whether Single Threaded Apartment state (STA) will be used. - /// Corresponds to the /apartment command line option + /// Corresponds to the /apartment command line option. /// public bool UseSingleThreadedApartment { get; set; } @@ -136,5 +137,21 @@ public NUnitSettings() /// Otherwise a single domain is used. /// public NUnitAppDomainUsage AppDomainUsage { get; set; } + + /// + /// Gets or sets a value indicating whether to run tests in an x86 process on 64 bit systems. + /// + /// + /// true to run tests in an x86 process on 64 bit systems; otherwise, false. + /// + public bool X86 { get; set; } + + /// + /// Gets or sets a value indicating whether to cause an identifying label to be displayed at the start of each test case. + /// + /// + /// true to cause an identifying label to be displayed at the start of each test case; otherwise, false. + /// + public bool Labels { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Add/NuGetAddSettings.cs b/src/Cake.Common/Tools/NuGet/Add/NuGetAddSettings.cs new file mode 100644 index 0000000000..dcc893992f --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Add/NuGetAddSettings.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Add +{ + /// + /// Contains settings used by . + /// + public sealed class NuGetAddSettings : ToolSettings + { + /// + /// Gets or sets a a package sources to use for this command. + /// + /// The package sources to use for this command. + public string Source { get; set; } + + /// + /// Gets or sets a value indicating whether a package added to an offline feed is also expanded. + /// + /// true if package should also be expanded; otherwise, false. + public bool Expand { get; set; } + + /// + /// Gets or sets the output verbosity. + /// + /// The output verbosity. + public NuGetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. + /// + /// The NuGet configuration file. + public FilePath ConfigFile { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Add/NuGetAdder.cs b/src/Cake.Common/Tools/NuGet/Add/NuGetAdder.cs new file mode 100644 index 0000000000..47106bb4a7 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Add/NuGetAdder.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.IO.NuGet; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Add +{ + /// + /// The NuGet package add tool used to add NuGet packages to folder or UNC shares. + /// + public sealed class NuGetAdder : NuGetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The NuGet tool resolver. + public NuGetAdder(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, + IToolLocator tools, INuGetToolResolver resolver) + : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + } + + /// + /// Adds NuGet packages to the package source, which is a folder or a UNC share. Http sources are not supported. + /// + /// The source package id. + /// The settings. + public void Add(string packageId, NuGetAddSettings settings) + { + if (string.IsNullOrWhiteSpace(packageId)) + { + throw new ArgumentNullException(nameof(packageId)); + } + ArgumentNullException.ThrowIfNull(settings); + Run(settings, GetArguments(packageId, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageId, NuGetAddSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("add"); + builder.AppendQuoted(packageId); + + builder.Append("-Source"); + builder.AppendQuoted(settings.Source); + // Expand package? + if (settings.Expand) + { + builder.Append("-Expand"); + } + + // Verbosity? + if (settings.Verbosity.HasValue) + { + builder.Append("-Verbosity"); + builder.Append(settings.Verbosity.Value.ToString().ToLowerInvariant()); + } + + // Configuration file. + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + builder.Append("-NonInteractive"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleteSettings.cs b/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleteSettings.cs new file mode 100644 index 0000000000..fc59c95262 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleteSettings.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Delete +{ + /// + /// Contains settings used by . + /// + public sealed class NuGetDeleteSettings : ToolSettings + { + /// + /// Gets or sets the server URL. + /// When using NuGet pre 3.4.2, this value is optional + /// and nuget.org is used if omitted (unless DefaultPushSource + /// config value is set in the NuGet config file. + /// When using NuGet 3.4.2 (or more recent), this value is mandatory. + /// Starting with NuGet 2.5, if NuGet.exe identifies a UNC/folder source, + /// it will perform the file copy to the source. + /// + /// The server URL. + /// + /// For your convenience, here is the URL for some of the most popular + /// public NuGet servers: + /// - NuGet Gallery: https://nuget.org/api/v2/package + /// - MyGet: https://www.myget.org/F/<your_username>/api/v2/package. + /// + public string Source { get; set; } + + /// + /// Gets or sets the API key for the server. + /// + /// The API key for the server. + public string ApiKey { get; set; } + + /// + /// Gets or sets the verbosity. + /// + /// The verbosity. + public NuGetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// + /// The NuGet configuration file. + public FilePath ConfigFile { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleter.cs b/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleter.cs new file mode 100644 index 0000000000..7d9919c344 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Delete/NuGetDeleter.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Core.IO.NuGet; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Delete +{ + /// + /// The NuGet package pusher. + /// + public sealed class NuGetDeleter : NuGetTool + { + private readonly ICakeEnvironment _environment; + private readonly ICakeLog _log; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The NuGet tool resolver. + /// The logger. + public NuGetDeleter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + INuGetToolResolver resolver, + ICakeLog log) : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + _log = log; + } + + /// + /// Deletes or unlists a package from a package source. + /// + /// The package ID (name). + /// The package version. + /// The settings. + public void Delete(string packageID, string packageVersion, NuGetDeleteSettings settings) + { + if (string.IsNullOrWhiteSpace(packageID)) + { + throw new ArgumentNullException(nameof(packageID)); + } + if (string.IsNullOrWhiteSpace(packageVersion)) + { + throw new ArgumentNullException(nameof(packageVersion)); + } + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(packageID, packageVersion, settings)); + } + + private ProcessArgumentBuilder GetArguments(string packageID, string packageVersion, NuGetDeleteSettings settings) + { + var builder = new ProcessArgumentBuilder(); + builder.Append("delete"); + + builder.Append(packageID); + + builder.Append(packageVersion); + + if (settings.ApiKey != null) + { + builder.AppendSecret(settings.ApiKey); + } + + builder.Append("-NonInteractive"); + + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + if (settings.Source != null) + { + builder.Append("-Source"); + builder.AppendQuoted(settings.Source); + } + else + { + _log.Verbose("No Source property has been set. Depending on your configuration, this may cause problems."); + } + + if (settings.Verbosity != null) + { + builder.Append("-Verbosity"); + builder.Append(settings.Verbosity.Value.ToString().ToLowerInvariant()); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Init/NuGetInitSettings.cs b/src/Cake.Common/Tools/NuGet/Init/NuGetInitSettings.cs new file mode 100644 index 0000000000..afb69a7bf1 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Init/NuGetInitSettings.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Init +{ + /// + /// Contains settings used by . + /// + public class NuGetInitSettings : ToolSettings + { + /// + /// Gets or sets a value indicating whether a package added to an offline feed is also expanded. + /// + /// true if package should also be expanded; otherwise, false. + public bool Expand { get; set; } + + /// + /// Gets or sets the output verbosity. + /// + /// The output verbosity. + public NuGetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. + /// + /// The NuGet configuration file. + public FilePath ConfigFile { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Init/NuGetIniter.cs b/src/Cake.Common/Tools/NuGet/Init/NuGetIniter.cs new file mode 100644 index 0000000000..47e1d64413 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Init/NuGetIniter.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.IO.NuGet; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Init +{ + /// + /// The NuGet package init tool copies all the packages from the source to the hierarchical destination. + /// + public sealed class NuGetIniter : NuGetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The NuGet tool resolver. + public NuGetIniter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + INuGetToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + } + + /// + /// Init adds all the packages from the source to the hierarchical destination. + /// + /// Package source to be copied from. + /// Package destination to be copied to. + /// The settings. + public void Init(string sourcePackageSourcePath, string destinationPackageSourcePath, + NuGetInitSettings settings) + { + if (string.IsNullOrWhiteSpace(sourcePackageSourcePath)) + { + throw new ArgumentNullException(nameof(sourcePackageSourcePath)); + } + if (string.IsNullOrWhiteSpace(destinationPackageSourcePath)) + { + throw new ArgumentNullException(nameof(destinationPackageSourcePath)); + } + ArgumentNullException.ThrowIfNull(settings); + + var sourcePackagePath = sourcePackageSourcePath; + var destinationPackagePath = destinationPackageSourcePath; + + Run(settings, GetArguments(sourcePackagePath, destinationPackagePath, settings)); + } + + private ProcessArgumentBuilder GetArguments(string sourcePackageSourcePath, string destinationPackageSourcePath, NuGetInitSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("init"); + builder.AppendQuoted(sourcePackageSourcePath); + builder.AppendQuoted(destinationPackageSourcePath); + + // Expand package? + if (settings.Expand) + { + builder.Append("-Expand"); + } + + // Verbosity? + if (settings.Verbosity.HasValue) + { + builder.Append("-Verbosity"); + builder.Append(settings.Verbosity.Value.ToString().ToLowerInvariant()); + } + + // Configuration file. + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + builder.Append("-NonInteractive"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Install/NuGetInstallSettings.cs b/src/Cake.Common/Tools/NuGet/Install/NuGetInstallSettings.cs new file mode 100644 index 0000000000..4e86e47b0c --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Install/NuGetInstallSettings.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Install +{ + /// + /// Contains settings used by . + /// + public sealed class NuGetInstallSettings : ToolSettings + { + /// + /// Gets or sets the directory in which packages will be installed. + /// If none is specified, the current directory will be used. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets the version of the package to install. + /// If none specified, the latest will be used. + /// + public string Version { get; set; } + + /// + /// Gets or sets a value indicating whether to exclude the version number from the package folder. + /// + /// + /// true if to exclude the version number from the package folder; otherwise, false. + /// + public bool ExcludeVersion { get; set; } + + /// + /// Gets or sets a value indicating whether to allow installation of prerelease packages. + /// This flag is not required when restoring packages by installing from packages.config. + /// + /// + /// true to allow installation of prerelease packages; otherwise, false. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets a value indicating whether to check if package + /// install consent is granted before installing a package. + /// + /// + /// true if to check if package install consent is granted before installing a package; otherwise, false. + /// + public bool RequireConsent { get; set; } + + /// + /// Gets or sets the solution directory path for package restore. + /// + /// + /// The solution directory path. + /// + public DirectoryPath SolutionDirectory { get; set; } + + /// + /// Gets or sets a list of packages sources to use for this command. + /// + /// The list of packages sources to use for this command. + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether or not to use the machine cache as the first package source. + /// + /// + /// true to not use the machine cache as the first package source; otherwise, false. + /// + [System.Obsolete("NoCache is deprecated and has been renamed to NoHttpCache. Please use NoHttpCache instead.")] + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether or not use the HTTP cache and contact all configured package sources for live information. + /// + /// + /// true to not use the HTTP cache and contact package sources for live information; otherwise, false. + /// + public bool NoHttpCache { get; set; } + + /// + /// Gets or sets a value indicating whether to disable parallel processing of packages for this command. + /// Disable parallel processing of packages for this command. + /// + /// + /// true to disable parallel processing of packages for this command; otherwise, false. + /// + public bool DisableParallelProcessing { get; set; } + + /// + /// Gets or sets the output verbosity. + /// + /// The output verbosity. + public NuGetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. + /// + /// The NuGet configuration file. + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a list of packages sources to use as fallbacks for this command. + /// This setting requires NuGet V3 or later. + /// + /// The list of packages sources to use as fallbacks for this command. + public ICollection FallbackSource { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether or not NuGet suppresses prompts for user input or confirmations. + /// + /// + /// This setting is passed by NuGet.exe to any extensions such as authorization providers. + /// + /// + /// false to allow NuGet to show prompts for user input or confirmations; otherwise, true. + /// + public bool NonInteractive { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Install/NuGetInstaller.cs b/src/Cake.Common/Tools/NuGet/Install/NuGetInstaller.cs index ca3fa87ad9..9f421183f6 100644 --- a/src/Cake.Common/Tools/NuGet/Install/NuGetInstaller.cs +++ b/src/Cake.Common/Tools/NuGet/Install/NuGetInstaller.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -41,14 +42,8 @@ public NuGetInstaller( /// The settings. public void InstallFromConfig(FilePath packageConfigPath, NuGetInstallSettings settings) { - if (packageConfigPath == null) - { - throw new ArgumentNullException("packageConfigPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(packageConfigPath); + ArgumentNullException.ThrowIfNull(settings); var packageId = packageConfigPath.MakeAbsolute(_environment).FullPath; @@ -64,12 +59,9 @@ public void Install(string packageId, NuGetInstallSettings settings) { if (string.IsNullOrWhiteSpace(packageId)) { - throw new ArgumentNullException("packageId"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(packageId)); } + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(packageId, settings)); } @@ -124,21 +116,29 @@ private ProcessArgumentBuilder GetArguments(string packageId, NuGetInstallSettin if (settings.Source != null && settings.Source.Count > 0) { builder.Append("-Source"); - builder.AppendQuoted(string.Join(";", settings.Source)); + builder.AppendQuoted(string.Join(';', settings.Source)); } // List of package fallback sources. if (settings.FallbackSource != null && settings.FallbackSource.Count > 0) { builder.Append("-FallbackSource"); - builder.AppendQuoted(string.Join(";", settings.FallbackSource)); + builder.AppendQuoted(string.Join(';', settings.FallbackSource)); } // No Cache? +#pragma warning disable CS0618 if (settings.NoCache) { builder.Append("-NoCache"); } +#pragma warning restore CS0618 + + // No Http Cache? + if (settings.NoHttpCache) + { + builder.Append("-NoHttpCache"); + } // Disable Parallel Processing? if (settings.DisableParallelProcessing) @@ -160,9 +160,13 @@ private ProcessArgumentBuilder GetArguments(string packageId, NuGetInstallSettin builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); } - builder.Append("-NonInteractive"); + // NonInteractive? + if (settings.NonInteractive) + { + builder.Append("-NonInteractive"); + } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Install/NugetInstallSettings.cs b/src/Cake.Common/Tools/NuGet/Install/NugetInstallSettings.cs deleted file mode 100644 index e30872afe3..0000000000 --- a/src/Cake.Common/Tools/NuGet/Install/NugetInstallSettings.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.NuGet.Install -{ - /// - /// Contains settings used by . - /// - public sealed class NuGetInstallSettings : ToolSettings - { - /// - /// Gets or sets the directory in which packages will be installed. - /// If none is specified, the current directory will be used. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets the version of the package to install. - /// If none specified, the latest will be used. - /// - public string Version { get; set; } - - /// - /// Gets or sets a value indicating whether to exclude the version number from the package folder. - /// - /// - /// true if to exclude the version number from the package folder; otherwise, false. - /// - public bool ExcludeVersion { get; set; } - - /// - /// Gets or sets a value indicating whether to allow installation of prerelease packages. - /// This flag is not required when restoring packages by installing from packages.config. - /// - /// - /// true to allow installation of prerelease packages; otherwise, false. - /// - public bool Prerelease { get; set; } - - /// - /// Gets or sets a value indicating whether to check if package - /// install consent is granted before installing a package. - /// - /// - /// true if to check if package install consent is granted before installing a package; otherwise, false. - /// - public bool RequireConsent { get; set; } - - /// - /// Gets or sets the solution directory path for package restore. - /// - /// - /// The solution directory path. - /// - public DirectoryPath SolutionDirectory { get; set; } - - /// - /// Gets or sets a list of packages sources to use for this command. - /// - /// The list of packages sources to use for this command. - public ICollection Source { get; set; } - - /// - /// Gets or sets a value indicating whether or not to use the machine cache as the first package source. - /// - /// - /// true to not use the machine cache as the first package source; otherwise, false. - /// - public bool NoCache { get; set; } - - /// - /// Gets or sets a value indicating whether to disable parallel processing of packages for this command. - /// Disable parallel processing of packages for this command. - /// - /// - /// true to disable parallel processing of packages for this command; otherwise, false. - /// - public bool DisableParallelProcessing { get; set; } - - /// - /// Gets or sets the output verbosity. - /// - /// The output verbosity. - public NuGetVerbosity? Verbosity { get; set; } - - /// - /// Gets or sets the NuGet configuration file. - /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. - /// - /// The NuGet configuration file. - public FilePath ConfigFile { get; set; } - - /// - /// Gets or sets a list of packages sources to use as fallbacks for this command. - /// This setting requires NuGet V3 or later. - /// - /// The list of packages sources to use as fallbacks for this command. - public ICollection FallbackSource { get; set; } - } -} diff --git a/src/Cake.Common/Tools/NuGet/List/NuGetList.cs b/src/Cake.Common/Tools/NuGet/List/NuGetList.cs new file mode 100644 index 0000000000..4a5e92b3ca --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/List/NuGetList.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.IO.NuGet; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.List +{ + /// + /// The NuGet package lister used to list NuGet packages from a source. + /// + public sealed class NuGetList : NuGetTool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + /// The NuGet tool resolver. + public NuGetList( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools, + INuGetToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + { + _environment = environment; + } + + /// + /// Lists available packages with their versions. + /// + /// The settings. + /// A list of available packages. + public IEnumerable List(NuGetListSettings settings) + { + return List(String.Empty, settings); + } + + /// + /// Lists available packages with their versions. + /// + /// The source package id. If it equals an empty string, it will match all packageIds. + /// The settings. + /// A list of available packages. + public IEnumerable List(string packageId, NuGetListSettings settings) + { + ArgumentNullException.ThrowIfNull(packageId); + ArgumentNullException.ThrowIfNull(settings); + + var processSettings = new ProcessSettings + { + RedirectStandardOutput = true + }; + + IEnumerable result = null; + Run(settings, GetHasArguments(packageId, settings), processSettings, + process => result = process.GetStandardOutput()); + + return result.Select(line => ConvertToNuGetListItem(line)).ToList(); + } + + private NuGetListItem ConvertToNuGetListItem(string line) + { + var splitline = line.Split(' ', '\t'); + return new NuGetListItem() + { + Name = splitline[0], + Version = splitline[1] + }; + } + + private ProcessArgumentBuilder GetHasArguments(string packageId, NuGetListSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("list"); + if (!string.IsNullOrEmpty(packageId)) + { + builder.AppendQuoted(packageId); + } + + if (settings.AllVersions) + { + builder.Append("-AllVersions"); + } + + if (settings.IncludeDelisted) + { + builder.Append("-IncludeDelisted"); + } + + if (settings.Prerelease) + { + builder.Append("-Prerelease"); + } + + if (settings.Source != null && settings.Source.Count > 0) + { + builder.Append("-Source"); + builder.AppendQuoted(string.Join(';', settings.Source)); + } + + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.MakeAbsolute(_environment).FullPath); + } + + builder.Append("-Verbosity Normal"); + builder.Append("-NonInteractive"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/NuGet/List/NuGetListItem.cs b/src/Cake.Common/Tools/NuGet/List/NuGetListItem.cs new file mode 100644 index 0000000000..1833b32d4c --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/List/NuGetListItem.cs @@ -0,0 +1,18 @@ +namespace Cake.Common.Tools.NuGet.List +{ + /// + /// An item as returned by . + /// + public sealed class NuGetListItem + { + /// + /// Gets or sets the name of the NuGetListItem. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the NuGetListItem as string. + /// + public string Version { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/List/NuGetListSettings.cs b/src/Cake.Common/Tools/NuGet/List/NuGetListSettings.cs new file mode 100644 index 0000000000..c1b3c94df5 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/List/NuGetListSettings.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.List +{ + /// + /// Contains settings used by . + /// + public sealed class NuGetListSettings : ToolSettings + { + /// + /// Gets or sets a value indicating whether list all versions of a package.By default, only the latest package version is displayed. + /// + public bool AllVersions { get; set; } + + /// + /// Gets or sets a value indicating whether to allow prerelease packages to be shown. + /// + public bool Prerelease { get; set; } + + /// + /// Gets or sets a value indicating whether to allow unlisted packages to be shown. + /// + public bool IncludeDelisted { get; set; } + + /// + /// Gets or sets the NuGet configuration file. If not specified, file %AppData%\NuGet\NuGet.config is used as configuration file. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a list of packages sources to search. + /// + public ICollection Source { get; set; } = new List(); + } +} diff --git a/src/Cake.Common/Tools/NuGet/NuGetAliases.cs b/src/Cake.Common/Tools/NuGet/NuGetAliases.cs index cff51f9b9a..2325698d46 100644 --- a/src/Cake.Common/Tools/NuGet/NuGetAliases.cs +++ b/src/Cake.Common/Tools/NuGet/NuGetAliases.cs @@ -1,9 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using Cake.Common.Tools.NuGet.Add; +using Cake.Common.Tools.NuGet.Delete; +using Cake.Common.Tools.NuGet.Init; using Cake.Common.Tools.NuGet.Install; +using Cake.Common.Tools.NuGet.List; using Cake.Common.Tools.NuGet.Pack; using Cake.Common.Tools.NuGet.Push; using Cake.Common.Tools.NuGet.Restore; @@ -23,13 +28,13 @@ namespace Cake.Common.Tools.NuGet /// /// /// Since Cake requires NuGet to be available very early in the build pipeline, we recommend that NuGet is made - /// available via the Cake BootStrapper. + /// available via the Cake Bootstrapper. /// [CakeAliasCategory("NuGet")] public static class NuGetAliases { /// - /// Creates a NuGet package using the specified Nuspec or project file. + /// Creates a NuGet package using the specified nuspec or project file. /// /// The context. /// The nuspec or project file path. @@ -37,16 +42,16 @@ public static class NuGetAliases /// /// /// var nuGetPackSettings = new NuGetPackSettings { - /// Id = "TestNuget", + /// Id = "TestNuGet", /// Version = "0.0.0.1", /// Title = "The tile of the package", /// Authors = new[] {"John Doe"}, /// Owners = new[] {"Contoso"}, /// Description = "The description of the package", /// Summary = "Excellent summary of what the package does", - /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuget/"), - /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuget/master/icons/testnuget.png"), - /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuget/blob/master/LICENSE.md"), + /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuGet/"), + /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuGet/master/icons/testNuGet.png"), + /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuGet/blob/master/LICENSE.md"), /// Copyright = "Some company 2015", /// ReleaseNotes = new [] {"Bug fixes", "Issue fixes", "Typos"}, /// Tags = new [] {"Cake", "Script", "Build"}, @@ -54,13 +59,13 @@ public static class NuGetAliases /// Symbols = false, /// NoPackageAnalysis = true, /// Files = new [] { - /// new NuSpecContent {Source = "bin/TestNuget.dll", Target = "bin"}, + /// new NuSpecContent {Source = "bin/TestNuGet.dll", Target = "bin"}, /// }, - /// BasePath = "./src/TestNuget/bin/release", - /// OutputDirectory = "./nuget" + /// BasePath = "./src/TestNuGet/bin/release", + /// OutputDirectory = "./NuGet" /// }; /// - /// NuGetPack("./nuspec/TestNuget.nuspec", nuGetPackSettings); + /// NuGetPack("./nuspec/TestNuGet.nuspec", nuGetPackSettings); /// /// [CakeMethodAlias] @@ -68,10 +73,7 @@ public static class NuGetAliases [CakeNamespaceImport("Cake.Common.Tools.NuGet.Pack")] public static void NuGetPack(this ICakeContext context, FilePath filePath, NuGetPackSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var packer = new NuGetPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Log, context.Tools, resolver); @@ -79,7 +81,7 @@ public static void NuGetPack(this ICakeContext context, FilePath filePath, NuGet } /// - /// Creates NuGet packages using the specified Nuspec or project files. + /// Creates NuGet packages using the specified nuspec or project files. /// /// The context. /// The nuspec or project file paths. @@ -87,16 +89,16 @@ public static void NuGetPack(this ICakeContext context, FilePath filePath, NuGet /// /// /// var nuGetPackSettings = new NuGetPackSettings { - /// Id = "TestNuget", + /// Id = "TestNuGet", /// Version = "0.0.0.1", /// Title = "The tile of the package", /// Authors = new[] {"John Doe"}, /// Owners = new[] {"Contoso"}, /// Description = "The description of the package", /// Summary = "Excellent summary of what the package does", - /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuget/"), - /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuget/master/icons/testnuget.png"), - /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuget/blob/master/LICENSE.md"), + /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuGet/"), + /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuGet/master/icons/testNuGet.png"), + /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuGet/blob/master/LICENSE.md"), /// Copyright = "Some company 2015", /// ReleaseNotes = new [] {"Bug fixes", "Issue fixes", "Typos"}, /// Tags = new [] {"Cake", "Script", "Build"}, @@ -104,10 +106,10 @@ public static void NuGetPack(this ICakeContext context, FilePath filePath, NuGet /// Symbols = false, /// NoPackageAnalysis = true, /// Files = new [] { - /// new NuSpecContent {Source = "bin/TestNuget.dll", Target = "bin"}, + /// new NuSpecContent {Source = "bin/TestNuGet.dll", Target = "bin"}, /// }, - /// BasePath = "./src/TestNuget/bin/release", - /// OutputDirectory = "./nuget" + /// BasePath = "./src/TestNuGet/bin/release", + /// OutputDirectory = "./NuGet" /// }; /// /// var nuspecFiles = GetFiles("./**/*.nuspec"); @@ -119,10 +121,7 @@ public static void NuGetPack(this ICakeContext context, FilePath filePath, NuGet [CakeNamespaceImport("Cake.Common.Tools.NuGet.Pack")] public static void NuGetPack(this ICakeContext context, IEnumerable filePaths, NuGetPackSettings settings) { - if (filePaths == null) - { - throw new ArgumentNullException("filePaths"); - } + ArgumentNullException.ThrowIfNull(filePaths); foreach (var filePath in filePaths) { @@ -138,16 +137,16 @@ public static void NuGetPack(this ICakeContext context, IEnumerable fi /// /// /// var nuGetPackSettings = new NuGetPackSettings { - /// Id = "TestNuget", + /// Id = "TestNuGet", /// Version = "0.0.0.1", /// Title = "The tile of the package", /// Authors = new[] {"John Doe"}, /// Owners = new[] {"Contoso"}, /// Description = "The description of the package", /// Summary = "Excellent summary of what the package does", - /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuget/"), - /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuget/master/icons/testnuget.png"), - /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuget/blob/master/LICENSE.md"), + /// ProjectUrl = new Uri("https://github.com/SomeUser/TestNuGet/"), + /// IconUrl = new Uri("http://cdn.rawgit.com/SomeUser/TestNuGet/master/icons/testNuGet.png"), + /// LicenseUrl = new Uri("https://github.com/SomeUser/TestNuGet/blob/master/LICENSE.md"), /// Copyright = "Some company 2015", /// ReleaseNotes = new [] {"Bug fixes", "Issue fixes", "Typos"}, /// Tags = new [] {"Cake", "Script", "Build"}, @@ -155,10 +154,10 @@ public static void NuGetPack(this ICakeContext context, IEnumerable fi /// Symbols = false, /// NoPackageAnalysis = true, /// Files = new [] { - /// new NuSpecContent {Source = "bin/TestNuget.dll", Target = "bin"}, + /// new NuSpecContent {Source = "bin/TestNuGet.dll", Target = "bin"}, /// }, - /// BasePath = "./src/TestNuget/bin/release", - /// OutputDirectory = "./nuget" + /// BasePath = "./src/TestNuGet/bin/release", + /// OutputDirectory = "./NuGet" /// }; /// /// NuGetPack(nuGetPackSettings); @@ -169,10 +168,7 @@ public static void NuGetPack(this ICakeContext context, IEnumerable fi [CakeNamespaceImport("Cake.Common.Tools.NuGet.Pack")] public static void NuGetPack(this ICakeContext context, NuGetPackSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var packer = new NuGetPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Log, context.Tools, resolver); @@ -188,7 +184,7 @@ public static void NuGetPack(this ICakeContext context, NuGetPackSettings settin /// /// var solutions = GetFiles("./**/*.sln"); /// // Restore all NuGet packages. - /// foreach(var solution in solutions) + /// foreach (var solution in solutions) /// { /// Information("Restoring {0}", solution); /// NuGetRestore(solution); @@ -234,7 +230,7 @@ public static void NuGetRestore(this ICakeContext context, IEnumerable /// /// var solutions = GetFiles("./**/*.sln"); /// // Restore all NuGet packages. - /// foreach(var solution in solutions) + /// foreach (var solution in solutions) /// { /// Information("Restoring {0}", solution); /// NuGetRestore(solution, new NuGetRestoreSettings { NoCache = true }); @@ -246,10 +242,7 @@ public static void NuGetRestore(this ICakeContext context, IEnumerable [CakeNamespaceImport("Cake.Common.Tools.NuGet.Restore")] public static void NuGetRestore(this ICakeContext context, FilePath targetFilePath, NuGetRestoreSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetRestorer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -273,10 +266,7 @@ public static void NuGetRestore(this ICakeContext context, FilePath targetFilePa [CakeNamespaceImport("Cake.Common.Tools.NuGet.Restore")] public static void NuGetRestore(this ICakeContext context, IEnumerable targetFilePaths, NuGetRestoreSettings settings) { - if (targetFilePaths == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(targetFilePaths); foreach (var targetFilePath in targetFilePaths) { @@ -291,6 +281,8 @@ public static void NuGetRestore(this ICakeContext context, IEnumerable /// The .nupkg file path. /// The settings. /// + /// NOTE: Starting with NuGet 3.4.2, the Source parameter is a mandatory parameter. + /// It is strongly recommended that you ALWAYS set the Source property within the instance. /// /// // Get the path to the package. /// var package = "./nuget/SlackPRTGCommander.0.0.1.nupkg"; @@ -307,13 +299,10 @@ public static void NuGetRestore(this ICakeContext context, IEnumerable [CakeNamespaceImport("Cake.Common.Tools.NuGet.Push")] public static void NuGetPush(this ICakeContext context, FilePath packageFilePath, NuGetPushSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); - var packer = new NuGetPusher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + var packer = new NuGetPusher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver, context.Log); packer.Push(packageFilePath, settings); } @@ -324,6 +313,8 @@ public static void NuGetPush(this ICakeContext context, FilePath packageFilePath /// The .nupkg file paths. /// The settings. /// + /// NOTE: Starting with NuGet 3.4.2, the Source parameter is a mandatory parameter. + /// It is strongly recommended that you ALWAYS set the Source property within the instance. /// /// // Get the paths to the packages. /// var packages = GetFiles("./**/*.nupkg"); @@ -340,10 +331,7 @@ public static void NuGetPush(this ICakeContext context, FilePath packageFilePath [CakeNamespaceImport("Cake.Common.Tools.NuGet.Push")] public static void NuGetPush(this ICakeContext context, IEnumerable packageFilePaths, NuGetPushSettings settings) { - if (packageFilePaths == null) - { - throw new ArgumentNullException("packageFilePaths"); - } + ArgumentNullException.ThrowIfNull(packageFilePaths); foreach (var packageFilePath in packageFilePaths) { @@ -352,7 +340,7 @@ public static void NuGetPush(this ICakeContext context, IEnumerable pa } /// - /// Adds NuGet package source using the specified name &source to global user config + /// Adds NuGet package source using the specified name &source to global user config. /// /// The context. /// Name of the source. @@ -380,7 +368,7 @@ public static void NuGetAddSource(this ICakeContext context, string name, string } /// - /// Adds NuGet package source using the specified name, source & settings to global user config + /// Adds NuGet package source using the specified name, source & settings to global user config. /// /// The context. /// Name of the source. @@ -414,10 +402,7 @@ public static void NuGetAddSource(this ICakeContext context, string name, string [CakeNamespaceImport("Cake.Common.Tools.NuGet.Sources")] public static void NuGetAddSource(this ICakeContext context, string name, string source, NuGetSourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetSources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -425,7 +410,7 @@ public static void NuGetAddSource(this ICakeContext context, string name, string } /// - /// Removes NuGet package source using the specified name & source from global user config + /// Removes NuGet package source using the specified name & source from global user config. /// /// The context. /// Name of the source. @@ -453,7 +438,7 @@ public static void NuGetRemoveSource(this ICakeContext context, string name, str } /// - /// Removes NuGet package source using the specified name, source & settings from global user config + /// Removes NuGet package source using the specified name, source & settings from global user config. /// /// The context. /// Name of the source. @@ -487,10 +472,7 @@ public static void NuGetRemoveSource(this ICakeContext context, string name, str [CakeNamespaceImport("Cake.Common.Tools.NuGet.Sources")] public static void NuGetRemoveSource(this ICakeContext context, string name, string source, NuGetSourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetSources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -566,10 +548,7 @@ public static bool NuGetHasSource(this ICakeContext context, string source) [CakeNamespaceImport("Cake.Common.Tools.NuGet.Sources")] public static bool NuGetHasSource(this ICakeContext context, string source, NuGetSourcesSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetSources(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -583,7 +562,7 @@ public static bool NuGetHasSource(this ICakeContext context, string source, NuGe /// The id of the package to install. /// /// - /// NuGetInstall("MyNugetPackage"); + /// NuGetInstall("MyNuGetPackage"); /// /// [CakeMethodAlias] @@ -602,7 +581,7 @@ public static void NuGetInstall(this ICakeContext context, string packageId) /// The id's of the package to install. /// /// - /// NuGetInstall(new[] { "MyNugetPackage", "OtherNugetPackage" }); + /// NuGetInstall(new[] { "MyNuGetPackage", "OtherNuGetPackage" }); /// /// [CakeMethodAlias] @@ -622,7 +601,7 @@ public static void NuGetInstall(this ICakeContext context, IEnumerable p /// The settings. /// /// - /// NuGetInstall("MyNugetPackage", new NuGetInstallSettings { + /// NuGetInstall("MyNuGetPackage", new NuGetInstallSettings { /// ExcludeVersion = true, /// OutputDirectory = "./tools" /// }); @@ -633,10 +612,7 @@ public static void NuGetInstall(this ICakeContext context, IEnumerable p [CakeNamespaceImport("Cake.Common.Tools.NuGet.Install")] public static void NuGetInstall(this ICakeContext context, string packageId, NuGetInstallSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -651,7 +627,7 @@ public static void NuGetInstall(this ICakeContext context, string packageId, NuG /// The settings. /// /// - /// NuGetInstall(new[] { "MyNugetPackage", "OtherNugetPackage" }, new NuGetInstallSettings { + /// NuGetInstall(new[] { "MyNuGetPackage", "OtherNuGetPackage" }, new NuGetInstallSettings { /// ExcludeVersion = true, /// OutputDirectory = "./tools" /// }); @@ -662,10 +638,7 @@ public static void NuGetInstall(this ICakeContext context, string packageId, NuG [CakeNamespaceImport("Cake.Common.Tools.NuGet.Install")] public static void NuGetInstall(this ICakeContext context, IEnumerable packageIds, NuGetInstallSettings settings) { - if (packageIds == null) - { - throw new ArgumentNullException("packageIds"); - } + ArgumentNullException.ThrowIfNull(packageIds); foreach (var packageId in packageIds) { @@ -732,10 +705,7 @@ public static void NuGetInstallFromConfig(this ICakeContext context, IEnumerable [CakeNamespaceImport("Cake.Common.Tools.NuGet.Install")] public static void NuGetInstallFromConfig(this ICakeContext context, FilePath packageConfigPath, NuGetInstallSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetInstaller(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -763,10 +733,7 @@ public static void NuGetInstallFromConfig(this ICakeContext context, FilePath pa [CakeNamespaceImport("Cake.Common.Tools.NuGet.Install")] public static void NuGetInstallFromConfig(this ICakeContext context, IEnumerable packageConfigPaths, NuGetInstallSettings settings) { - if (packageConfigPaths == null) - { - throw new ArgumentNullException("packageConfigPaths"); - } + ArgumentNullException.ThrowIfNull(packageConfigPaths); foreach (var packageConfigPath in packageConfigPaths) { @@ -794,10 +761,7 @@ public static void NuGetInstallFromConfig(this ICakeContext context, IEnumerable [CakeNamespaceImport("Cake.Common.Tools.NuGet.SetApiKey")] public static void NuGetSetApiKey(this ICakeContext context, string apiKey, string source, NuGetSetApiKeySettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetSetApiKey(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -844,10 +808,7 @@ public static void NuGetSetApiKey(this ICakeContext context, string apiKey, stri [CakeNamespaceImport("Cake.Common.Tools.NuGet.SetProxy")] public static void NuGetSetProxy(this ICakeContext context, string proxy, string username, string password, NuGetSetProxySettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetSetProxy(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -932,10 +893,7 @@ public static void NuGetUpdate(this ICakeContext context, IEnumerable [CakeNamespaceImport("Cake.Common.Tools.NuGet.Update")] public static void NuGetUpdate(this ICakeContext context, FilePath targetFile, NuGetUpdateSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); var runner = new NuGetUpdater(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); @@ -962,15 +920,233 @@ public static void NuGetUpdate(this ICakeContext context, FilePath targetFile, N [CakeNamespaceImport("Cake.Common.Tools.NuGet.Update")] public static void NuGetUpdate(this ICakeContext context, IEnumerable targetFiles, NuGetUpdateSettings settings) { - if (targetFiles == null) - { - throw new ArgumentNullException("targetFiles"); - } + ArgumentNullException.ThrowIfNull(targetFiles); foreach (var targetFile in targetFiles) { NuGetUpdate(context, targetFile, settings); } } + + /// + /// Adds a NuGet package using package id and source. + /// + /// The context. + /// The id of the package to add. + /// Path to the local feed source. + /// + /// + /// NuGetAdd("MyNuGetPackage", "//bar/packages/"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Add")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.Add")] + public static void NuGetAdd(this ICakeContext context, string packageId, string source) + { + ArgumentNullException.ThrowIfNull(context); + if (string.IsNullOrWhiteSpace(source)) + { + throw new ArgumentNullException(nameof(source)); + } + + NuGetAdd(context, packageId, new NuGetAddSettings { Source = source }); + } + + /// + /// Adds a NuGet package using package id and source. + /// + /// The context. + /// The id of the package to add. + /// The settings. + /// + /// + /// NuGetAdd("MyNuGetPackage", new NuGetAddSettings { + /// Source = "//bar/packages/" + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Add")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.Add")] + public static void NuGetAdd(this ICakeContext context, string packageId, NuGetAddSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(settings); + + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var runner = new NuGetAdder(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.Add(packageId, settings); + } + + /// + /// Adds all packages from source to destination. + /// + /// The context. + /// The local feed package source. + /// The local feed destination source. + /// + /// + /// NuGetInit("//foo/packages", "//bar/packages/"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Init")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.Init")] + public static void NuGetInit(this ICakeContext context, string source, string destination) + { + ArgumentNullException.ThrowIfNull(context); + if (string.IsNullOrWhiteSpace(source)) + { + throw new ArgumentNullException(nameof(source)); + } + if (string.IsNullOrWhiteSpace(destination)) + { + throw new ArgumentNullException(nameof(destination)); + } + + NuGetInit(context, source, destination, new NuGetInitSettings()); + } + + /// + /// Adds all packages from source to destination using specified settings. + /// + /// The context. + /// The local feed package source. + /// The local feed destination source. + /// The settings. + /// + /// + /// NuGetInit("//foo/packages", "//bar/packages/", new NuGetInitSettings { + /// Expand = true + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Init")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.Init")] + public static void NuGetInit(this ICakeContext context, string source, string destination, NuGetInitSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var runner = new NuGetIniter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + runner.Init(source, destination, settings); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The package Id. + /// The settings. + /// List of packages with their version. + /// + /// + /// var packageList = NuGetList("Cake", new NuGetListSettings { + /// AllVersions = false, + /// Prerelease = false + /// }); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("List")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.List")] + public static IEnumerable NuGetList(this ICakeContext context, string packageId, NuGetListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var runner = new NuGetList(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + return runner.List(packageId, settings); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The package Id. + /// List of packages with their version. + /// + /// + /// var packageList = NuGetList("Cake"); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("List")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.List")] + public static IEnumerable NuGetList(this ICakeContext context, string packageId) + { + ArgumentNullException.ThrowIfNull(context); + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var runner = new NuGetList(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + return runner.List(packageId, new NuGetListSettings()); + } + + /// + /// List packages on available from source using specified settings. + /// + /// The context. + /// The settings. + /// List of packages with their version. + /// + /// + /// var packageList = NuGetList(new NuGetListSettings { + /// AllVersions = false, + /// Prerelease = false + /// }); + /// foreach (var package in packageList) + /// { + /// Information("Found package {0}, version {1}", package.Name, package.Version); + /// } + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("List")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.List")] + public static IEnumerable NuGetList(this ICakeContext context, NuGetListSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var runner = new NuGetList(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver); + return runner.List(settings); + } + + /// + /// Deletes or unlists a package from a package source. + /// + /// The context. + /// The package ID (name). + /// The package version. + /// The settings. + /// + /// NOTE: Starting with NuGet 3.4.2, the Source parameter is a mandatory parameter. + /// It is strongly recommended that you ALWAYS set the Source property within the instance. + /// + /// // Delete the package. + /// NuGetDelete("PackageName", "PackageVersion", new NuGetPushSettings { + /// Source = "http://example.com/nugetfeed", + /// ApiKey = "4003d786-cc37-4004-bfdf-c4f3e8ef9b3a" + /// }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Delete")] + [CakeNamespaceImport("Cake.Common.Tools.NuGet.Delete")] + public static void NuGetDelete(this ICakeContext context, string packageID, string packageVersion, NuGetDeleteSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var resolver = new NuGetToolResolver(context.FileSystem, context.Environment, context.Tools); + var packer = new NuGetDeleter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, resolver, context.Log); + packer.Delete(packageID, packageVersion, settings); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersion.cs b/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersion.cs index c1373f5b8d..39d37c30d9 100644 --- a/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersion.cs +++ b/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersion.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NuGet { /// - /// NuGet MSBuild version + /// NuGet MSBuild version. /// public enum NuGetMSBuildVersion { @@ -21,6 +22,51 @@ public enum NuGetMSBuildVersion /// /// MSBuildVersion : 14 /// - MSBuild14 = 14 + MSBuild14 = 14, + + /// + /// MSBuildVersion : 15.1 + /// + MSBuild15_1 = 1501, + + /// + /// MSBuildVersion : 15.3 + /// + MSBuild15_3 = 1503, + + /// + /// MSBuildVersion : 15.4 + /// + MSBuild15_4 = 1504, + + /// + /// MSBuildVersion : 15.5 + /// + MSBuild15_5 = 1505, + + /// + /// MSBuildVersion : 15.6 + /// + MSBuild15_6 = 1506, + + /// + /// MSBuildVersion : 15.7 + /// + MSBuild15_7 = 1507, + + /// + /// MSBuildVersion : 15.8 + /// + MSBuild15_8 = 1508, + + /// + /// MSBuildVersion : 15.9 + /// + MSBuild15_9 = 1509, + + /// + /// MSBuildVersion : 16.0 + /// + MSBuild16_0 = 1600 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersionExtensions.cs b/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersionExtensions.cs new file mode 100644 index 0000000000..9c776ea4c7 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/NuGetMSBuildVersionExtensions.cs @@ -0,0 +1,60 @@ +using System; + +namespace Cake.Common.Tools.NuGet +{ + /// + /// Contains extension methods for . + /// + public static class NuGetMSBuildVersionExtensions + { + /// + /// Gets the string with MSBuild version. + /// + /// The NuGet MSBuild version. + /// The string with MSBuild version. + public static string GetNuGetMSBuildVersionString(this NuGetMSBuildVersion nuGetMSBuildVersion) + { + switch (nuGetMSBuildVersion) + { + case NuGetMSBuildVersion.MSBuild4: + return "4"; + + case NuGetMSBuildVersion.MSBuild12: + return "12"; + + case NuGetMSBuildVersion.MSBuild14: + return "14"; + + case NuGetMSBuildVersion.MSBuild15_1: + return "15.1"; + + case NuGetMSBuildVersion.MSBuild15_3: + return "15.3"; + + case NuGetMSBuildVersion.MSBuild15_4: + return "15.4"; + + case NuGetMSBuildVersion.MSBuild15_5: + return "15.5"; + + case NuGetMSBuildVersion.MSBuild15_6: + return "15.6"; + + case NuGetMSBuildVersion.MSBuild15_7: + return "15.7"; + + case NuGetMSBuildVersion.MSBuild15_8: + return "15.8"; + + case NuGetMSBuildVersion.MSBuild15_9: + return "15.9"; + + case NuGetMSBuildVersion.MSBuild16_0: + return "16.0"; + + default: + throw new ArgumentOutOfRangeException(nameof(nuGetMSBuildVersion), nuGetMSBuildVersion, "Invalid nuGetMSBuildVersion supplied."); + } + } + } +} diff --git a/src/Cake.Common/Tools/NuGet/NuGetTool.cs b/src/Cake.Common/Tools/NuGet/NuGetTool.cs index 9c4f4191ec..54304a6163 100644 --- a/src/Cake.Common/Tools/NuGet/NuGetTool.cs +++ b/src/Cake.Common/Tools/NuGet/NuGetTool.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Core; @@ -56,7 +57,7 @@ protected sealed override IEnumerable GetToolExecutableNames() } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. @@ -71,4 +72,4 @@ protected sealed override IEnumerable GetAlternativeToolPaths(TSetting return Enumerable.Empty(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/NuGetVerbosity.cs b/src/Cake.Common/Tools/NuGet/NuGetVerbosity.cs index a402616685..c6e222a657 100644 --- a/src/Cake.Common/Tools/NuGet/NuGetVerbosity.cs +++ b/src/Cake.Common/Tools/NuGet/NuGetVerbosity.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NuGet { /// @@ -23,4 +24,4 @@ public enum NuGetVerbosity /// Detailed = 4 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuGetPackSettings.cs b/src/Cake.Common/Tools/NuGet/Pack/NuGetPackSettings.cs index 5d0c248233..c13d12a91b 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuGetPackSettings.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuGetPackSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.IO; @@ -52,6 +53,12 @@ public sealed class NuGetPackSettings : ToolSettings /// public bool Symbols { get; set; } + /// + /// Gets or sets the symbol package format. + /// + /// The symbol package format. + public string SymbolPackageFormat { get; set; } + /// /// Gets or sets the package ID. /// @@ -59,11 +66,17 @@ public sealed class NuGetPackSettings : ToolSettings public string Id { get; set; } /// - /// Gets or sets the Nuspec version. + /// Gets or sets the nuspec version. /// - /// The Nuspec version. + /// The nuspec version. public string Version { get; set; } + /// + /// Gets or sets the nuspec version suffix. + /// + /// The nuspec version suffix. + public string Suffix { get; set; } + /// /// Gets or sets the package title. /// @@ -74,13 +87,13 @@ public sealed class NuGetPackSettings : ToolSettings /// Gets or sets the package authors. /// /// The package authors. - public ICollection Authors { get; set; } + public ICollection Authors { get; set; } = new List(); /// /// Gets or sets the package owners. /// /// The package owners. - public ICollection Owners { get; set; } + public ICollection Owners { get; set; } = new List(); /// /// Gets or sets the package description. @@ -100,6 +113,12 @@ public sealed class NuGetPackSettings : ToolSettings /// The package project URL. public Uri ProjectUrl { get; set; } + /// + /// Gets or sets the Icon path. + /// + /// The path to the icon file contained within the NuGet package. + public string Icon { get; set; } + /// /// Gets or sets the package icon URL. /// @@ -122,13 +141,45 @@ public sealed class NuGetPackSettings : ToolSettings /// Gets or sets the package release notes. /// /// The package release notes. - public ICollection ReleaseNotes { get; set; } + public ICollection ReleaseNotes { get; set; } = new List(); /// /// Gets or sets the package tags. /// /// The package tags. - public ICollection Tags { get; set; } + public ICollection Tags { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether this package should be marked as a serviceable. + /// + /// + /// true if serviceable; otherwise, false. + /// + public bool? Serviceable { get; set; } + + /// + /// Gets or sets the package repository data. + /// + /// The package repository data. + public NuGetRepository Repository { get; set; } + + /// + /// Gets or sets the package license. + /// + /// The package license. + public NuSpecLicense License { get; set; } + + /// + /// Gets or sets the package types. + /// + /// The package types. + public ICollection PackageTypes { get; set; } = new List(); + + /// + /// Gets or sets the package framework assemblies. + /// + /// The package framework assemblies. + public ICollection FrameworkAssemblies { get; set; } = new List(); /// /// Gets or sets a value indicating whether this package should be marked as a development dependency. @@ -136,7 +187,7 @@ public sealed class NuGetPackSettings : ToolSettings /// /// true if a development dependency; otherwise, false. /// - public bool DevelopmentDependency { get; set; } + public bool? DevelopmentDependency { get; set; } /// /// Gets or sets a value indicating whether users has to accept the package license. @@ -144,19 +195,37 @@ public sealed class NuGetPackSettings : ToolSettings /// /// true if users has to accept the package license; otherwise, false. /// - public bool RequireLicenseAcceptance { get; set; } + public bool? RequireLicenseAcceptance { get; set; } + + /// + /// Gets or sets the package references. + /// + /// The package references. + public ICollection References { get; set; } = new List(); + + /// + /// Gets or sets the package content files. + /// + /// The package content files. + public ICollection ContentFiles { get; set; } = new List(); + + /// + /// Gets or sets the package minimum client version. + /// + /// The package minimum client version. + public string MinClientVersion { get; set; } /// /// Gets or sets the package files. /// /// The package files. - public ICollection Files { get; set; } + public ICollection Files { get; set; } = new List(); /// /// Gets or sets the package dependencies. /// /// The package files. - public ICollection Dependencies { get; set; } + public ICollection Dependencies { get; set; } = new List(); /// /// Gets or sets the verbosity. @@ -170,7 +239,7 @@ public sealed class NuGetPackSettings : ToolSettings /// /// The properties. /// - public IDictionary Properties { get; set; } + public IDictionary Properties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets or sets the version of MSBuild to be used with this command. @@ -179,5 +248,38 @@ public sealed class NuGetPackSettings : ToolSettings /// /// The version of MSBuild to be used with this command. public NuGetMSBuildVersion? MSBuildVersion { get; set; } + + /// + /// Gets or sets a value indicating whether the temporarily autogenerated NuSpec file should be kept or not. + /// Defaults to false. + /// + /// + /// true if the temporarily autogenerated NuSpec file should be kept; otherwise false. + /// + public bool KeepTemporaryNuSpecFile { get; set; } + + /// + /// Gets or sets the package language. + /// + /// The package language. + public string Language { get; set; } + + /// + /// Gets or sets a value indicating whether the files should be packed into the tool folder. + /// Defaults to false. + /// + /// + /// true if the output should be placed in the tool folder inside the NuGet package; otherwise false. + /// + public bool OutputToToolFolder { get; set; } + + /// + /// Gets or sets the path to the readme file in the package. + /// The file needs to be part of the package files. + /// + /// + /// The path to a file included in the package, relative to the root of the package. + /// + public FilePath ReadMe { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuGetPacker.cs b/src/Cake.Common/Tools/NuGet/Pack/NuGetPacker.cs index 0c7e521cf8..48c0e750e5 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuGetPacker.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuGetPacker.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Linq; using Cake.Core; @@ -28,7 +29,7 @@ public sealed class NuGetPacker : NuGetTool /// The process runner. /// The log. /// The tool locator. - /// The NuGet tool resolver + /// The NuGet tool resolver. public NuGetPacker( IFileSystem fileSystem, ICakeEnvironment environment, @@ -48,10 +49,7 @@ public NuGetPacker( /// The settings. public void Pack(NuGetPackSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); if (settings.OutputDirectory == null || !_fileSystem.Exist(settings.OutputDirectory)) { throw new CakeException("Required setting OutputDirectory not specified or doesn't exists."); @@ -72,7 +70,7 @@ public void Pack(NuGetPackSettings settings) { throw new CakeException("Required setting Description not specified."); } - if (settings.Files == null || settings.Files.Count == 0) + if ((settings.Files == null || settings.Files.Count == 0) && (settings.Dependencies == null || settings.Dependencies.Count == 0)) { throw new CakeException("Required setting Files not specified."); } @@ -81,20 +79,14 @@ public void Pack(NuGetPackSettings settings) } /// - /// Creates a NuGet package from the specified Nuspec or project file. + /// Creates a NuGet package from the specified nuspec or project file. /// /// The nuspec or project file path. /// The settings. public void Pack(FilePath filePath, NuGetPackSettings settings) { - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(settings); string extension = filePath.GetExtension(); if (extension == ".csproj" @@ -123,7 +115,7 @@ private void Pack(NuGetPackSettings settings, Func process) } finally { - if (processedNuspecFilePath != null) + if (processedNuspecFilePath != null && !settings.KeepTemporaryNuSpecFile) { // Delete the processed file. var file = _fileSystem.GetFile(processedNuspecFilePath); @@ -147,6 +139,13 @@ private ProcessArgumentBuilder GetArguments(FilePath filePath, NuGetPackSettings builder.AppendQuoted(settings.Version); } + // Version suffix + if (!string.IsNullOrWhiteSpace(settings.Suffix)) + { + builder.Append("-Suffix"); + builder.AppendQuoted(settings.Suffix); + } + // Base path if (settings.BasePath != null) { @@ -182,6 +181,13 @@ private ProcessArgumentBuilder GetArguments(FilePath filePath, NuGetPackSettings builder.Append("-Symbols"); } + // SymbolPackageFormat + if (!string.IsNullOrWhiteSpace(settings.SymbolPackageFormat)) + { + builder.Append("-SymbolPackageFormat"); + builder.Append(settings.SymbolPackageFormat); + } + // Verbosity if (settings.Verbosity != null) { @@ -193,7 +199,13 @@ private ProcessArgumentBuilder GetArguments(FilePath filePath, NuGetPackSettings if (settings.MSBuildVersion.HasValue) { builder.Append("-MSBuildVersion"); - builder.Append(settings.MSBuildVersion.Value.ToString("D")); + builder.Append(settings.MSBuildVersion.Value.GetNuGetMSBuildVersionString()); + } + + // Use the tool folder + if (settings.OutputToToolFolder) + { + builder.Append("-Tool"); } // Properties @@ -209,11 +221,11 @@ private ProcessArgumentBuilder GetArguments(FilePath filePath, NuGetPackSettings throw new CakeException("Properties keys can not be null or empty."); } builder.Append("-Properties"); - builder.Append(string.Join(";", + builder.AppendQuoted(string.Join(';', settings.Properties.Select(property => string.Concat(property.Key, "=", property.Value)))); } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuGetRepository.cs b/src/Cake.Common/Tools/NuGet/Pack/NuGetRepository.cs new file mode 100644 index 0000000000..5cb910c900 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuGetRepository.cs @@ -0,0 +1,28 @@ +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies the package's source code location, allowing IDEs to download and debug the code. + /// + public class NuGetRepository + { + /// + /// Gets or sets the type of repository e.g. Git. + /// + public string Type { get; set; } + + /// + /// Gets or sets the repository's URL. + /// + public string Url { get; set; } + + /// + /// Gets or sets the name of the branch within the repository. + /// + public string Branch { get; set; } + + /// + /// Gets or sets the corresponding commit ID for the specified version of the package. + /// + public string Commit { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecContent.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecContent.cs index c8fae0db83..0d3af41972 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuSpecContent.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecContent.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.NuGet.Pack { /// - /// Represents a NuGet nuspec file + /// Represents a NuGet nuspec file. /// public sealed class NuSpecContent { @@ -29,4 +30,4 @@ public sealed class NuSpecContent /// public string Exclude { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecContentFile.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecContentFile.cs new file mode 100644 index 0000000000..94eb591d6e --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecContentFile.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies the package's content to identify the exact files that are included in the package. + /// + public class NuSpecContentFile + { + /// + /// Gets or sets the location of the file or files to include. + /// The wildcard character - * - is allowed. + /// Using a double wildcard - ** implies a recursive directory search. + /// + public string Include { get; set; } + + /// + /// Gets or sets files or file patterns to exclude from src location. + /// The wildcard character - * - is allowed. + /// Using a double wildcard - ** implies a recursive directory search. + /// + public string Exclude { get; set; } + + /// + /// Gets or sets the build action to assign to the content item for MSBuild, e.g. Compile. + /// Defaults to Compile. + /// + public string BuildAction { get; set; } + + /// + /// Gets or sets a value indicating whether to copy content items to the build (or publish) output folder. + /// Defaults to false. + /// + /// + /// true if content items should be copied to the output folder; otherwise, false. + /// + public bool CopyToOutput { get; set; } + + /// + /// Gets or sets a value indicating whether to copy content items to a single folder in the build output + /// or to preserve the folder structure in the package. This flag only works when copyToOutput + /// flag is set to true. Defaults to false. + /// + /// + /// true if content items should be copied to a single folder; otherwise, false. + /// + public bool Flatten { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecDependency.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecDependency.cs index 82862b3ff2..e17dbd6a32 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuSpecDependency.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecDependency.cs @@ -1,10 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + namespace Cake.Common.Tools.NuGet.Pack { /// - /// Represents a NuGet nuspec dependency + /// Represents a NuGet nuspec dependency. /// public class NuSpecDependency { @@ -19,5 +22,24 @@ public class NuSpecDependency /// /// The dependency's version. public string Version { get; set; } + + /// + /// Gets or sets a list of include/exclude tags indicating of the dependency to + /// include in the final package. The default value is all. + /// + public ICollection Include { get; set; } = new List(); + + /// + /// Gets or sets a list of include/exclude tags indicating of the dependency to + /// exclude in the final package. The default value is build,analyzers which can + /// be over-written. + /// + public ICollection Exclude { get; set; } = new List(); + + /// + /// Gets or sets the target framework for the dependency. + /// + /// The target framework for the dependency. + public string TargetFramework { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecFrameworkAssembly.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecFrameworkAssembly.cs new file mode 100644 index 0000000000..5a9a27b2d1 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecFrameworkAssembly.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies framework assemblies to ensure that required references are added to a project. + /// + public class NuSpecFrameworkAssembly + { + /// + /// Gets or sets the fully qualified assembly name. + /// + public string AssemblyName { get; set; } + + /// + /// Gets or sets the target framework to which this reference applies. If omitted, + /// indicates that the reference applies to all frameworks. + /// + public string TargetFramework { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecLicense.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecLicense.cs new file mode 100644 index 0000000000..8c90d657f8 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecLicense.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies the package's SPDX license expression or path to a license file. + /// + public class NuSpecLicense + { + /// + /// Gets or sets the license type. + /// + public string Type { get; set; } + + /// + /// Gets or sets the license version. + /// + public string Version { get; set; } + + /// + /// Gets or sets SPDX license identifier. + /// + public string Value { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecPackageType.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecPackageType.cs new file mode 100644 index 0000000000..c87c849a09 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecPackageType.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies the package's package type that indicates its intended use. + /// + public class NuSpecPackageType + { + /// + /// Gets or sets the package type, e.g DotnetCliTool + /// Defaults to Dependency. + /// + public string Name { get; set; } + + /// + /// Gets or sets the version of the package type. + /// + public string Version { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuSpecReference.cs b/src/Cake.Common/Tools/NuGet/Pack/NuSpecReference.cs new file mode 100644 index 0000000000..6d28affd77 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Pack/NuSpecReference.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.NuGet.Pack +{ + /// + /// Specifies the package's assemblies that the target project should reference when being used. + /// + public class NuSpecReference + { + /// + /// Gets or sets file for the reference. + /// + public string File { get; set; } + + /// + /// Gets or sets the target framework for the reference. + /// + public string TargetFramework { get; set; } + } +} diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuspecProcessor.cs b/src/Cake.Common/Tools/NuGet/Pack/NuspecProcessor.cs index 546e3e3a10..bd1e5cef93 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuspecProcessor.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuspecProcessor.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Globalization; using System.Xml; using Cake.Core; @@ -116,4 +117,4 @@ private FilePath SaveNuspecXml(FilePath nuspecFilePath, XmlDocument document) return nuspecFile.Path; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Pack/NuspecTransformer.cs b/src/Cake.Common/Tools/NuGet/Pack/NuspecTransformer.cs index 75029e1e01..91e7d44e33 100644 --- a/src/Cake.Common/Tools/NuGet/Pack/NuspecTransformer.cs +++ b/src/Cake.Common/Tools/NuGet/Pack/NuspecTransformer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -12,97 +13,316 @@ namespace Cake.Common.Tools.NuGet.Pack { internal static class NuspecTransformer { - private static readonly Dictionary> _mappings; - private static readonly List _cdataElements; - private const string NuSpecXsd = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"; - static NuspecTransformer() - { - _mappings = new Dictionary> - { - { "id", settings => ToString(settings.Id) }, - { "version", settings => ToString(settings.Version) }, - { "title", settings => ToString(settings.Title) }, - { "authors", settings => ToCommaSeparatedString(settings.Authors) }, - { "owners", settings => ToCommaSeparatedString(settings.Owners) }, - { "description", settings => ToString(settings.Description) }, - { "summary", settings => ToString(settings.Summary) }, - { "licenseUrl", settings => ToString(settings.LicenseUrl) }, - { "projectUrl", settings => ToString(settings.ProjectUrl) }, - { "iconUrl", settings => ToString(settings.IconUrl) }, - { "developmentDependency", settings => ToString(settings.DevelopmentDependency) }, - { "requireLicenseAcceptance", settings => ToString(settings.RequireLicenseAcceptance) }, - { "copyright", settings => ToString(settings.Copyright) }, - { "releaseNotes", settings => ToMultiLineString(settings.ReleaseNotes) }, - { "tags", settings => ToSpaceSeparatedString(settings.Tags) } - }; - - _cdataElements = new List { "releaseNotes" }; - } - public static void Transform(XmlDocument document, NuGetPackSettings settings) { // Create the namespace manager. var namespaceManager = new XmlNamespaceManager(document.NameTable); namespaceManager.AddNamespace("nu", NuSpecXsd); - foreach (var elementName in _mappings.Keys) + AddSimpleTypesToXmlDocument(document, settings, namespaceManager); + AddComplexTypesToXmlDocument(document, settings, namespaceManager); + } + + private static void AddSimpleTypesToXmlDocument(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + var mappings = CreateMappings(settings); + CreateSimpleTypeMetadataFromMappings(document, settings, namespaceManager, mappings); + CreateReleaseNotes(document, settings, namespaceManager); + CreateMinClientVersion(document, settings, namespaceManager); + } + + private static IDictionary> CreateMappings(NuGetPackSettings settings) + { + return new Dictionary> { - var content = _mappings[elementName](settings); - if (content != null) - { - // Replace the node content. - var node = FindOrCreateElement(document, namespaceManager, elementName); + { "id", x => ToString(settings.Id) }, + { "version", x => ToString(settings.Version) }, + { "title", x => ToString(settings.Title) }, + { "authors", x => ToCommaSeparatedString(settings.Authors) }, + { "owners", x => ToCommaSeparatedString(settings.Owners) }, + { "licenseUrl", x => ToString(settings.LicenseUrl) }, + { "projectUrl", x => ToString(settings.ProjectUrl) }, + { "icon", x => ToString(settings.Icon) }, + { "iconUrl", x => ToString(settings.IconUrl) }, + { "requireLicenseAcceptance", x => ToString(settings.RequireLicenseAcceptance) }, + { "developmentDependency", x => ToString(settings.DevelopmentDependency) }, + { "description", x => ToString(settings.Description) }, + { "summary", x => ToString(settings.Summary) }, + { "copyright", x => ToString(settings.Copyright) }, + { "language", x => ToString(settings.Language) }, + { "tags", x => ToSpaceSeparatedString(settings.Tags) }, + { "serviceable", x => ToString(settings.Serviceable) }, + { "readme", x => ToString(settings.ReadMe?.FullPath) } + }; + } - if (_cdataElements.Contains(elementName)) - { - node.AppendChild(document.CreateCDataSection(content)); - } - else - { - node.InnerText = content; - } + private static void CreateSimpleTypeMetadataFromMappings(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager, IDictionary> mappings) + { + mappings.ToList().ForEach(x => + { + var value = x.Value(settings); + if (value != null) + { + var node = FindOrCreateElement(document, namespaceManager, x.Key); + node.InnerText = value; } + }); + } + + private static void CreateReleaseNotes(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.ReleaseNotes != null && settings.ReleaseNotes.Count > 0) + { + var node = FindOrCreateElement(document, namespaceManager, "releaseNotes"); + node.AppendChild(document.CreateCDataSection(ToMultiLineString(settings.ReleaseNotes))); + } + } + + private static void CreateMinClientVersion(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.MinClientVersion != null) + { + var metadataNode = document.SelectSingleNode("//*[local-name()='metadata']"); + metadataNode.AddAttributeIfSpecified(settings.MinClientVersion, "minClientVersion"); } + } + private static void AddComplexTypesToXmlDocument(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + CreateRepositoryElement(document, settings, namespaceManager); + CreateLicenseElement(document, settings, namespaceManager); + CreatePackageTypeElements(document, settings, namespaceManager); + CreateDependencyElements(document, settings, namespaceManager); + CreateFrameworkAssemblyElements(document, settings, namespaceManager); + CreateReferenceElements(document, settings, namespaceManager); + CreateContentFileElements(document, settings, namespaceManager); + CreateFileElements(document, settings, namespaceManager); + } + + private static void CreateFileElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { if (settings.Files != null && settings.Files.Count > 0) { - var filesPath = string.Format(CultureInfo.InvariantCulture, "//*[local-name()='package']//*[local-name()='files']"); + var filesPath = string.Format(CultureInfo.InvariantCulture, + "//*[local-name()='package']/*[local-name()='files']"); var filesElement = document.SelectSingleNode(filesPath, namespaceManager); if (filesElement == null) { - // Get the package element. var package = GetPackageElement(document); filesElement = document.CreateAndAppendElement(package, "files"); } - // Add the files filesElement.RemoveAll(); foreach (var file in settings.Files) { - var fileElement = document.CreateAndAppendElement(filesElement, "file"); - fileElement.AddAttributeIfSpecified(file.Source, "src"); - fileElement.AddAttributeIfSpecified(file.Exclude, "exclude"); - fileElement.AddAttributeIfSpecified(file.Target, "target"); + if (file.Source != null) + { + var fileElement = document.CreateAndAppendElement(filesElement, "file"); + fileElement.AddAttributeIfSpecified(file.Source, "src"); + fileElement.AddAttributeIfSpecified(file.Exclude, "exclude"); + fileElement.AddAttributeIfSpecified(file.Target, "target"); + } + } + } + } + + private static void CreateContentFileElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.ContentFiles != null && settings.ContentFiles.Count > 0) + { + var contentFilesElement = FindOrCreateElement(document, namespaceManager, "contentFiles"); + + contentFilesElement.RemoveAll(); + foreach (var contentFile in settings.ContentFiles) + { + if (contentFile.Include != null) + { + var fileElement = document.CreateAndAppendElement(contentFilesElement, "files"); + fileElement.AddAttributeIfSpecified(contentFile.Include, "include"); + fileElement.AddAttributeIfSpecified(contentFile.Exclude, "exclude"); + fileElement.AddAttributeIfSpecified(contentFile.BuildAction, "buildAction"); + fileElement.AddAttributeIfSpecified(ToString(contentFile.CopyToOutput), "copyToOutput"); + fileElement.AddAttributeIfSpecified(ToString(contentFile.Flatten), "flatten"); + } + } + } + } + + private static void CreateReferenceElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.References != null && settings.References.Count > 0) + { + var referencesElement = FindOrCreateElement(document, namespaceManager, "references"); + + referencesElement.RemoveAll(); + if (settings.References.All(c => string.IsNullOrEmpty(c.TargetFramework))) + { + foreach (var reference in settings.References) + { + AddReference(document, referencesElement, reference); + } + } + else + { + foreach (var targetFrameworkReferences in settings.References.GroupBy(x => x.TargetFramework)) + { + CreateReferenceGroup(document, referencesElement, targetFrameworkReferences); + } } } + } + private static void CreateDependencyElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { if (settings.Dependencies != null && settings.Dependencies.Count > 0) { var dependenciesElement = FindOrCreateElement(document, namespaceManager, "dependencies"); - // Add the files dependenciesElement.RemoveAll(); - foreach (var dependency in settings.Dependencies) + if (settings.Dependencies.All(c => string.IsNullOrEmpty(c.TargetFramework))) + { + foreach (var dependency in settings.Dependencies) + { + AddDependency(document, dependenciesElement, dependency); + } + } + else { - var fileElement = document.CreateAndAppendElement(dependenciesElement, "dependency"); - fileElement.AddAttributeIfSpecified(dependency.Id, "id"); - fileElement.AddAttributeIfSpecified(dependency.Version, "version"); + foreach (var targetFrameworkDependencies in settings.Dependencies.GroupBy(x => x.TargetFramework)) + { + CreateDependencyGroup(document, dependenciesElement, targetFrameworkDependencies); + } } } } + private static void CreateReferenceGroup(XmlDocument document, XmlNode referencesElement, + IGrouping targetFrameworkReferences) + { + var groupElement = document.CreateAndAppendElement(referencesElement, "group"); + if (!string.IsNullOrEmpty(targetFrameworkReferences.Key)) + { + groupElement.AddAttributeIfSpecified(targetFrameworkReferences.Key, "targetFramework"); + } + + foreach (var reference in targetFrameworkReferences) + { + AddReference(document, groupElement, reference); + } + } + + private static void CreateDependencyGroup(XmlDocument document, XmlNode dependenciesElement, + IGrouping targetFrameworkDependencies) + { + var groupElement = document.CreateAndAppendElement(dependenciesElement, "group"); + if (!string.IsNullOrEmpty(targetFrameworkDependencies.Key)) + { + groupElement.AddAttributeIfSpecified(targetFrameworkDependencies.Key, "targetFramework"); + } + + foreach (var dependency in targetFrameworkDependencies) + { + AddDependency(document, groupElement, dependency); + } + } + + private static void CreateFrameworkAssemblyElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.FrameworkAssemblies != null && settings.FrameworkAssemblies.Count > 0) + { + var frameWorkAssembliesElement = FindOrCreateElement(document, namespaceManager, "frameworkAssemblies"); + + frameWorkAssembliesElement.RemoveAll(); + foreach (var frameworkAssembly in settings.FrameworkAssemblies) + { + if (frameworkAssembly.AssemblyName != null) + { + var fileElement = document.CreateAndAppendElement(frameWorkAssembliesElement, "frameworkAssembly"); + fileElement.AddAttributeIfSpecified(frameworkAssembly.AssemblyName, "assemblyName"); + fileElement.AddAttributeIfSpecified(frameworkAssembly.TargetFramework, "targetFramework"); + } + } + } + } + + private static void CreatePackageTypeElements(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.PackageTypes != null && settings.PackageTypes.Count > 0) + { + var packageTypesElement = FindOrCreateElement(document, namespaceManager, "packageTypes"); + + packageTypesElement.RemoveAll(); + foreach (var packageType in settings.PackageTypes) + { + if (packageType.Name != null) + { + var fileElement = document.CreateAndAppendElement(packageTypesElement, "packageType"); + fileElement.AddAttributeIfSpecified(packageType.Name, "name"); + fileElement.AddAttributeIfSpecified(packageType.Version, "version"); + } + } + } + } + + private static void AddDependency(XmlDocument document, XmlNode groupElement, NuSpecDependency dependency) + { + if (dependency.Id != null) + { + var fileElement = document.CreateAndAppendElement(groupElement, "dependency"); + fileElement.AddAttributeIfSpecified(dependency.Id, "id"); + fileElement.AddAttributeIfSpecified(dependency.Version, "version"); + fileElement.AddAttributeIfSpecified(ToCommaSeparatedString(dependency.Include), "include"); + fileElement.AddAttributeIfSpecified(ToCommaSeparatedString(dependency.Exclude), "exclude"); + } + } + + private static void AddReference(XmlDocument document, XmlNode groupElement, NuSpecReference reference) + { + if (reference.File != null) + { + var fileElement = document.CreateAndAppendElement(groupElement, "reference"); + fileElement.AddAttributeIfSpecified(reference.File, "file"); + } + } + + private static void CreateLicenseElement(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.License?.Type != null) + { + var licenseNode = FindOrCreateElement(document, namespaceManager, "license"); + licenseNode.AddAttributeIfSpecified(settings.License.Type, "type"); + licenseNode.AddAttributeIfSpecified(settings.License.Version, "version"); + licenseNode.InnerText = settings.License.Value; + } + } + + private static void CreateRepositoryElement(XmlDocument document, NuGetPackSettings settings, + XmlNamespaceManager namespaceManager) + { + if (settings.Repository != null) + { + var repositoryNode = FindOrCreateElement(document, namespaceManager, "repository"); + repositoryNode.AddAttributeIfSpecified(settings.Repository.Type, "type"); + repositoryNode.AddAttributeIfSpecified(settings.Repository.Url, "url"); + repositoryNode.AddAttributeIfSpecified(settings.Repository.Commit, "commit"); + repositoryNode.AddAttributeIfSpecified(settings.Repository.Branch, "branch"); + } + } + private static XmlNode GetPackageElement(XmlDocument document) { var package = document.SelectSingleNode("//*[local-name()='package']"); @@ -166,33 +386,33 @@ private static string ToString(string value) private static string ToString(Uri value) { - return value == null ? null : value.ToString().TrimEnd('/'); + return value?.ToString().TrimEnd('/'); } - private static string ToString(bool value) + private static string ToString(bool? value) { - return value.ToString().ToLowerInvariant(); + return value?.ToString().ToLowerInvariant(); } - private static string ToCommaSeparatedString(IEnumerable values) + private static string ToCommaSeparatedString(ICollection values) { - return values != null - ? string.Join(",", values) + return values != null && values.Count != 0 + ? string.Join(',', values) : null; } - private static string ToMultiLineString(IEnumerable values) + private static string ToMultiLineString(ICollection values) { - return values != null + return values != null && values.Count != 0 ? string.Join("\r\n", values).NormalizeLineEndings() : null; } - private static string ToSpaceSeparatedString(IEnumerable values) + private static string ToSpaceSeparatedString(ICollection values) { - return values != null - ? string.Join(" ", values.Select(x => x.Replace(" ", "-"))) + return values != null && values.Count != 0 + ? string.Join(' ', values.Select(x => x.Replace(" ", "-"))) : null; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Push/NuGetPushSettings.cs b/src/Cake.Common/Tools/NuGet/Push/NuGetPushSettings.cs index 9195cf4a2b..baa3e3801d 100644 --- a/src/Cake.Common/Tools/NuGet/Push/NuGetPushSettings.cs +++ b/src/Cake.Common/Tools/NuGet/Push/NuGetPushSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; using Cake.Core.Tooling; @@ -13,12 +14,21 @@ namespace Cake.Common.Tools.NuGet.Push public sealed class NuGetPushSettings : ToolSettings { /// - /// Gets or sets the server URL. If not specified, nuget.org is used unless - /// DefaultPushSource config value is set in the NuGet config file. + /// Gets or sets the server URL. + /// When using NuGet pre 3.4.2, this value is optional + /// and nuget.org is used if omitted (unless DefaultPushSource + /// config value is set in the NuGet config file. + /// When using NuGet 3.4.2 (or more recent), this value is mandatory. /// Starting with NuGet 2.5, if NuGet.exe identifies a UNC/folder source, /// it will perform the file copy to the source. /// /// The server URL. + /// + /// For your convenience, here is the URL for some of the most popular + /// public NuGet servers: + /// - NuGet Gallery: https://nuget.org/api/v2/package + /// - MyGet: https://www.myget.org/F/<your_username>/api/v2/package. + /// public string Source { get; set; } /// @@ -45,5 +55,15 @@ public sealed class NuGetPushSettings : ToolSettings /// /// The NuGet configuration file. public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a value indicating whether to skip a package and continue with + /// the next package in the push, if any when a package with the same version + /// already exists. + /// + /// + /// true if skipping duplicates; otherwise, false. + /// + public bool SkipDuplicate { get; set; } } } diff --git a/src/Cake.Common/Tools/NuGet/Push/NuGetPusher.cs b/src/Cake.Common/Tools/NuGet/Push/NuGetPusher.cs index b13914d2a4..85aab00164 100644 --- a/src/Cake.Common/Tools/NuGet/Push/NuGetPusher.cs +++ b/src/Cake.Common/Tools/NuGet/Push/NuGetPusher.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using Cake.Core; +using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.IO.NuGet; using Cake.Core.Tooling; @@ -16,6 +18,7 @@ namespace Cake.Common.Tools.NuGet.Push public sealed class NuGetPusher : NuGetTool { private readonly ICakeEnvironment _environment; + private readonly ICakeLog _log; /// /// Initializes a new instance of the class. @@ -25,14 +28,17 @@ public sealed class NuGetPusher : NuGetTool /// The process runner. /// The tool locator. /// The NuGet tool resolver. + /// The logger. public NuGetPusher( IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator tools, - INuGetToolResolver resolver) : base(fileSystem, environment, processRunner, tools, resolver) + INuGetToolResolver resolver, + ICakeLog log) : base(fileSystem, environment, processRunner, tools, resolver) { _environment = environment; + _log = log; } /// @@ -42,14 +48,8 @@ public NuGetPusher( /// The settings. public void Push(FilePath packageFilePath, NuGetPushSettings settings) { - if (packageFilePath == null) - { - throw new ArgumentNullException("packageFilePath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(packageFilePath); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(packageFilePath, settings)); } @@ -63,7 +63,7 @@ private ProcessArgumentBuilder GetArguments(FilePath packageFilePath, NuGetPushS if (settings.ApiKey != null) { - builder.AppendSecret(settings.ApiKey); + builder.AppendQuotedSecret(settings.ApiKey); } builder.Append("-NonInteractive"); @@ -79,6 +79,15 @@ private ProcessArgumentBuilder GetArguments(FilePath packageFilePath, NuGetPushS builder.Append("-Source"); builder.AppendQuoted(settings.Source); } + else + { + _log.Verbose("No Source property has been set. Depending on your configuration, this may cause problems."); + } + + if (settings.SkipDuplicate) + { + builder.Append("-SkipDuplicate"); + } if (settings.Timeout != null) { @@ -95,4 +104,4 @@ private ProcessArgumentBuilder GetArguments(FilePath packageFilePath, NuGetPushS return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Restore/NuGetRestoreSettings.cs b/src/Cake.Common/Tools/NuGet/Restore/NuGetRestoreSettings.cs new file mode 100644 index 0000000000..6bbb08a374 --- /dev/null +++ b/src/Cake.Common/Tools/NuGet/Restore/NuGetRestoreSettings.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.NuGet.Restore +{ + /// + /// Contains settings used by . + /// + public sealed class NuGetRestoreSettings : ToolSettings + { + /// + /// Gets or sets a value indicating whether package restore consent is granted before installing a package. + /// + /// + /// true if package restore consent is granted; otherwise, false. + /// + public bool RequireConsent { get; set; } + + /// + /// Gets or sets the packages folder. + /// + public DirectoryPath PackagesDirectory { get; set; } + + /// + /// Gets or sets a list of packages sources to use for this command. + /// + public ICollection Source { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether or not to use the machine cache as the first package source. + /// + /// + /// true to not use the machine cache as the first package source; otherwise, false. + /// + public bool NoCache { get; set; } + + /// + /// Gets or sets a value indicating whether or not to disable parallel processing of packages for this command. + /// + /// + /// true to disable parallel processing; otherwise, false. + /// + public bool DisableParallelProcessing { get; set; } + + /// + /// Gets or sets the amount of output details. + /// + public NuGetVerbosity? Verbosity { get; set; } + + /// + /// Gets or sets the NuGet configuration file. + /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. + /// + public FilePath ConfigFile { get; set; } + + /// + /// Gets or sets a list of packages sources to use as fallbacks for this command. + /// This setting requires NuGet V3 or later. + /// + /// The list of packages sources to use as fallbacks for this command. + public ICollection FallbackSource { get; set; } = new List(); + + /// + /// Gets or sets the version of MSBuild to be used with this command. + /// By default the MSBuild in your path is picked, otherwise it defaults to the highest installed version of MSBuild. + /// This setting requires NuGet V3 or later. + /// + /// The version of MSBuild to be used with this command. + public NuGetMSBuildVersion? MSBuildVersion { get; set; } + + /// + /// Gets or sets the path of MSBuild to use. + /// This setting takes precedence over -MSBuildVersion and requires NuGet V4 or later. + /// + public DirectoryPath MSBuildPath { get; set; } + + /// + /// Gets or sets a value indicating whether or not NuGet suppresses prompts for user input or confirmations. + /// + /// + /// This setting is passed by NuGet.exe to any extensions such as authorization providers. + /// + /// + /// false to allow NuGet to show prompts for user input or confirmations; otherwise, true. + /// + public bool NonInteractive { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Restore/NuGetRestorer.cs b/src/Cake.Common/Tools/NuGet/Restore/NuGetRestorer.cs index 74399c2ec1..8c7e0325b6 100644 --- a/src/Cake.Common/Tools/NuGet/Restore/NuGetRestorer.cs +++ b/src/Cake.Common/Tools/NuGet/Restore/NuGetRestorer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -23,7 +24,7 @@ public sealed class NuGetRestorer : NuGetTool /// The environment. /// The process runner. /// The tool locator. - /// The NuGet tool resolver + /// The NuGet tool resolver. public NuGetRestorer( IFileSystem fileSystem, ICakeEnvironment environment, @@ -41,14 +42,8 @@ public NuGetRestorer( /// The settings. public void Restore(FilePath targetFilePath, NuGetRestoreSettings settings) { - if (targetFilePath == null) - { - throw new ArgumentNullException("targetFilePath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(targetFilePath); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(targetFilePath, settings)); } @@ -77,14 +72,14 @@ private ProcessArgumentBuilder GetArguments(FilePath targetFilePath, NuGetRestor if (settings.Source != null && settings.Source.Count > 0) { builder.Append("-Source"); - builder.AppendQuoted(string.Join(";", settings.Source)); + builder.AppendQuoted(string.Join(';', settings.Source)); } // List of package fallback sources. if (settings.FallbackSource != null && settings.FallbackSource.Count > 0) { builder.Append("-FallbackSource"); - builder.AppendQuoted(string.Join(";", settings.FallbackSource)); + builder.AppendQuoted(string.Join(';', settings.FallbackSource)); } // No Cache? @@ -117,12 +112,23 @@ private ProcessArgumentBuilder GetArguments(FilePath targetFilePath, NuGetRestor if (settings.MSBuildVersion.HasValue) { builder.Append("-MSBuildVersion"); - builder.Append(settings.MSBuildVersion.Value.ToString("D")); + builder.Append(settings.MSBuildVersion.Value.GetNuGetMSBuildVersionString()); } - builder.Append("-NonInteractive"); + // MSBuildPath + if (settings.MSBuildPath != null) + { + builder.Append("-MSBuildPath"); + builder.AppendQuoted(settings.MSBuildPath.MakeAbsolute(_environment).FullPath); + } + + // NonInteractive? + if (settings.NonInteractive) + { + builder.Append("-NonInteractive"); + } return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Restore/NugetRestoreSettings.cs b/src/Cake.Common/Tools/NuGet/Restore/NugetRestoreSettings.cs deleted file mode 100644 index 58056571f6..0000000000 --- a/src/Cake.Common/Tools/NuGet/Restore/NugetRestoreSettings.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.NuGet.Restore -{ - /// - /// Contains settings used by . - /// - public sealed class NuGetRestoreSettings : ToolSettings - { - /// - /// Gets or sets a value indicating whether package restore consent is granted before installing a package. - /// - /// - /// true if package restore consent is granted; otherwise, false. - /// - public bool RequireConsent { get; set; } - - /// - /// Gets or sets the packages folder. - /// - public DirectoryPath PackagesDirectory { get; set; } - - /// - /// Gets or sets a list of packages sources to use for this command. - /// - public ICollection Source { get; set; } - - /// - /// Gets or sets a value indicating whether or not to use the machine cache as the first package source. - /// - /// - /// true to not use the machine cache as the first package source; otherwise, false. - /// - public bool NoCache { get; set; } - - /// - /// Gets or sets a value indicating whether or not to disable parallel processing of packages for this command. - /// - /// - /// true to disable parallel processing; otherwise, false. - /// - public bool DisableParallelProcessing { get; set; } - - /// - /// Gets or sets the amount of output details. - /// - public NuGetVerbosity? Verbosity { get; set; } - - /// - /// Gets or sets the NuGet configuration file. - /// If not specified, the file %AppData%\NuGet\NuGet.config is used as the configuration file. - /// - public FilePath ConfigFile { get; set; } - - /// - /// Gets or sets a list of packages sources to use as fallbacks for this command. - /// This setting requires NuGet V3 or later. - /// - /// The list of packages sources to use as fallbacks for this command. - public ICollection FallbackSource { get; set; } - - /// - /// Gets or sets the version of MSBuild to be used with this command. - /// By default the MSBuild in your path is picked, otherwise it defaults to the highest installed version of MSBuild. - /// This setting requires NuGet V3 or later. - /// - /// The version of MSBuild to be used with this command. - public NuGetMSBuildVersion? MSBuildVersion { get; set; } - } -} diff --git a/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKey.cs b/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKey.cs index 30007b10ee..41885088b4 100644 --- a/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKey.cs +++ b/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKey.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -44,31 +45,22 @@ public void SetApiKey(string apiKey, string source, NuGetSetApiKeySettings setti { if (string.IsNullOrWhiteSpace(apiKey)) { - throw new ArgumentNullException("apiKey"); + throw new ArgumentNullException(nameof(apiKey)); } - if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentNullException("source"); + throw new ArgumentNullException(nameof(source)); } + ArgumentNullException.ThrowIfNull(settings); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - string output = null; + // Read but do not validate redirected standard output. + // Process wrapper logs redacted output at diagnostic verbosity. var processSettings = new ProcessSettings { Arguments = GetArguments(apiKey, source, settings), RedirectStandardOutput = true }; - Run(settings, null, processSettings, process => output = string.Join("\r\n", process.GetStandardOutput())); - - if (string.IsNullOrWhiteSpace(output) || - !output.Contains(string.Concat("The API Key '", apiKey, "' was saved for '", source, "'."))) - { - throw new CakeException("SetApiKey returned unexpected response."); - } + Run(settings, null, processSettings, process => string.Join(Environment.NewLine, process.GetStandardOutput())); } private ProcessArgumentBuilder GetArguments(string apiKey, string source, NuGetSetApiKeySettings settings) @@ -101,4 +93,4 @@ private ProcessArgumentBuilder GetArguments(string apiKey, string source, NuGetS return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKeySettings.cs b/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKeySettings.cs index e704a8694e..23e8559892 100644 --- a/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKeySettings.cs +++ b/src/Cake.Common/Tools/NuGet/SetApiKey/NuGetSetApiKeySettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -24,4 +25,4 @@ public sealed class NuGetSetApiKeySettings : ToolSettings /// The NuGet configuration file. public FilePath ConfigFile { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxy.cs b/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxy.cs index c91e95652e..3024c7197b 100644 --- a/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxy.cs +++ b/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxy.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -45,13 +46,10 @@ public void SetProxy(string url, string username, string password, NuGetSetProxy { if (string.IsNullOrWhiteSpace(url)) { - throw new ArgumentNullException("url"); + throw new ArgumentNullException(nameof(url)); } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); string output = null; var processSettings = new ProcessSettings @@ -105,4 +103,4 @@ private ProcessArgumentBuilder GetArguments(string url, string username, string return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxySettings.cs b/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxySettings.cs index 12c916a298..ed7e443d2b 100644 --- a/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxySettings.cs +++ b/src/Cake.Common/Tools/NuGet/SetProxy/NuGetSetProxySettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -24,4 +25,4 @@ public sealed class NuGetSetProxySettings : ToolSettings /// The NuGet configuration file. public FilePath ConfigFile { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Sources/NuGetSources.cs b/src/Cake.Common/Tools/NuGet/Sources/NuGetSources.cs index a8096c8510..392d87e582 100644 --- a/src/Cake.Common/Tools/NuGet/Sources/NuGetSources.cs +++ b/src/Cake.Common/Tools/NuGet/Sources/NuGetSources.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using System.Linq; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.NuGet.Sources { /// - /// The NuGet sources is used to work with user config feeds & credentials + /// The NuGet sources is used to work with user config feeds & credentials. /// public sealed class NuGetSources : NuGetTool { @@ -34,33 +35,24 @@ public NuGetSources( } /// - /// Adds NuGet package source using the specified settings to global user config + /// Adds NuGet package source using the specified settings to global user config. /// /// Name of the source. /// Path to the package(s) source. /// The settings. public void AddSource(string name, string source, NuGetSourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (source == null) - { - throw new ArgumentNullException("source"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + ArgumentNullException.ThrowIfNull(source); if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentException("Source cannot be empty.", "source"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source cannot be empty.", nameof(source)); } + ArgumentNullException.ThrowIfNull(settings); if (HasSource(source, settings)) { @@ -72,33 +64,24 @@ public void AddSource(string name, string source, NuGetSourcesSettings settings) } /// - /// Remove specified NuGet package source + /// Remove specified NuGet package source. /// /// Name of the source. /// Path to the package(s) source. /// The settings. public void RemoveSource(string name, string source, NuGetSourcesSettings settings) { - if (name == null) - { - throw new ArgumentNullException("name"); - } + ArgumentNullException.ThrowIfNull(name); if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Source name cannot be empty.", "name"); - } - if (source == null) - { - throw new ArgumentNullException("source"); + throw new ArgumentException("Source name cannot be empty.", nameof(name)); } + ArgumentNullException.ThrowIfNull(source); if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentException("Source cannot be empty.", "source"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source cannot be empty.", nameof(source)); } + ArgumentNullException.ThrowIfNull(settings); if (!HasSource(source, settings)) { @@ -117,33 +100,44 @@ public void RemoveSource(string name, string source, NuGetSourcesSettings settin /// Whether the specified NuGet package source exist. public bool HasSource(string source, NuGetSourcesSettings settings) { - if (source == null) - { - throw new ArgumentNullException("source"); - } + ArgumentNullException.ThrowIfNull(source); if (string.IsNullOrWhiteSpace(source)) { - throw new ArgumentException("Source cannot be empty.", "source"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentException("Source cannot be empty.", nameof(source)); } + ArgumentNullException.ThrowIfNull(settings); var processSettings = new ProcessSettings { - Arguments = "sources List", RedirectStandardOutput = true }; var result = false; - Run(settings, null, processSettings, - process => result = process.GetStandardOutput().Any(line => line.TrimStart() == source)); + + Run(settings, GetHasArguments(settings), processSettings, + process => result = process.GetStandardOutput().Any(line => line.TrimStart().Equals(source, StringComparison.OrdinalIgnoreCase))); // Return whether or not the source exist. return result; } + private static ProcessArgumentBuilder GetHasArguments(NuGetSourcesSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("sources List"); + + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.FullPath); + } + + builder.Append("-NonInteractive"); + + return builder; + } + private static ProcessArgumentBuilder GetAddArguments(string name, string source, NuGetSourcesSettings settings) { var builder = new ProcessArgumentBuilder(); @@ -209,7 +203,13 @@ private static void AddCommonParameters(string name, string source, NuGetSources builder.Append(settings.Verbosity.Value.ToString().ToLowerInvariant()); } + if (settings.ConfigFile != null) + { + builder.Append("-ConfigFile"); + builder.AppendQuoted(settings.ConfigFile.FullPath); + } + builder.Append("-NonInteractive"); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Sources/NuGetSourcesSettings.cs b/src/Cake.Common/Tools/NuGet/Sources/NuGetSourcesSettings.cs index 5ebf0a563f..2e6dd9a11a 100644 --- a/src/Cake.Common/Tools/NuGet/Sources/NuGetSourcesSettings.cs +++ b/src/Cake.Common/Tools/NuGet/Sources/NuGetSourcesSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Core.IO; using Cake.Core.Tooling; namespace Cake.Common.Tools.NuGet.Sources @@ -37,11 +39,16 @@ public sealed class NuGetSourcesSettings : ToolSettings public bool IsSensitiveSource { get; set; } /// - /// Gets or sets a value indicating whether to not encrypt the password and store it in clear text. (Default: false) + /// Gets or sets a value indicating whether to not encrypt the password and store it in clear text. (Default: false). /// /// /// true if password is stored as unencrypted; otherwise, false. /// public bool StorePasswordInClearText { get; set; } + + /// + /// Gets or sets the location of the NuGet configuration file. If not specified, file %AppData%\NuGet\NuGet.config is used as configuration file. + /// + public FilePath ConfigFile { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Update/NuGetUpdateSettings.cs b/src/Cake.Common/Tools/NuGet/Update/NuGetUpdateSettings.cs index 78e995c75b..025ed878f3 100644 --- a/src/Cake.Common/Tools/NuGet/Update/NuGetUpdateSettings.cs +++ b/src/Cake.Common/Tools/NuGet/Update/NuGetUpdateSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.Tooling; @@ -15,12 +16,12 @@ public sealed class NuGetUpdateSettings : ToolSettings /// Gets or sets the package ids to update. /// /// The package ids to update. - public ICollection Id { get; set; } + public ICollection Id { get; set; } = new List(); /// /// Gets or sets a list of package sources to use for this command. /// - public ICollection Source { get; set; } + public ICollection Source { get; set; } = new List(); /// /// Gets or sets a value indicating whether to look for updates with the highest @@ -52,5 +53,11 @@ public sealed class NuGetUpdateSettings : ToolSettings /// /// The version of MSBuild to be used with this command. public NuGetMSBuildVersion? MSBuildVersion { get; set; } + + /// + /// Gets or sets package version to be used with this command. + /// + /// The package version to be used with this command. + public string Version { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/NuGet/Update/NuGetUpdater.cs b/src/Cake.Common/Tools/NuGet/Update/NuGetUpdater.cs index 158676fe1c..c8ec5173f0 100644 --- a/src/Cake.Common/Tools/NuGet/Update/NuGetUpdater.cs +++ b/src/Cake.Common/Tools/NuGet/Update/NuGetUpdater.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -23,7 +24,7 @@ public sealed class NuGetUpdater : NuGetTool /// The environment. /// The process runner. /// The tool locator. - /// The nuget tool resolver. + /// The NuGet tool resolver. public NuGetUpdater(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, @@ -40,14 +41,8 @@ public NuGetUpdater(IFileSystem fileSystem, /// The settings. public void Update(FilePath targetFile, NuGetUpdateSettings settings) { - if (targetFile == null) - { - throw new ArgumentNullException("targetFile"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(targetFile); + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(targetFile, settings)); } @@ -62,14 +57,14 @@ private ProcessArgumentBuilder GetArguments(FilePath targetFile, NuGetUpdateSett if (settings.Id != null && settings.Id.Count > 0) { builder.Append("-Id"); - builder.AppendQuoted(string.Join(";", settings.Id)); + builder.AppendQuoted(string.Join(';', settings.Id)); } // List of package sources if (settings.Source != null && settings.Source.Count > 0) { builder.Append("-Source"); - builder.AppendQuoted(string.Join(";", settings.Source)); + builder.AppendQuoted(string.Join(';', settings.Source)); } // Verbosity? @@ -95,7 +90,14 @@ private ProcessArgumentBuilder GetArguments(FilePath targetFile, NuGetUpdateSett if (settings.MSBuildVersion.HasValue) { builder.Append("-MSBuildVersion"); - builder.Append(settings.MSBuildVersion.Value.ToString("D")); + builder.Append(settings.MSBuildVersion.Value.GetNuGetMSBuildVersionString()); + } + + // Version + if (!string.IsNullOrWhiteSpace(settings.Version)) + { + builder.Append("-Version"); + builder.Append(settings.Version); } builder.Append("-NonInteractive"); @@ -103,4 +105,4 @@ private ProcessArgumentBuilder GetArguments(FilePath targetFile, NuGetUpdateSett return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseArgumentBuilder.cs index 6ebc53159a..aca88bc446 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseArgumentBuilder.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseArgumentBuilder.cs @@ -1,101 +1,124 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Globalization; using Cake.Core; using Cake.Core.IO; namespace Cake.Common.Tools.OctopusDeploy { - internal class CreateReleaseArgumentBuilder + internal sealed class CreateReleaseArgumentBuilder : OctopusDeployArgumentBuilder { private readonly string _projectName; private readonly CreateReleaseSettings _settings; private readonly ICakeEnvironment _environment; - private readonly ProcessArgumentBuilder _builder; - - public CreateReleaseArgumentBuilder(string projectName, CreateReleaseSettings settings, ICakeEnvironment environment) + public CreateReleaseArgumentBuilder(string projectName, CreateReleaseSettings settings, ICakeEnvironment environment) : base(environment, settings) { _projectName = projectName; _settings = settings; _environment = environment; - _builder = new ProcessArgumentBuilder(); } public ProcessArgumentBuilder Get() { + Builder.Append("create-release"); + Builder.AppendSwitchQuoted("--project", _projectName); + AppendCommonArguments(); AppendArgumentIfNotNull("releaseNumber", _settings.ReleaseNumber); AppendArgumentIfNotNull("defaultpackageversion", _settings.DefaultPackageVersion); - AppendPackages(_settings, _builder); + AppendPackages(_settings, Builder); AppendArgumentIfNotNull("packagesFolder", _settings.PackagesFolder); AppendArgumentIfNotNull("releasenotes", _settings.ReleaseNotes); AppendArgumentIfNotNull("releasenotesfile", _settings.ReleaseNotesFile); + AppendArgumentIfNotNull("channel", _settings.Channel); + AppendArgumentIfNotNull("excludemachines", _settings.ExcludeMachines); + + if (_settings.IgnoreChannelRules) + { + Builder.Append("--ignorechannelrules"); + } + + if (_settings.DeploymentProgress) + { + Builder.Append("--progress"); + } if (_settings.IgnoreExisting) { - _builder.Append("--ignoreexisting"); + Builder.Append("--ignoreexisting"); } - return _builder; + AppendDeploymnetArguments(); + + return Builder; } - private void AppendCommonArguments() + private void AppendDeploymnetArguments() { - _builder.Append("create-release"); - - _builder.Append("--project"); - _builder.AppendQuoted(_projectName); + AppendConditionalFlag(_settings.ShowProgress, "--progress"); + AppendConditionalFlag(_settings.ForcePackageDownload, "--forcepackagedownload"); + AppendConditionalFlag(_settings.WaitForDeployment, "--waitfordeployment"); - _builder.Append("--server"); - _builder.Append(_settings.Server); + AppendArgumentIfNotNull("deployto", _settings.DeployTo); - _builder.Append("--apiKey"); - _builder.AppendSecret(_settings.ApiKey); - - AppendArgumentIfNotNull("username", _settings.Username); + if (_settings.DeployToMultiple != null) + { + foreach (var target in _settings.DeployToMultiple) + { + AppendArgumentIfNotNull("deployto", target); + } + } - if (_settings.Password != null) + if (_settings.DeploymentTimeout.HasValue) { - _builder.Append("--password"); - _builder.AppendQuotedSecret(_settings.Password); + Builder.AppendSwitchQuoted("--deploymenttimeout", "=", _settings.DeploymentTimeout.Value.ToString("hh\\:mm\\:ss")); } - AppendArgumentIfNotNull("configFile", _settings.ConfigurationFile); + AppendConditionalFlag(_settings.CancelOnTimeout, "--cancelontimeout"); - if (_settings.EnableDebugLogging) + if (_settings.DeploymentChecksLeepCycle.HasValue) { - _builder.Append("--debug"); + Builder.AppendSwitchQuoted("--deploymentchecksleepcycle", "=", _settings.DeploymentChecksLeepCycle.Value.ToString("hh\\:mm\\:ss")); } - if (_settings.IgnoreSslErrors) + if (_settings.GuidedFailure.HasValue) { - _builder.Append("--ignoreSslErrors"); + Builder.AppendSwitch("--guidedfailure", "=", _settings.GuidedFailure.ToString()); } - if (_settings.EnableServiceMessages) + if (_settings.SpecificMachines != null && _settings.SpecificMachines.Length > 0) { - _builder.Append("--enableServiceMessages"); + Builder.AppendSwitchQuoted("--specificmachines", "=", string.Join(",", _settings.SpecificMachines)); } - } - private void AppendArgumentIfNotNull(string argumentName, string value) - { - if (value != null) + AppendConditionalFlag(_settings.Force, "--force"); + + AppendMultipleTimes("skip", _settings.SkipSteps); + + AppendConditionalFlag(_settings.NoRawLog, "--norawlog"); + + AppendArgumentIfNotNull("rawlogfile", _settings.RawLogFile); + + if (_settings.Variables != null && _settings.Variables.Count > 0) { - _builder.Append("--" + argumentName); - _builder.AppendQuoted(value); + foreach (var pair in _settings.Variables) + { + Builder.AppendSwitchQuoted("--variable", "=", $"{pair.Key}:{pair.Value}"); + } } - } - private void AppendArgumentIfNotNull(string argumentName, FilePath value) - { - if (value != null) + if (_settings.DeployAt.HasValue) { - _builder.Append("--" + argumentName); - _builder.AppendQuoted(value.MakeAbsolute(_environment).FullPath); + Builder.AppendSwitchQuoted("--deployat", "=", _settings.DeployAt.Value.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture)); } + + AppendMultipleTimes("tenant", _settings.Tenant); + + AppendMultipleTimes("tenanttag", _settings.TenantTags); } private static void AppendPackages(CreateReleaseSettings settings, ProcessArgumentBuilder builder) @@ -114,4 +137,4 @@ private static void AppendPackages(CreateReleaseSettings settings, ProcessArgume } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseSettings.cs b/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseSettings.cs index 1efce7c733..27442da86c 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseSettings.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/CreateReleaseSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.IO; @@ -8,9 +10,18 @@ namespace Cake.Common.Tools.OctopusDeploy { /// /// Contains settings used by . + /// See Octopus Deploy documentation here. /// - public sealed class CreateReleaseSettings : OctopusDeploySettings + public sealed class CreateReleaseSettings : OctopusDeployCommonToolSettings { + /// + /// Initializes a new instance of the class. + /// + public CreateReleaseSettings() + { + Variables = new List>(); + } + /// /// Gets or sets the release number to use for the new release. /// @@ -24,7 +35,7 @@ public sealed class CreateReleaseSettings : OctopusDeploySettings /// /// Gets or sets the version number to use for a package in the release. /// - public Dictionary Packages { get; set; } + public Dictionary Packages { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets or sets the folder containing NuGet packages. @@ -42,8 +53,119 @@ public sealed class CreateReleaseSettings : OctopusDeploySettings public FilePath ReleaseNotesFile { get; set; } /// - /// Gets or sets a value indicating whether the Ignore Existing flag. + /// Gets or sets a value indicating whether to Ignore Existing release flag. /// public bool IgnoreExisting { get; set; } + + /// + /// Gets or sets environment to automatically deploy to, e.g., Production. + /// + public string DeployTo { get; set; } + + /// + /// Gets or sets multiple environments to automatically deploy to, e.g., Production, Staging, etc. + /// + public string[] DeployToMultiple { get; set; } + + /// + /// Gets or sets a value indicating whether progress of the deployment should be followed. (Sets --waitfordeployment and --norawlog to true.) + /// + public bool ShowProgress { get; set; } + + /// + /// Gets or sets a value indicating whether to force downloading of already installed packages. Default false. + /// + public bool ForcePackageDownload { get; set; } + + /// + /// Gets or sets a value indicating whether to wait synchronously for deployment to finish. + /// + public bool WaitForDeployment { get; set; } + + /// + /// Gets or sets maximum time (timespan format) that the console session will wait for the deployment to finish (default 00:10:00). + /// This will not stop the deployment. Requires WaitForDeployment parameter set. + /// + public TimeSpan? DeploymentTimeout { get; set; } + + /// + /// Gets or sets a value indicating whether to cancel the deployment if the deployment timeout is reached(default false). + /// + public bool CancelOnTimeout { get; set; } + + /// + /// Gets or sets how much time should elapse between deployment status checks(default 00:00:10). + /// + public TimeSpan? DeploymentChecksLeepCycle { get; set; } + + /// + /// Gets or sets a value indicating whether to use Guided Failure mode. If not specified, will use default setting from environment. + /// + public bool? GuidedFailure { get; set; } + + /// + /// Gets or sets list of machines names to target in the deployed environment.If not specified all machines in the environment will be considered. + /// + public string[] SpecificMachines { get; set; } + + /// + /// Gets or sets a value indicating whether a project is configured to skip packages with already-installed versions, override this setting to force re-deployment (flag, default false). + /// + public bool Force { get; set; } + + /// + /// Gets or sets a list of steps to be skipped. Takes step names. + /// + public string[] SkipSteps { get; set; } + + /// + /// Gets or sets a value indicating whether print the raw log of failed tasks or not. + /// + public bool NoRawLog { get; set; } + + /// + /// Gets or sets a file where to redirect the raw log of failed tasks. + /// + public FilePath RawLogFile { get; set; } + + /// + /// Gets or sets values for any prompted variables. + /// + public List> Variables { get; set; } + + /// + /// Gets or sets time at which deployment should start (scheduled deployment), specified as any valid DateTimeOffset format, and assuming the time zone is the current local time zone. + /// + public DateTimeOffset? DeployAt { get; set; } + + /// + /// Gets or sets a tenant the deployment will be performed for; specify this argument multiple times to add multiple tenants or use `*` wildcard to deploy to tenants able to deploy. + /// + public string[] Tenant { get; set; } + + /// + /// Gets or sets a tenant tags used to match tenants that the deployment will be performed for; specify this argument multiple times to add multiple tenant tags. + /// + public string[] TenantTags { get; set; } + + /// + /// Gets or sets the octopus channel for the new release. + /// + public string Channel { get; set; } + + /// + /// Gets or sets a value indicating whether octopus channel rules should be ignored. + /// + public bool IgnoreChannelRules { get; set; } + + /// + /// Gets or sets a value indicating whether progress of the deployment will be shown. + /// + public bool DeploymentProgress { get; set; } + + /// + /// Gets or sets the comma-separated list of machine names to exclude in the deployed environment.If not specified all machines in the environment will be considered. + /// + public string ExcludeMachines { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/DeployReleaseArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/DeployReleaseArgumentBuilder.cs new file mode 100644 index 0000000000..f299d1ca40 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/DeployReleaseArgumentBuilder.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + internal sealed class DeployReleaseArgumentBuilder : OctopusDeployArgumentBuilder + { + private readonly ICakeEnvironment _environment; + + private readonly string _projectName; + private readonly string[] _deployTo; + private readonly string _releaseNumber; + private readonly OctopusDeployReleaseDeploymentSettings _settings; + + public DeployReleaseArgumentBuilder(string server, string apiKey, string projectName, string[] deployTo, string releaseNumber, OctopusDeployReleaseDeploymentSettings settings, ICakeEnvironment environment) + : base(server, apiKey, environment, settings) + { + _projectName = projectName; + _deployTo = deployTo; + _releaseNumber = releaseNumber; + + _environment = environment; + _settings = settings; + } + + public ProcessArgumentBuilder Get() + { + Builder.Append("deploy-release"); + + Builder.AppendSwitchQuoted("--project", "=", _projectName); + Builder.AppendSwitchQuoted("--releasenumber", "=", _releaseNumber); + + foreach (var environment in _deployTo) + { + Builder.AppendSwitchQuoted("--deployto", "=", environment); + } + + AppendCommonArguments(); + + AppendDeploymentParameters(); + + return Builder; + } + + private void AppendDeploymentParameters() + { + AppendConditionalFlag(_settings.ShowProgress, "--progress"); + AppendConditionalFlag(_settings.ForcePackageDownload, "--forcepackagedownload"); + AppendConditionalFlag(_settings.WaitForDeployment, "--waitfordeployment"); + + if (_settings.DeploymentTimeout.HasValue) + { + Builder.AppendSwitchQuoted("--deploymenttimeout", "=", + _settings.DeploymentTimeout.Value.ToString("hh\\:mm\\:ss")); + } + + AppendConditionalFlag(_settings.CancelOnTimeout, "--cancelontimeout"); + + if (_settings.DeploymentChecksLeepCycle.HasValue) + { + Builder.AppendSwitchQuoted("--deploymentchecksleepcycle", "=", + _settings.DeploymentChecksLeepCycle.Value.ToString("hh\\:mm\\:ss")); + } + + if (_settings.GuidedFailure.HasValue) + { + Builder.AppendSwitch("--guidedfailure", "=", _settings.GuidedFailure.ToString()); + } + + if (_settings.SpecificMachines != null && _settings.SpecificMachines.Length > 0) + { + Builder.AppendSwitchQuoted("--specificmachines", "=", string.Join(",", _settings.SpecificMachines)); + } + + AppendConditionalFlag(_settings.Force, "--force"); + + AppendMultipleTimes("skip", _settings.SkipSteps); + + AppendConditionalFlag(_settings.NoRawLog, "--norawlog"); + + AppendArgumentIfNotNull("rawlogfile", _settings.RawLogFile); + + if (_settings.Variables != null && _settings.Variables.Count > 0) + { + foreach (var pair in _settings.Variables) + { + Builder.AppendSwitchQuoted("--variable", "=", $"{pair.Key}:{pair.Value}"); + } + } + + if (_settings.DeployAt.HasValue) + { + Builder.AppendSwitchQuoted("--deployat", "=", _settings.DeployAt.Value.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture)); + } + + AppendMultipleTimes("tenant", _settings.Tenant); + + AppendMultipleTimes("tenanttag", _settings.TenantTags); + + AppendArgumentIfNotNull("channel", _settings.Channel); + + AppendArgumentIfNotNull("excludemachines", _settings.ExcludeMachines); + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/DeploymentQueryResultParser.cs b/src/Cake.Common/Tools/OctopusDeploy/DeploymentQueryResultParser.cs new file mode 100644 index 0000000000..b7c0bf8614 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/DeploymentQueryResultParser.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Parses the Console Output of the Octo.exe call when called with list-deployments. + /// + public class DeploymentQueryResultParser + { + /// + /// Initializes a new instance of the class. + /// + public DeploymentQueryResultParser() + { + } + + /// + /// Parse the results from The Deployment Query. + /// + /// Console Output from the Run Process. + /// A collection of Octopus deployments. + public IEnumerable ParseResults(IEnumerable output) + { + var results = new List(); + if (output == null) + { + return results; + } + var l = new List(output); + if (l.Count < 10) + { + return results; + } + /* yes, this is fragile and I'm open to better ideas, + * however, i can't ask the tool to format as something serialized + * dump the status lines */ + l.RemoveRange(0, 10); + while (l.Count > 0) + { + var nibSize = l.Count >= 9 ? 9 : l.Count; + var interesting = l.GetRange(0, nibSize); + var deployment = ParseSet(interesting); + if (deployment != null) + { + results.Add(deployment); + } + l.RemoveRange(0, nibSize); + } + return results; + } + + /// + /// Parses a set of lines from the output. + /// + /// A set of lines to parse. + /// an OctopusDeployment or null. + protected virtual OctopusDeployment ParseSet(List lineSet) + { + // the ninth line is blank, so 8 is technically ok, but w/e + if (lineSet.Count < 8) + { + return null; + } + var d = new OctopusDeployment() + { + ProjectName = getValueFromLine("Project", lineSet[0]), + Environment = getValueFromLine("Environment", lineSet[1]), + Channel = getValueFromLine("Channel", lineSet[2]), + Created = DateTimeOffset.Parse(getValueFromLine("Created", lineSet[3])), + Version = getValueFromLine("Version", lineSet[4]), + Assembled = DateTimeOffset.Parse(getValueFromLine("Assembled", lineSet[5])), + PackageVersions = getValueFromLine("Package Version", lineSet[6]), + ReleaseNotesHtml = getValueFromLine("Release Notes", lineSet[7]) + }; + return d; + } + + private string getValueFromLine(string propName, string line) + { + /* this could probably accept a string[] lines instead, and hunt + * but direct index is faster and can still validate. */ + if (!line.Contains(propName)) + { + throw new Exception(string.Format("Could not parse Deployment - Expected: '{0}', was '{1}'", propName, line)); + } + return line.Split(new[] { ':' }, 2)[1].Trim(); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployAliases.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployAliases.cs index 0d3e10672e..eacb96bf51 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployAliases.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,7 @@ namespace Cake.Common.Tools.OctopusDeploy /// Contains functionality related to Octopus Deploy. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the appropriate settings class: + /// install from nuget.org, or specify the ToolPath within the appropriate settings class: /// /// #tool "nuget:?package=OctopusTools" /// @@ -44,7 +45,7 @@ public static class OctopusDeployAliases /// }); /// /// OctoCreateRelease(projectNameOnServer, new CreateReleaseSettings { - /// ConfigurationFile = "C:\OctopusDeploy.config" + /// ConfigurationFile = @"C:\OctopusDeploy.config" /// }); /// /// // Additional Options @@ -60,7 +61,7 @@ public static class OctopusDeployAliases /// { "PackageOne", "1.0.2.3" }, /// { "PackageTwo", "5.2.3" } /// }, - /// PackagesFolder = "C:\MyOtherNugetFeed", + /// PackagesFolder = @"C:\MyOtherNuGetFeed", /// /// // One or the other /// ReleaseNotes = "Version 2.0 \n What a milestone we have ...", @@ -73,23 +74,20 @@ public static class OctopusDeployAliases [CakeMethodAlias] public static void OctoCreateRelease(this ICakeContext context, string projectName, CreateReleaseSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var packer = new OctopusDeployReleaseCreator(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); packer.CreateRelease(projectName, settings); } /// - /// Pushes the specified package to the Octopus Deploy repository + /// Pushes the specified package to the Octopus Deploy repository. /// - /// The cake context - /// /// The Octopus server URL - /// The user's API key - /// Path to the package - /// The settings + /// The cake context. + /// The Octopus server URL. + /// The user's API key. + /// Path to the package. + /// The settings. [CakeMethodAlias] public static void OctoPush(this ICakeContext context, string server, string apiKey, FilePath packagePath, OctopusPushSettings settings) { @@ -97,18 +95,183 @@ public static void OctoPush(this ICakeContext context, string server, string api } /// - /// Pushes the specified packages to the Octopus Deploy repository + /// Pushes the specified packages to the Octopus Deploy repository. /// - /// The cake context - /// The Octopus server URL - /// The user's API key - /// Paths to the packages - /// The settings + /// The cake context. + /// The Octopus server URL. + /// The user's API key. + /// Paths to the packages. + /// The settings. [CakeMethodAlias] public static void OctoPush(this ICakeContext context, string server, string apiKey, IEnumerable packagePaths, OctopusPushSettings settings) { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(packagePaths); + var pusher = new OctopusDeployPusher(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); pusher.PushPackage(server, apiKey, packagePaths.ToArray(), settings); } + + /// + /// Packs the specified folder into an Octopus Deploy package. + /// + /// The cake context. + /// The package ID. + [CakeMethodAlias] + public static void OctoPack(this ICakeContext context, string id) + { + OctoPack(context, id, null); + } + + /// + /// Packs the specified folder into an Octopus Deploy package. + /// + /// The cake context. + /// The package ID. + /// The settings. + [CakeMethodAlias] + public static void OctoPack(this ICakeContext context, string id, OctopusPackSettings settings = null) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(id); + + var packer = new OctopusDeployPacker(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + packer.Pack(id, settings); + } + + /// + /// Deploys the specified already existing release into a specified environment + /// See Octopus Documentation for more details. + /// + /// The cake context. + /// The Octopus server URL. + /// The user's API key. + /// Name of the target project. + /// Target environment name. + /// Version number of the release to deploy. Specify "latest" for the latest release. + /// Deployment settings. + /// + /// + /// // bare minimum + /// OctoDeployRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", "Testing", "2.1.15-RC" new OctopusDeployReleaseDeploymentSettings()); + /// + /// // All of deployment arguments + /// OctoDeployRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", "Testing", "2.1.15-RC" new OctopusDeployReleaseDeploymentSettings { + /// ShowProgress = true, + /// ForcePackageDownload = true, + /// WaitForDeployment = true, + /// DeploymentTimeout = TimeSpan.FromMinutes(1), + /// CancelOnTimeout = true, + /// DeploymentChecksLeapCycle = TimeSpan.FromMinutes(77), + /// GuidedFailure = true, + /// SpecificMachines = new string[] { "Machine1", "Machine2" }, + /// Force = true, + /// SkipSteps = new[] { "Step1", "Step2" }, + /// NoRawLog = true, + /// RawLogFile = "someFile.txt", + /// DeployAt = new DateTime(2010, 6, 15).AddMinutes(1), + /// Tenant = new[] { "Tenant1", "Tenant2" }, + /// TenantTags = new[] { "Tag1", "Tag2" }, + /// }); + /// + /// + [CakeMethodAlias] + public static void OctoDeployRelease(this ICakeContext context, string server, string apiKey, string projectName, string deployTo, string releaseNumber, OctopusDeployReleaseDeploymentSettings settings) + { + OctoDeployRelease(context, server, apiKey, projectName, new string[] { deployTo }, releaseNumber, settings); + } + + /// + /// Deploys the specified already existing release into a specified environment + /// See Octopus Documentation for more details. + /// + /// The cake context. + /// The Octopus server URL. + /// The user's API key. + /// Name of the target project. + /// Multiple target environment names. + /// Version number of the release to deploy. Specify "latest" for the latest release. + /// Deployment settings. + /// + /// + /// // bare minimum + /// OctoDeployRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", "Testing", "2.1.15-RC" new OctopusDeployReleaseDeploymentSettings()); + /// + /// // All of deployment arguments + /// OctoDeployRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", new string[] {"Testing", "Testing2"}, "2.1.15-RC" new OctopusDeployReleaseDeploymentSettings { + /// ShowProgress = true, + /// ForcePackageDownload = true, + /// WaitForDeployment = true, + /// DeploymentTimeout = TimeSpan.FromMinutes(1), + /// CancelOnTimeout = true, + /// DeploymentChecksLeapCycle = TimeSpan.FromMinutes(77), + /// GuidedFailure = true, + /// SpecificMachines = new string[] { "Machine1", "Machine2" }, + /// Force = true, + /// SkipSteps = new[] { "Step1", "Step2" }, + /// NoRawLog = true, + /// RawLogFile = "someFile.txt", + /// DeployAt = new DateTime(2010, 6, 15).AddMinutes(1), + /// Tenant = new[] { "Tenant1", "Tenant2" }, + /// TenantTags = new[] { "Tag1", "Tag2" }, + /// + /// }); + /// + /// + [CakeMethodAlias] + public static void OctoDeployRelease(this ICakeContext context, string server, string apiKey, string projectName, string[] deployToMultiple, string releaseNumber, OctopusDeployReleaseDeploymentSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var releaseDeployer = new OctopusDeployReleaseDeployer(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + releaseDeployer.DeployRelease(server, apiKey, projectName, deployToMultiple, releaseNumber, settings); + } + + /// + /// Promotes the specified already existing release into a specified environment + /// See Octopus Documentation for more details. + /// + /// The cake context. + /// The Octopus server URL. + /// The user's API key. + /// Name of the target project. + /// Source environment name. + /// Target environment name. + /// Deployment settings. + /// + /// + /// // bare minimum + /// OctoPromoteRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", "Testing", "Staging", new OctopusDeployPromoteReleaseSettings()); + /// + /// // All of deployment arguments + /// OctoPromoteRelease("http://octopus-deploy.example", "API-XXXXXXXXXXXXXXXXXXXX", "MyGreatProject", "Testing", "Staging", new OctopusDeployPromoteReleaseSettings { + /// ShowProgress = true, + /// ForcePackageDownload = true, + /// WaitForDeployment = true, + /// DeploymentTimeout = TimeSpan.FromMinutes(1), + /// CancelOnTimeout = true, + /// DeploymentChecksLeapCycle = TimeSpan.FromMinutes(77), + /// GuidedFailure = true, + /// SpecificMachines = new string[] { "Machine1", "Machine2" }, + /// Force = true, + /// SkipSteps = new[] { "Step1", "Step2" }, + /// NoRawLog = true, + /// RawLogFile = "someFile.txt", + /// DeployAt = new DateTime(2010, 6, 15).AddMinutes(1), + /// Tenant = new[] { "Tenant1", "Tenant2" }, + /// TenantTags = new[] { "Tag1", "Tag2" }, + /// }); + /// + /// + [CakeMethodAlias] + public static void OctoPromoteRelease(this ICakeContext context, string server, string apiKey, string projectName, string deployFrom, string deployTo, OctopusDeployPromoteReleaseSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + var releasePromoter = new OctopusDeployReleasePromoter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + releasePromoter.PromoteRelease(server, apiKey, projectName, deployFrom, deployTo, settings); + } } -} \ No newline at end of file +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployArgumentBuilder.cs index da73b767ea..333abf4653 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployArgumentBuilder.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployArgumentBuilder.cs @@ -1,19 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.IO; namespace Cake.Common.Tools.OctopusDeploy { - internal abstract class OctopusDeployArgumentBuilder where T : OctopusDeploySettings + internal abstract class OctopusDeployArgumentBuilder + where T : OctopusDeployCommonToolSettings { - protected readonly ICakeEnvironment Environment; - protected readonly ProcessArgumentBuilder Builder; - protected readonly T Settings; private readonly string _serverUrl; private readonly string _apiKey; + protected ICakeEnvironment Environment { get; } + + protected ProcessArgumentBuilder Builder { get; } + + protected T Settings { get; } + protected OctopusDeployArgumentBuilder(ICakeEnvironment environment, T settings) : this(settings.Server, settings.ApiKey, environment, settings) { @@ -21,11 +26,12 @@ protected OctopusDeployArgumentBuilder(ICakeEnvironment environment, T settings) protected OctopusDeployArgumentBuilder(string server, string apiKey, ICakeEnvironment environment, T settings) { - Settings = settings; _serverUrl = server; _apiKey = apiKey; + Environment = environment; Builder = new ProcessArgumentBuilder(); + Settings = settings; } protected void AppendArgumentIfNotNull(string argumentName, string value) @@ -46,6 +52,26 @@ protected void AppendArgumentIfNotNull(string argumentName, FilePath value) } } + protected void AppendMultipleTimes(string argumentName, string[] values) + { + if (values != null && values.Length > 0) + { + foreach (var value in values) + { + Builder.AppendSwitchQuoted("--" + argumentName, "=", value); + } + } + } + + protected ProcessArgumentBuilder AppendConditionalFlag(bool condition, string flag) + { + if (condition) + { + Builder.Append(flag); + } + return Builder; + } + protected void AppendCommonArguments() { Builder.Append("--server"); @@ -78,6 +104,8 @@ protected void AppendCommonArguments() { Builder.Append("--enableServiceMessages"); } + + AppendArgumentIfNotNull("space", Settings.Space); } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployDeploymentLister.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployDeploymentLister.cs new file mode 100644 index 0000000000..7e6032f31d --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployDeploymentLister.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Allows you to query your Octopus Deploy server deployment history. + /// + public class OctopusDeployDeploymentQuerier : OctopusDeployTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public OctopusDeployDeploymentQuerier( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Pushes the specified packages to Octopus Deploy internal repository. + /// + /// The Octopus server URL. + /// The user's API key. + /// The query. + /// A list of Octopus Deployments. + public IEnumerable QueryOctopusDeployments(string server, string apiKey, OctopusDeploymentQuerySettings querySettings) + { + ArgumentNullException.ThrowIfNull(querySettings); + if (string.IsNullOrEmpty(server)) + { + throw new ArgumentException("No server specified.", nameof(server)); + } + if (string.IsNullOrEmpty(apiKey)) + { + throw new ArgumentException("No API key specified.", nameof(apiKey)); + } + if (querySettings.Count < 1) + { + throw new ArgumentOutOfRangeException("Query must return at least one result", nameof(querySettings.Count)); + } + + var builder = new OctopusDeploymentQueryArgumentBuilder(server, apiKey, Environment, querySettings); + var process = RunProcess(querySettings, builder.Get()); + + if (querySettings.ToolTimeout.HasValue) + { + if (!process.WaitForExit((int)querySettings.ToolTimeout.Value.TotalMilliseconds)) + { + const string message = "Tool timeout ({0}): {1}"; + throw new TimeoutException(string.Format(CultureInfo.InvariantCulture, message, querySettings.ToolTimeout.Value, GetToolName())); + } + } + else + { + process.WaitForExit(); + } + + ProcessExitCode(process.GetExitCode()); + var parser = new DeploymentQueryResultParser(); + return parser.ParseResults(process.GetStandardOutput()); + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPacker.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPacker.cs new file mode 100644 index 0000000000..cbccde2eaf --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPacker.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// The Octopus deploy package packer. + /// + public sealed class OctopusDeployPacker : OctopusDeployTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public OctopusDeployPacker(IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Creates an Octopus deploy package with the specified ID. + /// + /// The package ID. + /// The settings. + public void Pack(string id, OctopusPackSettings settings) + { + ArgumentNullException.ThrowIfNull(id); + + var arguments = GetArguments(id, settings); + Run(settings, arguments); + } + + private ProcessArgumentBuilder GetArguments(string id, OctopusPackSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("pack"); + builder.Append($"--id {id}"); + + if (!string.IsNullOrWhiteSpace(settings.Version)) + { + builder.AppendSwitch("--version", settings.Version); + } + + if (settings.OutFolder != null) + { + builder.AppendSwitchQuoted("--outFolder", settings.OutFolder.MakeAbsolute(Environment).FullPath); + } + + if (settings.BasePath != null) + { + builder.AppendSwitchQuoted("--basePath", settings.BasePath.MakeAbsolute(Environment).FullPath); + } + + if (!string.IsNullOrWhiteSpace(settings.Author)) + { + builder.AppendSwitchQuoted("--author", settings.Author); + } + + if (!string.IsNullOrWhiteSpace(settings.Title)) + { + builder.AppendSwitchQuoted("--title", settings.Title); + } + + if (!string.IsNullOrWhiteSpace(settings.Description)) + { + builder.AppendSwitchQuoted("--description", settings.Description); + } + + if (!string.IsNullOrWhiteSpace(settings.ReleaseNotes)) + { + builder.AppendSwitchQuoted("--releaseNotes", settings.ReleaseNotes); + } + + if (settings.ReleaseNotesFile != null) + { + builder.AppendSwitchQuoted("--releaseNotesFile", settings.ReleaseNotesFile.MakeAbsolute(Environment).FullPath); + } + + if (settings.Include != null) + { + foreach (var include in settings.Include) + { + builder.AppendSwitchQuoted("--include", include); + } + } + + if (settings.Overwrite) + { + builder.Append("--overwrite"); + } + + if (settings.Format == OctopusPackFormat.Zip) + { + builder.AppendSwitch("--format", "Zip"); + } + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPromoteReleaseSettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPromoteReleaseSettings.cs new file mode 100644 index 0000000000..49b04b3d06 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPromoteReleaseSettings.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Possible arguments to pass to Octo.exe for promoting a release. See Octopus Deploy documentation. + /// + public sealed class OctopusDeployPromoteReleaseSettings : OctopusDeployCommonToolSettings + { + /// + /// Initializes a new instance of the class. + /// + public OctopusDeployPromoteReleaseSettings() + { + Variables = new Dictionary(); + } + + /// + /// Gets or sets a value indicating whether overwrite the variable snapshot for the release by re-importing the variables from the project. + /// + public bool UpdateVariables { get; set; } + + /// + /// Gets or sets a value indicating whether progress of the deployment should be followed. (Sets --waitfordeployment and --norawlog to true.) + /// + public bool ShowProgress { get; set; } + + /// + /// Gets or sets a value indicating whether to force downloading of already installed packages. Default false. + /// + public bool ForcePackageDownload { get; set; } + + /// + /// Gets or sets a value indicating whether to wait synchronously for deployment to finish. + /// + public bool WaitForDeployment { get; set; } + + /// + /// Gets or sets maximum time (timespan format) that the console session will wait for the deployment to finish (default 00:10:00). + /// This will not stop the deployment. Requires WaitForDeployment parameter set. + /// + public TimeSpan? DeploymentTimeout { get; set; } + + /// + /// Gets or sets a value indicating whether to cancel the deployment if the deployment timeout is reached(default false). + /// + public bool CancelOnTimeout { get; set; } + + /// + /// Gets or sets how much time should elapse between deployment status checks(default 00:00:10). + /// + public TimeSpan? DeploymentChecksLeepCycle { get; set; } + + /// + /// Gets or sets a value indicating whether to use Guided Failure mode. If not specified, will use default setting from environment. + /// + public bool? GuidedFailure { get; set; } + + /// + /// Gets or sets list of machines names to target in the deployed environment.If not specified all machines in the environment will be considered. + /// + public string[] SpecificMachines { get; set; } + + /// + /// Gets or sets a value indicating whether a project is configured to skip packages with already-installed versions, override this setting to force re-deployment (flag, default false). + /// + public bool Force { get; set; } + + /// + /// Gets or sets a list of steps to be skipped. Takes step names. + /// + public string[] SkipSteps { get; set; } + + /// + /// Gets or sets a value indicating whether print the raw log of failed tasks or not. + /// + public bool NoRawLog { get; set; } + + /// + /// Gets or sets a file where to redirect the raw log of failed tasks. + /// + public FilePath RawLogFile { get; set; } + + /// + /// Gets or sets values for any prompted variables. + /// + public IDictionary Variables { get; set; } + + /// + /// Gets or sets time at which deployment should start (scheduled deployment), specified as any valid DateTimeOffset format, and assuming the time zone is the current local time zone. + /// + public DateTimeOffset? DeployAt { get; set; } + + /// + /// Gets or sets a tenant the deployment will be performed for; specify this argument multiple times to add multiple tenants or use `*` wildcard to deploy to tenants able to deploy. + /// + public string[] Tenant { get; set; } + + /// + /// Gets or sets a tenant tags used to match tenants that the deployment will be performed for; specify this argument multiple times to add multiple tenant tags. + /// + public string[] TenantTags { get; set; } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPusher.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPusher.cs index 0d97c4d602..a915961841 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPusher.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployPusher.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Collections.Generic; using System.Linq; using Cake.Core; using Cake.Core.IO; @@ -11,12 +11,10 @@ namespace Cake.Common.Tools.OctopusDeploy { /// - /// The Octopus Deploy package push runner + /// The Octopus Deploy package push runner. /// - public class OctopusDeployPusher : Tool + public class OctopusDeployPusher : OctopusDeployTool { - private readonly ICakeEnvironment _environment; - /// /// Initializes a new instance of the class. /// @@ -31,55 +29,33 @@ public OctopusDeployPusher( IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { - _environment = environment; - } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return "Octo"; } /// - /// Gets the possible names of the tool executable. + /// Pushes the specified packages to Octopus Deploy internal repository. /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "Octo.exe" }; - } - - /// - /// Pushes the specified packages to Octopus Deploy internal repository - /// - /// The Octopus server URL - /// The user's API key - /// Paths to the packages to be pushed - /// The settings + /// The Octopus server URL. + /// The user's API key. + /// Paths to the packages to be pushed. + /// The settings. public void PushPackage(string server, string apiKey, FilePath[] packagePaths, OctopusPushSettings settings) { if (packagePaths == null || !packagePaths.Any()) { - throw new ArgumentNullException("packagePaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(packagePaths)); } + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrEmpty(server)) { - throw new ArgumentException("No server specified.", "settings"); + throw new ArgumentException("No server specified.", nameof(settings)); } if (string.IsNullOrEmpty(apiKey)) { - throw new ArgumentException("No API key specified.", "settings"); + throw new ArgumentException("No API key specified.", nameof(settings)); } - var builder = new OctopusPushArgumentBuilder(packagePaths, server, apiKey, _environment, settings); + var builder = new OctopusPushArgumentBuilder(packagePaths, server, apiKey, Environment, settings); Run(settings, builder.Get()); } } -} \ No newline at end of file +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseCreator.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseCreator.cs index 2ffc181201..1e90248439 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseCreator.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseCreator.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; -using System.Collections.Generic; using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -10,12 +10,10 @@ namespace Cake.Common.Tools.OctopusDeploy { /// - /// The Octopus Deploy release creator runner + /// The Octopus Deploy release creator runner. /// - public sealed class OctopusDeployReleaseCreator : Tool + public sealed class OctopusDeployReleaseCreator : OctopusDeployTool { - private readonly ICakeEnvironment _environment; - /// /// Initializes a new instance of the class. /// @@ -27,55 +25,31 @@ public OctopusDeployReleaseCreator( IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) { - _environment = environment; } /// - /// Creates a release for the specified project in OctopusDeploy + /// Creates a release for the specified project in OctopusDeploy. /// - /// The target project name - /// The settings + /// The target project name. + /// The settings. public void CreateRelease(string projectName, CreateReleaseSettings settings) { - if (projectName == null) - { - throw new ArgumentNullException("projectName"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(projectName); + ArgumentNullException.ThrowIfNull(settings); if (string.IsNullOrEmpty(settings.Server)) { - throw new ArgumentException("No server specified.", "settings"); + throw new ArgumentException("No server specified.", nameof(settings)); } if (string.IsNullOrEmpty(settings.ApiKey)) { - throw new ArgumentException("No API key specified.", "settings"); + throw new ArgumentException("No API key specified.", nameof(settings)); } - var argumentBuilder = new CreateReleaseArgumentBuilder(projectName, settings, _environment); + var argumentBuilder = new CreateReleaseArgumentBuilder(projectName, settings, Environment); Run(settings, argumentBuilder.Get()); } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return "Octo"; - } - - /// - /// Gets the possible names of the tool executable. - /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "Octo.exe" }; - } } } diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeployer.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeployer.cs new file mode 100644 index 0000000000..b7e497e287 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeployer.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// The Octopus Deploy Release Deploy runner. This class facilitates deploying existing releases in Octopus Deploy. + /// + public sealed class OctopusDeployReleaseDeployer : OctopusDeployTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public OctopusDeployReleaseDeployer( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Requests a deployment of a specified release to an environment. + /// + /// Octopus Server URL. + /// The user's API key. + /// Name of the target project. + /// Environment to deploy to, e.g., Production. + /// Release number to be deployed to. + /// Settings for the deployment. + public void DeployRelease(string server, string apiKey, string projectName, string[] deployTo, string releaseNumber, OctopusDeployReleaseDeploymentSettings settings) + { + if (String.IsNullOrEmpty(server)) + { + throw new ArgumentNullException(nameof(server)); + } + + if (String.IsNullOrEmpty(apiKey)) + { + throw new ArgumentNullException(nameof(apiKey)); + } + + if (String.IsNullOrEmpty(projectName)) + { + throw new ArgumentNullException(nameof(projectName)); + } + + if (deployTo == null || deployTo.Length == 0 || deployTo.Any(environment => string.IsNullOrEmpty(environment))) + { + throw new ArgumentNullException(nameof(deployTo)); + } + + if (String.IsNullOrEmpty(releaseNumber)) + { + throw new ArgumentNullException(nameof(releaseNumber)); + } + + ArgumentNullException.ThrowIfNull(settings); + + var argumentBuilder = new DeployReleaseArgumentBuilder(server, apiKey, projectName, deployTo, releaseNumber, settings, Environment); + Run(settings, argumentBuilder.Get()); + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeploymentSettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeploymentSettings.cs new file mode 100644 index 0000000000..a8e0f7b504 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleaseDeploymentSettings.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Possible arguments to pass to Octo.exe for deploying a release. See Octopus Deploy documentation. + /// + public sealed class OctopusDeployReleaseDeploymentSettings : OctopusDeployCommonToolSettings + { + /// + /// Initializes a new instance of the class. + /// + public OctopusDeployReleaseDeploymentSettings() + { + Variables = new Dictionary(); + } + + /// + /// Gets or sets a value indicating whether progress of the deployment should be followed. (Sets --waitfordeployment and --norawlog to true.) + /// + public bool ShowProgress { get; set; } + + /// + /// Gets or sets a value indicating whether to force downloading of already installed packages. Default false. + /// + public bool ForcePackageDownload { get; set; } + + /// + /// Gets or sets a value indicating whether to wait synchronously for deployment to finish. + /// + public bool WaitForDeployment { get; set; } + + /// + /// Gets or sets maximum time (timespan format) that the console session will wait for the deployment to finish (default 00:10:00). + /// This will not stop the deployment. Requires WaitForDeployment parameter set. + /// + public TimeSpan? DeploymentTimeout { get; set; } + + /// + /// Gets or sets a value indicating whether to cancel the deployment if the deployment timeout is reached(default false). + /// + public bool CancelOnTimeout { get; set; } + + /// + /// Gets or sets how much time should elapse between deployment status checks(default 00:00:10). + /// + public TimeSpan? DeploymentChecksLeepCycle { get; set; } + + /// + /// Gets or sets a value indicating whether to use Guided Failure mode. If not specified, will use default setting from environment. + /// + public bool? GuidedFailure { get; set; } + + /// + /// Gets or sets list of machines names to target in the deployed environment.If not specified all machines in the environment will be considered. + /// + public string[] SpecificMachines { get; set; } + + /// + /// Gets or sets a value indicating whether a project is configured to skip packages with already-installed versions, override this setting to force re-deployment (flag, default false). + /// + public bool Force { get; set; } + + /// + /// Gets or sets a list of steps to be skipped. Takes step names. + /// + public string[] SkipSteps { get; set; } + + /// + /// Gets or sets a value indicating whether print the raw log of failed tasks or not. + /// + public bool NoRawLog { get; set; } + + /// + /// Gets or sets a file where to redirect the raw log of failed tasks. + /// + public FilePath RawLogFile { get; set; } + + /// + /// Gets or sets values for any prompted variables. + /// + public Dictionary Variables { get; set; } + + /// + /// Gets or sets time at which deployment should start (scheduled deployment), specified as any valid DateTimeOffset format, and assuming the time zone is the current local time zone. + /// + public DateTimeOffset? DeployAt { get; set; } + + /// + /// Gets or sets a tenant the deployment will be performed for; specify this argument multiple times to add multiple tenants or use `*` wildcard to deploy to tenants able to deploy. + /// + public string[] Tenant { get; set; } + + /// + /// Gets or sets a tenant tags used to match tenants that the deployment will be performed for; specify this argument multiple times to add multiple tenant tags. + /// + public string[] TenantTags { get; set; } + + /// + /// Gets or sets the channel to use when getting the release to deploy. + /// + public string Channel { get; set; } + + /// + /// Gets or sets the comma-separated list of machine names to exclude in the deployed environment.If not specified all machines in the environment will be considered. + /// + public string ExcludeMachines { get; set; } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleasePromoter.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleasePromoter.cs new file mode 100644 index 0000000000..40106c7f60 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployReleasePromoter.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// The Octopus Deploy Promote Release runner. This class facilitates promoting existing releases in Octopus Deploy. + /// + public sealed class OctopusDeployReleasePromoter : OctopusDeployTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public OctopusDeployReleasePromoter( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + } + + /// + /// Requests a promotion of a specified release to an environment. + /// + /// Octopus Server URL. + /// The user's API key. + /// Name of the target project. + /// Environment to promote from, e.g., Staging. + /// Environment to promote to, e.g., Production. + /// Settings for the deployment. + public void PromoteRelease(string server, string apiKey, string projectName, string deployFrom, string deployTo, OctopusDeployPromoteReleaseSettings settings) + { + if (String.IsNullOrEmpty(server)) + { + throw new ArgumentNullException(nameof(server)); + } + + if (String.IsNullOrEmpty(apiKey)) + { + throw new ArgumentNullException(nameof(apiKey)); + } + + if (String.IsNullOrEmpty(projectName)) + { + throw new ArgumentNullException(nameof(projectName)); + } + + if (String.IsNullOrEmpty(deployFrom)) + { + throw new ArgumentNullException(nameof(deployFrom)); + } + + if (String.IsNullOrEmpty(deployTo)) + { + throw new ArgumentNullException(nameof(deployTo)); + } + + ArgumentNullException.ThrowIfNull(settings); + + var argumentBuilder = new PromoteReleaseArgumentBuilder(server, apiKey, projectName, deployFrom, deployTo, settings, Environment); + Run(settings, argumentBuilder.Get()); + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploySettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploySettings.cs index b820954373..70369aa57d 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploySettings.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploySettings.cs @@ -1,23 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; namespace Cake.Common.Tools.OctopusDeploy { /// - /// Contains the common settings used by all commands in . + /// Base class for tool settings used by all commands in . /// - public abstract class OctopusDeploySettings : ToolSettings + public abstract class OctopusDeployToolSettings : ToolSettings + { + } + + /// + /// Contains the common settings used by all commands in . + /// + public abstract class OctopusDeployCommonToolSettings : OctopusDeployToolSettings { /// - /// Gets or sets the username to use when authenticating with the server + /// Gets or sets the username to use when authenticating with the server. /// public string Username { get; set; } /// - /// Gets or sets the password to use when authenticating with the server + /// Gets or sets the password to use when authenticating with the server. /// public string Password { get; set; } @@ -32,23 +40,28 @@ public abstract class OctopusDeploySettings : ToolSettings public string ApiKey { get; set; } /// - /// Gets or sets the text file of default values + /// Gets or sets the text file of default values. /// public FilePath ConfigurationFile { get; set; } /// - /// Gets or sets a value indicating whether the enable debug logging flag is set + /// Gets or sets a value indicating whether the enable debug logging flag is set. /// public bool EnableDebugLogging { get; set; } /// - /// Gets or sets a value indicating whether the ignore SSL errors flag is set + /// Gets or sets a value indicating whether the ignore SSL errors flag is set. /// public bool IgnoreSslErrors { get; set; } /// - /// Gets or sets a value indicating whether the enable service messages flag is set + /// Gets or sets a value indicating whether the enable service messages flag is set. /// public bool EnableServiceMessages { get; set; } + + /// + /// Gets or sets the name of a space within which this command will be executed. The default space will be used if it is omitted. + /// + public string Space { get; set; } } -} \ No newline at end of file +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployTool.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployTool.cs new file mode 100644 index 0000000000..455f809c9a --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployTool.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Base class for all octopus deploy related tools. + /// + /// The settings type. + public class OctopusDeployTool : Tool + where TSettings : OctopusDeployToolSettings + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public OctopusDeployTool( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) + : base(fileSystem, environment, processRunner, tools) + { + Environment = environment; + } + + /// + /// Gets the environment. + /// + protected ICakeEnvironment Environment { get; } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "Octo"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "Octo.exe", "dotnet-octo", "dotnet-octo.exe" }; + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployment.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployment.cs new file mode 100644 index 0000000000..a53eb8a77c --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeployment.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// An object representing a deployment in Octopus Deploy. + /// + public class OctopusDeployment + { + /// + /// gets or sets the Name of the Deployment's Project. + /// + public string ProjectName { get; set; } + + /// + /// gets or sets the Environment the project was deployed to. + /// + public string Environment { get; set; } + + /// + /// gets or sets the Deployment's channel. + /// + public string Channel { get; set; } + + /// + /// gets or sets When the deployment was created. + /// + public DateTimeOffset Created { get; set; } + + /// + /// gets or sets when the deployment was assembled. + /// + public DateTimeOffset Assembled { get; set; } + + /// + /// gets or sets the deployed project version. + /// + public string Version { get; set; } + + /// + /// gets or sets the list of packages in the deployment. + /// + public string PackageVersions { get; set; } + + /// + /// gets or sets the release notes for the deployment (HTML Markup). + /// + public string ReleaseNotesHtml { get; set; } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQueryArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQueryArgumentBuilder.cs new file mode 100644 index 0000000000..879fc0d747 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQueryArgumentBuilder.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + internal class OctopusDeploymentQueryArgumentBuilder : OctopusDeployArgumentBuilder + { + public OctopusDeploymentQueryArgumentBuilder(string server, string apiKey, ICakeEnvironment environment, OctopusDeploymentQuerySettings settings) : base(server, apiKey, environment, settings) + { + } + + public ProcessArgumentBuilder Get() + { + AppendPackageArguments(); + AppendCommonArguments(); + return Builder; + } + + private void AppendPackageArguments() + { + Builder.Append("list-deployments"); + + if (!string.IsNullOrEmpty(Settings.EnvironmentName)) + { + Builder.Append("--environment \"{0}\"", Settings.EnvironmentName); + } + + if (!string.IsNullOrEmpty(Settings.ProjectName)) + { + Builder.Append("--project \"{0}\"", Settings.ProjectName); + } + + if (!string.IsNullOrEmpty(Settings.TenantName)) + { + Builder.Append("--tenant \"{0}\"", Settings.TenantName); + } + + Builder.Append("--number {0}", Settings.Count); + } + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQuerySettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQuerySettings.cs new file mode 100644 index 0000000000..bb06d81364 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusDeploymentQuerySettings.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Contains settings used by . + /// + public sealed class OctopusDeploymentQuerySettings : OctopusDeployCommonToolSettings + { + /// + /// Gets or Sets a value that is an Octopus Environment Name to filter for. + /// + public string EnvironmentName { get; set; } + + /// + /// Gets or Sets a value that is an Octopus Project Name to filter for. + /// + public string ProjectName { get; set; } + + /// + /// Gets or Sets a value that is an Octopus Tenant Name to filter for. + /// + public string TenantName { get; set; } + + /// + /// Gets or Sets a value that indicates how many deployments to retrieve + /// in Date Descending order (most recent first) + /// Default: 1. + /// + public int Count { get; set; } = 1; + } +} diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusPackFormat.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusPackFormat.cs new file mode 100644 index 0000000000..9b1a95a071 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusPackFormat.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Represents the format of an Octopus package. + /// + public enum OctopusPackFormat + { + /// + /// NuGet package + /// + NuPkg, + + /// + /// Zip package + /// + Zip + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusPackSettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusPackSettings.cs new file mode 100644 index 0000000000..ea47b9a0a9 --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusPackSettings.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + /// + /// Contains the settings used by OctoPack. + /// + public sealed class OctopusPackSettings : OctopusDeployToolSettings + { + /// + /// Gets or sets the version. + /// + public string Version { get; set; } + + /// + /// Gets or sets the package format. + /// + public OctopusPackFormat Format { get; set; } + + /// + /// Gets or sets the folder into which the package will be written. Defaults to the current folder. + /// + public DirectoryPath OutFolder { get; set; } + + /// + /// Gets or sets the root folder containing files and folders to pack. Defaults to the current folder. + /// + public DirectoryPath BasePath { get; set; } + + /// + /// Gets or sets the author. Only applies to NuGet packages. + /// + public string Author { get; set; } + + /// + /// Gets or sets the title. Only applies to NuGet packages. + /// + public string Title { get; set; } + + /// + /// Gets or sets the description. Only applies to NuGet packages. + /// + public string Description { get; set; } + + /// + /// Gets or sets the release notes. Only applies to NuGet packages. + /// + public string ReleaseNotes { get; set; } + + /// + /// Gets or sets the release notes file. Only applies to NuGet packages. + /// + public FilePath ReleaseNotesFile { get; set; } + + /// + /// Gets or sets the file patterns to include. If none are specified, defaults to **. + /// + public ICollection Include { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether to allow an existing package with the same ID/version to be overwritten. + /// + public bool Overwrite { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusPushArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusPushArgumentBuilder.cs index e398eb3f16..1a7f28f002 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusPushArgumentBuilder.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusPushArgumentBuilder.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Core; diff --git a/src/Cake.Common/Tools/OctopusDeploy/OctopusPushSettings.cs b/src/Cake.Common/Tools/OctopusDeploy/OctopusPushSettings.cs index bf62060978..7bf6a4405b 100644 --- a/src/Cake.Common/Tools/OctopusDeploy/OctopusPushSettings.cs +++ b/src/Cake.Common/Tools/OctopusDeploy/OctopusPushSettings.cs @@ -7,10 +7,10 @@ namespace Cake.Common.Tools.OctopusDeploy /// /// Contains settings used by . /// - public sealed class OctopusPushSettings : OctopusDeploySettings + public sealed class OctopusPushSettings : OctopusDeployCommonToolSettings { /// - /// Gets or sets a value indicating whether to overwrite an existing package + /// Gets or sets a value indicating whether to overwrite an existing package. /// public bool ReplaceExisting { get; set; } } diff --git a/src/Cake.Common/Tools/OctopusDeploy/PromoteReleaseArgumentBuilder.cs b/src/Cake.Common/Tools/OctopusDeploy/PromoteReleaseArgumentBuilder.cs new file mode 100644 index 0000000000..d0da8af9da --- /dev/null +++ b/src/Cake.Common/Tools/OctopusDeploy/PromoteReleaseArgumentBuilder.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Tools.OctopusDeploy +{ + internal sealed class PromoteReleaseArgumentBuilder : OctopusDeployArgumentBuilder + { + private readonly string _projectName; + private readonly string _deployFrom; + private readonly string _deployTo; + private readonly OctopusDeployPromoteReleaseSettings _settings; + private readonly ICakeEnvironment _environment; + + public PromoteReleaseArgumentBuilder(string server, string apiKey, string projectName, string deployFrom, string deployTo, OctopusDeployPromoteReleaseSettings settings, ICakeEnvironment environment) + : base(server, apiKey, environment, settings) + { + _projectName = projectName; + _deployFrom = deployFrom; + _deployTo = deployTo; + _settings = settings; + _environment = environment; + } + + public ProcessArgumentBuilder Get() + { + Builder.Append("promote-release"); + + Builder.AppendSwitchQuoted("--project", "=", _projectName); + Builder.AppendSwitchQuoted("--from", "=", _deployFrom); + Builder.AppendSwitchQuoted("--to", "=", _deployTo); + + AppendConditionalFlag(_settings.UpdateVariables, "--force"); + AppendCommonAndDeploymentParameters(); + + return Builder; + } + + private void AppendCommonAndDeploymentParameters() + { + AppendCommonArguments(); + AppendDeploymentParameters(); + } + + private void AppendDeploymentParameters() + { + AppendConditionalFlag(_settings.ShowProgress, "--progress"); + AppendConditionalFlag(_settings.ForcePackageDownload, "--forcepackagedownload"); + AppendConditionalFlag(_settings.WaitForDeployment, "--waitfordeployment"); + + if (_settings.DeploymentTimeout.HasValue) + { + Builder.AppendSwitchQuoted("--deploymenttimeout", "=", + _settings.DeploymentTimeout.Value.ToString("hh\\:mm\\:ss")); + } + + AppendConditionalFlag(_settings.CancelOnTimeout, "--cancelontimeout"); + + if (_settings.DeploymentChecksLeepCycle.HasValue) + { + Builder.AppendSwitchQuoted("--deploymentchecksleepcycle", "=", + _settings.DeploymentChecksLeepCycle.Value.ToString("hh\\:mm\\:ss")); + } + + if (_settings.GuidedFailure.HasValue) + { + Builder.AppendSwitch("--guidedfailure", "=", _settings.GuidedFailure.ToString()); + } + + if (_settings.SpecificMachines != null && _settings.SpecificMachines.Length > 0) + { + Builder.AppendSwitchQuoted("--specificmachines", "=", string.Join(",", _settings.SpecificMachines)); + } + + AppendConditionalFlag(_settings.Force, "--force"); + + AppendMultipleTimes("skip", _settings.SkipSteps); + + AppendConditionalFlag(_settings.NoRawLog, "--norawlog"); + + AppendArgumentIfNotNull("rawlogfile", _settings.RawLogFile); + + if (_settings.Variables != null && _settings.Variables.Count > 0) + { + foreach (var pair in _settings.Variables) + { + Builder.AppendSwitchQuoted("--variable", "=", $"{pair.Key}:{pair.Value}"); + } + } + + if (_settings.DeployAt.HasValue) + { + Builder.AppendSwitchQuoted("--deployat", "=", _settings.DeployAt.Value.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture)); + } + + AppendMultipleTimes("tenant", _settings.Tenant); + + AppendMultipleTimes("tenanttag", _settings.TenantTags); + } + } +} diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverAliases.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverAliases.cs index 686fcaa520..3fa3adfbae 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverAliases.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.OpenCover /// Contains functionality related to OpenCover. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=OpenCover" /// @@ -50,10 +51,7 @@ public static void OpenCover( FilePath outputFile, OpenCoverSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); // Create the OpenCover runner. var runner = new OpenCoverRunner( @@ -64,4 +62,4 @@ public static void OpenCover( runner.Run(context, action, outputFile, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverContext.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverContext.cs index ac5e17f214..6f27575a57 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverContext.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverContext.cs @@ -1,74 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.OpenCover { - internal sealed class OpenCoverContext : ICakeContext + internal sealed class OpenCoverContext : CakeContextAdapter { - private readonly ICakeContext _context; - private readonly ICakeLog _log; private readonly OpenCoverProcessRunner _runner; - public IFileSystem FileSystem - { - get { return _context.FileSystem; } - } - - public ICakeEnvironment Environment - { - get { return _context.Environment; } - } - - public IGlobber Globber - { - get { return _context.Globber; } - } + public override ICakeLog Log { get; } - public ICakeLog Log - { - get { return _log; } - } + public override IProcessRunner ProcessRunner => _runner; - public ICakeArguments Arguments - { - get { return _context.Arguments; } - } + public FilePath FilePath => _runner.FilePath; - public IProcessRunner ProcessRunner - { - get { return _runner; } - } - - public IRegistry Registry - { - get { return _context.Registry; } - } - - public IToolLocator Tools - { - get { return _context.Tools; } - } - - public FilePath FilePath - { - get { return _runner.FilePath; } - } - - public ProcessSettings Settings - { - get { return _runner.ProcessSettings; } - } + public ProcessSettings Settings => _runner.ProcessSettings; - public OpenCoverContext(ICakeContext context) + public OpenCoverContext(ICakeContext context) : base(context) { - _context = context; - _log = new NullLog(); + Log = new NullLog(); _runner = new OpenCoverProcessRunner(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOption.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOption.cs new file mode 100644 index 0000000000..c41d753cb6 --- /dev/null +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOption.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.OpenCover +{ + using System; + + /// + /// Represents the hide skipped option for OpenCover. + /// + [Flags] + public enum OpenCoverHideSkippedOption + { + /// + /// Removes no information from output file. + /// + None = 0, + + /// + /// Removes information from output files that relates to classes/modules that have been skipped (filtered) due to use of the -excludebyfile -switch. + /// + File = 1, + + /// + /// Removes information from output files that relates to classes/modules that have been skipped (filtered) due to use of the -filter -switch. + /// + Filter = 2, + + /// + /// Removes information from output files that relates to classes/modules that have been skipped (filtered) due to use of the -excludebyattribute -switch. + /// + Attribute = 4, + + /// + /// Removes missing Pdb information from output file. + /// + MissingPdb = 8, + + /// + /// Removes all information from output file. + /// + All = File | Filter | Attribute | MissingPdb + } +} diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOptionExtensions.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOptionExtensions.cs new file mode 100644 index 0000000000..91466b7d8c --- /dev/null +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverHideSkippedOptionExtensions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Cake.Common.Tools.OpenCover +{ + /// + /// Extensions class for . + /// + public static class OpenCoverHideSkippedOptionExtensions + { + /// + /// Get flags. + /// + /// + /// The input. + /// + /// + /// The . + /// + public static IEnumerable GetFlags(this OpenCoverHideSkippedOption openCoverHideSkippedOption) + { + foreach (OpenCoverHideSkippedOption value in Enum.GetValues(openCoverHideSkippedOption.GetType())) + { + if (openCoverHideSkippedOption.HasFlag(value)) + { + yield return value; + } + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverLogLevel.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverLogLevel.cs new file mode 100644 index 0000000000..c793d4ad51 --- /dev/null +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverLogLevel.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.OpenCover +{ + /// + /// Represents the logging output level for OpenCover. + /// + public enum OpenCoverLogLevel + { + /// + /// Logs info messages and above (default) + /// + Info, + + /// + /// This log level disables logging + /// + Off, + + /// + /// Logs fatal messages + /// + Fatal, + + /// + /// Logs error messages and above + /// + Error, + + /// + /// Logs warn messages and above + /// + Warn, + + /// + /// Logs debug messages and above + /// + Debug, + + /// + /// Logs verbose messages and above + /// + Verbose, + + /// + /// Logs all messages + /// + All + } +} diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverProcessRunner.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverProcessRunner.cs index fad9b16cea..178dbeab96 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverProcessRunner.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverProcessRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Core.IO; @@ -33,6 +34,11 @@ public int GetExitCode() return 0; } + public IEnumerable GetStandardError() + { + return Enumerable.Empty(); + } + public IEnumerable GetStandardOutput() { return Enumerable.Empty(); @@ -50,4 +56,4 @@ public IProcess Start(FilePath filePath, ProcessSettings settings) return new InterceptedProcess(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverRegisterOption.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverRegisterOption.cs new file mode 100644 index 0000000000..a7b2c4970d --- /dev/null +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverRegisterOption.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +using Cake.Core.IO; + +namespace Cake.Common.Tools.OpenCover +{ + /// + /// Represents the register-options: + /// + /// + /// empty + /// Registers and de-register the code coverage profiler. (Administrative permissions to the registry are required.) + /// + /// + /// "user" + /// Does per-user registration where the user account does not have administrative permissions. + /// + /// + /// path + /// If you do not want to use the registry entries, use -register:path to select the profiler. + /// + /// + /// + public abstract class OpenCoverRegisterOption + { + private readonly string commandLineValue; + + /// + /// Initializes a new instance of the class. + /// + /// The value, as required for the commandline. + /// + /// Note that no value (null or string.Empty) is valid. + /// However, if a value exists it NEEDS to start with a colon (":"). + /// + protected OpenCoverRegisterOption(string commandLineValue) + { + if (string.IsNullOrEmpty(commandLineValue)) + { + commandLineValue = string.Empty; + } + + if (!string.IsNullOrEmpty(commandLineValue)) + { + if (!commandLineValue.StartsWith(":", StringComparison.Ordinal)) + { + throw new ArgumentException(nameof(commandLineValue), "if a non-empty value is given, it needs to start with ':'"); + } + } + + this.commandLineValue = commandLineValue; + } + + /// + /// Converts to string. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return commandLineValue; + } + } + + /// + /// Gets the register-option representing the "user"-mode. + /// (This will translate to "-register:user".) + /// + /// + public class OpenCoverRegisterOptionUser + : OpenCoverRegisterOption + { + /// + /// Initializes a new instance of the class. + /// + public OpenCoverRegisterOptionUser() + : base(":user") + { + } + } + + /// + /// Gets the register-option representing the "admin"-mode. + /// (This will translate to "-register".) + /// + /// + public class OpenCoverRegisterOptionAdmin + : OpenCoverRegisterOption + { + /// + /// Initializes a new instance of the class. + /// + public OpenCoverRegisterOptionAdmin() + : base(string.Empty) + { + } + } + + /// + /// Gets a register-option pointing to a dll. + /// (This will translate to "-register:[path-to-dll]".) + /// + /// + public class OpenCoverRegisterOptionDll + : OpenCoverRegisterOption + { + /// + /// Initializes a new instance of the class. + /// + /// Path to the dll. + public OpenCoverRegisterOptionDll(FilePath pathToDll) + : base($":{pathToDll.FullPath}") + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverRunner.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverRunner.cs index 220f07db12..803ffeaec2 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverRunner.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverRunner.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Linq; + using Cake.Core; using Cake.Core.IO; using Cake.Core.Tooling; @@ -14,6 +17,7 @@ namespace Cake.Common.Tools.OpenCover /// public sealed class OpenCoverRunner : Tool { + private const string HideSkippedConstant = "-hideskipped"; private readonly ICakeEnvironment _environment; /// @@ -45,22 +49,10 @@ public void Run( FilePath outputPath, OpenCoverSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (action == null) - { - throw new ArgumentNullException("action"); - } - if (outputPath == null) - { - throw new ArgumentNullException("outputPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + ArgumentNullException.ThrowIfNull(outputPath); + ArgumentNullException.ThrowIfNull(settings); // Run the tool using the interceptor. var interceptor = InterceptAction(context, action); @@ -93,37 +85,34 @@ private ProcessArgumentBuilder GetArguments( var builder = new ProcessArgumentBuilder(); // The target application to call. - builder.AppendSwitch("-target", ":", context.FilePath.FullPath.Quote()); + builder.AppendSwitch("-target", ":", context.FilePath.MakeAbsolute(_environment).FullPath.Quote()); // The arguments to the target application. - if (context.Settings != null && context.Settings.Arguments != null) + var arguments = context.Settings?.Arguments?.Render(); + if (!string.IsNullOrWhiteSpace(arguments)) { - var arguments = context.Settings.Arguments.Render(); - if (!string.IsNullOrWhiteSpace(arguments)) - { - arguments = arguments.Replace("\"", "\\\""); - builder.AppendSwitch("-targetargs", ":", arguments.Quote()); - } + arguments = arguments.Replace("\"", "\\\""); + builder.AppendSwitch("-targetargs", ":", arguments.Quote()); } // Filters if (settings.Filters.Count > 0) { - var filters = string.Join(" ", settings.Filters); + var filters = string.Join(' ', settings.Filters); builder.AppendSwitch("-filter", ":", filters.Quote()); } // Exclude by attribute if (settings.ExcludedAttributeFilters.Count > 0) { - var filters = string.Join(";", settings.ExcludedAttributeFilters); + var filters = string.Join(';', settings.ExcludedAttributeFilters); builder.AppendSwitch("-excludebyattribute", ":", filters.Quote()); } // Exclude by file if (settings.ExcludedFileFilters.Count > 0) { - var filters = string.Join(";", settings.ExcludedFileFilters); + var filters = string.Join(';', settings.ExcludedFileFilters); builder.AppendSwitch("-excludebyfile", ":", filters.Quote()); } @@ -132,7 +121,22 @@ private ProcessArgumentBuilder GetArguments( builder.Append("-skipautoprops"); } - builder.AppendSwitch("-register", ":", settings.Register); + if (settings.OldStyle) + { + builder.Append("-oldStyle"); + } + + if (settings.MergeOutput) + { + builder.Append("-mergeoutput"); + } + + if (settings.Register != null) + { + // due to the fact that register sometimes needs a colon-separator and sometimes it does not + // there is no separator here but instead it's added in OpenCoverRegisterOption.ToString() + builder.AppendSwitch("-register", string.Empty, settings.Register.ToString()); + } if (settings.ReturnTargetCodeOffset != null) { @@ -143,6 +147,64 @@ private ProcessArgumentBuilder GetArguments( outputPath = outputPath.MakeAbsolute(_environment); builder.AppendSwitch("-output", ":", outputPath.FullPath.Quote()); + // Exclude directories + if (settings.ExcludeDirectories.Count > 0) + { + var excludeDirs = string.Join(';', settings.ExcludeDirectories.Select(d => d.MakeAbsolute(_environment).FullPath)); + builder.AppendSwitch("-excludedirs", ":", excludeDirs.Quote()); + } + + // Log level + if (settings.LogLevel != OpenCoverLogLevel.Info) + { + builder.AppendSwitch("-log", ":", settings.LogLevel.ToString()); + } + + // HideSkipped Option + if (settings.HideSkippedOption != OpenCoverHideSkippedOption.None) + { + if (settings.HideSkippedOption == OpenCoverHideSkippedOption.All) + { + builder.AppendSwitch(HideSkippedConstant, ":", "All"); + } + else + { + var hideSkippedOptions = string.Join(";", settings.HideSkippedOption.GetFlags()); + builder.AppendSwitch(HideSkippedConstant, ":", hideSkippedOptions); + } + } + + // Merge by hash + if (settings.MergeByHash) + { + builder.Append("-mergebyhash"); + } + + // No default filters + if (settings.NoDefaultFilters) + { + builder.Append("-nodefaultfilters"); + } + + // Search directories + if (settings.SearchDirectories.Count > 0) + { + var excludeDirs = string.Join(';', settings.SearchDirectories.Select(d => d.MakeAbsolute(_environment).FullPath)); + builder.AppendSwitch("-searchdirs", ":", excludeDirs.Quote()); + } + + // No default filters + if (settings.IsService) + { + builder.Append("-service"); + } + + // Target directory + if (settings.TargetDirectory != null) + { + builder.AppendSwitch("-targetdir", ":", settings.TargetDirectory.MakeAbsolute(_environment).FullPath.Quote()); + } + return builder; } @@ -164,4 +226,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "OpenCover.Console.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs index dfcba74d9e..dc30666ac1 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverSettings.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using Cake.Core.IO; using Cake.Core.Tooling; namespace Cake.Common.Tools.OpenCover @@ -15,36 +17,29 @@ public sealed class OpenCoverSettings : ToolSettings private readonly HashSet _filters; private readonly HashSet _excludedAttributeFilters; private readonly HashSet _excludedFileFilters; + private readonly HashSet _excludeDirectories; + private readonly HashSet _searchDirectories; /// /// Gets the filters. /// This represents the -filter option. /// /// The filters. - public ISet Filters - { - get { return _filters; } - } + public ISet Filters => _filters; /// /// Gets attribute filters used to exclude classes or methods. /// This represents the -excludebyattribute option. /// /// The excluded attributes. - public ISet ExcludedAttributeFilters - { - get { return _excludedAttributeFilters; } - } + public ISet ExcludedAttributeFilters => _excludedAttributeFilters; /// /// Gets file filters used to excluded classes or methods. /// This represents the -excludebyfile option. /// /// The excluded file filters. - public ISet ExcludedFileFilters - { - get { return _excludedFileFilters; } - } + public ISet ExcludedFileFilters => _excludedFileFilters; /// /// Gets or sets a value indicating whether or not auto-implemented properties should be skipped. @@ -52,24 +47,82 @@ public ISet ExcludedFileFilters public bool SkipAutoProps { get; set; } /// - /// Gets or sets the register option + /// Gets or sets the register option. /// - public string Register { get; set; } + public OpenCoverRegisterOption Register { get; set; } /// - /// Gets or sets the Return target code offset to be used + /// Gets or sets the Return target code offset to be used. /// public int? ReturnTargetCodeOffset { get; set; } + /// + /// Gets or sets a value indicating whether the OldStyle option for OpenCover should be used. + /// + public bool OldStyle { get; set; } + + /// + /// Gets or sets a value indicating whether to merge the results with an existing file. + /// + public bool MergeOutput { get; set; } + + /// + /// Gets a list of directories where assemblies being loaded from will be ignored. + /// + public ISet ExcludeDirectories => _excludeDirectories; + + /// + /// Gets or sets the log level of OpenCover. + /// + public OpenCoverLogLevel LogLevel { get; set; } + + /// + /// Gets or sets the hide skipped option of OpenCover. + /// + public OpenCoverHideSkippedOption HideSkippedOption { get; set; } + + /// + /// Gets or sets a value indicating whether to merge the coverage results for an assembly + /// regardless of where it was loaded assuming it has the same file-hash in each location. + /// + public bool MergeByHash { get; set; } + + /// + /// Gets or sets a value indicating whether the default filters should be applied or not. + /// + public bool NoDefaultFilters { get; set; } + + /// + /// Gets a list of directories with alternative locations to look for PDBs. + /// + public ISet SearchDirectories => _searchDirectories; + + /// + /// Gets or sets a value indicating whether if the value provided in the target parameter + /// is the name of a service rather than a name of a process. + /// + public bool IsService { get; set; } + + /// + /// Gets or sets a value indicating the path to the target directory. + /// If the target already contains a path, this parameter can be used + /// as additional path where PDBs may be found. + /// + public DirectoryPath TargetDirectory { get; set; } + /// /// Initializes a new instance of the class. /// public OpenCoverSettings() { - _filters = new HashSet(StringComparer.OrdinalIgnoreCase); + _filters = new HashSet(StringComparer.Ordinal); _excludedAttributeFilters = new HashSet(StringComparer.OrdinalIgnoreCase); _excludedFileFilters = new HashSet(StringComparer.Ordinal); - this.Register = "user"; + Register = new OpenCoverRegisterOptionUser(); + LogLevel = OpenCoverLogLevel.Info; + HideSkippedOption = OpenCoverHideSkippedOption.None; + _excludeDirectories = new HashSet(); + _searchDirectories = new HashSet(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/OpenCover/OpenCoverSettingsExtensions.cs b/src/Cake.Common/Tools/OpenCover/OpenCoverSettingsExtensions.cs index deddc6e9c7..a616d8365e 100644 --- a/src/Cake.Common/Tools/OpenCover/OpenCoverSettingsExtensions.cs +++ b/src/Cake.Common/Tools/OpenCover/OpenCoverSettingsExtensions.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using Cake.Core.IO; + namespace Cake.Common.Tools.OpenCover { /// @@ -18,10 +21,7 @@ public static class OpenCoverSettingsExtensions /// The same instance so that multiple calls can be chained. public static OpenCoverSettings WithFilter(this OpenCoverSettings settings, string filter) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Filters.Add(filter); return settings; } @@ -35,10 +35,7 @@ public static OpenCoverSettings WithFilter(this OpenCoverSettings settings, stri /// The same instance so that multiple calls can be chained. public static OpenCoverSettings ExcludeByAttribute(this OpenCoverSettings settings, string filter) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.ExcludedAttributeFilters.Add(filter); return settings; } @@ -51,12 +48,58 @@ public static OpenCoverSettings ExcludeByAttribute(this OpenCoverSettings settin /// The same instance so that multiple calls can be chained. public static OpenCoverSettings ExcludeByFile(this OpenCoverSettings settings, string filter) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.ExcludedFileFilters.Add(filter); return settings; } + + /// + /// Sets the register-option to "none". + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static OpenCoverSettings WithoutRegister(this OpenCoverSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + settings.Register = null; + return settings; + } + + /// + /// Sets the register-option to admin-registry-access. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static OpenCoverSettings WithRegisterAdmin(this OpenCoverSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + settings.Register = new OpenCoverRegisterOptionAdmin(); + return settings; + } + + /// + /// Sets the register-option to user-registry-access. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static OpenCoverSettings WithRegisterUser(this OpenCoverSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + settings.Register = new OpenCoverRegisterOptionUser(); + return settings; + } + + /// + /// Sets the register-option to dll-registration (i.e no registry-access). + /// + /// The settings. + /// The path. + /// The same instance so that multiple calls can be chained. + public static OpenCoverSettings WithRegisterDll(this OpenCoverSettings settings, FilePath path) + { + ArgumentNullException.ThrowIfNull(settings); + settings.Register = new OpenCoverRegisterOptionDll(path); + return settings; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorAliases.cs b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorAliases.cs index 8100f534d1..3f00ab12bb 100644 --- a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorAliases.cs +++ b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -13,7 +14,7 @@ namespace Cake.Common.Tools.ReportGenerator /// Contains functionality related to ReportGenerator. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=ReportGenerator" /// @@ -34,12 +35,9 @@ public static class ReportGeneratorAliases /// /// [CakeMethodAlias] - public static void ReportGenerator(this ICakeContext context, string pattern, DirectoryPath targetDir) + public static void ReportGenerator(this ICakeContext context, GlobPattern pattern, DirectoryPath targetDir) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var reports = context.Globber.GetFiles(pattern); ReportGenerator(context, reports, targetDir); } @@ -59,12 +57,9 @@ public static void ReportGenerator(this ICakeContext context, string pattern, Di /// /// [CakeMethodAlias] - public static void ReportGenerator(this ICakeContext context, string pattern, DirectoryPath targetDir, ReportGeneratorSettings settings) + public static void ReportGenerator(this ICakeContext context, GlobPattern pattern, DirectoryPath targetDir, ReportGeneratorSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var reports = context.Globber.GetFiles(pattern); ReportGenerator(context, reports, targetDir, settings); } @@ -140,13 +135,10 @@ public static void ReportGenerator(this ICakeContext context, IEnumerable reports, DirectoryPath targetDir, ReportGeneratorSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new ReportGeneratorRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(reports, targetDir, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorReportType.cs b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorReportType.cs index 4f78b19cc6..7f37eaf91e 100644 --- a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorReportType.cs +++ b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorReportType.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.ReportGenerator { /// - /// Represents ReportGenerator's output formats + /// Represents ReportGenerator's output formats. /// public enum ReportGeneratorReportType { @@ -46,6 +47,113 @@ public enum ReportGeneratorReportType /// /// Xml summary report. /// - XmlSummary = 8 + XmlSummary = 8, + + /// + /// Cobertura report + /// + Cobertura = 9, + + /// + /// CSV summary report + /// + CsvSummary = 10, + + /// + /// HTML Chart report + /// + HtmlChart = 11, + + /// + /// Html report with inline CSS and JavaScript + /// + HtmlInline = 12, + + /// + /// Same as HTMLInline but with modified CSS that matches the look and feel of Azure Pipelines. + /// + HtmlInline_AzurePipelines = 13, + + /// + /// A single PNG file containing a chart with historic coverage information. + /// + PngChart = 14, + + /// + /// Same as HTML but packaged into a single MHTML file. + /// + MHtml = 15, + + /// + /// Creates xml report in SonarQube 'Generic Test Data' format. + /// + /// + /// Requires ReportGenerator 4.0.6+ + /// + SonarQube = 16, + + /// + /// Same as HTMLInline but with modified CSS that matches the dark look and feel of Azure Pipelines. + /// + /// + /// Requires ReportGenerator 4.0.10+ + /// + HtmlInline_AzurePipelines_Dark = 17, + + /// + /// Creates xml report in Clover format. + /// + /// + /// Requires ReportGenerator 4.0.6+ + /// + Clover = 18, + + /// + /// Creates summary report in JSON format. + /// + /// + /// Requires ReportGenerator 4.5.2+ + /// + JsonSummary = 19, + + /// + /// Creates summary report in lcov format. + /// + /// + /// Requires ReportGenerator 4.3.0+ + /// + lcov = 20, + + /// + /// Outputs summary report as TeamCity statistics messages. + /// + /// + /// Requires ReportGenerator 4.1.3+ + /// + TeamCitySummary = 21, + + /// + /// Outputs a single Markdown file containing coverage information per class. + /// + /// + /// Requires ReportGenerator 5.1.10+ + /// + MarkdownSummary = 22, + + /// + /// Outputs a single Markdown file containing coverage information per assembly. + /// + /// + /// Requires ReportGenerator 5.3.0+ + /// + MarkdownAssembliesSummary = 23, + + /// + /// Outputs a single Markdown file containing coverage information per class. The report is optimized for GitHub. + /// + /// + /// Requires ReportGenerator 5.1.13+ + /// + MarkdownSummaryGithub = 24 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorRunner.cs b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorRunner.cs index 3070073e4a..d312021845 100644 --- a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorRunner.cs +++ b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -42,23 +43,14 @@ public ReportGeneratorRunner( /// The settings. public void Run(IEnumerable reports, DirectoryPath targetDir, ReportGeneratorSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - if (reports == null) - { - throw new ArgumentNullException("reports"); - } - if (targetDir == null) - { - throw new ArgumentNullException("targetDir"); - } + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(reports); + ArgumentNullException.ThrowIfNull(targetDir); var reportPaths = reports as FilePath[] ?? reports.ToArray(); if (!reportPaths.Any()) { - throw new ArgumentException("reports must not be empty", "reports"); + throw new ArgumentException("reports must not be empty", nameof(reports)); } Run(settings, GetArgument(settings, reportPaths, targetDir)); @@ -68,7 +60,7 @@ private ProcessArgumentBuilder GetArgument(ReportGeneratorSettings settings, IEn { var builder = new ProcessArgumentBuilder(); - var joinedReports = string.Join(";", reports.Select(r => r.MakeAbsolute(_environment).FullPath)); + var joinedReports = string.Join(';', reports.Select(r => r.MakeAbsolute(_environment).FullPath)); AppendQuoted(builder, "reports", joinedReports); AppendQuoted(builder, "targetdir", targetDir.MakeAbsolute(_environment).FullPath); @@ -81,7 +73,7 @@ private ProcessArgumentBuilder GetArgument(ReportGeneratorSettings settings, IEn if (settings.SourceDirectories != null && settings.SourceDirectories.Any()) { - var joined = string.Join(";", settings.SourceDirectories.Select(d => d.MakeAbsolute(_environment).FullPath)); + var joined = string.Join(';', settings.SourceDirectories.Select(d => d.MakeAbsolute(_environment).FullPath)); AppendQuoted(builder, "sourcedirs", joined); } @@ -92,13 +84,13 @@ private ProcessArgumentBuilder GetArgument(ReportGeneratorSettings settings, IEn if (settings.AssemblyFilters != null && settings.AssemblyFilters.Any()) { - var joined = string.Join(";", settings.AssemblyFilters); + var joined = string.Join(';', settings.AssemblyFilters); AppendQuoted(builder, "assemblyfilters", joined); } if (settings.ClassFilters != null && settings.ClassFilters.Any()) { - var joined = string.Join(";", settings.ClassFilters); + var joined = string.Join(';', settings.ClassFilters); AppendQuoted(builder, "classfilters", joined); } @@ -125,7 +117,7 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "ReportGenerator.exe" }; + return new[] { "reportgenerator", "ReportGenerator.exe" }; } private void AppendQuoted(ProcessArgumentBuilder builder, string key, string value) @@ -133,4 +125,4 @@ private void AppendQuoted(ProcessArgumentBuilder builder, string key, string val builder.AppendQuoted(string.Format(CultureInfo.InvariantCulture, "-{0}:{1}", key, value)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorSettings.cs b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorSettings.cs index 2a781510bf..a78581d67c 100644 --- a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorSettings.cs +++ b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -15,13 +16,13 @@ public sealed class ReportGeneratorSettings : ToolSettings /// /// Gets or sets the list of coverage reports that should be parsed. /// - public ICollection ReportTypes { get; set; } + public ICollection ReportTypes { get; set; } = new List(); /// /// Gets or sets the directories which contain the corresponding source code. /// The source files are used if coverage report contains classes without path information. /// - public ICollection SourceDirectories { get; set; } + public ICollection SourceDirectories { get; set; } = new List(); /// /// Gets or sets the directory for storing persistent coverage information. @@ -34,18 +35,18 @@ public sealed class ReportGeneratorSettings : ToolSettings /// Exclusion filters take precedence over inclusion filters. /// Wildcards are allowed. /// - public ICollection AssemblyFilters { get; set; } + public ICollection AssemblyFilters { get; set; } = new List(); /// /// Gets or sets the list of classes that should be included or excluded in the report. /// Exclusion filters take precedence over inclusion filters. /// Wildcards are allowed. /// - public ICollection ClassFilters { get; set; } + public ICollection ClassFilters { get; set; } = new List(); /// /// Gets or sets the verbosity level of the log messages. /// public ReportGeneratorVerbosity? Verbosity { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorVerbosity.cs b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorVerbosity.cs index aa8dd43d86..ee262f7d89 100644 --- a/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorVerbosity.cs +++ b/src/Cake.Common/Tools/ReportGenerator/ReportGeneratorVerbosity.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.ReportGenerator { /// @@ -23,4 +24,4 @@ public enum ReportGeneratorVerbosity /// Error = 3 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportUnit/ReportUnitAliases.cs b/src/Cake.Common/Tools/ReportUnit/ReportUnitAliases.cs index 69fbc5393e..32c0f48977 100644 --- a/src/Cake.Common/Tools/ReportUnit/ReportUnitAliases.cs +++ b/src/Cake.Common/Tools/ReportUnit/ReportUnitAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -9,10 +10,10 @@ namespace Cake.Common.Tools.ReportUnit { /// - /// Contains functionality related to ReportUnit. + /// Contains functionality related to ReportUnit. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=ReportUnit" /// @@ -77,10 +78,7 @@ public static void ReportUnit(this ICakeContext context, DirectoryPath inputFold [CakeMethodAlias] public static void ReportUnit(this ICakeContext context, DirectoryPath inputFolder, DirectoryPath outputFolder, ReportUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new ReportUnitRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(inputFolder, outputFolder, settings); @@ -102,10 +100,7 @@ public static void ReportUnit(this ICakeContext context, DirectoryPath inputFold [CakeMethodAlias] public static void ReportUnit(this ICakeContext context, FilePath inputFile, FilePath outputFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new ReportUnitRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(inputFile, outputFile, new ReportUnitSettings()); @@ -130,10 +125,7 @@ public static void ReportUnit(this ICakeContext context, FilePath inputFile, Fil [CakeMethodAlias] public static void ReportUnit(this ICakeContext context, FilePath inputFile, FilePath outputFile, ReportUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new ReportUnitRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(inputFile, outputFile, settings); diff --git a/src/Cake.Common/Tools/ReportUnit/ReportUnitRunner.cs b/src/Cake.Common/Tools/ReportUnit/ReportUnitRunner.cs index 17034eacff..77a658e1f7 100644 --- a/src/Cake.Common/Tools/ReportUnit/ReportUnitRunner.cs +++ b/src/Cake.Common/Tools/ReportUnit/ReportUnitRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -40,15 +41,9 @@ public ReportUnitRunner( /// The ReportUnit settings. public void Run(DirectoryPath inputFolder, DirectoryPath outputFolder, ReportUnitSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - if (inputFolder == null) - { - throw new ArgumentNullException("inputFolder"); - } + ArgumentNullException.ThrowIfNull(inputFolder); Run(settings, GetArguments(inputFolder, outputFolder)); } @@ -61,20 +56,11 @@ public void Run(DirectoryPath inputFolder, DirectoryPath outputFolder, ReportUni /// The ReportUnit settings. public void Run(FilePath inputFile, FilePath outputFile, ReportUnitSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - if (inputFile == null) - { - throw new ArgumentNullException("inputFile"); - } + ArgumentNullException.ThrowIfNull(inputFile); - if (outputFile == null) - { - throw new ArgumentNullException("outputFile"); - } + ArgumentNullException.ThrowIfNull(outputFile); Run(settings, GetArguments(inputFile, outputFile)); } @@ -122,4 +108,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "ReportUnit.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/ReportUnit/ReportUnitSettings.cs b/src/Cake.Common/Tools/ReportUnit/ReportUnitSettings.cs index c7de32e549..2c78d321a2 100644 --- a/src/Cake.Common/Tools/ReportUnit/ReportUnitSettings.cs +++ b/src/Cake.Common/Tools/ReportUnit/ReportUnitSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tooling; namespace Cake.Common.Tools.ReportUnit @@ -11,4 +12,4 @@ namespace Cake.Common.Tools.ReportUnit public sealed class ReportUnitSettings : ToolSettings { } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Roundhouse/RecoveryMode.cs b/src/Cake.Common/Tools/Roundhouse/RecoveryMode.cs index 4125aae6c1..26c61ec092 100644 --- a/src/Cake.Common/Tools/Roundhouse/RecoveryMode.cs +++ b/src/Cake.Common/Tools/Roundhouse/RecoveryMode.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.Roundhouse { /// - /// Defines the recovery model for SQL Server + /// Defines the recovery model for SQL Server. /// public enum RecoveryMode { @@ -23,4 +24,4 @@ public enum RecoveryMode /// Full } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Roundhouse/RoundhouseAliases.cs b/src/Cake.Common/Tools/Roundhouse/RoundhouseAliases.cs index 31c215af6c..bc7bd14a23 100644 --- a/src/Cake.Common/Tools/Roundhouse/RoundhouseAliases.cs +++ b/src/Cake.Common/Tools/Roundhouse/RoundhouseAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -11,7 +12,7 @@ namespace Cake.Common.Tools.Roundhouse /// Contains functionality related to RoundhousE. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=roundhouse" /// @@ -25,16 +26,22 @@ public static class RoundhouseAliases /// /// The context. /// The settings. + /// + /// + /// RoundhouseMigrate(new RoundhouseSettings{ + /// ServerName = "Sql2008R2", + /// DatabaseName = "AdventureWorks2008R2", + /// SqlFilesDirectory = "./src/sql" + /// }); + /// + /// [CakeMethodAlias] public static void RoundhouseMigrate(this ICakeContext context, RoundhouseSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new RoundhouseRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(settings); + runner.Run(settings, settings.Drop); } /// @@ -42,16 +49,21 @@ public static void RoundhouseMigrate(this ICakeContext context, RoundhouseSettin /// /// The context. /// The settings. + /// + /// + /// RoundhouseDrop(new RoundhouseSettings{ + /// ServerName = "Sql2008R2", + /// DatabaseName = "AdventureWorks2008R2" + /// }); + /// + /// [CakeMethodAlias] public static void RoundhouseDrop(this ICakeContext context, RoundhouseSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new RoundhouseRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(settings, true); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/Roundhouse/RoundhouseRunner.cs b/src/Cake.Common/Tools/Roundhouse/RoundhouseRunner.cs index da1c5a4703..301e2714bf 100644 --- a/src/Cake.Common/Tools/Roundhouse/RoundhouseRunner.cs +++ b/src/Cake.Common/Tools/Roundhouse/RoundhouseRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -36,10 +37,7 @@ public RoundhouseRunner( /// Will drop/delete the database if set to true. public void Run(RoundhouseSettings settings, bool drop = false) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Drop |= drop; Run(settings, GetArguments(settings)); @@ -80,6 +78,13 @@ private static void AddFlagArguments(ProcessArgumentBuilder builder, RoundhouseS AppendFlag(builder, "dryrun", settings.DryRun); AppendFlag(builder, "restore", settings.Restore); AppendFlag(builder, "silent", settings.Silent); + AppendFlag(builder, "baseline", settings.Baseline); + AppendFlag(builder, "searchallinsteadoftraverse", settings.SearchAllSubdirectoriesInsteadOfTraverse); + AppendFlag(builder, "disabletokens", settings.DisableTokenReplacement); + AppendFlag(builder, "runallanytimescripts", settings.RunAllAnyTimeScripts); + AppendFlag(builder, "debug", settings.Debug); + AppendFlag(builder, "disableoutput", settings.DisableOutput); + AppendFlag(builder, "donotcreatedatabase", settings.DoNotCreateDatabase); AppendFlag(builder, "w", settings.WarnOnOneTimeScriptChanges); AppendFlag(builder, "t", settings.WithTransaction); } @@ -148,7 +153,7 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "rh.exe" }; + return new[] { "rh.exe", "rh" }; } } } diff --git a/src/Cake.Common/Tools/Roundhouse/RoundhouseSettings.cs b/src/Cake.Common/Tools/Roundhouse/RoundhouseSettings.cs index c41940dc39..d9629114b5 100644 --- a/src/Cake.Common/Tools/Roundhouse/RoundhouseSettings.cs +++ b/src/Cake.Common/Tools/Roundhouse/RoundhouseSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tooling; namespace Cake.Common.Tools.Roundhouse @@ -67,10 +68,10 @@ public sealed class RoundhouseSettings : ToolSettings public string SqlFilesDirectory { get; set; } /// - /// Gets or sets the location of the source code repository + /// Gets or sets the location of the source code repository. /// /// - /// Path to code repository to be able to correlate versions + /// Path to code repository to be able to correlate versions. /// public string RepositoryPath { get; set; } @@ -198,7 +199,7 @@ public sealed class RoundhouseSettings : ToolSettings /// Gets or sets the schema name to use instead of [RoundhousE]. /// /// - /// The schema where RH stores it's tables. + /// The schema where RH stores its tables. /// public string SchemaName { get; set; } @@ -206,7 +207,7 @@ public sealed class RoundhouseSettings : ToolSettings /// Gets or sets the environment for RH to be scoped. /// /// - /// This allows RH to be environment aware and only run scripts that are in a particular environment based on the namingof the script. LOCAL.something**.ENV.**sql would only be run in the LOCAL environment. + /// This allows RH to be environment aware and only run scripts that are in a particular environment based on the naming of the script. LOCAL.something**.ENV.**sql would only be run in the LOCAL environment. /// public string Environment { get; set; } @@ -222,7 +223,7 @@ public sealed class RoundhouseSettings : ToolSettings /// Gets or sets the restore file path. /// /// - /// File path of back when Restore is set to true + /// File path of back when Restore is set to true. /// public string RestoreFilePath { get; set; } @@ -262,7 +263,7 @@ public sealed class RoundhouseSettings : ToolSettings /// Gets or sets database type. /// /// - /// Database Type (fully qualified class name implementing [roundhouse.sql.Database, roundhouse]) + /// Database Type (fully qualified class name implementing [roundhouse.sql.Database, roundhouse]). /// public string DatabaseType { get; set; } @@ -272,7 +273,7 @@ public sealed class RoundhouseSettings : ToolSettings /// /// This instructs RH to remove a database and not run migration scripts. /// - internal bool Drop { get; set; } + public bool Drop { get; set; } /// /// Gets or sets a value indicating whether to use transactions. @@ -286,7 +287,7 @@ public sealed class RoundhouseSettings : ToolSettings /// Gets or sets SQL Server recovery mode. /// /// - /// This sets the recovery model for SQL Server during migration. (NoChange, Simple, Full) + /// This sets the recovery model for SQL Server during migration. (NoChange, Simple, Full). /// public RecoveryMode? RecoveryMode { get; set; } @@ -297,5 +298,61 @@ public sealed class RoundhouseSettings : ToolSettings /// This instructs RH to log what would have run, but not to actually run anything against the database. Use this option if you are trying to figure out what RH is going to do. /// public bool DryRun { get; set; } + + /// + /// Gets or sets a value indicating whether to create a database if it does not exist. + /// + /// + /// This instructs RH to not create a database if it does not exists. Defaults to false. + /// + public bool DoNotCreateDatabase { get; set; } + + /// + /// Gets or sets a value indicating whether to disable output of backup, items ran, permissions dumps, etc. + /// + /// + /// Disable output of backups, items ran, permissions dumps, etc. Log files are kept. Useful for example in CI environment. Defaults to false. + /// + public bool DisableOutput { get; set; } + + /// + /// Gets or sets a value indicating whether to create an insert for its recording tables, but not run anything. + /// + /// + /// This instructs RH to create an insert for its recording tables, but not to actually run anything against the database. Use this option if you already have scripts that have been run through other means. Defaults to false. + /// + public bool Baseline { get; set; } + + /// + /// Gets or sets a value indicating whether to write debug messages. + /// + /// + /// This instructs RH to write out all messages. Defaults to false. + /// + public bool Debug { get; set; } + + /// + /// Gets or sets a value indicating whether to execute any time scripts. + /// + /// + /// This instructs RH to run any time scripts every time it is run. Defaults to false. + /// + public bool RunAllAnyTimeScripts { get; set; } + + /// + /// Gets or sets a value indicating whether to perform token replacement. + /// + /// + /// This instructs RH to not perform token replacement {{somename}}. Defaults to false. + /// + public bool DisableTokenReplacement { get; set; } + + /// + /// Gets or sets a value indicating whether to search all subdirectories. + /// + /// + /// Each Migration folder's subdirectories are traversed by default. This option pulls back scripts from the main directory and all subdirectories at once. Defaults to false. + /// + public bool SearchAllSubdirectoriesInsteadOfTraverse { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SignTool/ISignToolResolver.cs b/src/Cake.Common/Tools/SignTool/ISignToolResolver.cs index f360a9418c..1b891966bb 100644 --- a/src/Cake.Common/Tools/SignTool/ISignToolResolver.cs +++ b/src/Cake.Common/Tools/SignTool/ISignToolResolver.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; namespace Cake.Common.Tools.SignTool @@ -20,4 +21,4 @@ public interface ISignToolResolver /// The path to the sign tool. FilePath GetPath(); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SignTool/SignToolDigestAlgorithm.cs b/src/Cake.Common/Tools/SignTool/SignToolDigestAlgorithm.cs new file mode 100644 index 0000000000..1a9158ae81 --- /dev/null +++ b/src/Cake.Common/Tools/SignTool/SignToolDigestAlgorithm.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.SignTool +{ + /// + /// Digest algorithm for SignTool. + /// + public enum SignToolDigestAlgorithm + { + /// + /// SHA-1 digest algorithm + /// + Sha1, + + /// + /// SHA-256 digest algorithm. + /// + Sha256 + } +} diff --git a/src/Cake.Common/Tools/SignTool/SignToolResolver.cs b/src/Cake.Common/Tools/SignTool/SignToolResolver.cs index 70ad0f14f1..5965a2368b 100644 --- a/src/Cake.Common/Tools/SignTool/SignToolResolver.cs +++ b/src/Cake.Common/Tools/SignTool/SignToolResolver.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -22,20 +23,12 @@ public SignToolResolver(IFileSystem fileSystem, ICakeEnvironment environment, IR _environment = environment; _registry = registry; - if (fileSystem == null) - { - throw new ArgumentNullException("fileSystem"); - } - if (registry == null) - { - throw new ArgumentNullException("registry"); - } - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(fileSystem); + ArgumentNullException.ThrowIfNull(registry); + ArgumentNullException.ThrowIfNull(environment); } + /// public FilePath GetPath() { if (_signToolPath != null) @@ -44,7 +37,7 @@ public FilePath GetPath() } // Try look for the path. - _signToolPath = GetFromDisc() ?? GetFromRegistry(); + _signToolPath = GetFromRegistry() ?? GetFromDisc(); if (_signToolPath == null) { throw new CakeException("Failed to find signtool.exe."); @@ -57,15 +50,16 @@ public FilePath GetPath() private FilePath GetFromDisc() { // Get the path to program files. - var programFilesPath = _environment.Is64BitOperativeSystem() + var programFilesPath = _environment.Platform.Is64Bit ? _environment.GetSpecialPath(SpecialPath.ProgramFilesX86) : _environment.GetSpecialPath(SpecialPath.ProgramFiles); - // Get a list of the files we should check. + // Gets a list of the files we should check. var files = new List(); - if (_environment.Is64BitOperativeSystem()) + if (_environment.Platform.Is64Bit) { // 64-bit specific paths. + files.Add(programFilesPath.Combine(@"Windows Kits\10\bin\x64").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Windows Kits\8.1\bin\x64").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Windows Kits\8.0\bin\x64").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Microsoft SDKs\Windows\v7.1A\Bin").CombineWithFilePath("signtool.exe")); @@ -73,45 +67,81 @@ private FilePath GetFromDisc() else { // 32-bit specific paths. + files.Add(programFilesPath.Combine(@"Windows Kits\10\bin\x86").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Windows Kits\8.1\bin\x86").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Windows Kits\8.0\bin\x86").CombineWithFilePath("signtool.exe")); files.Add(programFilesPath.Combine(@"Microsoft SDKs\Windows\v7.1A\Bin").CombineWithFilePath("signtool.exe")); } + files.Add(programFilesPath.Combine(@"Windows Kits\10\App Certification Kit").CombineWithFilePath("signtool.exe")); - // Return the first path that exist. + // Return the first path that exists. return files.FirstOrDefault(file => _fileSystem.Exist(file)); } private FilePath GetFromRegistry() { - using (var root = _registry.LocalMachine.OpenKey("Software\\Microsoft\\Microsoft SDKs\\Windows")) + // Gets a list of the files we should check. + var files = new List(); + + using (var root = _registry.LocalMachine.OpenKey("Software\\Microsoft\\Windows Kits\\Installed Roots")) + { + string kitsRoot = root?.GetValue("KitsRoot10") as string; + if (!string.IsNullOrWhiteSpace(kitsRoot)) + { + var kitsPath = new DirectoryPath(kitsRoot); + string[] versions = root.GetSubKeyNames(); + + foreach (string version in versions.OrderByDescending(x => x)) + { + var versionPath = kitsPath.Combine($"bin\\{version}"); + + files.Add(_environment.Platform.Is64Bit + ? versionPath.CombineWithFilePath("x64\\signtool.exe") + : versionPath.CombineWithFilePath("x86\\signtool.exe")); + } + } + } + + using (var root = _registry.LocalMachine.OpenKey("Software\\Microsoft\\Windows Kits\\Installed Roots")) { - if (root == null) + if (root != null) { - return null; + var windowsKits = new[] { "KitsRoot10", "KitsRoot81", "KitsRoot" }; + foreach (var kit in windowsKits) + { + var kitsRoot = root.GetValue(kit) as string; + if (!string.IsNullOrWhiteSpace(kitsRoot)) + { + var kitsPath = new DirectoryPath(kitsRoot); + + files.Add(_environment.Platform.Is64Bit + ? kitsPath.CombineWithFilePath("bin\\x64\\signtool.exe") + : kitsPath.CombineWithFilePath("bin\\x86\\signtool.exe")); + } + } } + } - var keyName = root.GetSubKeyNames(); - foreach (var key in keyName) + using (var root = _registry.LocalMachine.OpenKey("Software\\Microsoft\\Microsoft SDKs\\Windows")) + { + if (root != null) { - var sdkKey = root.OpenKey(key); - if (sdkKey != null) + var keyName = root.GetSubKeyNames(); + foreach (var key in keyName) { - var installationFolder = sdkKey.GetValue("InstallationFolder") as string; + var sdkKey = root.OpenKey(key); + var installationFolder = sdkKey?.GetValue("InstallationFolder") as string; if (!string.IsNullOrWhiteSpace(installationFolder)) { var installationPath = new DirectoryPath(installationFolder); - var signToolPath = installationPath.CombineWithFilePath("bin\\signtool.exe"); - - if (_fileSystem.Exist(signToolPath)) - { - return signToolPath; - } + files.Add(installationPath.CombineWithFilePath("bin\\signtool.exe")); } } } } - return null; + + // Return the first path that exists. + return files.FirstOrDefault(file => _fileSystem.Exist(file)); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SignTool/SignToolSignAliases.cs b/src/Cake.Common/Tools/SignTool/SignToolSignAliases.cs index c11774ad6c..d5b0242f72 100644 --- a/src/Cake.Common/Tools/SignTool/SignToolSignAliases.cs +++ b/src/Cake.Common/Tools/SignTool/SignToolSignAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -46,10 +47,7 @@ public static class SignToolSignAliases [CakeMethodAlias] public static void Sign(this ICakeContext context, string assembly, SignToolSignSettings settings) { - if (assembly == null) - { - throw new ArgumentNullException("assembly"); - } + ArgumentNullException.ThrowIfNull(assembly); Sign(context, new FilePath(assembly), settings); } @@ -68,7 +66,7 @@ public static void Sign(this ICakeContext context, string assembly, SignToolSign /// .Does(() => /// { /// var file = new FilePath("Core.dll"); - /// Sign(files, new SignToolSignSettings { + /// Sign(file, new SignToolSignSettings { /// TimeStampUri = new Uri("http://timestamp.digicert.com"), /// CertPath = "digitalcertificate.pfx", /// Password = "TopSecret" @@ -79,10 +77,7 @@ public static void Sign(this ICakeContext context, string assembly, SignToolSign [CakeMethodAlias] public static void Sign(this ICakeContext context, FilePath assembly, SignToolSignSettings settings) { - if (assembly == null) - { - throw new ArgumentNullException("assembly"); - } + ArgumentNullException.ThrowIfNull(assembly); var paths = new[] { assembly }; Sign(context, paths, settings); } @@ -113,10 +108,7 @@ public static void Sign(this ICakeContext context, FilePath assembly, SignToolSi [CakeMethodAlias] public static void Sign(this ICakeContext context, IEnumerable assemblies, SignToolSignSettings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); Sign(context, paths, settings); } @@ -147,24 +139,12 @@ public static void Sign(this ICakeContext context, IEnumerable assemblie [CakeMethodAlias] public static void Sign(this ICakeContext context, IEnumerable assemblies, SignToolSignSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); + ArgumentNullException.ThrowIfNull(settings); var runner = new SignToolSignRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools, context.Registry); - foreach (var assembly in assemblies) - { - runner.Run(assembly, settings); - } + runner.Run(assemblies, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SignTool/SignToolSignRunner.cs b/src/Cake.Common/Tools/SignTool/SignToolSignRunner.cs index 42a72f369a..3500ddc1ce 100644 --- a/src/Cake.Common/Tools/SignTool/SignToolSignRunner.cs +++ b/src/Cake.Common/Tools/SignTool/SignToolSignRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -59,43 +60,30 @@ internal SignToolSignRunner( } /// - /// Signs the specified assembly. + /// Signs the specified assemblies. /// - /// The assembly path. + /// The assembly paths. /// The settings. - public void Run(FilePath assemblyPath, SignToolSignSettings settings) + public void Run(IEnumerable assemblyPaths, SignToolSignSettings settings) { - if (assemblyPath == null) - { - throw new ArgumentNullException("assemblyPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); - if (assemblyPath.IsRelative) - { - assemblyPath = assemblyPath.MakeAbsolute(_environment); - } + var absoluteAssemblyPaths = assemblyPaths.Select(p => p.IsRelative ? p.MakeAbsolute(_environment) : p).ToArray(); - Run(settings, GetArguments(assemblyPath, settings)); + Run(settings, GetArguments(absoluteAssemblyPaths, settings)); } - private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignSettings settings) + private ProcessArgumentBuilder GetArguments(FilePath[] absoluteAssemblyPaths, SignToolSignSettings settings) { - if (!_fileSystem.Exist(assemblyPath)) - { - const string format = "{0}: The assembly '{1}' do not exist."; - var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName(), assemblyPath.FullPath); - throw new CakeException(message); - } - - if (settings.TimeStampUri == null) + foreach (var path in absoluteAssemblyPaths) { - const string format = "{0}: Timestamp server URL is required but not specified."; - var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName()); - throw new CakeException(message); + if (!_fileSystem.Exist(path)) + { + const string format = "{0}: The assembly '{1}' does not exist."; + var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName(), path.FullPath); + throw new CakeException(message); + } } var builder = new ProcessArgumentBuilder(); @@ -103,13 +91,34 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS // SIGN Command. builder.Append("SIGN"); - // TimeStamp server. - builder.Append("/t"); - builder.AppendQuoted(settings.TimeStampUri.AbsoluteUri); + // SHA-256. + if (settings.DigestAlgorithm == SignToolDigestAlgorithm.Sha256) + { + builder.Append("/fd sha256"); + } - if (settings.CertPath == null && string.IsNullOrEmpty(settings.CertThumbprint)) + // TimeStamp. + if (settings.TimeStampUri != null) { - const string format = "{0}: One of Certificate path or Certificate thumbprint is required but neither are specified."; + if (settings.TimeStampDigestAlgorithm == SignToolDigestAlgorithm.Sha256) + { + // If Sha256 use RFC 3161 timestamp server. + builder.Append("/tr"); + builder.AppendQuoted(settings.TimeStampUri.AbsoluteUri); + + builder.Append("/td sha256"); + } + else + { + // Otherwise use SHA-1 Authenticode timestamp server + builder.Append("/t"); + builder.AppendQuoted(settings.TimeStampUri.AbsoluteUri); + } + } + + if (settings.CertPath == null && string.IsNullOrEmpty(settings.CertThumbprint) && string.IsNullOrEmpty(settings.CertSubjectName)) + { + const string format = "{0}: One of Certificate path, Certificate thumbprint or Certificate subject name is required but neither are specified."; var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName()); throw new CakeException(message); } @@ -121,9 +130,9 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS throw new CakeException(message); } - if (settings.CertPath != null && string.IsNullOrEmpty(settings.Password)) + if (settings.CertPath != null && !string.IsNullOrEmpty(settings.CertSubjectName)) { - const string format = "{0}: Password is required with Certificate path but not specified."; + const string format = "{0}: Certificate path and Certificate subject name cannot be specified together."; var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName()); throw new CakeException(message); } @@ -135,6 +144,13 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS throw new CakeException(message); } + if (!string.IsNullOrEmpty(settings.CertSubjectName) && !string.IsNullOrEmpty(settings.Password)) + { + const string format = "{0}: Certificate subject name and Password cannot be specified together."; + var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName()); + throw new CakeException(message); + } + if (settings.CertPath != null) { // Make certificate path absolute. @@ -144,7 +160,7 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS if (!_fileSystem.Exist(settings.CertPath)) { - const string format = "{0}: The certificate '{1}' do not exist."; + const string format = "{0}: The certificate '{1}' does not exist."; var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName(), settings.CertPath.FullPath); throw new CakeException(message); } @@ -154,8 +170,30 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS builder.AppendQuoted(settings.CertPath.MakeAbsolute(_environment).FullPath); // PFX Password. - builder.Append("/p"); - builder.AppendSecret(settings.Password); + if (!string.IsNullOrEmpty(settings.Password)) + { + builder.Append("/p"); + builder.AppendSecret(settings.Password); + } + } + + if (settings.AdditionalCertPath != null) + { + // Make additional certificate path absolute. + settings.AdditionalCertPath = settings.AdditionalCertPath.IsRelative + ? settings.AdditionalCertPath.MakeAbsolute(_environment) + : settings.AdditionalCertPath; + + if (!_fileSystem.Exist(settings.AdditionalCertPath)) + { + const string format = "{0}: The additional certificate '{1}' does not exist."; + var message = string.Format(CultureInfo.InvariantCulture, format, GetToolName(), settings.AdditionalCertPath.FullPath); + throw new CakeException(message); + } + + // Path to additional certificate. + builder.Append("/ac"); + builder.AppendQuoted(settings.AdditionalCertPath.MakeAbsolute(_environment).FullPath); } // Certificate thumbprint. @@ -165,6 +203,12 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS builder.AppendQuoted(settings.CertThumbprint); } + if (!string.IsNullOrEmpty(settings.CertSubjectName)) + { + builder.Append("/n"); + builder.AppendQuoted(settings.CertSubjectName); + } + // Signed content description. if (!string.IsNullOrEmpty(settings.Description)) { @@ -179,8 +223,44 @@ private ProcessArgumentBuilder GetArguments(FilePath assemblyPath, SignToolSignS builder.AppendQuoted(settings.DescriptionUri.AbsoluteUri); } - // Target Assembly to sign. - builder.AppendQuoted(assemblyPath.MakeAbsolute(_environment).FullPath); + // Append signature. + if (settings.AppendSignature) + { + builder.Append("/as"); + } + + // use machine store when requested + if (settings.UseMachineStore) + { + builder.Append("/sm"); + } + + // open a specific certificate store + if (!string.IsNullOrWhiteSpace(settings.StoreName)) + { + builder.Append("/s"); + builder.AppendQuoted(settings.StoreName); + } + + // Cryptographic Service Provider + if (!string.IsNullOrEmpty(settings.CspName)) + { + builder.Append("/csp"); + builder.AppendQuoted(settings.CspName); + } + + // Private Key Container Name + if (!string.IsNullOrEmpty(settings.PrivateKeyContainerName)) + { + builder.Append("/kc"); + builder.AppendQuoted(settings.PrivateKeyContainerName); + } + + // Target Assemblies to sign. + foreach (var path in absoluteAssemblyPaths) + { + builder.AppendQuoted(path.FullPath); + } return builder; } @@ -206,7 +286,7 @@ protected override IEnumerable GetToolExecutableNames() } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. @@ -218,4 +298,4 @@ protected override IEnumerable GetAlternativeToolPaths(SignToolSignSet : Enumerable.Empty(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SignTool/SignToolSignSettings.cs b/src/Cake.Common/Tools/SignTool/SignToolSignSettings.cs index 7df49f42ce..73c964df70 100644 --- a/src/Cake.Common/Tools/SignTool/SignToolSignSettings.cs +++ b/src/Cake.Common/Tools/SignTool/SignToolSignSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; using Cake.Core.Tooling; @@ -13,7 +14,7 @@ namespace Cake.Common.Tools.SignTool public sealed class SignToolSignSettings : ToolSettings { /// - /// Gets or sets the timestamp server's URL. + /// Gets or sets the timestamp server's URL. Timestamp will only be added if TimeStampUri is set. /// public Uri TimeStampUri { get; set; } @@ -22,6 +23,11 @@ public sealed class SignToolSignSettings : ToolSettings /// public string CertThumbprint { get; set; } + /// + /// Gets or sets the name of the subject of the signing certificate. This value can be a substring of the entire subject name. + /// + public string CertSubjectName { get; set; } + /// /// Gets or sets the PFX certificate path. /// @@ -41,5 +47,45 @@ public sealed class SignToolSignSettings : ToolSettings /// Gets or sets the signed content's expanded description URL. /// public Uri DescriptionUri { get; set; } + + /// + /// Gets or sets the file digest algorithm. + /// + public SignToolDigestAlgorithm DigestAlgorithm { get; set; } + + /// + /// Gets or sets the timestamp digest algorithm. + /// + public SignToolDigestAlgorithm TimeStampDigestAlgorithm { get; set; } + + /// + /// Gets or sets a value indicating whether the signature should be appended. + /// + public bool AppendSignature { get; set; } + + /// + /// Gets or sets a value indicating whether a machine store, instead of a user store, is used. + /// + public bool UseMachineStore { get; set; } + + /// + /// Gets or sets the path to an additional certificate that is to be added to the signature block. + /// + public FilePath AdditionalCertPath { get; set; } + + /// + /// Gets or sets the store to open when searching for the certificate. + /// + public string StoreName { get; set; } + + /// + /// Gets or sets the cryptographic service provider (CSP) that contains the private key container. + /// + public string CspName { get; set; } + + /// + /// Gets or sets the private key container name. + /// + public string PrivateKeyContainerName { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowAliases.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowAliases.cs index 7667388a41..33822f3e6f 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowAliases.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Tools.SpecFlow.StepDefinitionReport; using Cake.Common.Tools.SpecFlow.TestExecutionReport; @@ -14,7 +15,7 @@ namespace Cake.Common.Tools.SpecFlow /// Contains functionality related to SpecFlow. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the appropriate settings class: + /// install from nuget.org, or specify the ToolPath within the appropriate settings class: /// /// #tool "nuget:?package=SpecFlow" /// @@ -35,10 +36,7 @@ public static class SpecFlowAliases [CakeNamespaceImport("Cake.Common.Tools.SpecFlow.StepDefinitionReport")] public static void SpecFlowStepDefinitionReport(this ICakeContext context, FilePath projectFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); SpecFlowStepDefinitionReport(context, projectFile, new SpecFlowStepDefinitionReportSettings()); } @@ -56,10 +54,7 @@ public static void SpecFlowStepDefinitionReport(this ICakeContext context, FileP [CakeNamespaceImport("Cake.Common.Tools.SpecFlow.StepDefinitionReport")] public static void SpecFlowStepDefinitionReport(this ICakeContext context, FilePath projectFile, SpecFlowStepDefinitionReportSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new SpecFlowStepDefinitionReporter(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(projectFile, settings ?? new SpecFlowStepDefinitionReportSettings()); @@ -71,7 +66,7 @@ public static void SpecFlowStepDefinitionReport(this ICakeContext context, FileP /// See SpecFlow Documentation for more information. /// /// The context. - /// The action to run SpecFlow for. Supported actions are: MSTest, NUnit3 and XUnit2 + /// The action to run SpecFlow for. Supported actions are: MSTest, NUnit3 and XUnit2. /// The path of the project file containing the feature files. [CakeMethodAlias] [CakeAliasCategory("TestExecutionReport")] @@ -81,10 +76,7 @@ public static void SpecFlowTestExecutionReport( Action action, FilePath projectFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); SpecFlowTestExecutionReport(context, action, projectFile, new SpecFlowTestExecutionReportSettings()); } @@ -95,7 +87,7 @@ public static void SpecFlowTestExecutionReport( /// See SpecFlow Documentation for more information. /// /// The context. - /// The action to run SpecFlow for. Supported actions are: MSTest, NUNit, NUNit3, XUnit and XUnit2 + /// The action to run SpecFlow for. Supported actions are: MSTest, NUnit, NUnit3, XUnit and XUnit2. /// The path of the project file containing the feature files. /// The settings. [CakeMethodAlias] @@ -107,10 +99,7 @@ public static void SpecFlowTestExecutionReport( FilePath projectFile, SpecFlowTestExecutionReportSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); if (settings == null) { @@ -126,4 +115,4 @@ public static void SpecFlowTestExecutionReport( runner.Run(context, action, projectFile, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowContext.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowContext.cs index b7122fd60e..a173454e9f 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowContext.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowContext.cs @@ -1,74 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core; using Cake.Core.Diagnostics; using Cake.Core.IO; -using Cake.Core.Tooling; namespace Cake.Common.Tools.SpecFlow { - internal sealed class SpecFlowContext : ICakeContext + internal sealed class SpecFlowContext : CakeContextAdapter { - private readonly ICakeContext _context; - private readonly ICakeLog _log; private readonly SpecFlowProcessRunner _runner; - public IFileSystem FileSystem - { - get { return _context.FileSystem; } - } - - public ICakeEnvironment Environment - { - get { return _context.Environment; } - } - - public IGlobber Globber - { - get { return _context.Globber; } - } + public override ICakeLog Log { get; } - public ICakeLog Log - { - get { return _log; } - } + public override IProcessRunner ProcessRunner => _runner; - public ICakeArguments Arguments - { - get { return _context.Arguments; } - } + public FilePath FilePath => _runner.FilePath; - public IProcessRunner ProcessRunner - { - get { return _runner; } - } - - public IRegistry Registry - { - get { return _context.Registry; } - } - - public IToolLocator Tools - { - get { return _context.Tools; } - } - - public FilePath FilePath - { - get { return _runner.FilePath; } - } - - public ProcessSettings Settings - { - get { return _runner.ProcessSettings; } - } + public ProcessSettings Settings => _runner.ProcessSettings; - public SpecFlowContext(ICakeContext context) + public SpecFlowContext(ICakeContext context) : base(context) { - _context = context; - _log = new NullLog(); + Log = new NullLog(); _runner = new SpecFlowProcessRunner(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowContextExtensions.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowContextExtensions.cs index 3a774d157d..598301fa08 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowContextExtensions.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowContextExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Text.RegularExpressions; using Cake.Core; @@ -144,7 +145,7 @@ internal static ProcessArgumentBuilder GetXUnitArguments(this SpecFlowContext co private static string RenderArguments(this SpecFlowContext context) { // The arguments to the target application. - if (context.Settings == null || context.Settings.Arguments == null) + if (context.Settings?.Arguments == null) { throw new CakeException("No arguments were found for tool."); } @@ -159,4 +160,4 @@ private static string RenderArguments(this SpecFlowContext context) return arguments; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowProcessRunner.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowProcessRunner.cs index 7e8d200308..4ce60c59f0 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowProcessRunner.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowProcessRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; using Cake.Core.IO; @@ -33,6 +34,11 @@ public int GetExitCode() return 0; } + public IEnumerable GetStandardError() + { + return Enumerable.Empty(); + } + public IEnumerable GetStandardOutput() { return Enumerable.Empty(); @@ -50,4 +56,4 @@ public IProcess Start(FilePath filePath, ProcessSettings settings) return new InterceptedProcess(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowSettings.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowSettings.cs index 17c7588e22..ac3bbab849 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowSettings.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -13,14 +14,14 @@ public abstract class SpecFlowSettings : ToolSettings { /// /// Gets or sets the generated Output File. Optional. - /// Default: TestResult.html + /// Default: TestResult.html. /// public FilePath Out { get; set; } /// /// Gets or sets the custom XSLT file to use, defaults to built-in stylesheet if not provided. Optional. - /// Default: not specified + /// Default: not specified. /// public FilePath XsltFile { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/SpecFlowTool.cs b/src/Cake.Common/Tools/SpecFlow/SpecFlowTool.cs index 2c8da9b9e1..714387a68d 100644 --- a/src/Cake.Common/Tools/SpecFlow/SpecFlowTool.cs +++ b/src/Cake.Common/Tools/SpecFlow/SpecFlowTool.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -10,9 +11,9 @@ namespace Cake.Common.Tools.SpecFlow { /// - /// Base class for all SpecFlow related tools + /// Base class for all SpecFlow related tools. /// - /// The settings type + /// The settings type. public abstract class SpecFlowTool : Tool where TSettings : SpecFlowSettings { @@ -61,14 +62,8 @@ protected void AppendArguments( SpecFlowSettings settings, ProcessArgumentBuilder builder) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - if (builder == null) - { - throw new ArgumentNullException("builder"); - } + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(builder); // Set the out file. if (settings.Out != null) @@ -85,4 +80,4 @@ protected void AppendArguments( } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReportSettings.cs b/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReportSettings.cs index 6b15abd24c..1e8cd363ff 100644 --- a/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReportSettings.cs +++ b/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReportSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; namespace Cake.Common.Tools.SpecFlow.StepDefinitionReport @@ -12,8 +13,8 @@ public sealed class SpecFlowStepDefinitionReportSettings : SpecFlowSettings { /// /// Gets or sets the path for the compiled SpecFlow project. Optional. - /// Default: bin\Debug + /// Default: bin\Debug. /// public DirectoryPath BinFolder { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporter.cs b/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporter.cs index 726b52116b..6e0f33ea36 100644 --- a/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporter.cs +++ b/src/Cake.Common/Tools/SpecFlow/StepDefinitionReport/SpecFlowStepDefinitionReporter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.IO; @@ -39,14 +40,8 @@ public SpecFlowStepDefinitionReporter( public void Run(FilePath projectFile, SpecFlowStepDefinitionReportSettings settings) { - if (projectFile == null) - { - throw new ArgumentNullException("projectFile"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(projectFile); + ArgumentNullException.ThrowIfNull(settings); // Run the tool. Run(settings, GetArguments(settings, projectFile)); @@ -76,4 +71,4 @@ private ProcessArgumentBuilder GetArguments( return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReportSettings.cs b/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReportSettings.cs index 1b515065b1..bb7e83c970 100644 --- a/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReportSettings.cs +++ b/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReportSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.SpecFlow.TestExecutionReport { /// @@ -8,5 +9,10 @@ namespace Cake.Common.Tools.SpecFlow.TestExecutionReport /// public sealed class SpecFlowTestExecutionReportSettings : SpecFlowSettings { + /// + /// Gets or sets a value indicating whether exceptions from the + /// intercepted action should be rethrown after report generation. + /// + public bool ThrowOnTestFailure { get; set; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporter.cs b/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporter.cs index 8b39047113..8a027dbedd 100644 --- a/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporter.cs +++ b/src/Cake.Common/Tools/SpecFlow/TestExecutionReport/SpecFlowTestExecutionReporter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Common.Diagnostics; using Cake.Core; @@ -44,22 +45,10 @@ public void Run(ICakeContext context, FilePath projectFile, SpecFlowTestExecutionReportSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (action == null) - { - throw new ArgumentNullException("action"); - } - if (projectFile == null) - { - throw new ArgumentNullException("projectFile"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(action); + ArgumentNullException.ThrowIfNull(projectFile); + ArgumentNullException.ThrowIfNull(settings); // Run the tool using the interceptor. var interceptor = InterceptAction(context, action); @@ -68,6 +57,7 @@ public void Run(ICakeContext context, var builder = GetArguments(interceptor, settings, projectFile); // Execute the action + CakeException testException = null; try { action(context); @@ -76,10 +66,16 @@ public void Run(ICakeContext context, { // Write warning to log context.Warning(e.Message); + testException = e; } // Run the tool. Run(settings, builder); + + if (settings.ThrowOnTestFailure && testException != null) + { + throw testException; + } } private static SpecFlowContext InterceptAction( @@ -112,4 +108,4 @@ private ProcessArgumentBuilder GetArguments( return builder; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/TextTransform/TextTransformAliases.cs b/src/Cake.Common/Tools/TextTransform/TextTransformAliases.cs index b60dd1fa27..48b135ce20 100644 --- a/src/Cake.Common/Tools/TextTransform/TextTransformAliases.cs +++ b/src/Cake.Common/Tools/TextTransform/TextTransformAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -12,7 +13,7 @@ namespace Cake.Common.Tools.TextTransform /// Contains functionality related to TextTransform. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=Mono.TextTransform" /// @@ -37,10 +38,7 @@ public static class TextTransformAliases [CakeAliasCategory("T4 Text Templating")] public static void TransformTemplate(this ICakeContext context, FilePath sourceFile) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new TextTransformRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(sourceFile, new TextTransformSettings()); @@ -63,13 +61,10 @@ public static void TransformTemplate(this ICakeContext context, FilePath sourceF [CakeAliasCategory("T4 Text Templating")] public static void TransformTemplate(this ICakeContext context, FilePath sourceFile, TextTransformSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new TextTransformRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(sourceFile, settings ?? new TextTransformSettings()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/TextTransform/TextTransformRunner.cs b/src/Cake.Common/Tools/TextTransform/TextTransformRunner.cs index cff9f03959..cc9891ccd2 100644 --- a/src/Cake.Common/Tools/TextTransform/TextTransformRunner.cs +++ b/src/Cake.Common/Tools/TextTransform/TextTransformRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -23,15 +24,9 @@ public sealed class TextTransformRunner : Tool /// The settings. public void Run(FilePath sourceFile, TextTransformSettings settings) { - if (sourceFile == null) - { - throw new ArgumentNullException("sourceFile"); - } + ArgumentNullException.ThrowIfNull(sourceFile); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); Run(settings, GetArguments(sourceFile, settings)); } @@ -70,6 +65,23 @@ private ProcessArgumentBuilder GetArguments(FilePath sourceFilePath, TextTransfo builder.AppendQuoted(settings.IncludeDirectory.MakeAbsolute(_environment).FullPath); } + if (settings.Class != null) + { + builder.AppendSwitchQuoted( + "--class", + "=", + settings.Class); + } + + if (settings.Properties != null) + { + foreach (var property in settings.Properties) + { + builder.Append( + $"-p:{property.Key.Quote()}={property.Value.Quote()}"); + } + } + builder.AppendQuoted(sourceFilePath.MakeAbsolute(_environment).FullPath); return builder; @@ -88,12 +100,7 @@ public TextTransformRunner( IProcessRunner processRunner, IToolLocator tools) : base(fileSystem, environment, processRunner, tools) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - - _environment = environment; + _environment = environment ?? throw new ArgumentNullException(nameof(environment)); } /// @@ -111,7 +118,7 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "TextTransform.exe" }; + return new[] { "TextTransform.exe", "t4", "t4.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/TextTransform/TextTransformSettings.cs b/src/Cake.Common/Tools/TextTransform/TextTransformSettings.cs index 5e61a460e9..4dbecdcd4e 100644 --- a/src/Cake.Common/Tools/TextTransform/TextTransformSettings.cs +++ b/src/Cake.Common/Tools/TextTransform/TextTransformSettings.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -28,7 +30,7 @@ public sealed class TextTransformSettings : ToolSettings public FilePath OutputFile { get; set; } /// - /// Gets or sets namespace that is used for compiling the text template. + /// Gets or sets the namespace that is used for compiling the text template. /// /// /// The namespace. @@ -47,5 +49,29 @@ public sealed class TextTransformSettings : ToolSettings /// The reference path. /// public DirectoryPath ReferencePath { get; set; } + + /// + /// Gets or sets the class name used for converting T4 template into a C# class that can be compiled into your app and executed at runtime. + /// + /// + /// The class name. + /// + /// + /// Requires T4 text template processor version 2 or newer. + /// + public string Class { get; set; } + + /// + /// Gets or sets properties passes to the template's Session dictionary. + /// + /// + /// The properties dictionary. + /// + /// + /// Requires T4 text template processor version 2 or newer. + /// These can also be accessed using strongly typed properties + /// (declared with <#@ parameter name="<name>" type="<type>" #> directives.) + /// + public IDictionary Properties { get; set; } = new Dictionary(); } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSTest/VSTestAliases.cs b/src/Cake.Common/Tools/VSTest/VSTestAliases.cs index 9d4ce83190..857edb0fcb 100644 --- a/src/Cake.Common/Tools/VSTest/VSTestAliases.cs +++ b/src/Cake.Common/Tools/VSTest/VSTestAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -28,12 +29,9 @@ public static class VSTestAliases /// The context. /// The pattern. [CakeMethodAlias] - public static void VSTest(this ICakeContext context, string pattern) + public static void VSTest(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -50,19 +48,16 @@ public static void VSTest(this ICakeContext context, string pattern) /// /// /// - /// VSTest("./Tests/*.UnitTests.dll", new VSTestSettings() { Logger = VSTestLogger.Trx }); + /// VSTest("./Tests/*.UnitTests.dll", new VSTestSettings() { Logger = "trx" }); /// /// /// The context. /// The pattern. /// The settings. [CakeMethodAlias] - public static void VSTest(this ICakeContext context, string pattern, VSTestSettings settings) + public static void VSTest(this ICakeContext context, GlobPattern pattern, VSTestSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -106,14 +101,8 @@ public static void VSTest(this ICakeContext context, IEnumerable assem [CakeMethodAlias] public static void VSTest(this ICakeContext context, IEnumerable assemblyPaths, VSTestSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblyPaths); var runner = new VSTestRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblyPaths, settings); diff --git a/src/Cake.Common/Tools/VSTest/VSTestFrameworkVersion.cs b/src/Cake.Common/Tools/VSTest/VSTestFrameworkVersion.cs index f545d553f4..69da25b432 100644 --- a/src/Cake.Common/Tools/VSTest/VSTestFrameworkVersion.cs +++ b/src/Cake.Common/Tools/VSTest/VSTestFrameworkVersion.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.VSTest { /// @@ -28,4 +29,4 @@ public enum VSTestFrameworkVersion /// NET45 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSTest/VSTestLogger.cs b/src/Cake.Common/Tools/VSTest/VSTestLogger.cs deleted file mode 100644 index a0f96cf8a3..0000000000 --- a/src/Cake.Common/Tools/VSTest/VSTestLogger.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.VSTest -{ - /// - /// Loggers available for outputting test results. - /// - public enum VSTestLogger - { - /// - /// No logging of test results. - /// - None, - - /// - /// Log results to a Visual Studio test results file. - /// - Trx - } -} diff --git a/src/Cake.Common/Tools/VSTest/VSTestPlatform.cs b/src/Cake.Common/Tools/VSTest/VSTestPlatform.cs index cb13b37b98..f8a553f001 100644 --- a/src/Cake.Common/Tools/VSTest/VSTestPlatform.cs +++ b/src/Cake.Common/Tools/VSTest/VSTestPlatform.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.VSTest { /// @@ -31,4 +32,4 @@ public enum VSTestPlatform // ReSharper disable once InconsistentNaming ARM } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSTest/VSTestRunner.cs b/src/Cake.Common/Tools/VSTest/VSTestRunner.cs index 2e6810e997..cfdb88f6dd 100644 --- a/src/Cake.Common/Tools/VSTest/VSTestRunner.cs +++ b/src/Cake.Common/Tools/VSTest/VSTestRunner.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Cake.Core; using Cake.Core.IO; @@ -17,6 +17,7 @@ namespace Cake.Common.Tools.VSTest /// public sealed class VSTestRunner : Tool { + private const string VSTestConsoleExecutableName = "vstest.console.exe"; private readonly IFileSystem _fileSystem; private readonly ICakeEnvironment _environment; @@ -26,7 +27,7 @@ public sealed class VSTestRunner : Tool /// The file system. /// The environment. /// The process runner. - /// The tool servce. + /// The tool locator. public VSTestRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, @@ -44,16 +45,10 @@ public VSTestRunner(IFileSystem fileSystem, /// The settings. public void Run(IEnumerable assemblyPaths, VSTestSettings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); - base.Run(settings, GetArguments(assemblyPaths, settings)); + Run(settings, GetArguments(assemblyPaths, settings)); } private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, VSTestSettings settings) @@ -63,12 +58,22 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, // Add the assembly to build. foreach (var assemblyPath in assemblyPaths) { - builder.Append(assemblyPath.MakeAbsolute(_environment).FullPath.Quote()); + builder.AppendQuoted(assemblyPath.MakeAbsolute(_environment).FullPath); } if (settings.SettingsFile != null) { - builder.Append(string.Format(CultureInfo.InvariantCulture, "/Settings:{0}", settings.SettingsFile)); + builder.AppendSwitchQuoted("/Settings", ":", settings.SettingsFile.MakeAbsolute(_environment).FullPath); + } + + if (settings.Parallel) + { + builder.Append("/Parallel"); + } + + if (settings.EnableCodeCoverage) + { + builder.Append("/EnableCodeCoverage"); } if (settings.InIsolation) @@ -76,19 +81,44 @@ private ProcessArgumentBuilder GetArguments(IEnumerable assemblyPaths, builder.Append("/InIsolation"); } + if (settings.UseVsixExtensions != null) + { + builder.AppendSwitch("/UseVsixExtensions", ":", settings.UseVsixExtensions.Value ? "true" : "false"); + } + + if (settings.TestAdapterPath != null) + { + builder.AppendSwitchQuoted("/TestAdapterPath", ":", settings.TestAdapterPath.MakeAbsolute(_environment).FullPath); + } + if (settings.PlatformArchitecture != VSTestPlatform.Default) { - builder.Append(string.Format(CultureInfo.InvariantCulture, "/Platform:{0}", settings.PlatformArchitecture)); + builder.AppendSwitch("/Platform", ":", settings.PlatformArchitecture.ToString()); } if (settings.FrameworkVersion != VSTestFrameworkVersion.Default) { - builder.Append(string.Format(CultureInfo.InvariantCulture, "/Framework:{0}", settings.FrameworkVersion.ToString().Replace("NET", "Framework"))); + builder.AppendSwitch("/Framework", ":", settings.FrameworkVersion.ToString().Replace("NET", "Framework")); + } + + if (settings.TestCaseFilter != null) + { + builder.AppendSwitchQuoted("/TestCaseFilter", ":", settings.TestCaseFilter); + } + + if (settings.Diag != null) + { + builder.AppendSwitchQuoted("/Diag", ":", settings.Diag.MakeAbsolute(_environment).FullPath); } - if (settings.Logger == VSTestLogger.Trx) + if (settings.ResultsDirectory != null) { - builder.Append("/Logger:trx"); + builder.AppendSwitchQuoted("/ResultsDirectory", ":", settings.ResultsDirectory.MakeAbsolute(_environment).FullPath); + } + + if (!string.IsNullOrEmpty(settings.Logger)) + { + builder.Append("/Logger:{0}", settings.Logger.Trim()); } return builder; @@ -107,33 +137,38 @@ protected override string GetToolName() /// Gets the possible names of the tool executable. /// /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return Enumerable.Empty(); - } + protected override IEnumerable GetToolExecutableNames() => new[] { VSTestConsoleExecutableName }; /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. protected override IEnumerable GetAlternativeToolPaths(VSTestSettings settings) { - foreach (var version in new[] { "15.0", "14.0", "12.0", "11.0" }) + var vsRootRelativeToolPath = FilePath.FromString($"Common7/IDE/CommonExtensions/Microsoft/TestWindow/{VSTestConsoleExecutableName}"); + foreach (var year in VisualStudio.Versions.TwentySeventeenAndLater) { - var path = GetToolPath(version); + foreach (var edition in settings.AllowPreviewVersion + ? VisualStudio.Editions.All + : VisualStudio.Editions.Stable) + { + var path = VisualStudio.GetYearAndEditionToolPath(_environment, year, edition, vsRootRelativeToolPath); + if (_fileSystem.Exist(path)) + { + yield return path; + } + } + } + + foreach (var version in VisualStudio.Versions.TenToFourteen) + { + var path = VisualStudio.GetVersionNumberToolPath(_environment, version, vsRootRelativeToolPath); if (_fileSystem.Exist(path)) { yield return path; } } } - - private FilePath GetToolPath(string version) - { - var programFiles = _environment.GetSpecialPath(SpecialPath.ProgramFilesX86); - var root = programFiles.Combine(string.Concat("Microsoft Visual Studio ", version, "/Common7/IDE/CommonExtensions/Microsoft/TestWindow")); - return root.CombineWithFilePath("vstest.console.exe"); - } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSTest/VSTestSettings.cs b/src/Cake.Common/Tools/VSTest/VSTestSettings.cs index f2bdd85df2..ba7af336ee 100644 --- a/src/Cake.Common/Tools/VSTest/VSTestSettings.cs +++ b/src/Cake.Common/Tools/VSTest/VSTestSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -16,6 +17,16 @@ public sealed class VSTestSettings : ToolSettings /// public FilePath SettingsFile { get; set; } + /// + /// Gets or sets a value indicating whether the tests are executed in parallel. By default up to all available cores on the machine may be used. The number of cores to use may be configured using a settings file. + /// + public bool Parallel { get; set; } + + /// + /// Gets or sets a value indicating whether to enable data diagnostic adapter 'CodeCoverage' in the test run. Default settings are used if not specified using settings file. + /// + public bool EnableCodeCoverage { get; set; } + /// /// Gets or sets a value indicating whether to run tests within the vstest.console.exe process. /// This makes vstest.console.exe process less likely to be stopped on an error in the tests, but tests might run slower. @@ -26,6 +37,16 @@ public sealed class VSTestSettings : ToolSettings /// public bool InIsolation { get; set; } + /// + /// Gets or sets a value overriding whether VSTest will use or skip the VSIX extensions installed (if any) in the test run. + /// + public bool? UseVsixExtensions { get; set; } + + /// + /// Gets or sets a value that makes VSTest use custom test adapters from a given path (if any) in the test run. + /// + public DirectoryPath TestAdapterPath { get; set; } + /// /// Gets or sets the target platform architecture to be used for test execution. /// @@ -37,8 +58,43 @@ public sealed class VSTestSettings : ToolSettings public VSTestFrameworkVersion FrameworkVersion { get; set; } /// - /// Gets or sets the logger to use for test results. + /// Gets or sets an expression to run only tests that match, of the format <property>Operator<value>[|&<Expression>] + /// where Operator is one of =, != or ~ (Operator ~ has 'contains' + /// semantics and is applicable for string properties like DisplayName). + /// Parenthesis () can be used to group sub-expressions. + /// Examples: Priority=1 + /// (FullyQualifiedName~Nightly|Name=MyTestMethod). + /// + public string TestCaseFilter { get; set; } + + /// + /// Gets or sets a path which makes VSTest write diagnosis trace logs to specified file. + /// + public FilePath Diag { get; set; } + + /// + /// Gets or sets the result directory. + /// Test results directory will be created in specified path if not exists. + /// VSTest.Console.exe flag /ResultsDirectory. + /// + public DirectoryPath ResultsDirectory { get; set; } + + /// + /// Gets or sets the name of your logger. Possible values: + /// - A blank string (or null): no logger + /// - "trx": Visual Studio's built-in logger + /// - "AppVeyor": AppVeyor's custom logger which is available only when building your solution on the AppVeyor platform + /// - any custom value: the name of your custom logger. + /// + public string Logger { get; set; } + + /// + /// Gets or sets a value indicating whether tools from a preview edition of Visual Studio should be used. + /// + /// If set to true, VSTest from a Preview edition + /// (e.g. Visual Studio 2022 Preview) will be considered to be used. + /// /// - public VSTestLogger Logger { get; set; } + public bool AllowPreviewVersion { get; set; } = false; } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSTest/VSTestSettingsExtensions.cs b/src/Cake.Common/Tools/VSTest/VSTestSettingsExtensions.cs new file mode 100644 index 0000000000..6e720c8609 --- /dev/null +++ b/src/Cake.Common/Tools/VSTest/VSTestSettingsExtensions.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Cake.Common.Tools.VSTest +{ + /// + /// Contains functionality related to VSTest settings. + /// + public static class VSTestSettingsExtensions + { + /// + /// Do not Log. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static VSTestSettings WithoutAnyLogger(this VSTestSettings settings) + { + return settings.WithLogger(string.Empty); + } + + /// + /// Log to a trx file. + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static VSTestSettings WithVisualStudioLogger(this VSTestSettings settings) + { + return settings.WithLogger("trx"); + } + + /// + /// Log to the AppVeyor logger (which is only available when building your solution on the AppVeyor platform). + /// + /// The settings. + /// The same instance so that multiple calls can be chained. + public static VSTestSettings WithAppVeyorLogger(this VSTestSettings settings) + { + return settings.WithLogger("AppVeyor"); + } + + /// + /// Log to a custom logger. + /// + /// The settings. + /// The name of the logger. + /// The same instance so that multiple calls can be chained. + public static VSTestSettings WithLogger(this VSTestSettings settings, string loggerName) + { + ArgumentNullException.ThrowIfNull(settings); + settings.Logger = loggerName; + return settings; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/VSWhere/All/VSWhereAll.cs b/src/Cake.Common/Tools/VSWhere/All/VSWhereAll.cs new file mode 100644 index 0000000000..1258d55ae9 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/All/VSWhereAll.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere.All +{ + /// + /// The VSWhere tool that finds all instances regardless if they are complete. + /// + public sealed class VSWhereAll : VSWhereTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public VSWhereAll(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, + IToolLocator toolLocator) : base(fileSystem, environment, processRunner, toolLocator) + { + } + + /// + /// Finds all instances regardless if they are complete. + /// + /// The settings. + /// Installation paths for all instances. + public DirectoryPathCollection All(VSWhereAllSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + return RunVSWhere(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(VSWhereAllSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("-all"); + + AddCommonArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/All/VSWhereAllSettings.cs b/src/Cake.Common/Tools/VSWhere/All/VSWhereAllSettings.cs new file mode 100644 index 0000000000..a4c93824b6 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/All/VSWhereAllSettings.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.VSWhere.All +{ + /// + /// Contains settings used by . + /// + public class VSWhereAllSettings : VSWhereSettings + { + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatest.cs b/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatest.cs new file mode 100644 index 0000000000..dda8097f24 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatest.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere.Latest +{ + /// + /// The VSWhere tool that returns only the newest version and last installed. + /// + public sealed class VSWhereLatest : VSWhereTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public VSWhereLatest(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, + IToolLocator toolLocator) : base(fileSystem, environment, processRunner, toolLocator) + { + } + + /// + /// Returns only the newest version and last installed. + /// + /// The settings. + /// Installation path of the newest or last install. + public DirectoryPath Latest(VSWhereLatestSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + return RunVSWhere(settings, GetArguments(settings)).FirstOrDefault(); + } + + private ProcessArgumentBuilder GetArguments(VSWhereLatestSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + if (!string.IsNullOrWhiteSpace(settings.Products)) + { + builder.Append("-products"); + builder.AppendQuoted(settings.Products); + } + + builder.Append("-latest"); + + AddCommonArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatestSettings.cs b/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatestSettings.cs new file mode 100644 index 0000000000..265cf3e8c7 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Latest/VSWhereLatestSettings.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.VSWhere.Latest +{ + /// + /// Contains settings used by . + /// + public sealed class VSWhereLatestSettings : VSWhereSettings + { + /// + /// Gets or sets the products to find. Defaults to Community, Professional, and Enterprise. Specify "*" by itself to search all product instances installed. + /// + public string Products { get; set; } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacy.cs b/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacy.cs new file mode 100644 index 0000000000..7bbe028349 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacy.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere.Legacy +{ + /// + /// The VSWhere tool that finds Visual Studio products. + /// + public sealed class VSWhereLegacy : VSWhereTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public VSWhereLegacy(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, + IToolLocator toolLocator) : base(fileSystem, environment, processRunner, toolLocator) + { + } + + /// + /// Also searches Visual Studio 2015 and older products. Information is limited. + /// + /// The settings. + /// Installation paths for all instances. + public DirectoryPathCollection Legacy(VSWhereLegacySettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + return RunVSWhere(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(VSWhereLegacySettings settings) + { + var builder = new ProcessArgumentBuilder(); + + builder.Append("-legacy"); + + if (settings.Latest) + { + builder.Append("-latest"); + } + + AddCommonArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacySettings.cs b/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacySettings.cs new file mode 100644 index 0000000000..428a2224c0 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Legacy/VSWhereLegacySettings.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.VSWhere.Legacy +{ + /// + /// Contains settings used by . + /// + public class VSWhereLegacySettings : VSWhereSettings + { + /// + /// Gets or sets a value indicating whether to return only the newest version and last installed. + /// + /// true to find the newest version or last installed; otherwise, false. + public bool Latest { get; set; } + + /// + /// Gets the workload(s) or component(s) required when finding instances, immutable to always be empty per documentation. + /// + public new string Requires => string.Empty; + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Product/VSWhereProduct.cs b/src/Cake.Common/Tools/VSWhere/Product/VSWhereProduct.cs new file mode 100644 index 0000000000..aeb64887f9 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Product/VSWhereProduct.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere.Product +{ + /// + /// The VSWhere tool that finds Visual Studio products. + /// + public sealed class VSWhereProduct : VSWhereTool + { + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public VSWhereProduct(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, + IToolLocator toolLocator) : base(fileSystem, environment, processRunner, toolLocator) + { + } + + /// + /// Finds one ore more products. + /// + /// The settings. + /// Installation paths for all instances. + public DirectoryPathCollection Products(VSWhereProductSettings settings) + { + ArgumentNullException.ThrowIfNull(settings); + + return RunVSWhere(settings, GetArguments(settings)); + } + + private ProcessArgumentBuilder GetArguments(VSWhereProductSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + if (!string.IsNullOrWhiteSpace(settings.Products)) + { + builder.Append("-products"); + builder.Append(settings.Products); + } + + AddCommonArguments(settings, builder); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/Product/VSWhereProductSettings.cs b/src/Cake.Common/Tools/VSWhere/Product/VSWhereProductSettings.cs new file mode 100644 index 0000000000..2d9b9b9ce9 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/Product/VSWhereProductSettings.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.VSWhere.Product +{ + /// + /// Contains settings used by . + /// + public sealed class VSWhereProductSettings : VSWhereSettings + { + /// + /// Gets or sets the products to find. Defaults to Community, Professional, and Enterprise. Specify "*" by itself to search all product instances installed. + /// + internal string Products { get; set; } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/VSWhereAliases.cs b/src/Cake.Common/Tools/VSWhere/VSWhereAliases.cs new file mode 100644 index 0000000000..30dd5d9c1f --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/VSWhereAliases.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Common.Tools.VSWhere.All; +using Cake.Common.Tools.VSWhere.Latest; +using Cake.Common.Tools.VSWhere.Legacy; +using Cake.Common.Tools.VSWhere.Product; +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +namespace Cake.Common.Tools.VSWhere +{ + /// + /// Contains functionality related to running VSWhere tool. + /// + /// In order to use the commands for this alias, include the following in your build.cake file to download and + /// install from nuget.org, or specify the ToolPath within the settings class: + /// + /// #tool "nuget:?package=vswhere" + /// + /// + /// + [CakeAliasCategory("VSWhere")] + public static class VSWhereAliases + { + /// + /// Gets the legacy Visual Studio product installation paths. + /// + /// The context. + /// Get the latest version. + /// The Visual Studio installation path. + /// + /// + /// var legacyInstallationPath = VSWhereLegacy(true); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Legacy")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Legacy")] + public static DirectoryPath VSWhereLegacy(this ICakeContext context, bool latest) + { + var settings = new VSWhereLegacySettings(); + settings.Latest = latest; + return VSWhereLegacy(context, settings).FirstOrDefault(); + } + + /// + /// Gets the legacy Visual Studio product installation paths. + /// + /// The context. + /// The settings. + /// The Visual Studio installation paths. + /// + /// + /// var legacyInstallationPaths = VSWhereLegacy(new VSWhereLegacySettings()); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Legacy")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Legacy")] + public static DirectoryPathCollection VSWhereLegacy(this ICakeContext context, VSWhereLegacySettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(settings); + + var legacy = new VSWhereLegacy(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return legacy.Legacy(settings); + } + + /// + /// Gets the latest Visual Studio product installation path. + /// + /// The context. + /// The Visual Studio installation path. + /// + /// + /// var latestInstallationPath = VSWhereLatest(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Latest")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Latest")] + public static DirectoryPath VSWhereLatest(this ICakeContext context) + { + var settings = new VSWhereLatestSettings(); + return VSWhereLatest(context, settings); + } + + /// + /// Gets the latest Visual Studio product installation path. + /// + /// The context. + /// The settings. + /// The Visual Studio installation path. + /// + /// + /// var latestInstallationPath = VSWhereLatest(new VSWhereLatestSettings { Requires = "'Microsoft.Component.MSBuild" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Latest")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Latest")] + public static DirectoryPath VSWhereLatest(this ICakeContext context, VSWhereLatestSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + var latest = new VSWhereLatest(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return latest.Latest(settings); + } + + /// + /// Gets all Visual Studio product installation paths. + /// + /// The context. + /// The Visual Studio installation paths. + /// + /// + /// var latestInstallationPaths = VSWhereAll(); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("All")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.All")] + public static DirectoryPathCollection VSWhereAll(this ICakeContext context) + { + var settings = new VSWhereAllSettings(); + return VSWhereAll(context, settings); + } + + /// + /// Gets all Visual Studio product installation paths. + /// + /// The context. + /// The settings. + /// The Visual Studio installation paths. + /// + /// + /// var latestInstallationPaths = VSWhereAll(new VSWhereAllSettings { Requires = "'Microsoft.Component.MSBuild" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("All")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.All")] + public static DirectoryPathCollection VSWhereAll(this ICakeContext context, VSWhereAllSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + ArgumentNullException.ThrowIfNull(settings); + + var all = new VSWhereAll(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return all.All(settings); + } + + /// + /// Gets Visual Studio product installation paths. + /// + /// The context. + /// The products to find. + /// The Visual Studio installation paths. + /// + /// + /// var latestInstallationPaths = VSWhereProducts("Microsoft.VisualStudio.Product.BuildTools"); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Product")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Product")] + public static DirectoryPathCollection VSWhereProducts(this ICakeContext context, string products) + { + var settings = new VSWhereProductSettings(); + return VSWhereProducts(context, products, settings); + } + + /// + /// Gets Visual Studio product installation paths. + /// + /// The context. + /// The products to find. + /// The settings. + /// The Visual Studio installation paths. + /// + /// + /// var latestInstallationPaths = VSWhereProducts("Microsoft.VisualStudio.Product.BuildTools", new VSWhereProductSettings { Requires = "'Microsoft.Component.MSBuild" }); + /// + /// + [CakeMethodAlias] + [CakeAliasCategory("Product")] + [CakeNamespaceImport("Cake.Common.Tools.VSWhere.Product")] + public static DirectoryPathCollection VSWhereProducts(this ICakeContext context, string products, VSWhereProductSettings settings) + { + ArgumentNullException.ThrowIfNull(context); + + if (string.IsNullOrWhiteSpace(products)) + { + throw new ArgumentNullException(nameof(products)); + } + + ArgumentNullException.ThrowIfNull(settings); + settings.Products = products; + var product = new VSWhereProduct(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + return product.Products(settings); + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/VSWhereSettings.cs b/src/Cake.Common/Tools/VSWhere/VSWhereSettings.cs new file mode 100644 index 0000000000..11a80f0714 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/VSWhereSettings.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere +{ + /// + /// Base class for all settings for VSWhere tools. + /// + public abstract class VSWhereSettings : ToolSettings + { + /// + /// Gets or sets the workload(s) or component(s) required when finding instances. + /// + public string Requires { get; set; } + + /// + /// Gets or sets version range for instances to find. Example: ["15.0","16.0"] will find versions 15.*. + /// + public string Version { get; set; } + + /// + /// Gets or sets the name of the property to return. Defaults to "value" format. + /// + public string ReturnProperty { get; set; } + + /// + /// Gets or sets a value indicating whether VSWhere should include prerelease installations. + /// + public bool IncludePrerelease { get; set; } + + /// + /// Initializes a new instance of the class. + /// + protected VSWhereSettings() + { + ReturnProperty = "installationPath"; + } + } +} diff --git a/src/Cake.Common/Tools/VSWhere/VSWhereTool.cs b/src/Cake.Common/Tools/VSWhere/VSWhereTool.cs new file mode 100644 index 0000000000..28e7d7fe56 --- /dev/null +++ b/src/Cake.Common/Tools/VSWhere/VSWhereTool.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.VSWhere +{ + /// + /// Base class for all VSWhere related tools. + /// Used to locate Visual Studio. + /// + /// The settings type. + public abstract class VSWhereTool : Tool + where TSettings : ToolSettings + { + private const string VSWhereExecutableName = "vswhere.exe"; + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator service. + protected VSWhereTool(IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator toolLocator) + : base(fileSystem, environment, processRunner, toolLocator) + { + _environment = environment; + } + + /// + /// Gets the name of the tool. + /// + /// The tool name. + protected override string GetToolName() + { + return "VSWhere"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { VSWhereExecutableName }; + } + + /// + /// Gets alternative file paths which the tool may exist in. + /// + /// The settings. + /// The default tool path. + protected override IEnumerable GetAlternativeToolPaths(TSettings settings) + { + /* + * According to https://blogs.msdn.microsoft.com/heaths/2017/04/21/vswhere-is-now-installed-with-visual-studio-2017/, + * Starting in the latest preview release of Visual Studio version 15.2 (26418.1-Preview), you can now find vswhere installed in + * “%ProgramFiles(x86)%\Microsoft Visual Studio\Installer” (on 32-bit operating systems before Windows 10, you should use + * “%ProgramFiles%\Microsoft Visual Studio\Installer”). + */ + + return new FilePath[] + { + _environment.GetSpecialPath(_environment.Platform.Is64Bit ? SpecialPath.ProgramFilesX86 : SpecialPath.ProgramFiles).CombineWithFilePath("Microsoft Visual Studio/Installer/" + VSWhereExecutableName), + }; + } + + /// + /// Runs VSWhere with supplied arguments and parses installation paths. + /// + /// The settings. + /// The process argument builder. + /// The parsed file paths. + protected DirectoryPathCollection RunVSWhere(TSettings settings, ProcessArgumentBuilder builder) + { + IEnumerable installationPaths = null; + Run(settings, builder, new ProcessSettings { RedirectStandardOutput = true }, + process => installationPaths = process.GetStandardOutput()); + + return new DirectoryPathCollection(installationPaths?.Select(DirectoryPath.FromString) ?? Enumerable.Empty()); + } + + /// + /// Adds common arguments to the process builder. + /// + /// The settings. + /// The process argument builder. + /// The process argument builder. + protected ProcessArgumentBuilder AddCommonArguments(VSWhereSettings settings, ProcessArgumentBuilder builder) + { + if (!string.IsNullOrWhiteSpace(settings.Version)) + { + builder.Append("-version"); + builder.AppendQuoted(settings.Version); + } + + if (!string.IsNullOrWhiteSpace(settings.Requires)) + { + builder.Append("-requires"); + builder.Append(settings.Requires); + } + + if (!string.IsNullOrWhiteSpace(settings.ReturnProperty)) + { + builder.Append("-property"); + builder.Append(settings.ReturnProperty); + } + + if (settings.IncludePrerelease) + { + builder.Append("-prerelease"); + } + + builder.Append("-nologo"); + + return builder; + } + } +} diff --git a/src/Cake.Common/Tools/VisualStudio.cs b/src/Cake.Common/Tools/VisualStudio.cs new file mode 100644 index 0000000000..528c3cc6c6 --- /dev/null +++ b/src/Cake.Common/Tools/VisualStudio.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; + +namespace Cake.Common.Tools +{ + internal static class VisualStudio + { + internal static class Versions + { + internal static ICollection TenToFourteen { get; } = new[] { "14.0", "12.0", "11.0", "10.0" }; + + internal static ICollection TwentySeventeenAndLater { get; } = new[] + { + "2022", + "2019", + "2017" + }; + } + + internal static class Editions + { + internal static ICollection Preview { get; } = new[] + { + "Preview", + "Insiders" + }; + + internal static ICollection Stable { get; } = new[] + { + "Enterprise", + "Professional", + "Community", + "BuildTools" + }; + + internal static ICollection All { get; } = Preview + .Concat(Stable) + .ToArray(); + } + + internal static FilePath GetYearAndEditionToolPath(ICakeEnvironment environment, string year, string edition, FilePath relativeFile) + { + var root = GetYearAndEditionRootPath(environment, year, edition); + return root.CombineWithFilePath(relativeFile); + } + + internal static DirectoryPath GetYearAndEditionRootPath(ICakeEnvironment environment, string year, string edition) + { + var programFiles = (year, edition) switch + { + ("18", "BuildTools") => environment.GetSpecialPath(SpecialPath.ProgramFilesX86), + ("18", _) => environment.GetSpecialPath(SpecialPath.ProgramFiles), + ("2022", "BuildTools") => environment.GetSpecialPath(SpecialPath.ProgramFilesX86), + ("2022", _) => environment.GetSpecialPath(SpecialPath.ProgramFiles), + (_, _) => environment.GetSpecialPath(SpecialPath.ProgramFilesX86), + }; + + return programFiles.Combine($"Microsoft Visual Studio/{year}/{edition}"); + } + + internal static FilePath GetVersionNumberToolPath(ICakeEnvironment environment, string version, FilePath relativeFile) + { + var root = GetVersionNumberRootPath(environment, version); + return root.CombineWithFilePath(relativeFile); + } + + internal static DirectoryPath GetVersionNumberRootPath(ICakeEnvironment environment, string version) + { + var programFiles = environment.GetSpecialPath(SpecialPath.ProgramFilesX86); + return programFiles.Combine($"Microsoft Visual Studio {version}"); + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/Architecture.cs b/src/Cake.Common/Tools/WiX/Architecture.cs index a96d3c3ffe..c93cf2e1a6 100644 --- a/src/Cake.Common/Tools/WiX/Architecture.cs +++ b/src/Cake.Common/Tools/WiX/Architecture.cs @@ -1,26 +1,27 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -namespace Cake.Common.Tools.WiX -{ - /// - /// The architecture for the package. - /// - public enum Architecture - { - /// - /// Architecture: x86_64 - /// - X64, - - /// - /// Architecture: x86 - /// - X86, - - /// - /// Architecture: Itanium - /// - IA64 - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.WiX +{ + /// + /// The architecture for the package. + /// + public enum Architecture + { + /// + /// Architecture: x86_64 + /// + X64, + + /// + /// Architecture: x86 + /// + X86, + + /// + /// Architecture: Itanium + /// + IA64 + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/CandleRunner.cs b/src/Cake.Common/Tools/WiX/CandleRunner.cs index 487dfa4060..e4e685cea6 100644 --- a/src/Cake.Common/Tools/WiX/CandleRunner.cs +++ b/src/Cake.Common/Tools/WiX/CandleRunner.cs @@ -1,180 +1,172 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.WiX -{ - /// - /// The WiX Candle runner. - /// - public sealed class CandleRunner : Tool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The environment. - /// The process runner. - /// The tool locator. - public CandleRunner( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } - _environment = environment; - } - - /// - /// Runs Candle with the specified source files and settings. - /// - /// The source files (.wxs) to compile. - /// The settings. - public void Run(IEnumerable sourceFiles, CandleSettings settings) - { - if (sourceFiles == null) - { - throw new ArgumentNullException("sourceFiles"); - } - - var sourceFilesArray = sourceFiles as FilePath[] ?? sourceFiles.ToArray(); - if (!sourceFilesArray.Any()) - { - throw new ArgumentException("No source files specified.", "sourceFiles"); - } - - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - Run(settings, GetArguments(sourceFilesArray, settings)); - } - - private ProcessArgumentBuilder GetArguments(IEnumerable sourceFiles, CandleSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - // Architecture - if (settings.Architecture.HasValue) - { - builder.Append("-arch"); - builder.Append(GetArchitectureName(settings.Architecture.Value)); - } - - // Add defines - if (settings.Defines != null && settings.Defines.Any()) - { - var defines = settings.Defines.Select(define => string.Format(CultureInfo.InvariantCulture, "-d{0}={1}", define.Key, define.Value)); - foreach (var define in defines) - { - builder.Append(define); - } - } - - // Add extensions - if (settings.Extensions != null && settings.Extensions.Any()) - { - var extensions = settings.Extensions.Select(extension => string.Format(CultureInfo.InvariantCulture, "-ext {0}", extension)); - foreach (var extension in extensions) - { - builder.Append(extension); - } - } - - // FIPS - if (settings.FIPS) - { - builder.Append("-fips"); - } - - // No logo - if (settings.NoLogo) - { - builder.Append("-nologo"); - } - - // Output directory - if (settings.OutputDirectory != null && !string.IsNullOrEmpty(settings.OutputDirectory.FullPath)) - { - // Candle want the path to end with \\, double separator chars. - var fullPath = string.Concat(settings.OutputDirectory.MakeAbsolute(_environment).FullPath, '\\', '\\'); - - builder.Append("-o"); - builder.AppendQuoted(fullPath); - } - - // Pedantic - if (settings.Pedantic) - { - builder.Append("-pedantic"); - } - - // Show source trace - if (settings.ShowSourceTrace) - { - builder.Append("-trace"); - } - - // Verbose - if (settings.Verbose) - { - builder.Append("-v"); - } - - // Source files (.wxs) - foreach (var sourceFile in sourceFiles.Select(file => file.MakeAbsolute(_environment).FullPath)) - { - builder.AppendQuoted(sourceFile); - } - - return builder; - } - - private static string GetArchitectureName(Architecture arch) - { - switch (arch) - { - case Architecture.IA64: - return "ia64"; - case Architecture.X64: - return "x64"; - case Architecture.X86: - return "x86"; - default: - throw new NotSupportedException("The provided architecture is not valid."); - } - } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return "Candle"; - } - - /// - /// Gets the possible names of the tool executable. - /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "candle.exe" }; - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.WiX +{ + /// + /// The WiX Candle runner. + /// + public sealed class CandleRunner : Tool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The environment. + /// The process runner. + /// The tool locator. + public CandleRunner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + ArgumentNullException.ThrowIfNull(environment); + _environment = environment; + } + + /// + /// Runs Candle with the specified source files and settings. + /// + /// The source files (.wxs) to compile. + /// The settings. + public void Run(IEnumerable sourceFiles, CandleSettings settings) + { + ArgumentNullException.ThrowIfNull(sourceFiles); + + var sourceFilesArray = sourceFiles as FilePath[] ?? sourceFiles.ToArray(); + if (!sourceFilesArray.Any()) + { + throw new ArgumentException("No source files specified.", nameof(sourceFiles)); + } + + ArgumentNullException.ThrowIfNull(settings); + + Run(settings, GetArguments(sourceFilesArray, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable sourceFiles, CandleSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // Architecture + if (settings.Architecture.HasValue) + { + builder.Append("-arch"); + builder.Append(GetArchitectureName(settings.Architecture.Value)); + } + + // Add defines + if (settings.Defines != null && settings.Defines.Any()) + { + var defines = settings.Defines.Select(define => string.Format(CultureInfo.InvariantCulture, "-d{0}=\"{1}\"", define.Key, define.Value)); + foreach (var define in defines) + { + builder.Append(define); + } + } + + // Add extensions + if (settings.Extensions != null && settings.Extensions.Any()) + { + var extensions = settings.Extensions.Select(extension => string.Format(CultureInfo.InvariantCulture, "-ext {0}", extension)); + foreach (var extension in extensions) + { + builder.Append(extension); + } + } + + // FIPS + if (settings.FIPS) + { + builder.Append("-fips"); + } + + // No logo + if (settings.NoLogo) + { + builder.Append("-nologo"); + } + + // Output directory + if (!string.IsNullOrEmpty(settings.OutputDirectory?.FullPath)) + { + // Candle want the path to end with \\, double separator chars. + var fullPath = string.Concat(settings.OutputDirectory.MakeAbsolute(_environment).FullPath, '\\', '\\'); + + builder.Append("-o"); + builder.AppendQuoted(fullPath); + } + + // Pedantic + if (settings.Pedantic) + { + builder.Append("-pedantic"); + } + + // Show source trace + if (settings.ShowSourceTrace) + { + builder.Append("-trace"); + } + + // Verbose + if (settings.Verbose) + { + builder.Append("-v"); + } + + // Source files (.wxs) + foreach (var sourceFile in sourceFiles.Select(file => file.MakeAbsolute(_environment).FullPath)) + { + builder.AppendQuoted(sourceFile); + } + + return builder; + } + + private static string GetArchitectureName(Architecture arch) + { + switch (arch) + { + case Architecture.IA64: + return "ia64"; + case Architecture.X64: + return "x64"; + case Architecture.X86: + return "x86"; + default: + throw new NotSupportedException("The provided architecture is not valid."); + } + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "Candle"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "candle.exe" }; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/CandleSettings.cs b/src/Cake.Common/Tools/WiX/CandleSettings.cs index 186ab5dd73..e239701a2a 100644 --- a/src/Cake.Common/Tools/WiX/CandleSettings.cs +++ b/src/Cake.Common/Tools/WiX/CandleSettings.cs @@ -1,63 +1,67 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.WiX -{ - /// - /// Contains settings used by . - /// - public sealed class CandleSettings : ToolSettings - { - /// - /// Gets or sets a value indicating which architecture to build the MSI package for. - /// - public Architecture? Architecture { get; set; } - - /// - /// Gets or sets the pre processor defines. - /// - public IDictionary Defines { get; set; } - - /// - /// Gets or sets the WiX extensions to use. - /// - public IEnumerable Extensions { get; set; } - - /// - /// Gets or sets a value indicating whether FIPS compliant algorithms should be used. - /// - /// - /// true if FIPS compliant algorithms should be used, otherwise false. - /// - public bool FIPS { get; set; } - - /// - /// Gets or sets a value indicating whether to show the logo information. - /// - public bool NoLogo { get; set; } - - /// - /// Gets or sets the output directory for the object files. - /// - public DirectoryPath OutputDirectory { get; set; } - - /// - /// Gets or sets a value indicating whether to show pedantic messages. - /// - public bool Pedantic { get; set; } - - /// - /// Gets or sets a value indicating whether to show source trace for errors, warnings and verbose messages. - /// - public bool ShowSourceTrace { get; set; } - - /// - /// Gets or sets a value indicating whether to show verbose output. - /// - public bool Verbose { get; set; } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.WiX +{ + /// + /// Contains settings used by . + /// + public sealed class CandleSettings : ToolSettings + { + /// + /// Gets or sets a value indicating which architecture to build the MSI package for. + /// + public Architecture? Architecture { get; set; } + + /// + /// Gets or sets the pre processor defines. + /// + public IDictionary Defines { get; set; } = + // “Variable names are case-sensitive.” http://wixtoolset.org/documentation/manual/v3/overview/preprocessor.html#custom-variables-define + new Dictionary(StringComparer.Ordinal); + + /// + /// Gets or sets the WiX extensions to use. + /// + public IEnumerable Extensions { get; set; } + + /// + /// Gets or sets a value indicating whether FIPS compliant algorithms should be used. + /// + /// + /// true if FIPS compliant algorithms should be used, otherwise false. + /// + public bool FIPS { get; set; } + + /// + /// Gets or sets a value indicating whether to show the logo information. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets the output directory for the object files. + /// + public DirectoryPath OutputDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to show pedantic messages. + /// + public bool Pedantic { get; set; } + + /// + /// Gets or sets a value indicating whether to show source trace for errors, warnings and verbose messages. + /// + public bool ShowSourceTrace { get; set; } + + /// + /// Gets or sets a value indicating whether to show verbose output. + /// + public bool Verbose { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/Heat/HeatRunner.cs b/src/Cake.Common/Tools/WiX/Heat/HeatRunner.cs index a87196cbcb..b1282fb804 100644 --- a/src/Cake.Common/Tools/WiX/Heat/HeatRunner.cs +++ b/src/Cake.Common/Tools/WiX/Heat/HeatRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -28,10 +29,7 @@ public sealed class HeatRunner : Tool public HeatRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator toolService) : base(fileSystem, environment, processRunner, toolService) { - if (environment == null) - { - throw new ArgumentNullException("environment"); - } + ArgumentNullException.ThrowIfNull(environment); _environment = environment; } @@ -41,57 +39,35 @@ public HeatRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcess /// /// The directory path. /// The output file. + /// The WiX harvest type. /// The settings. - public void Run(DirectoryPath directoryPath, FilePath outputFile, HeatSettings settings) + public void Run(DirectoryPath directoryPath, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (directoryPath == null) - { - throw new ArgumentNullException("directoryPath"); - } + ArgumentNullException.ThrowIfNull(directoryPath); - if (outputFile == null) - { - throw new ArgumentNullException("outputFile"); - } + ArgumentNullException.ThrowIfNull(outputFile); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - Run(settings, GetArguments(directoryPath, outputFile, settings)); + Run(settings, GetArguments(directoryPath, outputFile, harvestType, settings)); } /// /// Runs the Wix Heat runner for the specified directory path. /// - /// The object files. + /// The object file. /// The output file. + /// The WiX harvest type. /// The settings. - public void Run(IEnumerable objectFiles, FilePath outputFile, HeatSettings settings) + public void Run(FilePath objectFile, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (objectFiles == null) - { - throw new ArgumentNullException("objectFiles"); - } + ArgumentNullException.ThrowIfNull(objectFile); - if (outputFile == null) - { - throw new ArgumentNullException("outputFile"); - } + ArgumentNullException.ThrowIfNull(outputFile); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - var objectFilesArray = objectFiles as FilePath[] ?? objectFiles.ToArray(); - if (!objectFilesArray.Any()) - { - throw new ArgumentException("No object files provided.", "objectFiles"); - } + ArgumentNullException.ThrowIfNull(settings); - Run(settings, GetArguments(objectFilesArray, outputFile, settings)); + Run(settings, GetArguments(objectFile, outputFile, harvestType, settings)); } /// @@ -99,54 +75,26 @@ public void Run(IEnumerable objectFiles, FilePath outputFile, HeatSett /// /// The harvest target. /// The output file. + /// The WiX harvest type. /// The settings. - public void Run(string harvestTarget, FilePath outputFile, HeatSettings settings) + public void Run(string harvestTarget, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (harvestTarget == null) - { - throw new ArgumentNullException("harvestTarget"); - } + ArgumentNullException.ThrowIfNull(harvestTarget); - if (outputFile == null) - { - throw new ArgumentNullException("outputFile"); - } + ArgumentNullException.ThrowIfNull(outputFile); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); - Run(settings, GetArguments(harvestTarget, outputFile, settings)); + Run(settings, GetArguments(harvestTarget, outputFile, harvestType, settings)); } - private ProcessArgumentBuilder GetArguments(IEnumerable objectFiles, FilePath outputFile, HeatSettings settings) + private ProcessArgumentBuilder GetArguments(FilePath objectFile, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { var builder = new ProcessArgumentBuilder(); - if (settings.HarvestType != null) - { - switch (settings.HarvestType) - { - case WiXHarvestType.File: - builder.Append("file"); - break; - case WiXHarvestType.Project: - builder.Append("project"); - break; - case WiXHarvestType.Reg: - builder.Append("reg"); - break; - default: - throw new ArgumentException("Incorrect harvest type for input.", "objectFiles"); - } - } + builder.Append(GetHarvestType(harvestType)); - // Object files - foreach (var objectFile in objectFiles.Select(file => file.MakeAbsolute(_environment).FullPath)) - { - builder.AppendQuoted(objectFile); - } + builder.AppendQuoted(objectFile.MakeAbsolute(_environment).FullPath); var args = GetArguments(outputFile, settings); @@ -155,22 +103,11 @@ private ProcessArgumentBuilder GetArguments(IEnumerable objectFiles, F return builder; } - private ProcessArgumentBuilder GetArguments(DirectoryPath directoryPath, FilePath outputFile, HeatSettings settings) + private ProcessArgumentBuilder GetArguments(DirectoryPath directoryPath, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { var builder = new ProcessArgumentBuilder(); - if (settings.HarvestType != null) - { - switch (settings.HarvestType) - { - case WiXHarvestType.Dir: - builder.Append("dir"); - break; - default: - throw new ArgumentException("Incorrect harvest type for input.", "directoryPath"); - } - } - + builder.Append(GetHarvestType(harvestType)); builder.AppendQuoted(directoryPath.MakeAbsolute(_environment).FullPath); var args = GetArguments(outputFile, settings); @@ -180,24 +117,11 @@ private ProcessArgumentBuilder GetArguments(DirectoryPath directoryPath, FilePat return builder; } - private ProcessArgumentBuilder GetArguments(string harvestTarget, FilePath outputFile, HeatSettings settings) + private ProcessArgumentBuilder GetArguments(string harvestTarget, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { var builder = new ProcessArgumentBuilder(); - if (settings.HarvestType != null) - { - switch (settings.HarvestType) - { - case WiXHarvestType.Perf: - builder.Append("perf"); - break; - case WiXHarvestType.Website: - builder.Append("website"); - break; - default: - throw new ArgumentException("Incorrect harvest type for input.", "harvestTarget"); - } - } + builder.Append(GetHarvestType(harvestType)); builder.AppendQuoted(harvestTarget); @@ -231,7 +155,7 @@ private ProcessArgumentBuilder GetArguments(FilePath outputFile, HeatSettings se // Suppress specific warnings if (settings.SuppressSpecificWarnings != null && settings.SuppressSpecificWarnings.Any()) { - var warnings = settings.SuppressSpecificWarnings.Select(warning => string.Format(CultureInfo.InstalledUICulture, "-sw{0}", warning)); + var warnings = settings.SuppressSpecificWarnings.Select(warning => string.Format(CultureInfo.InvariantCulture, "-sw{0}", warning)); foreach (var warning in warnings) { builder.Append(warning); @@ -310,26 +234,26 @@ private ProcessArgumentBuilder GetArguments(FilePath outputFile, HeatSettings se if (settings.OutputGroup != null) { - builder.Append("-pog:"); + builder.Append("-pog"); switch (settings.OutputGroup) { case WiXOutputGroupType.Binaries: - builder.Append("binaries"); + builder.Append("Binaries"); break; case WiXOutputGroupType.Symbols: - builder.Append("symbols"); + builder.Append("Symbols"); break; case WiXOutputGroupType.Documents: - builder.Append("documents"); + builder.Append("Documents"); break; - case WiXOutputGroupType.Satallites: - builder.Append("satallites"); + case WiXOutputGroupType.Satellites: + builder.Append("Satellites"); break; case WiXOutputGroupType.Sources: - builder.Append("sources"); + builder.Append("Sources"); break; case WiXOutputGroupType.Content: - builder.Append("content"); + builder.Append("Content"); break; } } @@ -431,6 +355,27 @@ private ProcessArgumentBuilder GetArguments(FilePath outputFile, HeatSettings se return builder; } + private string GetHarvestType(WiXHarvestType harvestType) + { + switch (harvestType) + { + case WiXHarvestType.Dir: + return "dir"; + case WiXHarvestType.File: + return "file"; + case WiXHarvestType.Project: + return "project"; + case WiXHarvestType.Reg: + return "reg"; + case WiXHarvestType.Perf: + return "perf"; + case WiXHarvestType.Website: + return "website"; + default: + return "dir"; + } + } + /// /// Gets the name of the tool. /// diff --git a/src/Cake.Common/Tools/WiX/Heat/HeatSettings.cs b/src/Cake.Common/Tools/WiX/Heat/HeatSettings.cs index bf00ac9ad9..fc00c0fb60 100644 --- a/src/Cake.Common/Tools/WiX/Heat/HeatSettings.cs +++ b/src/Cake.Common/Tools/WiX/Heat/HeatSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -12,15 +13,6 @@ namespace Cake.Common.Tools.WiX.Heat /// public sealed class HeatSettings : ToolSettings { - /// - /// Gets or sets the type for WiX harvest. - /// Default is dir. - /// - /// - /// The type of the harvest. - /// - public WiXHarvestType? HarvestType { get; set; } - /// /// Gets or sets the WiX extensions to use. /// diff --git a/src/Cake.Common/Tools/WiX/Heat/WiXGenerateType.cs b/src/Cake.Common/Tools/WiX/Heat/WiXGenerateType.cs index dcfc65d93d..a673bd9fbd 100644 --- a/src/Cake.Common/Tools/WiX/Heat/WiXGenerateType.cs +++ b/src/Cake.Common/Tools/WiX/Heat/WiXGenerateType.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.WiX.Heat { /// - /// Type of elements to generate + /// Type of elements to generate. /// public enum WiXGenerateType { diff --git a/src/Cake.Common/Tools/WiX/Heat/WiXHarvestType.cs b/src/Cake.Common/Tools/WiX/Heat/WiXHarvestType.cs index 90f237456e..af7f2ae5df 100644 --- a/src/Cake.Common/Tools/WiX/Heat/WiXHarvestType.cs +++ b/src/Cake.Common/Tools/WiX/Heat/WiXHarvestType.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.WiX.Heat { /// diff --git a/src/Cake.Common/Tools/WiX/Heat/WiXOutputGroupType.cs b/src/Cake.Common/Tools/WiX/Heat/WiXOutputGroupType.cs index 4bce792afc..6cf3818a27 100644 --- a/src/Cake.Common/Tools/WiX/Heat/WiXOutputGroupType.cs +++ b/src/Cake.Common/Tools/WiX/Heat/WiXOutputGroupType.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.WiX.Heat { /// - /// The Output Group of Visual Studio project + /// The Output Group of Visual Studio project. /// public enum WiXOutputGroupType { @@ -24,9 +25,9 @@ public enum WiXOutputGroupType Documents = 2, /// - /// OutputGroup: Satallites + /// OutputGroup: Satellites /// - Satallites = 3, + Satellites = 3, /// /// OutputGroup: Sources diff --git a/src/Cake.Common/Tools/WiX/Heat/WiXTemplateType.cs b/src/Cake.Common/Tools/WiX/Heat/WiXTemplateType.cs index bfed8c524e..52cdb1d5ff 100644 --- a/src/Cake.Common/Tools/WiX/Heat/WiXTemplateType.cs +++ b/src/Cake.Common/Tools/WiX/Heat/WiXTemplateType.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.WiX.Heat { /// diff --git a/src/Cake.Common/Tools/WiX/LightRunner.cs b/src/Cake.Common/Tools/WiX/LightRunner.cs index 7bde2f4fcf..86fa364965 100644 --- a/src/Cake.Common/Tools/WiX/LightRunner.cs +++ b/src/Cake.Common/Tools/WiX/LightRunner.cs @@ -1,132 +1,127 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Cake.Core; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.WiX -{ - /// - /// The WiX Light runner. - /// - public sealed class LightRunner : Tool - { - private readonly ICakeEnvironment _environment; - - /// - /// Initializes a new instance of the class. - /// - /// The file system. - /// The Cake environment. - /// The process runner. - /// The tool locator. - public LightRunner( - IFileSystem fileSystem, - ICakeEnvironment environment, - IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) - { - _environment = environment; - } - - /// - /// Runs Light with the specified input object files and settings. - /// - /// The object files (.wixobj). - /// The settings. - public void Run(IEnumerable objectFiles, LightSettings settings) - { - if (objectFiles == null) - { - throw new ArgumentNullException("objectFiles"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - - var objectFilesArray = objectFiles as FilePath[] ?? objectFiles.ToArray(); - if (!objectFilesArray.Any()) - { - throw new ArgumentException("No object files provided.", "objectFiles"); - } - - Run(settings, GetArguments(objectFilesArray, settings)); - } - - private ProcessArgumentBuilder GetArguments(IEnumerable objectFiles, LightSettings settings) - { - var builder = new ProcessArgumentBuilder(); - - // Add defines - if (settings.Defines != null && settings.Defines.Any()) - { - var defines = settings.Defines.Select(define => string.Format(CultureInfo.InvariantCulture, "-d{0}={1}", define.Key, define.Value)); - foreach (var define in defines) - { - builder.Append(define); - } - } - - // Add extensions - if (settings.Extensions != null && settings.Extensions.Any()) - { - var extensions = settings.Extensions.Select(extension => string.Format(CultureInfo.InvariantCulture, "-ext {0}", extension)); - foreach (var extension in extensions) - { - builder.Append(extension); - } - } - - // No logo - if (settings.NoLogo) - { - builder.Append("-nologo"); - } - - // Output file - if (settings.OutputFile != null && !string.IsNullOrEmpty(settings.OutputFile.FullPath)) - { - builder.Append("-o"); - builder.AppendQuoted(settings.OutputFile.MakeAbsolute(_environment).FullPath); - } - - // Raw arguments - if (!string.IsNullOrEmpty(settings.RawArguments)) - { - builder.Append(settings.RawArguments); - } - - // Object files (.wixobj) - foreach (var objectFile in objectFiles.Select(file => file.MakeAbsolute(_environment).FullPath)) - { - builder.AppendQuoted(objectFile); - } - - return builder; - } - - /// - /// Gets the name of the tool. - /// - /// The name of the tool. - protected override string GetToolName() - { - return "Light"; - } - - /// - /// Gets the possible names of the tool executable. - /// - /// The tool executable name. - protected override IEnumerable GetToolExecutableNames() - { - return new[] { "light.exe" }; - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Cake.Core; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.WiX +{ + /// + /// The WiX Light runner. + /// + public sealed class LightRunner : Tool + { + private readonly ICakeEnvironment _environment; + + /// + /// Initializes a new instance of the class. + /// + /// The file system. + /// The Cake environment. + /// The process runner. + /// The tool locator. + public LightRunner( + IFileSystem fileSystem, + ICakeEnvironment environment, + IProcessRunner processRunner, + IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + { + _environment = environment; + } + + /// + /// Runs Light with the specified input object files and settings. + /// + /// The object files (.wixobj). + /// The settings. + public void Run(IEnumerable objectFiles, LightSettings settings) + { + ArgumentNullException.ThrowIfNull(objectFiles); + ArgumentNullException.ThrowIfNull(settings); + + var objectFilesArray = objectFiles as FilePath[] ?? objectFiles.ToArray(); + if (!objectFilesArray.Any()) + { + throw new ArgumentException("No object files provided.", nameof(objectFiles)); + } + + Run(settings, GetArguments(objectFilesArray, settings)); + } + + private ProcessArgumentBuilder GetArguments(IEnumerable objectFiles, LightSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // Add defines + if (settings.Defines != null && settings.Defines.Any()) + { + var defines = settings.Defines.Select(define => string.Format(CultureInfo.InvariantCulture, "-d{0}={1}", define.Key, define.Value)); + foreach (var define in defines) + { + builder.Append(define); + } + } + + // Add extensions + if (settings.Extensions != null && settings.Extensions.Any()) + { + var extensions = settings.Extensions.Select(extension => string.Format(CultureInfo.InvariantCulture, "-ext {0}", extension)); + foreach (var extension in extensions) + { + builder.Append(extension); + } + } + + // No logo + if (settings.NoLogo) + { + builder.Append("-nologo"); + } + + // Output file + if (!string.IsNullOrEmpty(settings.OutputFile?.FullPath)) + { + builder.Append("-o"); + builder.AppendQuoted(settings.OutputFile.MakeAbsolute(_environment).FullPath); + } + + // Raw arguments + if (!string.IsNullOrEmpty(settings.RawArguments)) + { + builder.Append(settings.RawArguments); + } + + // Object files (.wixobj) + foreach (var objectFile in objectFiles.Select(file => file.MakeAbsolute(_environment).FullPath)) + { + builder.AppendQuoted(objectFile); + } + + return builder; + } + + /// + /// Gets the name of the tool. + /// + /// The name of the tool. + protected override string GetToolName() + { + return "Light"; + } + + /// + /// Gets the possible names of the tool executable. + /// + /// The tool executable name. + protected override IEnumerable GetToolExecutableNames() + { + return new[] { "light.exe" }; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/LightSettings.cs b/src/Cake.Common/Tools/WiX/LightSettings.cs index 9055660d7f..47c5661b47 100644 --- a/src/Cake.Common/Tools/WiX/LightSettings.cs +++ b/src/Cake.Common/Tools/WiX/LightSettings.cs @@ -1,40 +1,44 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using Cake.Core.IO; -using Cake.Core.Tooling; - -namespace Cake.Common.Tools.WiX -{ - /// - /// Contains settings used by the . - /// - public sealed class LightSettings : ToolSettings - { - /// - /// Gets or sets the defined WiX variables. - /// - public IDictionary Defines { get; set; } - - /// - /// Gets or sets the WiX extensions to use. - /// - public IEnumerable Extensions { get; set; } - - /// - /// Gets or sets raw command line arguments to pass through to the linker. - /// - public string RawArguments { get; set; } - - /// - /// Gets or sets a value indicating whether to show the logo information. - /// - public bool NoLogo { get; set; } - - /// - /// Gets or sets the path to the output file (i.e. the resulting MSI package). - /// - public FilePath OutputFile { get; set; } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Cake.Core.IO; +using Cake.Core.Tooling; + +namespace Cake.Common.Tools.WiX +{ + /// + /// Contains settings used by the . + /// + public sealed class LightSettings : ToolSettings + { + /// + /// Gets or sets the defined WiX variables. + /// + public IDictionary Defines { get; set; } = + // “Variable names are case-sensitive.” http://wixtoolset.org/documentation/manual/v3/overview/preprocessor.html#custom-variables-define + new Dictionary(StringComparer.Ordinal); + + /// + /// Gets or sets the WiX extensions to use. + /// + public IEnumerable Extensions { get; set; } + + /// + /// Gets or sets raw command line arguments to pass through to the linker. + /// + public string RawArguments { get; set; } + + /// + /// Gets or sets a value indicating whether to show the logo information. + /// + public bool NoLogo { get; set; } + + /// + /// Gets or sets the path to the output file (i.e. the resulting MSI package). + /// + public FilePath OutputFile { get; set; } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/WiX/WiXAliases.cs b/src/Cake.Common/Tools/WiX/WiXAliases.cs index 8cd12fdc84..ef650b30a6 100644 --- a/src/Cake.Common/Tools/WiX/WiXAliases.cs +++ b/src/Cake.Common/Tools/WiX/WiXAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -16,9 +17,9 @@ namespace Cake.Common.Tools.WiX /// Contains functionality related to WiX. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the appropriate settings class: + /// install from nuget.org, or specify the ToolPath within the appropriate settings class: /// - /// #tool "nuget:?package=WiX.Toolset" + /// #tool "nuget:?package=WiX" /// /// /// @@ -42,12 +43,9 @@ public static class WiXAliases /// The settings. [CakeMethodAlias] [CakeAliasCategory("Candle")] - public static void WiXCandle(this ICakeContext context, string pattern, CandleSettings settings = null) + public static void WiXCandle(this ICakeContext context, GlobPattern pattern, CandleSettings settings = null) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var files = context.Globber.GetFiles(pattern).ToArray(); if (files.Length == 0) @@ -79,10 +77,7 @@ public static void WiXCandle(this ICakeContext context, string pattern, CandleSe [CakeAliasCategory("Candle")] public static void WiXCandle(this ICakeContext context, IEnumerable sourceFiles, CandleSettings settings = null) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new CandleRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(sourceFiles, settings ?? new CandleSettings()); @@ -104,12 +99,9 @@ public static void WiXCandle(this ICakeContext context, IEnumerable so /// The settings. [CakeMethodAlias] [CakeAliasCategory("Light")] - public static void WiXLight(this ICakeContext context, string pattern, LightSettings settings = null) + public static void WiXLight(this ICakeContext context, GlobPattern pattern, LightSettings settings = null) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var files = context.Globber.GetFiles(pattern).ToArray(); if (files.Length == 0) @@ -140,10 +132,7 @@ public static void WiXLight(this ICakeContext context, string pattern, LightSett [CakeAliasCategory("Light")] public static void WiXLight(this ICakeContext context, IEnumerable objectFiles, LightSettings settings = null) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new LightRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(objectFiles, settings ?? new LightSettings()); @@ -154,26 +143,24 @@ public static void WiXLight(this ICakeContext context, IEnumerable obj /// /// /// - /// var harvestDirectory = Directory("./src"); - /// var filePath = new FilePath("cake.wxs"); - /// WiXHeat(harvestDirectory, filePath); + /// DirectoryPath harvestDirectory = Directory("./src"); + /// var filePath = new FilePath("Wix.Directory.wxs"); + /// WiXHeat(harvestDirectory, filePath, WiXHarvestType.Dir); /// /// /// The context. /// The object files. /// The output file. + /// The WiX harvest type. [CakeMethodAlias] [CakeAliasCategory("Heat")] [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] - public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPath, FilePath outputFile) + public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPath, FilePath outputFile, WiXHarvestType harvestType) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(directoryPath, outputFile, new HeatSettings()); + runner.Run(directoryPath, outputFile, harvestType, new HeatSettings()); } /// @@ -181,27 +168,26 @@ public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPat /// /// /// - /// var harvestDirectory = Directory("./src"); - /// var filePath = new FilePath("cake.wxs"); - /// WiXHeat(harvestDirectory, filePath, new HeatSettings { HarvestType = WiXHarvestType.Dir }); + /// DirectoryPath harvestDirectory = Directory("./src"); + /// var filePath = File("Wix.Directory.wxs"); + /// Information(MakeAbsolute(harvestDirectory).FullPath); + /// WiXHeat(harvestDirectory, filePath, WiXHarvestType.Dir, new HeatSettings { NoLogo = true }); /// /// /// The context. /// The directory path. /// The output file. + /// The WiX harvest type. /// The settings. [CakeMethodAlias] [CakeAliasCategory("Heat")] [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] - public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPath, FilePath outputFile, HeatSettings settings) + public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPath, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(directoryPath, outputFile, settings ?? new HeatSettings()); + runner.Run(directoryPath, outputFile, harvestType, settings ?? new HeatSettings()); } /// @@ -209,27 +195,74 @@ public static void WiXHeat(this ICakeContext context, DirectoryPath directoryPat /// /// /// - /// var harvestFiles = GetFiles(".src/website/bin/website.dll"); - /// var filePath = File("cake.wxs"); - /// WiXHeat(harvestFiles, filePath, new HeatSettings { HarvestType = WiXHarvestType.File }); + /// var harvestFile = File("./tools/Cake/Cake.Core.dll"); + /// var filePath = File("Wix.File.wxs"); + /// WiXHeat(harvestFile, filePath, WiXHarvestType.File); /// /// /// The context. - /// The object files. + /// The object file. /// The output file. + /// The WiX harvest type. + [CakeMethodAlias] + [CakeAliasCategory("Heat")] + [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] + public static void WiXHeat(this ICakeContext context, FilePath objectFile, FilePath outputFile, WiXHarvestType harvestType) + { + ArgumentNullException.ThrowIfNull(context); + + var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(objectFile, outputFile, harvestType, new HeatSettings()); + } + + /// + /// Harvests from the desired files. + /// + /// + /// + /// var harvestFiles = File("./tools/Cake/*.dll"); + /// var filePath = File("Wix.File.wxs"); + /// WiXHeat(harvestFiles, filePath, WiXHarvestType.File, new HeatSettings { NoLogo = true }); + /// + /// + /// The context. + /// The object file. + /// The output file. + /// The WiX harvest type. /// The settings. [CakeMethodAlias] [CakeAliasCategory("Heat")] [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] - public static void WiXHeat(this ICakeContext context, IEnumerable objectFiles, FilePath outputFile, HeatSettings settings) + public static void WiXHeat(this ICakeContext context, FilePath objectFile, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); + + var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); + runner.Run(objectFile, outputFile, harvestType, settings ?? new HeatSettings()); + } + + /// + /// Harvests files for a website or performance. + /// + /// + /// + /// var filePath = File("Wix.Website.wxs"); + /// WiXHeat("Default Web Site", filePath, WiXHarvestType.Website); + /// + /// + /// The context. + /// The harvest target. + /// The output file. + /// The WiX harvest type. + [CakeMethodAlias] + [CakeAliasCategory("Heat")] + [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] + public static void WiXHeat(this ICakeContext context, string harvestTarget, FilePath outputFile, WiXHarvestType harvestType) + { + ArgumentNullException.ThrowIfNull(context); var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(objectFiles, outputFile, settings ?? new HeatSettings()); + runner.Run(harvestTarget, outputFile, harvestType, new HeatSettings()); } /// @@ -237,26 +270,24 @@ public static void WiXHeat(this ICakeContext context, IEnumerable obje /// /// /// - /// var filePath = File("cake.wxs"); - /// WiXHeat("Default Web Site", filePath, new HeatSettings { HarvestType = WiXHarvestType.Website }); + /// var filePath = File("Wix.Website.wxs"); + /// WiXHeat("Default Web Site", filePath, WiXHarvestType.Website, new HeatSettings { NoLogo = true }); /// /// /// The context. /// The harvest target. /// The output file. + /// The WiX harvest type. /// The settings. [CakeMethodAlias] [CakeAliasCategory("Heat")] [CakeNamespaceImport("Cake.Common.Tools.WiX.Heat")] - public static void WiXHeat(this ICakeContext context, string harvestTarget, FilePath outputFile, HeatSettings settings) + public static void WiXHeat(this ICakeContext context, string harvestTarget, FilePath outputFile, WiXHarvestType harvestType, HeatSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var runner = new HeatRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - runner.Run(harvestTarget, outputFile, settings ?? new HeatSettings()); + runner.Run(harvestTarget, outputFile, harvestType, settings ?? new HeatSettings()); } } } diff --git a/src/Cake.Common/Tools/XBuild/XBuildAliases.cs b/src/Cake.Common/Tools/XBuild/XBuildAliases.cs index 08147dbdd0..4d3978568d 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildAliases.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -23,6 +24,11 @@ public static class XBuildAliases /// /// The context. /// The solution to build. + /// + /// + /// XBuild("./src/Cake.sln"); + /// + /// [CakeMethodAlias] public static void XBuild(this ICakeContext context, FilePath solution) { @@ -35,17 +41,19 @@ public static void XBuild(this ICakeContext context, FilePath solution) /// The context. /// The solution to build. /// The settings configurator. + /// + /// + /// XBuild("./src/Cake.sln", configurator => + /// configurator.SetConfiguration("Debug") + /// .SetVerbosity(Verbosity.Minimal) + /// .UseToolVersion(XBuildToolVersion.NET40)); + /// + /// [CakeMethodAlias] public static void XBuild(this ICakeContext context, FilePath solution, Action configurator) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (configurator == null) - { - throw new ArgumentNullException("configurator"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(configurator); var settings = new XBuildSettings(); configurator(settings); @@ -60,20 +68,23 @@ public static void XBuild(this ICakeContext context, FilePath solution, ActionThe context. /// The solution to build. /// The settings. + /// + /// + /// XBuild("./src/Cake.sln", new XBuildSettings { + /// Verbosity = Verbosity.Minimal, + /// ToolVersion = XBuildToolVersion.NET40, + /// Configuration = "Release" + /// }); + /// + /// [CakeMethodAlias] public static void XBuild(this ICakeContext context, FilePath solution, XBuildSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(settings); var runner = new XBuildRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(solution, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildResolver.cs b/src/Cake.Common/Tools/XBuild/XBuildResolver.cs index 574ccb00e8..f6772f2f15 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildResolver.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildResolver.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; + using System.Diagnostics; using Cake.Core; using Cake.Core.IO; @@ -18,7 +18,7 @@ public static FilePath GetXBuildPath(IFileSystem fileSystem, ICakeEnvironment en _environment = environment; _fileSystem = fileSystem; - if (_environment.IsUnix()) + if (_environment.Platform.IsUnix()) { return GetWhichXBuild(); } @@ -98,14 +98,14 @@ private static FilePath GetWhereMono() private static DirectoryPath GetMonoPathWindows() { - var programFiles = _environment.Is64BitOperativeSystem() - ? Environment.SpecialFolder.ProgramFilesX86 - : Environment.SpecialFolder.ProgramFiles; + var programFiles = _environment.Platform.Is64Bit + ? SpecialPath.ProgramFilesX86 + : SpecialPath.ProgramFiles; - var programFilesPath = new DirectoryPath(Environment.GetFolderPath(programFiles)); - var monoPath = programFilesPath.Combine("Mono").Combine("bin"); + var programFilesPath = _environment.GetSpecialPath(programFiles); + var monoPath = programFilesPath.Combine("Mono").Combine("bin").MakeAbsolute(_environment); return _fileSystem.GetDirectory(monoPath).Exists ? monoPath : null; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildRunner.cs b/src/Cake.Common/Tools/XBuild/XBuildRunner.cs index 6a7d56c32b..9816fc119d 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildRunner.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Globalization; @@ -71,7 +72,7 @@ private ProcessArgumentBuilder GetArguments(FilePath solution, XBuildSettings se // Got any targets? if (settings.Targets.Count > 0) { - var targets = string.Join(";", settings.Targets); + var targets = string.Join(';', settings.Targets); builder.Append(string.Concat("/t:", targets)); } else @@ -106,11 +107,11 @@ private static string GetVerbosityName(Verbosity verbosity) private static IEnumerable GetPropertyArguments(IDictionary> properties) { - foreach (var propertyKey in properties.Keys) + foreach (var (key, values) in properties) { - foreach (var propertyValue in properties[propertyKey]) + foreach (var propertyValue in values) { - yield return string.Concat("/p:", propertyKey.Quote(), "=", propertyValue.Quote()); + yield return string.Concat("/p:", key.Quote(), "=", propertyValue.Quote()); } } } @@ -130,20 +131,17 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "xunit", "xunit.bat" }; + return new[] { "xbuild", "xbuild.exe" }; } /// - /// Gets alternative file paths which the tool may exist in + /// Gets alternative file paths which the tool may exist in. /// /// The settings. /// The default tool path. protected override IEnumerable GetAlternativeToolPaths(XBuildSettings settings) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); var path = XBuildResolver.GetXBuildPath(_fileSystem, _environment, settings.ToolVersion); @@ -155,4 +153,4 @@ protected override IEnumerable GetAlternativeToolPaths(XBuildSettings return Enumerable.Empty(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildSettings.cs b/src/Cake.Common/Tools/XBuild/XBuildSettings.cs index 3a7cac3539..85a56d6db9 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildSettings.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.Diagnostics; @@ -20,19 +21,13 @@ public sealed class XBuildSettings : ToolSettings /// Gets the targets. /// /// The targets. - public ISet Targets - { - get { return _targets; } - } + public ISet Targets => _targets; /// /// Gets the properties. /// /// The properties. - public IDictionary> Properties - { - get { return _properties; } - } + public IDictionary> Properties => _properties; /// /// Gets or sets the tool version. @@ -66,4 +61,4 @@ public XBuildSettings() Verbosity = Verbosity.Normal; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildSettingsExtensions.cs b/src/Cake.Common/Tools/XBuild/XBuildSettingsExtensions.cs index 3e0626c15f..6e21a5fa11 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildSettingsExtensions.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildSettingsExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -21,10 +22,7 @@ public static class XBuildSettingsExtensions /// The same instance so that multiple calls can be chained. public static XBuildSettings WithTarget(this XBuildSettings settings, string target) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Targets.Add(target); return settings; } @@ -37,10 +35,7 @@ public static XBuildSettings WithTarget(this XBuildSettings settings, string tar /// The same instance so that multiple calls can be chained. public static XBuildSettings UseToolVersion(this XBuildSettings settings, XBuildToolVersion version) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.ToolVersion = version; return settings; } @@ -54,10 +49,7 @@ public static XBuildSettings UseToolVersion(this XBuildSettings settings, XBuild /// The same instance so that multiple calls can be chained. public static XBuildSettings WithProperty(this XBuildSettings settings, string name, params string[] values) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); IList currentValue; currentValue = new List( @@ -78,10 +70,7 @@ public static XBuildSettings WithProperty(this XBuildSettings settings, string n /// The same instance so that multiple calls can be chained. public static XBuildSettings SetConfiguration(this XBuildSettings settings, string configuration) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Configuration = configuration; return settings; } @@ -94,12 +83,9 @@ public static XBuildSettings SetConfiguration(this XBuildSettings settings, stri /// The same instance so that multiple calls can be chained. public static XBuildSettings SetVerbosity(this XBuildSettings settings, Verbosity verbosity) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); settings.Verbosity = verbosity; return settings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildToolVersion.cs b/src/Cake.Common/Tools/XBuild/XBuildToolVersion.cs index ee9dbacc42..f5c15108da 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildToolVersion.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildToolVersion.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.XBuild { /// @@ -33,4 +34,4 @@ public enum XBuildToolVersion /// NET40 = 3, } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XBuild/XBuildVersion.cs b/src/Cake.Common/Tools/XBuild/XBuildVersion.cs index addd68fdb9..3a9d861bfa 100644 --- a/src/Cake.Common/Tools/XBuild/XBuildVersion.cs +++ b/src/Cake.Common/Tools/XBuild/XBuildVersion.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.XBuild { internal enum XBuildVersion @@ -9,4 +10,4 @@ internal enum XBuildVersion XBuild35 = 2, XBuild45 = 3 } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/ParallelismOption.cs b/src/Cake.Common/Tools/XUnit/ParallelismOption.cs index 78ce62b0d9..d8805dbf6a 100644 --- a/src/Cake.Common/Tools/XUnit/ParallelismOption.cs +++ b/src/Cake.Common/Tools/XUnit/ParallelismOption.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Tools.XUnit { /// - /// Represents XUnit2's options for parallel test execution + /// Represents XUnit2's options for parallel test execution. /// public enum ParallelismOption { @@ -28,4 +29,4 @@ public enum ParallelismOption /// All } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnit2Aliases.cs b/src/Cake.Common/Tools/XUnit/XUnit2Aliases.cs index 84ff3aa02c..b25cd5a71e 100644 --- a/src/Cake.Common/Tools/XUnit/XUnit2Aliases.cs +++ b/src/Cake.Common/Tools/XUnit/XUnit2Aliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ namespace Cake.Common.Tools.XUnit /// Contains functionality related to running xunit tests. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// /// #tool "nuget:?package=xunit.runner.console" /// @@ -35,12 +36,9 @@ public static class XUnit2Aliases /// /// [CakeMethodAlias] - public static void XUnit2(this ICakeContext context, string pattern) + public static void XUnit2(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -70,12 +68,9 @@ public static void XUnit2(this ICakeContext context, string pattern) /// /// [CakeMethodAlias] - public static void XUnit2(this ICakeContext context, string pattern, XUnit2Settings settings) + public static void XUnit2(this ICakeContext context, GlobPattern pattern, XUnit2Settings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern).ToArray(); if (assemblies.Length == 0) @@ -105,10 +100,7 @@ public static void XUnit2(this ICakeContext context, string pattern, XUnit2Setti [CakeMethodAlias] public static void XUnit2(this ICakeContext context, IEnumerable assemblies) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); XUnit2(context, paths, new XUnit2Settings()); } @@ -155,10 +147,7 @@ public static void XUnit2(this ICakeContext context, IEnumerable assem [CakeMethodAlias] public static void XUnit2(this ICakeContext context, IEnumerable assemblies, XUnit2Settings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); XUnit2(context, paths, settings); } @@ -184,17 +173,11 @@ public static void XUnit2(this ICakeContext context, IEnumerable assembl [CakeMethodAlias] public static void XUnit2(this ICakeContext context, IEnumerable assemblies, XUnit2Settings settings) { - if (context == null) - { - throw new ArgumentNullException("assemblies"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); var runner = new XUnit2Runner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); runner.Run(assemblies, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnit2Runner.cs b/src/Cake.Common/Tools/XUnit/XUnit2Runner.cs index eb7f5a6c79..a6e08b5ed3 100644 --- a/src/Cake.Common/Tools/XUnit/XUnit2Runner.cs +++ b/src/Cake.Common/Tools/XUnit/XUnit2Runner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -16,6 +17,7 @@ namespace Cake.Common.Tools.XUnit public sealed class XUnit2Runner : Tool { private readonly ICakeEnvironment _environment; + private bool _useX86; /// /// Initializes a new instance of the class. @@ -40,17 +42,11 @@ public XUnit2Runner( /// The settings. public void Run(IEnumerable assemblyPaths, XUnit2Settings settings) { - if (assemblyPaths == null) - { - throw new ArgumentNullException("assemblyPaths"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPaths); + ArgumentNullException.ThrowIfNull(settings); // Make sure we got output directory set when generating reports. - if (settings.OutputDirectory == null || string.IsNullOrWhiteSpace(settings.OutputDirectory.FullPath)) + if (string.IsNullOrWhiteSpace(settings.OutputDirectory?.FullPath)) { if (settings.HtmlReport) { @@ -60,8 +56,16 @@ public void Run(IEnumerable assemblyPaths, XUnit2Settings settings) { throw new CakeException("Cannot generate XML report when no output directory has been set."); } + if (settings.NUnitReport) + { + throw new CakeException("Cannot generate NUnit XML report when no output directory has been set."); + } + if (settings.JUnitReport) + { + throw new CakeException("Cannot generate JUnit XML report when no output directory has been set."); + } } - + _useX86 = settings.UseX86; var assemblies = assemblyPaths as FilePath[] ?? assemblyPaths.ToArray(); Run(settings, GetArguments(assemblies, settings)); } @@ -88,10 +92,32 @@ private ProcessArgumentBuilder GetArguments(IReadOnlyList assemblyPath builder.Append("-noappdomain"); } + // Generate NUnit Style XML report? + if (settings.NUnitReport) + { + var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths, settings); + var assemblyFilename = reportFileName.AppendExtension(".xml"); + var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); + + builder.Append("-nunit"); + builder.AppendQuoted(outputPath.FullPath); + } + + // Generate JUnit Style XML report? + if (settings.JUnitReport) + { + var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths, settings); + var assemblyFilename = reportFileName.AppendExtension(".xml"); + var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); + + builder.Append("-junit"); + builder.AppendQuoted(outputPath.FullPath); + } + // Generate HTML report? if (settings.HtmlReport) { - var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths); + var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths, settings); var assemblyFilename = reportFileName.AppendExtension(".html"); var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); @@ -102,7 +128,7 @@ private ProcessArgumentBuilder GetArguments(IReadOnlyList assemblyPath // Generate XML report? if (settings.XmlReport || settings.XmlReportV1) { - var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths); + var reportFileName = XUnitRunnerUtilities.GetReportFileName(assemblyPaths, settings); var assemblyFilename = reportFileName.AppendExtension(".xml"); var outputPath = settings.OutputDirectory.MakeAbsolute(_environment).GetFilePath(assemblyFilename); @@ -111,7 +137,7 @@ private ProcessArgumentBuilder GetArguments(IReadOnlyList assemblyPath } // parallelize test execution? - if (settings.Parallelism != ParallelismOption.None) + if (settings.Parallelism.HasValue) { builder.Append("-parallel " + settings.Parallelism.ToString().ToLowerInvariant()); } @@ -141,6 +167,21 @@ private ProcessArgumentBuilder GetArguments(IReadOnlyList assemblyPath builder.Append("-notrait \"{0}={1}\"", trait.Name, trait.Value); } + foreach (var ns in settings.NamespacesToInclude) + { + builder.Append("-namespace \"{0}\"", ns); + } + + foreach (var cn in settings.ClassesToInclude) + { + builder.Append("-class \"{0}\"", cn); + } + + foreach (var mn in settings.MethodsToInclude) + { + builder.Append("-method \"{0}\"", mn); + } + return builder; } @@ -159,7 +200,7 @@ protected override string GetToolName() /// The tool executable name. protected override IEnumerable GetToolExecutableNames() { - return new[] { "xunit.console.exe" }; + return _useX86 ? new[] { "xunit.console.x86.exe" } : new[] { "xunit.console.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnit2Settings.cs b/src/Cake.Common/Tools/XUnit/XUnit2Settings.cs index 9ba85df927..07f469bbd2 100644 --- a/src/Cake.Common/Tools/XUnit/XUnit2Settings.cs +++ b/src/Cake.Common/Tools/XUnit/XUnit2Settings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.IO; @@ -30,6 +31,22 @@ public sealed class XUnit2Settings : ToolSettings /// The output directory. public DirectoryPath OutputDirectory { get; set; } + /// + /// Gets or sets a value indicating whether an NUnit style XML report should be generated. + /// + /// + /// true if an NUnit Style XML report should be generated; otherwise, false. + /// + public bool NUnitReport { get; set; } + + /// + /// Gets or sets a value indicating whether an JUnit style XML report should be generated. + /// + /// + /// true if an JUnit Style XML report should be generated; otherwise, false. + /// + public bool JUnitReport { get; set; } + /// /// Gets or sets a value indicating whether an XML report should be generated. /// @@ -51,6 +68,12 @@ public sealed class XUnit2Settings : ToolSettings /// public bool HtmlReport { get; set; } + /// + /// Gets or sets the name that should be used for the HTML and XML reports. + /// + /// The custom report name. + public string ReportName { get; set; } + /// /// Gets or sets a value indicating whether to not use app domains to run test code. /// @@ -66,7 +89,15 @@ public sealed class XUnit2Settings : ToolSettings /// /// The parallelism option. /// - public ParallelismOption Parallelism { get; set; } + public ParallelismOption? Parallelism { get; set; } + + /// + /// Gets or sets a value indicating whether to run tests in using x86 test runner. + /// + /// + /// true to run tests with the x86 test runner; otherwise, false. + /// + public bool UseX86 { get; set; } /// /// Gets or sets the maximum thread count for collection parallelization. @@ -74,9 +105,9 @@ public sealed class XUnit2Settings : ToolSettings /// /// null (default); /// 0: run with unbounded thread count; - /// >0: limit task thread pool size to value; + /// >0: limit task thread pool size to value;. /// - /// value < 0 + /// value < 0. public int? MaxThreads { get @@ -88,7 +119,7 @@ public int? MaxThreads { if (value.HasValue && value < 0) { - throw new ArgumentOutOfRangeException("value", value, "Value may not be negative."); + throw new ArgumentOutOfRangeException(nameof(value), value, "Value may not be negative."); } _maxThreads = value; } @@ -118,6 +149,42 @@ public int? MaxThreads /// public IDictionary> TraitsToExclude { get; private set; } + /// + /// Gets the namespaces to include. + /// + /// + /// Runs all methods in a given namespace (i.e., 'MyNamespace.MySubNamespace') + /// If more than one is specified, it acts as an OR operation. + /// + /// + /// The namespaces to include. + /// + public ICollection NamespacesToInclude { get; } + + /// + /// Gets the class names to include. + /// + /// + /// Runs all methods in a given test class (should be fully specified; i.e., 'MyNamespace.MyClass') + /// If more than one is specified, it acts as an OR operation. + /// + /// + /// The class names to include. + /// + public ICollection ClassesToInclude { get; } + + /// + /// Gets the test methods to include. + /// + /// + /// Runs the given test methods (should be fully specified; i.e., 'MyNamespace.MyClass.MyTestMethod') + /// If more than one is specified, it acts as an OR operation. + /// + /// + /// The namespaces to include. + /// + public ICollection MethodsToInclude { get; } + /// /// Initializes a new instance of the class. /// @@ -126,6 +193,9 @@ public XUnit2Settings() TraitsToInclude = new Dictionary>(StringComparer.OrdinalIgnoreCase); TraitsToExclude = new Dictionary>(StringComparer.OrdinalIgnoreCase); ShadowCopy = true; + NamespacesToInclude = new List(); + ClassesToInclude = new List(); + MethodsToInclude = new List(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnit2SettingsExtensions.cs b/src/Cake.Common/Tools/XUnit/XUnit2SettingsExtensions.cs index ac587ae974..69708fa817 100644 --- a/src/Cake.Common/Tools/XUnit/XUnit2SettingsExtensions.cs +++ b/src/Cake.Common/Tools/XUnit/XUnit2SettingsExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -21,22 +22,13 @@ public static class XUnit2SettingsExtensions /// The same instance so that multiple calls can be chained. public static XUnit2Settings IncludeTrait(this XUnit2Settings settings, string name, params string[] values) { - if (settings == null) - { - throw new ArgumentNullException("settings"); - } - if (name == null) - { - throw new ArgumentNullException("name"); - } - if (values == null) - { - throw new ArgumentNullException("values"); - } + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(values); if (values.Any(v => v == null)) { - throw new ArgumentException("values may not contain a null value.", "values"); + throw new ArgumentException("values may not contain a null value.", nameof(values)); } if (!settings.TraitsToInclude.ContainsKey(name)) @@ -61,35 +53,86 @@ public static XUnit2Settings IncludeTrait(this XUnit2Settings settings, string n /// The same instance so that multiple calls can be chained. public static XUnit2Settings ExcludeTrait(this XUnit2Settings settings, string name, params string[] values) { - if (settings == null) + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(values); + + if (values.Any(v => v == null)) { - throw new ArgumentNullException("settings"); + throw new ArgumentException("values may not contain a null value.", nameof(values)); } - if (name == null) + + if (!settings.TraitsToExclude.ContainsKey(name)) { - throw new ArgumentNullException("name"); + settings.TraitsToExclude.Add(name, new List()); } - if (values == null) + + foreach (var value in values.Where(v => v != null)) { - throw new ArgumentNullException("values"); + settings.TraitsToExclude[name].Add(value); } - if (values.Any(v => v == null)) + return settings; + } + + /// + /// Adds a namespace to the settings, to include in test execution. Namespace should be fully qualified; i.e., MyNameSpace.MySubNamespace. + /// + /// The settings. + /// The namespace to include. + /// The same instance so that multiple calls can be chained. + public static XUnit2Settings IncludeNamespace(this XUnit2Settings settings, string namespaceToInclude) + { + ArgumentNullException.ThrowIfNull(settings); + + if (string.IsNullOrEmpty(namespaceToInclude)) { - throw new ArgumentException("values may not contain a null value.", "values"); + throw new ArgumentException("Value cannot be null or empty.", nameof(namespaceToInclude)); } - if (!settings.TraitsToExclude.ContainsKey(name)) + settings.NamespacesToInclude.Add(namespaceToInclude); + + return settings; + } + + /// + /// Adds a class name to the settings, to include in test execution. Class name should be fully qualified; i.e., MyNameSpace.MyClassName. + /// + /// The settings. + /// The class name to include. + /// The same instance so that multiple calls can be chained. + public static XUnit2Settings IncludeClass(this XUnit2Settings settings, string classNameToInclude) + { + ArgumentNullException.ThrowIfNull(settings); + + if (string.IsNullOrEmpty(classNameToInclude)) { - settings.TraitsToExclude.Add(name, new List()); + throw new ArgumentException("Value cannot be null or empty.", nameof(classNameToInclude)); } - foreach (var value in values.Where(v => v != null)) + settings.ClassesToInclude.Add(classNameToInclude); + + return settings; + } + + /// + /// Adds a method name to the settings, to include in test execution. Method name should be fully qualified; i.e., MyNameSpace.MyClassName.MyMethod. + /// + /// The settings. + /// The method name to include. + /// The same instance so that multiple calls can be chained. + public static XUnit2Settings IncludeMethod(this XUnit2Settings settings, string methodNameToInclude) + { + ArgumentNullException.ThrowIfNull(settings); + + if (string.IsNullOrEmpty(methodNameToInclude)) { - settings.TraitsToExclude[name].Add(value); + throw new ArgumentException("Value cannot be null or empty.", nameof(methodNameToInclude)); } + settings.MethodsToInclude.Add(methodNameToInclude); + return settings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnitAliases.cs b/src/Cake.Common/Tools/XUnit/XUnitAliases.cs index a17351345e..6513089a3d 100644 --- a/src/Cake.Common/Tools/XUnit/XUnitAliases.cs +++ b/src/Cake.Common/Tools/XUnit/XUnitAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -14,9 +15,9 @@ namespace Cake.Common.Tools.XUnit /// Contains functionality related to running xunit tests. /// /// In order to use the commands for this alias, include the following in your build.cake file to download and - /// install from NuGet.org, or specify the ToolPath within the class: + /// install from nuget.org, or specify the ToolPath within the class: /// - /// #tool "nuget:?package=xunit.runners&version=1.9.2" + /// #tool "nuget:?package=xunit.runner.console&version=2.2.0" /// /// /// @@ -28,13 +29,15 @@ public static class XUnitAliases /// /// The context. /// The pattern. + /// + /// + /// XUnit("./src/**/bin/Release/*.Tests.dll"); + /// + /// [CakeMethodAlias] - public static void XUnit(this ICakeContext context, string pattern) + public static void XUnit(this ICakeContext context, GlobPattern pattern) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern); XUnit(context, assemblies, new XUnitSettings()); @@ -46,13 +49,19 @@ public static void XUnit(this ICakeContext context, string pattern) /// The context. /// The pattern. /// The settings. + /// + /// + /// XUnit("./src/**/bin/Release/*.Tests.dll", + /// new XUnitSettings { + /// HtmlReport = true, + /// OutputDirectory = "./build" + /// }); + /// + /// [CakeMethodAlias] - public static void XUnit(this ICakeContext context, string pattern, XUnitSettings settings) + public static void XUnit(this ICakeContext context, GlobPattern pattern, XUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var assemblies = context.Globber.GetFiles(pattern); XUnit(context, assemblies, settings); @@ -63,13 +72,20 @@ public static void XUnit(this ICakeContext context, string pattern, XUnitSetting /// /// The context. /// The assemblies. + /// + /// + /// XUnit(new []{ + /// "./src/Cake.Common.Tests/bin/Release/Cake.Common.Tests.dll", + /// "./src/Cake.Core.Tests/bin/Release/Cake.Core.Tests.dll", + /// "./src/Cake.NuGet.Tests/bin/Release/Cake.NuGet.Tests.dll", + /// "./src/Cake.Tests/bin/Release/Cake.Tests.dll" + /// }); + /// + /// [CakeMethodAlias] public static void XUnit(this ICakeContext context, IEnumerable assemblies) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); XUnit(context, paths, new XUnitSettings()); } @@ -79,6 +95,12 @@ public static void XUnit(this ICakeContext context, IEnumerable assembli /// /// The context. /// The assemblies. + /// + /// + /// var testAssemblies = GetFiles("./src/**/bin/Release/*.Tests.dll"); + /// XUnit(testAssemblies); + /// + /// [CakeMethodAlias] public static void XUnit(this ICakeContext context, IEnumerable assemblies) { @@ -91,13 +113,24 @@ public static void XUnit(this ICakeContext context, IEnumerable assemb /// The context. /// The assemblies. /// The settings. + /// + /// + /// XUnit(new []{ + /// "./src/Cake.Common.Tests/bin/Release/Cake.Common.Tests.dll", + /// "./src/Cake.Core.Tests/bin/Release/Cake.Core.Tests.dll", + /// "./src/Cake.NuGet.Tests/bin/Release/Cake.NuGet.Tests.dll", + /// "./src/Cake.Tests/bin/Release/Cake.Tests.dll" + /// }, + /// new XUnitSettings { + /// HtmlReport = true, + /// OutputDirectory = "./build" + /// }); + /// + /// [CakeMethodAlias] public static void XUnit(this ICakeContext context, IEnumerable assemblies, XUnitSettings settings) { - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(assemblies); var paths = assemblies.Select(p => new FilePath(p)); XUnit(context, paths, settings); } @@ -108,17 +141,21 @@ public static void XUnit(this ICakeContext context, IEnumerable assembli /// The context. /// The assemblies. /// The settings. + /// + /// + /// var testAssemblies = GetFiles("./src/**/bin/Release/*.Tests.dll"); + /// XUnit(testAssemblies, + /// new XUnitSettings { + /// HtmlReport = true, + /// OutputDirectory = "./build" + /// }); + /// + /// [CakeMethodAlias] public static void XUnit(this ICakeContext context, IEnumerable assemblies, XUnitSettings settings) { - if (context == null) - { - throw new ArgumentNullException("assemblies"); - } - if (assemblies == null) - { - throw new ArgumentNullException("assemblies"); - } + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assemblies); var runner = new XUnitRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); foreach (var assembly in assemblies) diff --git a/src/Cake.Common/Tools/XUnit/XUnitRunner.cs b/src/Cake.Common/Tools/XUnit/XUnitRunner.cs index a2118b9cad..14cbba9c07 100644 --- a/src/Cake.Common/Tools/XUnit/XUnitRunner.cs +++ b/src/Cake.Common/Tools/XUnit/XUnitRunner.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core; @@ -39,17 +40,11 @@ public XUnitRunner( /// The settings. public void Run(FilePath assemblyPath, XUnitSettings settings) { - if (assemblyPath == null) - { - throw new ArgumentNullException("assemblyPath"); - } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(assemblyPath); + ArgumentNullException.ThrowIfNull(settings); // Make sure we got output directory set when generating reports. - if (settings.OutputDirectory == null || string.IsNullOrWhiteSpace(settings.OutputDirectory.FullPath)) + if (string.IsNullOrWhiteSpace(settings.OutputDirectory?.FullPath)) { if (settings.HtmlReport) { @@ -127,4 +122,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "xunit.console.clr4.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnitRunnerUtilities.cs b/src/Cake.Common/Tools/XUnit/XUnitRunnerUtilities.cs index eace9085e3..e7b0bab649 100644 --- a/src/Cake.Common/Tools/XUnit/XUnitRunnerUtilities.cs +++ b/src/Cake.Common/Tools/XUnit/XUnitRunnerUtilities.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.IO; @@ -8,11 +9,18 @@ namespace Cake.Common.Tools.XUnit { internal static class XUnitRunnerUtilities { - internal static FilePath GetReportFileName(IReadOnlyList assemblyPaths) + internal static FilePath GetReportFileName(IReadOnlyList assemblyPaths, XUnit2Settings settings) { - return assemblyPaths.Count == 1 - ? assemblyPaths[0].GetFilename() - : new FilePath("TestResults"); + if (string.IsNullOrEmpty(settings.ReportName)) + { + return assemblyPaths.Count == 1 + ? assemblyPaths[0].GetFilename() + : new FilePath("TestResults"); + } + else + { + return settings.ReportName; + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/XUnit/XUnitSettings.cs b/src/Cake.Common/Tools/XUnit/XUnitSettings.cs index e12b38ffe2..356766ff81 100644 --- a/src/Cake.Common/Tools/XUnit/XUnitSettings.cs +++ b/src/Cake.Common/Tools/XUnit/XUnitSettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Tooling; @@ -58,4 +59,4 @@ public XUnitSettings() ShadowCopy = true; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlDtdProcessing.cs b/src/Cake.Common/Xml/XmlDtdProcessing.cs index ca5aee4bb2..a1568962b6 100644 --- a/src/Cake.Common/Xml/XmlDtdProcessing.cs +++ b/src/Cake.Common/Xml/XmlDtdProcessing.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Common.Xml { /// - /// Speficies how will an XmlReader handle DTDs in the XML document. + /// Specifies how will an XmlReader handle DTDs in the XML document. /// public enum XmlDtdProcessing { @@ -25,4 +26,4 @@ public enum XmlDtdProcessing /// Parse, } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlPeekAliases.cs b/src/Cake.Common/Xml/XmlPeekAliases.cs index d6ff8b6379..9772ce68d3 100644 --- a/src/Cake.Common/Xml/XmlPeekAliases.cs +++ b/src/Cake.Common/Xml/XmlPeekAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Xml; @@ -48,16 +49,21 @@ public static string XmlPeek(this ICakeContext context, FilePath filePath, strin /// XML document: /// - /// - /// < cake price="1.62" /> + /// + /// /// /// ]]> /// /// XmlPeek usage: /// - /// string version = XmlPeek("./pastery.xml", "/pastery:pastery/pastery:cake/@price", + /// string version = XmlPeek("./pastry.xml", "/pastry:pastry/pastry:cake/@price", + /// new XmlPeekSettings { + /// Namespaces = new Dictionary<string, string> {{ "pastry", "https://cakebuild.net/pastry" }} + /// }); + /// string unknown = XmlPeek("./pastry.xml", "/pastry:pastry/pastry:cake/@recipe", /// new XmlPeekSettings { - /// Namespaces = new Dictionary<string, string> {{ "pastery", "http://cakebuild.net/pastery" }} + /// Namespaces = new Dictionary<string, string> {{ "pastry", "https://cakebuild.net/pastry" }}, + /// SuppressWarning = true /// }); /// /// @@ -65,20 +71,11 @@ public static string XmlPeek(this ICakeContext context, FilePath filePath, strin [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public static string XmlPeek(this ICakeContext context, FilePath filePath, string xpath, XmlPeekSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(filePath); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); var file = context.FileSystem.GetFile(filePath); if (!file.Exists) @@ -86,11 +83,11 @@ public static string XmlPeek(this ICakeContext context, FilePath filePath, strin throw new FileNotFoundException("Source File not found.", file.Path.FullPath); } - using (var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None)) + using (var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) using (var xmlReader = XmlReader.Create(fileStream, GetXmlReaderSettings(settings))) { var xmlValue = XmlPeek(xmlReader, xpath, settings); - if (xmlValue == null) + if (xmlValue == null && !settings.SuppressWarning) { context.Log.Warning("Warning: Failed to find node matching the XPath '{0}'", xpath); } @@ -107,38 +104,33 @@ public static string XmlPeek(this ICakeContext context, FilePath filePath, strin /// Additional settings to tweak Xml Peek behavior. private static string XmlPeek(XmlReader source, string xpath, XmlPeekSettings settings) { - if (source == null) - { - throw new ArgumentNullException("source"); - } + ArgumentNullException.ThrowIfNull(source); if (string.IsNullOrWhiteSpace(xpath)) { - throw new ArgumentNullException("xpath"); + throw new ArgumentNullException(nameof(xpath)); } - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); var document = new XmlDocument(); document.PreserveWhitespace = settings.PreserveWhitespace; document.Load(source); - var namespaceManager = new XmlNamespaceManager(document.NameTable); + var navigator = document.CreateNavigator(); + var namespaceManager = new XmlNamespaceManager(navigator.NameTable); + foreach (var xmlNamespace in settings.Namespaces) { namespaceManager.AddNamespace(xmlNamespace.Key /* Prefix */, xmlNamespace.Value /* URI */); } - var node = document.SelectSingleNode(xpath, namespaceManager); - - return node != null ? node.Value : null; + var node = navigator.SelectSingleNode(xpath, namespaceManager); + return node?.Value; } /// - /// Gets a XmlReaderSettings from a XmlPeekSettings + /// Gets a XmlReaderSettings from a XmlPeekSettings. /// /// The xml reader settings. /// Additional settings to tweak Xml Peek behavior. @@ -149,4 +141,4 @@ private static XmlReaderSettings GetXmlReaderSettings(XmlPeekSettings settings) return xmlReaderSettings; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlPeekSettings.cs b/src/Cake.Common/Xml/XmlPeekSettings.cs index cb9443ad17..20144afdf8 100644 --- a/src/Cake.Common/Xml/XmlPeekSettings.cs +++ b/src/Cake.Common/Xml/XmlPeekSettings.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; namespace Cake.Common.Xml { /// - /// Contains settings for + /// Contains settings for . /// public sealed class XmlPeekSettings { @@ -20,6 +21,11 @@ public sealed class XmlPeekSettings /// public bool PreserveWhitespace { get; set; } + /// + /// Gets or sets a value indicating whether to suppress the xpath not found warning. + /// + public bool SuppressWarning { get; set; } + /// /// Gets or sets a value that determines the processing of DTDs. /// @@ -35,4 +41,4 @@ public XmlPeekSettings() DtdProcessing = XmlDtdProcessing.Prohibit; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlPokeAliases.cs b/src/Cake.Common/Xml/XmlPokeAliases.cs index ae25950f04..60786189cb 100644 --- a/src/Cake.Common/Xml/XmlPokeAliases.cs +++ b/src/Cake.Common/Xml/XmlPokeAliases.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Globalization; using System.IO; @@ -79,6 +80,7 @@ public static class XmlPokeAliases /// }); /// ]]> /// + /// /// /// /// Remove an app setting from a config file. @@ -109,9 +111,8 @@ public static class XmlPokeAliases /// /// /// Credit to NAnt for the original example. - /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html + /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html. /// - /// [CakeMethodAlias] public static void XmlPoke(this ICakeContext context, FilePath filePath, string xpath, string value) { @@ -181,6 +182,7 @@ public static void XmlPoke(this ICakeContext context, FilePath filePath, string /// }); /// ]]> /// + /// /// /// /// Remove an app setting from a config file. @@ -211,27 +213,17 @@ public static void XmlPoke(this ICakeContext context, FilePath filePath, string /// /// /// Credit to NAnt for the original example. - /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html + /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html. /// - /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] [CakeMethodAlias] public static void XmlPoke(this ICakeContext context, FilePath filePath, string xpath, string value, XmlPokeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); - if (filePath == null) - { - throw new ArgumentNullException("filePath"); - } + ArgumentNullException.ThrowIfNull(filePath); - if (settings == null) - { - throw new ArgumentNullException("settings"); - } + ArgumentNullException.ThrowIfNull(settings); var file = context.FileSystem.GetFile(filePath); if (!file.Exists) @@ -239,19 +231,16 @@ public static void XmlPoke(this ICakeContext context, FilePath filePath, string throw new FileNotFoundException("Source File not found.", file.Path.FullPath); } - using (var memoryStream = new MemoryStream()) + using (var resultStream = new MemoryStream()) { using (var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None)) - using (var xmlReader = XmlReader.Create(fileStream, GetXmlReaderSettings(settings))) - using (var xmlWriter = XmlWriter.Create(memoryStream)) { - XmlPoke(xmlReader, xmlWriter, xpath, value, settings); + XmlPoke(fileStream, resultStream, xpath, value, settings); } - using (var fileStream = file.Open(FileMode.Create, FileAccess.Write, FileShare.None)) { - memoryStream.Position = 0; - memoryStream.CopyTo(fileStream); + resultStream.Position = 0; + resultStream.CopyTo(fileStream); } } } @@ -317,6 +306,7 @@ public static void XmlPoke(this ICakeContext context, FilePath filePath, string /// }); /// ]]> /// + /// /// /// /// Remove an app setting from a config file. @@ -346,9 +336,8 @@ public static void XmlPoke(this ICakeContext context, FilePath filePath, string /// /// /// Credit to NAnt for the original example. - /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html + /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html. /// - /// [CakeMethodAlias] public static string XmlPokeString(this ICakeContext context, string sourceXml, string xpath, string value) { @@ -417,6 +406,7 @@ public static string XmlPokeString(this ICakeContext context, string sourceXml, /// }); /// ]]> /// + /// /// /// /// Remove an app setting from a config file. @@ -446,29 +436,25 @@ public static string XmlPokeString(this ICakeContext context, string sourceXml, /// /// /// Credit to NAnt for the original example. - /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html + /// http://nant.sourceforge.net/release/latest/help/tasks/xmlpoke.html. /// - /// [CakeMethodAlias] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] public static string XmlPokeString(this ICakeContext context, string sourceXml, string xpath, string value, XmlPokeSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); if (string.IsNullOrWhiteSpace(sourceXml)) { - throw new ArgumentNullException("sourceXml"); + throw new ArgumentNullException(nameof(sourceXml)); } using (var resultStream = new MemoryStream()) - using (var fileReader = new StringReader(sourceXml)) - using (var xmlReader = XmlReader.Create(fileReader, GetXmlReaderSettings(settings))) - using (var xmlWriter = XmlWriter.Create(resultStream)) { - XmlPoke(xmlReader, xmlWriter, xpath, value, settings); + using (var sourceStream = new MemoryStream(settings.Encoding.GetBytes(sourceXml))) + { + XmlPoke(sourceStream, resultStream, xpath, value, settings); + } resultStream.Position = 0; return settings.Encoding.GetString(resultStream.ToArray()); } @@ -482,77 +468,105 @@ public static string XmlPokeString(this ICakeContext context, string sourceXml, /// The xpath of the nodes to set. /// The value to set too. Leave blank to remove the selected nodes. /// Additional settings to tweak Xml Poke behavior. - private static void XmlPoke(XmlReader source, XmlWriter destination, string xpath, string value, XmlPokeSettings settings) + private static void XmlPoke(Stream source, Stream destination, string xpath, string value, XmlPokeSettings settings) { - if (source == null) - { - throw new ArgumentNullException("source"); - } + ArgumentNullException.ThrowIfNull(source); - if (destination == null) - { - throw new ArgumentNullException("destination"); - } + ArgumentNullException.ThrowIfNull(destination); if (string.IsNullOrWhiteSpace(xpath)) { - throw new ArgumentNullException("xpath"); - } - - if (settings == null) - { - throw new ArgumentNullException("settings"); + throw new ArgumentNullException(nameof(xpath)); } - var document = new XmlDocument(); - document.PreserveWhitespace = settings.PreserveWhitespace; - document.Load(source); + ArgumentNullException.ThrowIfNull(settings); - var namespaceManager = new XmlNamespaceManager(document.NameTable); - foreach (var xmlNamespace in settings.Namespaces) + using (var xmlReader = XmlReader.Create(source, GetXmlReaderSettings(settings))) { - namespaceManager.AddNamespace(xmlNamespace.Key /* Prefix */, xmlNamespace.Value /* URI */); - } + // Load the document from the reader + var document = new XmlDocument(); + document.PreserveWhitespace = settings.PreserveWhitespace; + document.Load(xmlReader); + // Add namespaces + var namespaceManager = new XmlNamespaceManager(document.NameTable); + foreach (var xmlNamespace in settings.Namespaces) + { + namespaceManager.AddNamespace(xmlNamespace.Key /* Prefix */, xmlNamespace.Value /* URI */); + } + // Select the desired nodes + var nodes = document.SelectNodes(xpath, namespaceManager); + if (nodes == null || nodes.Count == 0) + { + const string format = "Failed to find nodes matching the XPath '{0}'"; + var message = string.Format(CultureInfo.InvariantCulture, format, xpath); + throw new CakeException(message); + } - var nodes = document.SelectNodes(xpath, namespaceManager); - if (nodes == null || nodes.Count == 0) - { - const string format = "Failed to find nodes matching the XPath '{0}'"; - var message = string.Format(CultureInfo.InvariantCulture, format, xpath); - throw new CakeException(message); - } + // Adjust the nodes / values + if (value == null) + { + // Remove the nodes + foreach (XmlNode node in nodes) + { + // ReSharper disable once PossibleNullReferenceException + // Pretty sure we should never be working with orphaned nodes. + node.ParentNode.RemoveChild(node); + } + } + else + { + // Set the value + foreach (XmlNode node in nodes) + { + node.InnerXml = value; + } + } - if (value == null) - { - foreach (XmlNode node in nodes) + // Get the writer settings + var writerSettings = GetXmlWriterSettings(settings); + // Adjust the writer settings according to the declaration + var hasDeclaration = document.FirstChild.NodeType == XmlNodeType.XmlDeclaration; + if (!hasDeclaration) { - // ReSharper disable once PossibleNullReferenceException - // Pretty sure we should never be working with orphaned nodes. - node.ParentNode.RemoveChild(node); + writerSettings.OmitXmlDeclaration = true; } - } - else - { - foreach (XmlNode node in nodes) + // Write the document + using (var xmlWriter = XmlWriter.Create(destination, writerSettings)) { - node.InnerXml = value; + document.Save(xmlWriter); } } - - document.Save(destination); } /// - /// Gets a XmlReaderSettings from a XmlPokeSettings + /// Gets a XmlReaderSettings from a XmlPokeSettings. /// /// The xml reader settings. /// Additional settings to tweak Xml Poke behavior. private static XmlReaderSettings GetXmlReaderSettings(XmlPokeSettings settings) { var xmlReaderSettings = new XmlReaderSettings(); - xmlReaderSettings.DtdProcessing = (DtdProcessing)settings.DtdProcessing; + xmlReaderSettings.DtdProcessing = (DtdProcessing)settings.DtdProcessing; return xmlReaderSettings; } + + /// + /// Gets a XmlWriterSettings from a XmlPokeSettings. + /// + /// Additional settings to tweak Xml Poke behavior. + /// The xml writer settings. + private static XmlWriterSettings GetXmlWriterSettings(XmlPokeSettings settings) + { + var writerSettings = new XmlWriterSettings + { + Encoding = settings.Encoding, + NewLineOnAttributes = settings.NewLineOnAttributes, + IndentChars = settings.IndentChars, + Indent = settings.Indent + }; + // Currently only returns the default settings + return writerSettings; + } } } diff --git a/src/Cake.Common/Xml/XmlPokeSettings.cs b/src/Cake.Common/Xml/XmlPokeSettings.cs index b5edadbb90..896244048d 100644 --- a/src/Cake.Common/Xml/XmlPokeSettings.cs +++ b/src/Cake.Common/Xml/XmlPokeSettings.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Text; namespace Cake.Common.Xml { /// - /// Contains settings for + /// Contains settings for . /// public sealed class XmlPokeSettings { @@ -31,6 +32,24 @@ public sealed class XmlPokeSettings /// public XmlDtdProcessing DtdProcessing { get; set; } + /// + /// Gets or sets a value indicating whether to write attributes on a new line. + /// This setting is used when is set to true. + /// + public bool NewLineOnAttributes { get; set; } + + /// + /// Gets or sets the character string to use when indenting. + /// This setting is used when is set to true. + /// The default is two spaces. + /// + public string IndentChars { get; set; } = " "; + + /// + /// Gets or sets a value indicating whether to indent elements. + /// + public bool Indent { get; set; } + /// /// Initializes a new instance of the class. /// @@ -42,4 +61,4 @@ public XmlPokeSettings() DtdProcessing = XmlDtdProcessing.Prohibit; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlTransformation.cs b/src/Cake.Common/Xml/XmlTransformation.cs index 85bb193351..be2b69dc5e 100644 --- a/src/Cake.Common/Xml/XmlTransformation.cs +++ b/src/Cake.Common/Xml/XmlTransformation.cs @@ -1,23 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Text; using System.Xml; using System.Xml.Xsl; +using Cake.Common.Polyfill; using Cake.Core; using Cake.Core.IO; namespace Cake.Common.Xml { /// - /// Provides functionality to perform XML transformation + /// Provides functionality to perform XML transformation. /// public static class XmlTransformation { /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// XML style sheet. /// XML data. @@ -34,27 +36,27 @@ public static string Transform(string xsl, string xml) } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// XML style sheet. /// XML data. - /// Settings for result file xml transformation. + /// Settings for result file XML transformation. /// Transformed XML string. public static string Transform(string xsl, string xml, XmlTransformationSettings settings) { if (string.IsNullOrWhiteSpace(xsl)) { - throw new ArgumentNullException("xsl", "Null or empty XML style sheet supplied."); + throw new ArgumentNullException(nameof(xsl), "Null or empty XML style sheet supplied."); } if (string.IsNullOrWhiteSpace(xml)) { - throw new ArgumentNullException("xml", "Null or empty XML data supplied."); + throw new ArgumentNullException(nameof(xml), "Null or empty XML data supplied."); } if (settings == null) { - throw new ArgumentNullException("settings", "Null settings supplied."); + throw new ArgumentNullException(nameof(settings), "Null settings supplied."); } using (TextReader @@ -63,7 +65,7 @@ public static string Transform(string xsl, string xml, XmlTransformationSettings { using (var result = new MemoryStream()) { - Transform(xslReader, xmlReader, result, settings.XmlWriterSettings); + Transform(xslReader, settings.XsltArgumentList, xmlReader, result, settings.XmlWriterSettings); result.Position = 0; return settings.Encoding.GetString(result.ToArray()); } @@ -71,11 +73,11 @@ public static string Transform(string xsl, string xml, XmlTransformationSettings } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The file system. - /// Path to xml style sheet. - /// Path xml data. + /// Path to XML style sheet. + /// Path XML data. /// Transformation result path. public static void Transform(IFileSystem fileSystem, FilePath xslPath, FilePath xmlPath, FilePath resultPath) { @@ -84,38 +86,38 @@ public static void Transform(IFileSystem fileSystem, FilePath xslPath, FilePath } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The file system. - /// Path to xml style sheet. - /// Path xml data. + /// Path to XML style sheet. + /// Path XML data. /// Transformation result path. - /// Settings for result file xml transformation. + /// Settings for result file XML transformation. public static void Transform(IFileSystem fileSystem, FilePath xslPath, FilePath xmlPath, FilePath resultPath, XmlTransformationSettings settings) { if (fileSystem == null) { - throw new ArgumentNullException("fileSystem", "Null filesystem supplied."); + throw new ArgumentNullException(nameof(fileSystem), "Null filesystem supplied."); } if (xslPath == null) { - throw new ArgumentNullException("xslPath", "Null XML style sheet path supplied."); + throw new ArgumentNullException(nameof(xslPath), "Null XML style sheet path supplied."); } if (xmlPath == null) { - throw new ArgumentNullException("xmlPath", "Null XML data path supplied."); + throw new ArgumentNullException(nameof(xmlPath), "Null XML data path supplied."); } if (resultPath == null) { - throw new ArgumentNullException("resultPath", "Null result path supplied."); + throw new ArgumentNullException(nameof(resultPath), "Null result path supplied."); } if (settings == null) { - throw new ArgumentNullException("settings", "Null settings supplied."); + throw new ArgumentNullException(nameof(settings), "Null settings supplied."); } IFile @@ -125,12 +127,12 @@ public static void Transform(IFileSystem fileSystem, FilePath xslPath, FilePath if (!xslFile.Exists) { - throw new FileNotFoundException("Xsl File not found.", xslFile.Path.FullPath); + throw new FileNotFoundException("XSL file not found.", xslFile.Path.FullPath); } if (!xmlFile.Exists) { - throw new FileNotFoundException("XML File not found.", xmlFile.Path.FullPath); + throw new FileNotFoundException("XML file not found.", xmlFile.Path.FullPath); } if (!settings.Overwrite && resultFile.Exists) @@ -148,72 +150,71 @@ public static void Transform(IFileSystem fileSystem, FilePath xslPath, FilePath xmlReader = XmlReader.Create(xmlStream); var resultWriter = XmlWriter.Create(resultStream, settings.XmlWriterSettings); - - Transform(xslReader, xmlReader, resultWriter); + Transform(xslReader, settings.XsltArgumentList, xmlReader, resultWriter); } } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// XML style sheet. + /// XSLT argument list. /// XML data. /// Transformation result. - /// Optional settings for result file xml writer - private static void Transform(TextReader xsl, TextReader xml, Stream result, XmlWriterSettings settings = null) + /// Optional settings for result file XML writer. + private static void Transform(TextReader xsl, XsltArgumentList arguments, TextReader xml, Stream result, XmlWriterSettings settings = null) { if (xsl == null) { - throw new ArgumentNullException("xsl", "Null XML style sheet supplied."); + throw new ArgumentNullException(nameof(xsl), "Null XML style sheet supplied."); } if (xml == null) { - throw new ArgumentNullException("xml", "Null XML data supplied."); + throw new ArgumentNullException(nameof(xml), "Null XML data supplied."); } if (result == null) { - throw new ArgumentNullException("result", "Null result supplied."); + throw new ArgumentNullException(nameof(result), "Null result supplied."); } if (settings == null) { - throw new ArgumentNullException("settings", "Null settings supplied."); + throw new ArgumentNullException(nameof(settings), "Null settings supplied."); } var xslXmlReader = XmlReader.Create(xsl); var xmlXmlReader = XmlReader.Create(xml); var resultXmlTextWriter = XmlWriter.Create(result, settings); - Transform(xslXmlReader, xmlXmlReader, resultXmlTextWriter); + Transform(xslXmlReader, arguments, xmlXmlReader, resultXmlTextWriter); } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// XML style sheet. + /// XSLT argument list. /// XML data. /// Transformation result. - private static void Transform(XmlReader xsl, XmlReader xml, XmlWriter result) + private static void Transform(XmlReader xsl, XsltArgumentList arguments, XmlReader xml, XmlWriter result) { if (xsl == null) { - throw new ArgumentNullException("xsl", "Null XML style sheet supplied."); + throw new ArgumentNullException(nameof(xsl), "Null XML style sheet supplied."); } if (xml == null) { - throw new ArgumentNullException("xml", "Null XML data supplied."); + throw new ArgumentNullException(nameof(xml), "Null XML data supplied."); } if (result == null) { - throw new ArgumentNullException("result", "Null result supplied."); + throw new ArgumentNullException(nameof(result), "Null result supplied."); } - var xslTransform = new XslCompiledTransform(); - xslTransform.Load(xsl); - xslTransform.Transform(xml, result); + XmlTransformationHelper.Transform(xsl, arguments, xml, result); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlTransformationAlias.cs b/src/Cake.Common/Xml/XmlTransformationAlias.cs index b09ee0fc9a..49a8c4f2c6 100644 --- a/src/Cake.Common/Xml/XmlTransformationAlias.cs +++ b/src/Cake.Common/Xml/XmlTransformationAlias.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core; using Cake.Core.Annotations; @@ -15,7 +16,7 @@ namespace Cake.Common.Xml public static class XmlTransformationAlias { /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The context. /// XML style sheet. @@ -23,7 +24,7 @@ public static class XmlTransformationAlias /// Transformed XML string. /// /// - /// This example code will convert xml to a new xml strucure using XmlTransform alias. + /// This example code will convert xml to a new xml structure using XmlTransform alias. /// /// @@ -50,21 +51,18 @@ public static class XmlTransformationAlias [CakeMethodAlias] public static string XmlTransform(this ICakeContext context, string xsl, string xml) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); return XmlTransformation.Transform(xsl, xml); } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The context. /// XML style sheet. /// XML data. - /// Optional settings for result file xml writer + /// Optional settings for result file xml writer. /// Transformed XML string. /// /// @@ -88,16 +86,13 @@ public static string XmlTransform(this ICakeContext context, string xsl, string [CakeMethodAlias] public static string XmlTransform(this ICakeContext context, string xsl, string xml, XmlTransformationSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); return XmlTransformation.Transform(xsl, xml, settings); } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The context. /// Path to xml style sheet. @@ -153,23 +148,20 @@ public static string XmlTransform(this ICakeContext context, string xsl, string [CakeMethodAlias] public static void XmlTransform(this ICakeContext context, FilePath xslPath, FilePath xmlPath, FilePath resultPath) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); var settings = new XmlTransformationSettings(); XmlTransformation.Transform(context.FileSystem, xslPath, xmlPath, resultPath, settings); } /// - /// Performs XML XSL transformation + /// Performs XML XSL transformation. /// /// The context. /// Path to xml style sheet. /// Path xml data. /// Transformation result path. - /// Optional settings for result file xml writer + /// Optional settings for result file xml writer. /// /// /// This example code will convert the Cake nuspec into html using the XmlTransform alias, @@ -222,12 +214,9 @@ public static void XmlTransform(this ICakeContext context, FilePath xslPath, Fil [CakeMethodAlias] public static void XmlTransform(this ICakeContext context, FilePath xslPath, FilePath xmlPath, FilePath resultPath, XmlTransformationSettings settings) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); XmlTransformation.Transform(context.FileSystem, xslPath, xmlPath, resultPath, settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/Xml/XmlTransformationSettings.cs b/src/Cake.Common/Xml/XmlTransformationSettings.cs index 547572324c..aa5542d1c7 100644 --- a/src/Cake.Common/Xml/XmlTransformationSettings.cs +++ b/src/Cake.Common/Xml/XmlTransformationSettings.cs @@ -1,20 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Text; using System.Xml; +using System.Xml.Xsl; +using Cake.Common.Polyfill; namespace Cake.Common.Xml { /// - /// Contains settings for + /// Contains settings for . /// public sealed class XmlTransformationSettings { - internal XmlWriterSettings XmlWriterSettings { get; private set; } + internal XmlWriterSettings XmlWriterSettings { get; } /// - /// Gets or sets a value indicating whether overwriting existing file is permitted + /// Gets or sets a value indicating whether overwriting existing file is permitted. /// public bool Overwrite { get; set; } @@ -36,16 +39,14 @@ public ConformanceLevel ConformanceLevel set { XmlWriterSettings.ConformanceLevel = value; } } - #if !__MonoCS__ /// /// Gets or sets a value indicating whether the XmlWriter does not escape URI attributes. /// public bool DoNotEscapeUriAttributes { - get { return XmlWriterSettings.DoNotEscapeUriAttributes; } - set { XmlWriterSettings.DoNotEscapeUriAttributes = value; } + get { return XmlWriterSettingsHelper.GetDoNotEscapeUriAttributes(XmlWriterSettings); } + set { XmlWriterSettingsHelper.SetDoNotEscapeUriAttributes(XmlWriterSettings, value); } } - #endif /// /// Gets or sets the type of text encoding to use. @@ -120,7 +121,7 @@ public bool OmitXmlDeclaration } /// - /// Gets or sets a value indicating whether the XmlWriter will add closing tags to all unclosed element tags when the Close method is called + /// Gets or sets a value indicating whether the XmlWriter will add closing tags to all unclosed element tags when the Close method is called. /// public bool WriteEndDocumentOnClose { @@ -128,6 +129,11 @@ public bool WriteEndDocumentOnClose set { XmlWriterSettings.WriteEndDocumentOnClose = value; } } + /// + /// Gets or sets an argument list for XSL transformation. + /// + public XsltArgumentList XsltArgumentList { get; set; } + /// /// Initializes a new instance of the class. /// @@ -137,4 +143,4 @@ public XmlTransformationSettings() Overwrite = true; } } -} +} \ No newline at end of file diff --git a/src/Cake.Common/packages.config b/src/Cake.Common/packages.config deleted file mode 100644 index 818b649ac0..0000000000 --- a/src/Cake.Common/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Cake.Core.Tests/Cake.Core.Tests.csproj b/src/Cake.Core.Tests/Cake.Core.Tests.csproj index 0a49891583..4d5c7bd061 100644 --- a/src/Cake.Core.Tests/Cake.Core.Tests.csproj +++ b/src/Cake.Core.Tests/Cake.Core.Tests.csproj @@ -1,203 +1,41 @@ - - - + - Debug - AnyCPU - {75F86C7D-AF8D-4948-91A4-8D54A6F8655D} - Library - Properties - Cake.Core.Tests Cake.Core.Tests - v4.5 - 512 - 31e0750e - 5 + true + true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - ..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll - - - - - - - - - - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.extensibility.core.2.1.0\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - - - - - - - - - - - - - - - - - - - {8074B833-11B8-459F-BB98-BFBA2BC5C698} - Cake.Core - - - {5AF751D1-BB54-4268-9E42-3A898B034B06} - Cake.Testing.Xunit - - - {5572610D-D857-450A-9CC9-F3E08B0E1449} - Cake.Testing - - + + + - + + + + - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + - + - + - - - - \ No newline at end of file diff --git a/src/Cake.Core.Tests/Data/MethodAliasGeneratorData.cs b/src/Cake.Core.Tests/Data/MethodAliasGeneratorData.cs index 18a58c1028..c81008e33e 100644 --- a/src/Cake.Core.Tests/Data/MethodAliasGeneratorData.cs +++ b/src/Cake.Core.Tests/Data/MethodAliasGeneratorData.cs @@ -1,14 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; using Cake.Core.Annotations; namespace Cake.Core.Tests.Data { internal static class MethodAliasGeneratorData { + internal enum TestNestedEnum + { + Unknown, + One, + Two + } + public static void NotAnExtensionMethod() { throw new NotImplementedException(); @@ -37,16 +48,46 @@ public static void NonGeneric_ExtensionMethodWithParameter(this ICakeContext con throw new NotImplementedException(); } + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOutputParameter(this ICakeContext context, out IDisposable arg) + { + throw new NotImplementedException(); + } + [CakeMethodAlias] public static void NonGeneric_ExtensionMethodWithGenericParameter(this ICakeContext context, Action value) { throw new NotImplementedException(); } + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithGenericExpressionParameter(this ICakeContext context, Expression> expression) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithGenericExpressionArrayParameter(this ICakeContext context, Expression>[] expression) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithGenericExpressionParamsArrayParameter(this ICakeContext context, params Expression>[] expression) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithArrayParameter(this ICakeContext context, string[] values) + { + throw new NotImplementedException(); + } + [CakeMethodAlias] public static void Generic_ExtensionMethod(this ICakeContext context) { - Debug.Assert(typeof(TTest) != null); // Resharper + Debug.Assert(typeof(TTest) != null); // ReSharper throw new NotImplementedException(); } @@ -62,6 +103,14 @@ public static TTest Generic_ExtensionMethodWithGenericReturnValue(this IC throw new NotImplementedException(); } + [CakeMethodAlias] + public static TOut Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints(this ICakeContext context, TIn arg) + where TIn : class, new() + where TOut : System.Collections.ArrayList, IDisposable + { + throw new NotImplementedException(); + } + [CakeMethodAlias] public static void NonGeneric_ExtensionMethodWithParameterArray(this ICakeContext context, params int[] values) { @@ -95,5 +144,123 @@ public static void Obsolete_ExplicitError_WithMessage(this ICakeContext context) { throw new NotImplementedException(); } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalObjectParameter(this ICakeContext context, int value, object option = null) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalBooleanParameter(this ICakeContext context, int value, bool flag = false) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalStringParameter(this ICakeContext context, int value, string s = @"there is a ""string"" here and a \t tab") + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalEnumParameter(this ICakeContext context, int value, AttributeTargets targets = AttributeTargets.Class) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalCharParameter(this ICakeContext context, string s, char c = 's') + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalDecimalParameter(this ICakeContext context, string s, decimal value = 12.12m) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableTParameter(this ICakeContext context, string s, int? value = 0) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableBooleanParameter(this ICakeContext context, string s, bool? value = false) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableCharParameter(this ICakeContext context, string s, char? value = 's') + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableEnumParameter(this ICakeContext context, string s, AttributeTargets? targets = AttributeTargets.Class) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableDecimalParameter(this ICakeContext context, string s, decimal? value = 123.12m) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableLongParameter(this ICakeContext context, string s, long? value = 1234567890L) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithOptionalNullableDoubleParameter(this ICakeContext context, string s, double? value = 1234567890.12) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithReservedKeywordParameter(this ICakeContext context, int @new) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithGenericCollectionOfNestedType(this ICakeContext context, ICollection items) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static void NonGeneric_ExtensionMethodWithParameterAttributes(this ICakeContext context, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] + public static dynamic NonGeneric_ExtensionMethodWithDynamicReturnValue(this ICakeContext context) + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] +#nullable enable + public static void NonGeneric_ExtensionMethodWithNullableParameter(this ICakeContext context, string? parameter) +#nullable disable + { + throw new NotImplementedException(); + } + + [CakeMethodAlias] +#nullable enable + public static string? NonGeneric_ExtensionMethodWithNullableReturnValue(this ICakeContext context) +#nullable disable + { + throw new NotImplementedException(); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Data/PropertyAliasGeneratorData.cs b/src/Cake.Core.Tests/Data/PropertyAliasGeneratorData.cs index 0447c6786f..bcfc01a80f 100644 --- a/src/Cake.Core.Tests/Data/PropertyAliasGeneratorData.cs +++ b/src/Cake.Core.Tests/Data/PropertyAliasGeneratorData.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.Annotations; @@ -43,6 +44,12 @@ public static int NonCached_Value_Type(this ICakeContext context) return 42; } + [CakePropertyAlias] + public static dynamic NonCached_Dynamic_Type(this ICakeContext context) + { + return new { }; + } + [CakePropertyAlias(Cache = true)] public static string Cached_Reference_Type(this ICakeContext context) { @@ -55,6 +62,12 @@ public static bool Cached_Value_Type(this ICakeContext context) return true; } + [CakePropertyAlias(Cache = true)] + public static dynamic Cached_Dynamic_Type(this ICakeContext context) + { + return new { }; + } + [CakePropertyAlias] [Obsolete] public static int NonCached_Obsolete_ImplicitWarning_NoMessage(this ICakeContext context) @@ -110,5 +123,13 @@ public static int Cached_Obsolete_ExplicitError_WithMessage(this ICakeContext co { throw new NotImplementedException(); } + + [CakePropertyAlias(Cache = true)] +#nullable enable + public static string? Cached_Nullable_Type(this ICakeContext context) +#nullable disable + { + throw new NotImplementedException(); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Add_All_Tasks_As_Nodes_In_Graph.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Add_All_Tasks_As_Nodes_In_Graph.verified.txt new file mode 100644 index 0000000000..8b5ff6171f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Add_All_Tasks_As_Nodes_In_Graph.verified.txt @@ -0,0 +1,4 @@ +[ + A, + B +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Dependencies.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Dependencies.verified.txt new file mode 100644 index 0000000000..efd6c98209 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Dependencies.verified.txt @@ -0,0 +1,4 @@ +{ + Start: A, + End: B +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Reversed_Dependencies.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Reversed_Dependencies.verified.txt new file mode 100644 index 0000000000..8ed953746f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphBuilderTests.TheBuildMethod.Should_Create_Edges_Between_Reversed_Dependencies.verified.txt @@ -0,0 +1,4 @@ +{ + Start: B, + End: A +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target.verified.txt new file mode 100644 index 0000000000..87212030f4 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target.verified.txt @@ -0,0 +1,6 @@ +[ + A, + B, + D, + E +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order.verified.txt new file mode 100644 index 0000000000..7ed499c411 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order.verified.txt @@ -0,0 +1,6 @@ +[ + A, + B, + C, + D +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root.verified.txt new file mode 100644 index 0000000000..438aaa55f9 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMethod.Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root.verified.txt @@ -0,0 +1,6 @@ +[ + A, + B, + C, + d +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMultipleMethod.Should_Return_Union_Of_All_Targets_With_Shared_Dependencies_Once.verified.txt b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMultipleMethod.Should_Return_Union_Of_All_Targets_With_Shared_Dependencies_Once.verified.txt new file mode 100644 index 0000000000..e12a18d240 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeGraphTests.TheTraverseMultipleMethod.Should_Return_Union_Of_All_Targets_With_Shared_Dependencies_Once.verified.txt @@ -0,0 +1,5 @@ +[ + A, + B, + C +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..467f99383b --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,6 @@ +[ + , + ----------------------------------------, + Setup, + ---------------------------------------- +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..467f99383b --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Normal.verified.txt @@ -0,0 +1,6 @@ +[ + , + ----------------------------------------, + Setup, + ---------------------------------------- +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..467f99383b --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteLifeCycleStep_verbosity=Verbose.verified.txt @@ -0,0 +1,6 @@ +[ + , + ----------------------------------------, + Setup, + ---------------------------------------- +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..53ae21e86d --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,6 @@ +[ + , + ----------------------------------------, + Skipped Step, + ---------------------------------------- +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Normal.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..53ae21e86d --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteSkippedStep_verbosity=Verbose.verified.txt @@ -0,0 +1,6 @@ +[ + , + ----------------------------------------, + Skipped Step, + ---------------------------------------- +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..9da407a796 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Diagnostic.verified.txt @@ -0,0 +1,6 @@ +[ + , + ========================================, + Test Step, + ======================================== +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Minimal.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Normal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..9da407a796 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Normal.verified.txt @@ -0,0 +1,6 @@ +[ + , + ========================================, + Test Step, + ======================================== +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..ad47dbb93f --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Quiet.verified.txt @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..9da407a796 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.WriteStep_verbosity=Verbose.verified.txt @@ -0,0 +1,6 @@ +[ + , + ========================================, + Test Step, + ======================================== +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Diagnostic.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Diagnostic.verified.txt new file mode 100644 index 0000000000..319e09611e --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Diagnostic.verified.txt @@ -0,0 +1,19 @@ +[ + , + Task Duration Status , + ----------------------------------------------------------------------, + SetupExecuted 00:00:00.2220000 Succeeded , + SetupSkipped - Skipped , + SetupFailed 00:00:00.8880000 Failed , + SetupDelegated 00:00:00.4440000 Succeeded , + TaskExecuted 00:00:00.1110000 Succeeded , + TaskSkipped - Skipped , + TaskFailed 00:00:00.4440000 Failed , + TaskDelegated 00:00:00.2220000 Succeeded , + TeardownExecuted 00:00:00.3330000 Succeeded , + TeardownSkipped - Skipped , + TeardownFailed 00:00:01.3320000 Failed , + TeardownDelegated 00:00:00.6660000 Succeeded , + ----------------------------------------------------------------------, + Total: 00:00:06.6600000 +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Minimal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Minimal.verified.txt new file mode 100644 index 0000000000..d11ea647cc --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Minimal.verified.txt @@ -0,0 +1,16 @@ +[ + , + Task Duration Status , + ----------------------------------------------------------------------, + SetupExecuted 00:00:00.2220000 Succeeded , + SetupSkipped - Skipped , + SetupFailed 00:00:00.8880000 Failed , + TaskExecuted 00:00:00.1110000 Succeeded , + TaskSkipped - Skipped , + TaskFailed 00:00:00.4440000 Failed , + TeardownExecuted 00:00:00.3330000 Succeeded , + TeardownSkipped - Skipped , + TeardownFailed 00:00:01.3320000 Failed , + ----------------------------------------------------------------------, + Total: 00:00:06.6600000 +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Normal.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Normal.verified.txt new file mode 100644 index 0000000000..d11ea647cc --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Normal.verified.txt @@ -0,0 +1,16 @@ +[ + , + Task Duration Status , + ----------------------------------------------------------------------, + SetupExecuted 00:00:00.2220000 Succeeded , + SetupSkipped - Skipped , + SetupFailed 00:00:00.8880000 Failed , + TaskExecuted 00:00:00.1110000 Succeeded , + TaskSkipped - Skipped , + TaskFailed 00:00:00.4440000 Failed , + TeardownExecuted 00:00:00.3330000 Succeeded , + TeardownSkipped - Skipped , + TeardownFailed 00:00:01.3320000 Failed , + ----------------------------------------------------------------------, + Total: 00:00:06.6600000 +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Quiet.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Quiet.verified.txt new file mode 100644 index 0000000000..d11ea647cc --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Quiet.verified.txt @@ -0,0 +1,16 @@ +[ + , + Task Duration Status , + ----------------------------------------------------------------------, + SetupExecuted 00:00:00.2220000 Succeeded , + SetupSkipped - Skipped , + SetupFailed 00:00:00.8880000 Failed , + TaskExecuted 00:00:00.1110000 Succeeded , + TaskSkipped - Skipped , + TaskFailed 00:00:00.4440000 Failed , + TeardownExecuted 00:00:00.3330000 Succeeded , + TeardownSkipped - Skipped , + TeardownFailed 00:00:01.3320000 Failed , + ----------------------------------------------------------------------, + Total: 00:00:06.6600000 +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Verbose.verified.txt b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Verbose.verified.txt new file mode 100644 index 0000000000..319e09611e --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/CakeReportPrinterTests.Write_verbosity=Verbose.verified.txt @@ -0,0 +1,19 @@ +[ + , + Task Duration Status , + ----------------------------------------------------------------------, + SetupExecuted 00:00:00.2220000 Succeeded , + SetupSkipped - Skipped , + SetupFailed 00:00:00.8880000 Failed , + SetupDelegated 00:00:00.4440000 Succeeded , + TaskExecuted 00:00:00.1110000 Succeeded , + TaskSkipped - Skipped , + TaskFailed 00:00:00.4440000 Failed , + TaskDelegated 00:00:00.2220000 Succeeded , + TeardownExecuted 00:00:00.3330000 Succeeded , + TeardownSkipped - Skipped , + TeardownFailed 00:00:01.3320000 Failed , + TeardownDelegated 00:00:00.6660000 Succeeded , + ----------------------------------------------------------------------, + Total: 00:00:06.6600000 +] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethod.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethod.verified.cake new file mode 100644 index 0000000000..32e48d1be2 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethod.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Generic_ExtensionMethod() + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethod(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValue.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValue.verified.cake new file mode 100644 index 0000000000..6ed38f7ff1 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValue.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public TTest Generic_ExtensionMethodWithGenericReturnValue(TTest value) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethodWithGenericReturnValue(Context, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints.verified.cake new file mode 100644 index 0000000000..460f1bef3c --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints.verified.cake @@ -0,0 +1,5 @@ +[System.Diagnostics.DebuggerStepThrough] +public TOut Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints(TIn arg) +where TIn : class, new() +where TOut : System.Collections.ArrayList, System.IDisposable + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints(Context, arg); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithParameter.verified.cake new file mode 100644 index 0000000000..243e4c734a --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Generic_Methods_name=Generic_ExtensionMethodWithParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Generic_ExtensionMethodWithParameter(TTest value) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethodWithParameter(Context, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithDynamicReturnValue.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithDynamicReturnValue.verified.cake new file mode 100644 index 0000000000..176b3d095a --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithDynamicReturnValue.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public dynamic NonGeneric_ExtensionMethodWithDynamicReturnValue() + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithDynamicReturnValue(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericCollectionOfNestedType.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericCollectionOfNestedType.verified.cake new file mode 100644 index 0000000000..3df11a5ceb --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericCollectionOfNestedType.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithGenericCollectionOfNestedType(System.Collections.Generic.ICollection items) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericCollectionOfNestedType(Context, items); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionArrayParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionArrayParameter.verified.cake new file mode 100644 index 0000000000..0473c84c68 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionArrayParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithGenericExpressionArrayParameter(System.Linq.Expressions.Expression>[] expression) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericExpressionArrayParameter(Context, expression); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParameter.verified.cake new file mode 100644 index 0000000000..6fab8485d2 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithGenericExpressionParameter(System.Linq.Expressions.Expression> expression) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericExpressionParameter(Context, expression); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParamsArrayParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParamsArrayParameter.verified.cake new file mode 100644 index 0000000000..e0fc61c506 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericExpressionParamsArrayParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithGenericExpressionParamsArrayParameter(params System.Linq.Expressions.Expression>[] expression) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericExpressionParamsArrayParameter(Context, expression); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericParameter.verified.cake new file mode 100644 index 0000000000..72fbd4a722 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithGenericParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithGenericParameter(System.Action value) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericParameter(Context, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNoParameters.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNoParameters.verified.cake new file mode 100644 index 0000000000..ca74b8c2c7 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNoParameters.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithNoParameters() + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithNoParameters(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableParameter.verified.cake new file mode 100644 index 0000000000..756f9ffafc --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithNullableParameter(System.String? parameter) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithNullableParameter(Context, parameter); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableReturnValue.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableReturnValue.verified.cake new file mode 100644 index 0000000000..a2ed6d19ac --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithNullableReturnValue.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public System.String? NonGeneric_ExtensionMethodWithNullableReturnValue() + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithNullableReturnValue(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalBooleanParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalBooleanParameter.verified.cake new file mode 100644 index 0000000000..a582e31fdb --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalBooleanParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalBooleanParameter(System.Int32 value, System.Boolean flag = false) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalBooleanParameter(Context, value, flag); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalCharParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalCharParameter.verified.cake new file mode 100644 index 0000000000..a89ab1efed --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalCharParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalCharParameter(System.String s, System.Char c = 's') + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalCharParameter(Context, s, c); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalDecimalParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalDecimalParameter.verified.cake new file mode 100644 index 0000000000..bf6d48a86e --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalDecimalParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalDecimalParameter(System.String s, System.Decimal value = (System.Decimal)12.12) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalDecimalParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalEnumParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalEnumParameter.verified.cake new file mode 100644 index 0000000000..9b9f861896 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalEnumParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalEnumParameter(System.Int32 value, System.AttributeTargets targets = (System.AttributeTargets)4) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalEnumParameter(Context, value, targets); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableBooleanParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableBooleanParameter.verified.cake new file mode 100644 index 0000000000..ed9dc89338 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableBooleanParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableBooleanParameter(System.String s, System.Nullable value = (System.Boolean)false) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableBooleanParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableCharParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableCharParameter.verified.cake new file mode 100644 index 0000000000..b883d52848 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableCharParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableCharParameter(System.String s, System.Nullable value = (System.Char)'s') + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableCharParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDecimalParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDecimalParameter.verified.cake new file mode 100644 index 0000000000..96bfe95405 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDecimalParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableDecimalParameter(System.String s, System.Nullable value = (System.Decimal)123.12) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableDecimalParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDoubleParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDoubleParameter.verified.cake new file mode 100644 index 0000000000..ff56f6e3e0 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableDoubleParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableDoubleParameter(System.String s, System.Nullable value = (System.Double)1234567890.12) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableDoubleParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableEnumParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableEnumParameter.verified.cake new file mode 100644 index 0000000000..e7fa4aeeb1 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableEnumParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableEnumParameter(System.String s, System.Nullable targets = (System.AttributeTargets)4) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableEnumParameter(Context, s, targets); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableLongParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableLongParameter.verified.cake new file mode 100644 index 0000000000..53d3146e72 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableLongParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableLongParameter(System.String s, System.Nullable value = (System.Int64)1234567890) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableLongParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableTParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableTParameter.verified.cake new file mode 100644 index 0000000000..83efdef1d6 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalNullableTParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalNullableTParameter(System.String s, System.Nullable value = (System.Int32)0) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalNullableTParameter(Context, s, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalObjectParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalObjectParameter.verified.cake new file mode 100644 index 0000000000..921c14ade0 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalObjectParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalObjectParameter(System.Int32 value, System.Object option = null) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalObjectParameter(Context, value, option); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalStringParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalStringParameter.verified.cake new file mode 100644 index 0000000000..855d08957c --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOptionalStringParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOptionalStringParameter(System.Int32 value, System.String s = "there is a \"string\" here and a \t tab") + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOptionalStringParameter(Context, value, s); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOutputParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOutputParameter.verified.cake new file mode 100644 index 0000000000..c6f8631591 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithOutputParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithOutputParameter(out System.IDisposable arg) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithOutputParameter(Context, out arg); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameter.verified.cake new file mode 100644 index 0000000000..5ace2e0914 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithParameter(System.Int32 value) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithParameter(Context, value); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterArray.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterArray.verified.cake new file mode 100644 index 0000000000..8d44f06ec5 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterArray.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithParameterArray(params System.Int32[] values) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithParameterArray(Context, values); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterAttributes.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterAttributes.verified.cake new file mode 100644 index 0000000000..f3ae8f531d --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithParameterAttributes.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithParameterAttributes([System.Runtime.CompilerServices.CallerMemberName] System.String memberName = "", [System.Runtime.CompilerServices.CallerFilePath] System.String sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] System.Int32 sourceLineNumber = (System.Int32)0) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithParameterAttributes(Context, memberName, sourceFilePath, sourceLineNumber); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReservedKeywordParameter.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReservedKeywordParameter.verified.cake new file mode 100644 index 0000000000..f3fc73d725 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReservedKeywordParameter.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void NonGeneric_ExtensionMethodWithReservedKeywordParameter(System.Int32 @new) + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithReservedKeywordParameter(Context, @new); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReturnValue.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReturnValue.verified.cake new file mode 100644 index 0000000000..3af6e2ae3e --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Non_Generic_Methods_name=ExtensionMethodWithReturnValue.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public System.String NonGeneric_ExtensionMethodWithReturnValue() + => Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithReturnValue(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitError_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitError_WithMessage.verified.cake new file mode 100644 index 0000000000..a87e6874ad --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitError_WithMessage.verified.cake @@ -0,0 +1,3 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Obsolete_ExplicitError_WithMessage() + => throw new Cake.Core.CakeException("The alias Obsolete_ExplicitError_WithMessage has been made obsolete. Please use Foo.Bar instead."); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..c617650e9c --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ExplicitWarning_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Obsolete_ExplicitWarning_WithMessage() +#pragma warning disable 0618 + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ExplicitWarning_WithMessage(Context); +#pragma warning restore 0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_NoMessage.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_NoMessage.verified.cake new file mode 100644 index 0000000000..ace37bde77 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_NoMessage.verified.cake @@ -0,0 +1,5 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Obsolete_ImplicitWarning_NoMessage() +#pragma warning disable 0618 + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ImplicitWarning_NoMessage(Context); +#pragma warning restore 0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..1962dbc49e --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/MethodAliasGeneratorTests.TheGeneratorMethod.Should_Return_Correct_Generated_Code_For_Obsolete_Methods_name=Obsolete_ImplicitWarning_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[System.Diagnostics.DebuggerStepThrough] +public void Obsolete_ImplicitWarning_WithMessage() +#pragma warning disable 0618 + => Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ImplicitWarning_WithMessage(Context); +#pragma warning restore 0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitError_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitError_WithMessage.verified.cake new file mode 100644 index 0000000000..d25430c7c4 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitError_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[Obsolete("Please use Foo.Bar instead.", true)] +public System.Int32 Cached_Obsolete_ExplicitError_WithMessage +#pragma warning disable CS0618 + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ExplicitError_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..15df323b2b --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ExplicitWarning_WithMessage.verified.cake @@ -0,0 +1,6 @@ +private System.Int32? _Cached_Obsolete_ExplicitWarning_WithMessage; +[Obsolete("Please use Foo.Bar instead.", false)] +public System.Int32 Cached_Obsolete_ExplicitWarning_WithMessage +#pragma warning disable CS0618 + => _Cached_Obsolete_ExplicitWarning_WithMessage ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ExplicitWarning_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_NoMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_NoMessage.verified.cake new file mode 100644 index 0000000000..6758a9c930 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_NoMessage.verified.cake @@ -0,0 +1,6 @@ +private System.Int32? _Cached_Obsolete_ImplicitWarning_NoMessage; +[Obsolete] +public System.Int32 Cached_Obsolete_ImplicitWarning_NoMessage +#pragma warning disable CS0618 + => _Cached_Obsolete_ImplicitWarning_NoMessage ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ImplicitWarning_NoMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..ffea761aa4 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties_name=Cached_Obsolete_ImplicitWarning_WithMessage.verified.cake @@ -0,0 +1,6 @@ +private System.Int32? _Cached_Obsolete_ImplicitWarning_WithMessage; +[Obsolete("Please use Foo.Bar instead.", false)] +public System.Int32 Cached_Obsolete_ImplicitWarning_WithMessage +#pragma warning disable CS0618 + => _Cached_Obsolete_ImplicitWarning_WithMessage ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ImplicitWarning_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Dynamic_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Dynamic_Type.verified.cake new file mode 100644 index 0000000000..ee26752285 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Dynamic_Type.verified.cake @@ -0,0 +1,3 @@ +private dynamic _Cached_Dynamic_Type; +public dynamic Cached_Dynamic_Type + => _Cached_Dynamic_Type ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Dynamic_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Nullable_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Nullable_Type.verified.cake new file mode 100644 index 0000000000..247a2cb74c --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Nullable_Type.verified.cake @@ -0,0 +1,3 @@ +private System.String? _Cached_Nullable_Type; +public System.String? Cached_Nullable_Type + => _Cached_Nullable_Type ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Nullable_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Reference_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Reference_Type.verified.cake new file mode 100644 index 0000000000..f76291c846 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Reference_Type.verified.cake @@ -0,0 +1,3 @@ +private System.String _Cached_Reference_Type; +public System.String Cached_Reference_Type + => _Cached_Reference_Type ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Reference_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Value_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Value_Type.verified.cake new file mode 100644 index 0000000000..87cd6a7249 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Cached_Properties_name=Cached_Value_Type.verified.cake @@ -0,0 +1,3 @@ +private System.Boolean? _Cached_Value_Type; +public System.Boolean Cached_Value_Type + => _Cached_Value_Type ??= Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Value_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitError_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitError_WithMessage.verified.cake new file mode 100644 index 0000000000..a13ad1d445 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitError_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[Obsolete("Please use Foo.Bar instead.", true)] +public System.Int32 NonCached_Obsolete_ExplicitError_WithMessage +#pragma warning disable CS0618 + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ExplicitError_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..16d4002b73 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ExplicitWarning_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[Obsolete("Please use Foo.Bar instead.", false)] +public System.Int32 NonCached_Obsolete_ExplicitWarning_WithMessage +#pragma warning disable CS0618 + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ExplicitWarning_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_NoMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_NoMessage.verified.cake new file mode 100644 index 0000000000..c770e57722 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_NoMessage.verified.cake @@ -0,0 +1,5 @@ +[Obsolete] +public System.Int32 NonCached_Obsolete_ImplicitWarning_NoMessage +#pragma warning disable CS0618 + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ImplicitWarning_NoMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_WithMessage.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_WithMessage.verified.cake new file mode 100644 index 0000000000..cc739cad85 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties_name=NonCached_Obsolete_ImplicitWarning_WithMessage.verified.cake @@ -0,0 +1,5 @@ +[Obsolete("Please use Foo.Bar instead.", false)] +public System.Int32 NonCached_Obsolete_ImplicitWarning_WithMessage +#pragma warning disable CS0618 + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ImplicitWarning_WithMessage(Context); +#pragma warning restore CS0618 \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Dynamic_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Dynamic_Type.verified.cake new file mode 100644 index 0000000000..2d9c520175 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Dynamic_Type.verified.cake @@ -0,0 +1,2 @@ +public dynamic NonCached_Dynamic_Type + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Dynamic_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Value_Type.verified.cake b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Value_Type.verified.cake new file mode 100644 index 0000000000..3a339fd775 --- /dev/null +++ b/src/Cake.Core.Tests/Expectations/PropertyAliasGeneratorTests.TheGenerateMethod.Should_Return_Correct_Generated_Code_For_Non_Cached_Properties_name=NonCached_Value_Type.verified.cake @@ -0,0 +1,2 @@ +public System.Int32 NonCached_Value_Type + => Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Value_Type(Context); \ No newline at end of file diff --git a/src/Cake.Core.Tests/Extensions/StringExtensions.cs b/src/Cake.Core.Tests/Extensions/StringExtensions.cs index 7c811c319f..45f3821769 100644 --- a/src/Cake.Core.Tests/Extensions/StringExtensions.cs +++ b/src/Cake.Core.Tests/Extensions/StringExtensions.cs @@ -1,20 +1,28 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + // ReSharper disable once CheckNamespace namespace Cake.Core.Tests { public static class StringExtensions { - /// - /// Removes line endings from the specified text. - /// - /// The text. - /// The text without line ending public static string NormalizeGeneratedCode(this string text) { return text.NormalizeLineEndings() .TrimEnd('\r', '\n'); } + + public static string ReturnNullIfEmpty(this string text) + { + if (text != null) + { + if (string.IsNullOrWhiteSpace(text)) + { + return null; + } + } + return text; + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/CakeConfigurationProviderFixture.cs b/src/Cake.Core.Tests/Fixtures/CakeConfigurationProviderFixture.cs index c7149b1db2..37a8d6a8a9 100644 --- a/src/Cake.Core.Tests/Fixtures/CakeConfigurationProviderFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/CakeConfigurationProviderFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.Configuration; using Cake.Core.IO; @@ -30,4 +31,4 @@ public ICakeConfiguration Create() return provider.CreateConfiguration(Path, Arguments); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/CakeContextFixture.cs b/src/Cake.Core.Tests/Fixtures/CakeContextFixture.cs index 405d8ce86c..9d258d3fc7 100644 --- a/src/Cake.Core.Tests/Fixtures/CakeContextFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/CakeContextFixture.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Core.Configuration; using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Tooling; @@ -18,6 +20,8 @@ public sealed class CakeContextFixture public IProcessRunner ProcessRunner { get; set; } public IRegistry Registry { get; set; } public IToolLocator Tools { get; set; } + public ICakeDataService Data { get; set; } + public ICakeConfiguration Configuration { get; set; } public CakeContextFixture() { @@ -29,12 +33,14 @@ public CakeContextFixture() ProcessRunner = Substitute.For(); Registry = Substitute.For(); Tools = Substitute.For(); + Data = Substitute.For(); + Configuration = Substitute.For(); } public CakeContext CreateContext() { return new CakeContext(FileSystem, Environment, Globber, - Log, Arguments, ProcessRunner, Registry, Tools); + Log, Arguments, ProcessRunner, Registry, Tools, Data, Configuration); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/CakeEngineFixture.cs b/src/Cake.Core.Tests/Fixtures/CakeEngineFixture.cs index a0867d6fac..ffd4c6b953 100644 --- a/src/Cake.Core.Tests/Fixtures/CakeEngineFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/CakeEngineFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Testing; using NSubstitute; @@ -16,7 +17,9 @@ internal sealed class CakeEngineFixture public ICakeArguments Arguments { get; set; } public IProcessRunner ProcessRunner { get; set; } public ICakeContext Context { get; set; } + public ICakeReportPrinter ReportPrinter { get; set; } public IExecutionStrategy ExecutionStrategy { get; set; } + public ICakeDataService DataService { get; set; } public CakeEngineFixture() { @@ -26,7 +29,9 @@ public CakeEngineFixture() Globber = Substitute.For(); Arguments = Substitute.For(); ProcessRunner = Substitute.For(); - ExecutionStrategy = new DefaultExecutionStrategy(Log); + ReportPrinter = Substitute.For(); + ExecutionStrategy = new DefaultExecutionStrategy(Log, ReportPrinter); + DataService = Substitute.For(); Context = Substitute.For(); Context.Arguments.Returns(Arguments); @@ -39,7 +44,7 @@ public CakeEngineFixture() public CakeEngine CreateEngine() { - return new CakeEngine(Log); + return new CakeEngine(DataService, Log); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/DummyToolFixture.cs b/src/Cake.Core.Tests/Fixtures/DummyToolFixture.cs index 94aff61a9e..6ac3a92b7e 100644 --- a/src/Cake.Core.Tests/Fixtures/DummyToolFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/DummyToolFixture.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using Cake.Core.Tests.Stubs; using Cake.Testing.Fixtures; @@ -8,15 +10,18 @@ namespace Cake.Core.Tests.Fixtures { public sealed class DummyToolFixture : ToolFixture { + public Action ExitCodeValidation { get; set; } + public DummyToolFixture() : base("dummy.exe") { + ExitCodeValidation = null; } protected override void RunTool() { - var tool = new DummyTool(FileSystem, Environment, ProcessRunner, Tools); + var tool = new DummyTool(FileSystem, Environment, ProcessRunner, Tools, ExitCodeValidation); tool.Run(Settings); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/GlobberFixture.cs b/src/Cake.Core.Tests/Fixtures/GlobberFixture.cs index 231daa0c0c..c42efc19e6 100644 --- a/src/Cake.Core.Tests/Fixtures/GlobberFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/GlobberFixture.cs @@ -1,95 +1,147 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Linq; -using Cake.Core.IO; -using Cake.Testing; - -namespace Cake.Core.Tests.Fixtures -{ - internal sealed class GlobberFixture - { - public FakeFileSystem FileSystem { get; set; } - public ICakeEnvironment Environment { get; set; } - - public GlobberFixture(bool windows = false) - { - if (windows) - { - PrepareWindowsFixture(); - } - else - { - PrepareUnixFixture(); - } - } - - private void PrepareWindowsFixture() - { - Environment = FakeEnvironment.CreateWindowsEnvironment(); - FileSystem = new FakeFileSystem(Environment); - - // Directories - FileSystem.CreateDirectory("C://Working"); - FileSystem.CreateDirectory("C://Working/Foo"); - FileSystem.CreateDirectory("C://Working/Foo/Bar"); - FileSystem.CreateDirectory("C:"); - FileSystem.CreateDirectory("C:/Program Files (x86)"); - - // Files - FileSystem.CreateFile("C:/Working/Foo/Bar/Qux.c"); - FileSystem.CreateFile("C:/Program Files (x86)/Foo.c"); - FileSystem.CreateFile("C:/Working/Project.A.Test.dll"); - FileSystem.CreateFile("C:/Working/Project.B.Test.dll"); - FileSystem.CreateFile("C:/Working/Project.IntegrationTest.dll"); - FileSystem.CreateFile("C:/Tools & Services/MyTool.dll"); - FileSystem.CreateFile("C:/Tools + Services/MyTool.dll"); - } - - private void PrepareUnixFixture() - { - Environment = FakeEnvironment.CreateUnixEnvironment(); - FileSystem = new FakeFileSystem(Environment); - - // Directories - FileSystem.CreateDirectory("/Working"); - FileSystem.CreateDirectory("/Working/Foo"); - FileSystem.CreateDirectory("/Working/Foo/Bar"); - FileSystem.CreateDirectory("/Working/Bar"); - FileSystem.CreateDirectory("/Foo/Bar"); - FileSystem.CreateDirectory("/Foo (Bar)"); - - // Files - FileSystem.CreateFile("/Working/Foo/Bar/Qux.c"); - FileSystem.CreateFile("/Working/Foo/Bar/Qex.c"); - FileSystem.CreateFile("/Working/Foo/Bar/Qux.h"); - FileSystem.CreateFile("/Working/Foo/Baz/Qux.c"); - FileSystem.CreateFile("/Working/Foo/Bar/Baz/Qux.c"); - FileSystem.CreateFile("/Working/Bar/Qux.c"); - FileSystem.CreateFile("/Working/Bar/Qux.h"); - FileSystem.CreateFile("/Working/Foo.Bar.Test.dll"); - FileSystem.CreateFile("/Working/Bar.Qux.Test.dll"); - FileSystem.CreateFile("/Working/Quz.FooTest.dll"); - FileSystem.CreateFile("/Foo/Bar.baz"); - FileSystem.CreateFile("/Foo (Bar)/Baz.c"); - } - - public void SetWorkingDirectory(DirectoryPath path) - { - Environment.WorkingDirectory = path; - } - - public Path[] Match(string pattern) - { - return Match(pattern, null); - } - - public Path[] Match(string pattern, Func predicate) - { - return new Globber(FileSystem, Environment) - .Match(pattern, predicate) - .ToArray(); - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core.IO; +using Cake.Testing; + +namespace Cake.Core.Tests.Fixtures +{ + internal sealed class GlobberFixture + { + public FakeFileSystem FileSystem { get; set; } + public ICakeEnvironment Environment { get; set; } + + private GlobberFixture(FakeFileSystem filesystem, ICakeEnvironment environment) + { + FileSystem = filesystem; + Environment = environment; + } + + public static GlobberFixture Windows() + { + var environment = FakeEnvironment.CreateWindowsEnvironment(); + var filesystem = new FakeFileSystem(environment); + + // Directories + filesystem.CreateDirectory("C://Working"); + filesystem.CreateDirectory("C://Working/Foo"); + filesystem.CreateDirectory("C://Working/Foo/Bar"); + filesystem.CreateDirectory("C:"); + filesystem.CreateDirectory("C:/Program Files (x86)"); + + // UNC directories + filesystem.CreateDirectory(@"\\Server"); + filesystem.CreateDirectory(@"\\Server\Foo"); + filesystem.CreateDirectory(@"\\Server\Foo\Bar"); + filesystem.CreateDirectory(@"\\Server\Bar"); + filesystem.CreateDirectory(@"\\Foo\Bar"); + filesystem.CreateDirectory(@"\\Foo (Bar)"); + filesystem.CreateDirectory(@"\\Foo@Bar\"); + filesystem.CreateDirectory(@"\\嵌套"); + filesystem.CreateDirectory(@"\\嵌套\目录"); + + // Files + filesystem.CreateFile("C:/Working/Foo/Bar/Qux.c"); + filesystem.CreateFile("C:/Program Files (x86)/Foo.c"); + filesystem.CreateFile("C:/Working/Project.A.Test.dll"); + filesystem.CreateFile("C:/Working/Project.B.Test.dll"); + filesystem.CreateFile("C:/Working/Project.IntegrationTest.dll"); + filesystem.CreateFile("C:/Tools & Services/MyTool.dll"); + filesystem.CreateFile("C:/Tools + Services/MyTool.dll"); + filesystem.CreateFile("C:/Some %2F Directory/MyTool.dll"); + filesystem.CreateFile("C:/Some ! Directory/MyTool.dll"); + filesystem.CreateFile("C:/Some@Directory/MyTool.dll"); + filesystem.CreateFile("C:/Working/foobar.rs"); + filesystem.CreateFile("C:/Working/foobaz.rs"); + filesystem.CreateFile("C:/Working/foobax.rs"); + + // UNC files + filesystem.CreateFile(@"\\Server\Foo/Bar/Qux.c"); + filesystem.CreateFile(@"\\Server\Foo/Bar/Qex.c"); + filesystem.CreateFile(@"\\Server\Foo/Bar/Qux.h"); + filesystem.CreateFile(@"\\Server\Foo/Baz/Qux.c"); + filesystem.CreateFile(@"\\Server\Foo/Bar/Baz/Qux.c"); + filesystem.CreateFile(@"\\Server\Bar/Qux.c"); + filesystem.CreateFile(@"\\Server\Bar/Qux.h"); + filesystem.CreateFile(@"\\Server\Foo.Bar.Test.dll"); + filesystem.CreateFile(@"\\Server\Bar.Qux.Test.dll"); + filesystem.CreateFile(@"\\Server\Quz.FooTest.dll"); + filesystem.CreateFile(@"\\Foo\Bar.baz"); + filesystem.CreateFile(@"\\Foo (Bar)\Baz.c"); + filesystem.CreateFile(@"\\Foo@Bar\Baz.c"); + filesystem.CreateFile(@"\\嵌套/目录/文件.延期"); + + return new GlobberFixture(filesystem, environment); + } + + public static GlobberFixture UnixLike() + { + var environment = FakeEnvironment.CreateUnixEnvironment(); + var filesystem = new FakeFileSystem(environment); + + // Directories + filesystem.CreateDirectory("/RootDir"); + filesystem.CreateDirectory("/Working"); + filesystem.CreateDirectory("/Working/Foo"); + filesystem.CreateDirectory("/Working/Foo/Bar"); + filesystem.CreateDirectory("/Working/Bar"); + filesystem.CreateDirectory("/Working/Project"); + filesystem.CreateDirectory("/Foo/Bar"); + filesystem.CreateDirectory("/Foo (Bar)"); + filesystem.CreateDirectory("/Foo@Bar/"); + filesystem.CreateDirectory("/嵌套"); + filesystem.CreateDirectory("/嵌套/目录"); + + // Files + filesystem.CreateFile("/RootFile.sh"); + filesystem.CreateFile("/Working/Foo/Bar/Qux.c"); + filesystem.CreateFile("/Working/Foo/Bar/Qex.c"); + filesystem.CreateFile("/Working/Foo/Bar/Qux.h"); + filesystem.CreateFile("/Working/Foo/Baz/Qux.c"); + filesystem.CreateFile("/Working/Foo/Bar/Baz/Qux.c"); + filesystem.CreateFile("/Working/Bar/Qux.c"); + filesystem.CreateFile("/Working/Bar/Qux.h"); + filesystem.CreateFile("/Working/Foo.Bar.Test.dll"); + filesystem.CreateFile("/Working/Bar.Qux.Test.dll"); + filesystem.CreateFile("/Working/Quz.FooTest.dll"); + filesystem.CreateFile("/Foo/Bar.baz"); + filesystem.CreateFile("/Foo (Bar)/Baz.c"); + filesystem.CreateFile("/Foo@Bar/Baz.c"); + filesystem.CreateFile("/嵌套/目录/文件.延期"); + filesystem.CreateFile("/Working/foobar.rs"); + filesystem.CreateFile("/Working/foobaz.rs"); + filesystem.CreateFile("/Working/foobax.rs"); + filesystem.CreateFile("/Working/Project/package.json"); + filesystem.CreateFile("/Working/Project/package-lock.json"); + filesystem.CreateFile("/Working/Project/tsconfig.json"); + filesystem.CreateFile("/Working/Project/.npmrc"); + + return new GlobberFixture(filesystem, environment); + } + + public void SetWorkingDirectory(DirectoryPath path) + { + Environment.WorkingDirectory = path; + } + + public Path[] Match(string pattern) + { + return Match(pattern, null); + } + + public Path[] Match(string pattern, Func directoryPredicate) + { + return Match(pattern, directoryPredicate, null); + } + + public Path[] Match(string pattern, Func directoryPredicate, Func filePredicate) + { + return new Globber(FileSystem, Environment) + .Match(pattern, new GlobberSettings { Predicate = directoryPredicate, FilePredicate = filePredicate }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/MethodAliasGeneratorFixture.cs b/src/Cake.Core.Tests/Fixtures/MethodAliasGeneratorFixture.cs index 02be0171c0..eae535d452 100644 --- a/src/Cake.Core.Tests/Fixtures/MethodAliasGeneratorFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/MethodAliasGeneratorFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Linq; @@ -12,29 +13,11 @@ namespace Cake.Core.Tests.Fixtures { public sealed class MethodAliasGeneratorFixture { - private readonly Assembly _assembly; private readonly MethodInfo[] _methods; public MethodAliasGeneratorFixture() { - _assembly = typeof (MethodAliasGeneratorFixture).Assembly; - _methods = typeof (MethodAliasGeneratorData).GetMethods(); - } - - public string GetExpectedCode(string name) - { - var resource = string.Concat("Cake.Core.Tests.Unit.Scripting.CodeGen.Expected.Methods.", name); - using (var stream = _assembly.GetManifestResourceStream(resource)) - { - if (stream == null) - { - throw new InvalidOperationException("Could not load manifest resource stream."); - } - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd().NormalizeGeneratedCode(); - } - } + _methods = typeof(MethodAliasGeneratorData).GetMethods(); } public string Generate(string name) @@ -43,4 +26,4 @@ public string Generate(string name) return MethodAliasGenerator.Generate(method).NormalizeGeneratedCode(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/NotFormattableFixture.cs b/src/Cake.Core.Tests/Fixtures/NotFormattableFixture.cs new file mode 100644 index 0000000000..aaa5c1e977 --- /dev/null +++ b/src/Cake.Core.Tests/Fixtures/NotFormattableFixture.cs @@ -0,0 +1,6 @@ +namespace Cake.Core.Tests.Fixtures +{ + internal sealed class NotFormattableFixture + { + } +} diff --git a/src/Cake.Core.Tests/Fixtures/NuGetToolResolverFixture.cs b/src/Cake.Core.Tests/Fixtures/NuGetToolResolverFixture.cs index a86fa93403..7c44130e88 100644 --- a/src/Cake.Core.Tests/Fixtures/NuGetToolResolverFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/NuGetToolResolverFixture.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.IO.NuGet; using Cake.Core.Tooling; @@ -21,7 +23,7 @@ public NuGetToolResolverFixture(FakeEnvironment environment = null) Tools = new ToolLocator( Environment, new ToolRepository(Environment), - new ToolResolutionStrategy(FileSystem, Environment, new Globber(FileSystem, Environment), new FakeConfiguration())); + new ToolResolutionStrategy(FileSystem, Environment, new Globber(FileSystem, Environment), new FakeConfiguration(), new NullLog())); } public FilePath Resolve() @@ -30,4 +32,4 @@ public FilePath Resolve() return resolver.ResolvePath(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ProcessRunnerFixture.cs b/src/Cake.Core.Tests/Fixtures/ProcessRunnerFixture.cs new file mode 100644 index 0000000000..cb31c7c2b9 --- /dev/null +++ b/src/Cake.Core.Tests/Fixtures/ProcessRunnerFixture.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics; +using Cake.Core.Configuration; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Core.Tooling; +using Cake.Testing; +using NSubstitute; + +namespace Cake.Core.Tests.Fixtures +{ + public sealed class ProcessRunnerFixture + { + public FakeFileSystem FileSystem { get; set; } + public FakeEnvironment Environment { get; set; } + public ICakeLog Log { get; set; } + public IToolLocator Tools { get; set; } + public ICakeConfiguration Configuration { get; set; } + public FilePath ProcessFilePath { get; set; } + public ProcessSettings ProcessSettings { get; set; } + public IFile MonoFile { get; } + + public ProcessRunnerFixture(bool windows = false) + { + var environment = windows + ? FakeEnvironment.CreateWindowsEnvironment() + : FakeEnvironment.CreateUnixEnvironment(); + + environment.WorkingDirectory = "/Working"; + + Environment = environment; + FileSystem = new FakeFileSystem(Environment); + Log = Substitute.For(); + Tools = Substitute.For(); + Configuration = Substitute.For(); + ProcessFilePath = "/Program Files/Cake.exe"; + ProcessSettings = new ProcessSettings(); + + FileSystem.CreateDirectory("/Working"); + FileSystem.CreateDirectory("/Program Files"); + FileSystem.CreateFile("/Program Files/Cake.exe", ClrAssemblyData); + MonoFile = FileSystem.CreateFile("/Program Files/mono.exe", NonClrAssemblyData); + + Tools.Resolve("mono").Returns(file => MonoFile.Exists ? MonoFile.Path : null); + } + + public void GivenIsCoreClr() + { + Environment.SetIsCoreClr(true); + } + + public void GivenConfigNoMonoCoersion() + { + Configuration.GetValue(Constants.Settings.NoMonoCoersion).Returns("true"); + } + + public void GivenMonoNotResolved() + { + MonoFile.Delete(); + } + + internal void GivenSecretArgument() + { + ProcessSettings.Arguments = new ProcessArgumentBuilder().AppendSecret("TopSecret"); + } + + public ProcessRunner CreateProcessRunner() + { + return new ProcessRunner(FileSystem, Environment, Log, Tools, Configuration); + } + + public IProcess Start() + { + return CreateProcessRunner().Start(ProcessFilePath, ProcessSettings); + } + + public ProcessStartInfo GetProcessStartInfo() + { + return CreateProcessRunner().GetProcessStartInfo(ProcessFilePath, ProcessSettings, out _); + } + + private static readonly byte[] ClrAssemblyData = new byte[] + { + 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x03, 0x00, 0x8a, 0x54, 0x4a, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x22, 0x20, 0x0b, 0x01, 0x30, 0x00, 0x00, 0x84, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xa2, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x60, 0x85, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0xa2, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x88, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x94, 0xa1, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x48 + }; + + private static readonly byte[] NonClrAssemblyData = new byte[] + { + 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, + 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xab, 0x04, 0x28, 0x30, 0xef, 0x65, 0x46, 0x63, 0xef, 0x65, 0x46, 0x63, 0xef, 0x65, 0x46, 0x63, 0x71, 0xc5, 0x81, 0x63, 0xea, 0x65, 0x46, 0x63, 0xa9, 0x34, 0xa7, 0x63, 0xc2, 0x65, 0x46, 0x63, + 0xa9, 0x34, 0xa6, 0x63, 0x71, 0x65, 0x46, 0x63, 0xa9, 0x34, 0x99, 0x63, 0xe4, 0x65, 0x46, 0x63, 0x32, 0x9a, 0x88, 0x63, 0xed, 0x65, 0x46, 0x63, 0x32, 0x9a, 0x8d, 0x63, 0xe0, 0x65, 0x46, 0x63, + 0xef, 0x65, 0x47, 0x63, 0x43, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0xa2, 0x63, 0x4d, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0x9a, 0x63, 0xee, 0x65, 0x46, 0x63, 0xe2, 0x37, 0x9d, 0x63, 0xee, 0x65, 0x46, 0x63, + 0xef, 0x65, 0xd1, 0x63, 0xee, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0x98, 0x63, 0xee, 0x65, 0x46, 0x63, 0x52, 0x69, 0x63, 0x68, 0xef, 0x65, 0x46, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x06, 0x00, 0xe2, 0xac, 0x47, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x22, 0x20, + 0x0b, 0x02, 0x0c, 0x00, 0x00, 0x6e, 0x0d, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x87, 0x0c, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x11, 0x00, 0x00, 0x04, 0x00, 0x00, + 0xfe, 0x24, 0x12, 0x00, 0x03, 0x00, 0x60, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x0f, 0x00, 0x8a, 0x62, 0x00, 0x00, 0x8c, 0x3b, 0x10, 0x00, 0xa0, 0x00, 0x00, 0x00, + 0x00, 0x90, 0x11, 0x00, 0xb8, 0x05, 0x00, 0x00, 0x00, 0xb0, 0x10, 0x00, 0xb4, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x00, 0x3c, 0x09, 0x00, 0x00, + 0xa0, 0x85, 0x0d, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0xcd, 0x0e, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0d, 0x00, 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + } +} diff --git a/src/Cake.Core.Tests/Fixtures/ProcessWrapperFixture.cs b/src/Cake.Core.Tests/Fixtures/ProcessWrapperFixture.cs new file mode 100644 index 0000000000..345fd7f37b --- /dev/null +++ b/src/Cake.Core.Tests/Fixtures/ProcessWrapperFixture.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using NSubstitute; + +namespace Cake.Core.Tests.Fixtures +{ + internal sealed class ProcessWrapperFixture + { + public Process Process { get; set; } + public ICakeLog Log { get; set; } + public Func FilterOutput { get; set; } + public Func FilterError { get; set; } + public Func StandartOutputHandler { get; set; } + public Func StandardErrorHandler { get; set; } + + public ProcessWrapperFixture() + { + Log = Substitute.For(); + } + + public ProcessWrapper CreateProcessWrapper() + { + return new ProcessWrapper(Process, Log, FilterOutput, StandartOutputHandler, FilterError, StandardErrorHandler); + } + } +} diff --git a/src/Cake.Core.Tests/Fixtures/PropertyAliasGeneratorFixture.cs b/src/Cake.Core.Tests/Fixtures/PropertyAliasGeneratorFixture.cs index 3e6462f169..05edf31455 100644 --- a/src/Cake.Core.Tests/Fixtures/PropertyAliasGeneratorFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/PropertyAliasGeneratorFixture.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.IO; using System.Linq; @@ -17,24 +18,8 @@ public sealed class PropertyAliasGeneratorFixture public PropertyAliasGeneratorFixture() { - _assembly = typeof (PropertyAliasGeneratorFixture).Assembly; - _methods = typeof (PropertyAliasGeneratorData).GetMethods(); - } - - public string GetExpectedData(string name) - { - var resource = string.Concat("Cake.Core.Tests.Unit.Scripting.CodeGen.Expected.Properties.", name); - using (var stream = _assembly.GetManifestResourceStream(resource)) - { - if (stream == null) - { - throw new InvalidOperationException("Could not load manifest resource stream."); - } - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd().NormalizeGeneratedCode(); - } - } + _assembly = typeof(PropertyAliasGeneratorFixture).GetTypeInfo().Assembly; + _methods = typeof(PropertyAliasGeneratorData).GetMethods(); } public string Generate(string name) @@ -43,4 +28,4 @@ public string Generate(string name) return PropertyAliasGenerator.Generate(method).NormalizeGeneratedCode(); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ScriptAnalyzerFixture.cs b/src/Cake.Core.Tests/Fixtures/ScriptAnalyzerFixture.cs index 60dcdd7e27..7d7c5f4f3e 100644 --- a/src/Cake.Core.Tests/Fixtures/ScriptAnalyzerFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/ScriptAnalyzerFixture.cs @@ -1,9 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Scripting.Analysis; +using Cake.Core.Scripting.Processors.Loading; using Cake.Testing; using NSubstitute; @@ -13,23 +16,34 @@ public sealed class ScriptAnalyzerFixture { public FakeFileSystem FileSystem { get; set; } public FakeEnvironment Environment { get; set; } + public IGlobber Globber { get; set; } public ICakeLog Log { get; set; } + public List Providers { get; set; } - public ScriptAnalyzerFixture() + public ScriptAnalyzerFixture(bool windows = false) { - Environment = FakeEnvironment.CreateUnixEnvironment(); + Environment = windows + ? FakeEnvironment.CreateWindowsEnvironment() + : FakeEnvironment.CreateUnixEnvironment(); FileSystem = new FakeFileSystem(Environment); + Globber = new Globber(FileSystem, Environment); Log = Substitute.For(); + Providers = new List(); + } + + public void AddFileLoadDirectiveProvider() + { + Providers.Add(new FileLoadDirectiveProvider(Globber, Log)); } public ScriptAnalyzer CreateAnalyzer() { - return new ScriptAnalyzer(FileSystem, Environment, Log); + return new ScriptAnalyzer(FileSystem, Environment, Log, Providers); } public ScriptAnalyzerResult Analyze(FilePath script) { - return CreateAnalyzer().Analyze(script); + return CreateAnalyzer().Analyze(script, new ScriptAnalyzerSettings()); } public void GivenScriptExist(FilePath path, string content) @@ -37,4 +51,4 @@ public void GivenScriptExist(FilePath path, string content) FileSystem.CreateFile(path).SetContent(content); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ScriptHostFixture.cs b/src/Cake.Core.Tests/Fixtures/ScriptHostFixture.cs index be5abea744..4687d22fca 100644 --- a/src/Cake.Core.Tests/Fixtures/ScriptHostFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/ScriptHostFixture.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Scripting; @@ -17,9 +20,15 @@ public TestingScriptHost(ICakeEngine engine, ICakeContext context) { } - public override CakeReport RunTarget(string target) + public override Task RunTargetAsync(string target) + { + return System.Threading.Tasks.Task.FromResult(new CakeReport()); + } + + /// + public override Task RunTargetsAsync(IEnumerable targets) { - return new CakeReport(); + return System.Threading.Tasks.Task.FromResult(new CakeReport()); } } @@ -47,7 +56,7 @@ public ScriptHostFixture() Context.Log.Returns(Log); Engine = Substitute.For(); - Engine.RunTarget(Context, Arg.Any(), Arg.Any()) + Engine.RunTargetAsync(Context, Arg.Any(), Arg.Any()) .Returns(new CakeReport()); } @@ -56,4 +65,4 @@ public ScriptHost CreateHost() return new TestingScriptHost(Engine, Context); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ScriptProcessorFixture.cs b/src/Cake.Core.Tests/Fixtures/ScriptProcessorFixture.cs index 8a246822ba..36fc13bc78 100644 --- a/src/Cake.Core.Tests/Fixtures/ScriptProcessorFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/ScriptProcessorFixture.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; +using Cake.Core.Configuration; using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Packaging; @@ -18,10 +20,12 @@ public sealed class ScriptProcessorFixture public FakeFileSystem FileSystem { get; set; } public ICakeEnvironment Environment { get; set; } public ICakeLog Log { get; set; } - public IToolLocator Tools { get; set; } + public IToolLocator ToolLocator { get; set; } public IPackageInstaller Installer { get; set; } + public ICakeConfiguration Configuration { get; set; } - public ScriptAnalyzerResult Result { get; set; } + public List Addins { get; set; } + public List Tools { get; set; } public DirectoryPath InstallPath { get; set; } public ScriptProcessorFixture() @@ -29,17 +33,15 @@ public ScriptProcessorFixture() Environment = FakeEnvironment.CreateUnixEnvironment(); FileSystem = new FakeFileSystem(Environment); Log = Substitute.For(); - Tools = Substitute.For(); + ToolLocator = Substitute.For(); + Configuration = Substitute.For(); Installer = Substitute.For(); Installer.CanInstall(Arg.Any(), Arg.Any()).Returns(true); InstallPath = new DirectoryPath("/Working/Bin"); - // Create the script analyzer result. - var script = new ScriptInformation("/Working/build.cake"); - script.Addins.Add(new PackageReference("custom:?package=addin")); - script.Tools.Add(new PackageReference("custom:?package=tool")); - Result = new ScriptAnalyzerResult(script, new List()); + Addins = new List { new PackageReference("custom:?package=addin") }; + Tools = new List { new PackageReference("custom:?package=tool") }; } public void GivenFilesWillBeInstalled() @@ -61,17 +63,17 @@ public ScriptProcessor CreateProcessor() { installers.Add(Installer); } - return new ScriptProcessor(FileSystem, Environment, Log, Tools, installers); + return new ScriptProcessor(FileSystem, Environment, Log, ToolLocator, installers, Configuration); } public void InstallAddins() { - CreateProcessor().InstallAddins(Result, InstallPath); + CreateProcessor().InstallAddins(Addins, InstallPath); } public void InstallTools() { - CreateProcessor().InstallTools(Result, InstallPath); + CreateProcessor().InstallTools(Tools, InstallPath); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ScriptRunnerFixture.cs b/src/Cake.Core.Tests/Fixtures/ScriptRunnerFixture.cs index f932c3ec78..c59f1518df 100644 --- a/src/Cake.Core.Tests/Fixtures/ScriptRunnerFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/ScriptRunnerFixture.cs @@ -1,13 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; using Cake.Core.Configuration; using Cake.Core.Diagnostics; using Cake.Core.IO; +using Cake.Core.Reflection; using Cake.Core.Scripting; using Cake.Core.Scripting.Analysis; +using Cake.Core.Scripting.Processors.Loading; using Cake.Testing; using NSubstitute; @@ -15,6 +18,7 @@ namespace Cake.Core.Tests.Fixtures { internal sealed class ScriptRunnerFixture { + public IAssemblyLoader AssemblyLoader { get; set; } public FakeFileSystem FileSystem { get; set; } public FakeEnvironment Environment { get; set; } public ICakeConfiguration Configuration { get; set; } @@ -24,36 +28,41 @@ internal sealed class ScriptRunnerFixture public IScriptProcessor ScriptProcessor { get; set; } public IScriptConventions ScriptConventions { get; set; } public IScriptAliasFinder AliasFinder { get; set; } - public ICakeLog Log { get; set; } + public FakeLog Log { get; set; } public IScriptHost Host { get; set; } public FilePath Script { get; set; } public IDictionary ArgumentDictionary { get; set; } - public string Source { get; private set; } - public IGlobber Globber{ get; set; } + public string Source { get; } + public IGlobber Globber { get; set; } public ScriptRunnerFixture(string fileName = "/Working/build.cake") { Script = fileName; Source = "Hello World"; + AssemblyLoader = Substitute.For(); + Environment = FakeEnvironment.CreateUnixEnvironment(); FileSystem = new FakeFileSystem(Environment); FileSystem.CreateFile(Script.MakeAbsolute(Environment)).SetContent(Source); - Globber = Substitute.For(); + Globber = new Globber(FileSystem, Environment); Configuration = Substitute.For(); AliasFinder = Substitute.For(); - Log = Substitute.For(); + Log = new FakeLog(); Session = Substitute.For(); ArgumentDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Engine = Substitute.For(); - Engine.CreateSession(Arg.Any(), ArgumentDictionary).Returns(Session); + Engine.CreateSession(Arg.Any()).Returns(Session); - ScriptAnalyzer = new ScriptAnalyzer(FileSystem, Environment, Log); + var runtime = new CakeRuntime(); + var referenceAssemblyResolver = Substitute.For(); + referenceAssemblyResolver.GetReferenceAssemblies().Returns(Array.Empty()); + ScriptAnalyzer = new ScriptAnalyzer(FileSystem, Environment, Log, new[] { new FileLoadDirectiveProvider(Globber, Log) }); ScriptProcessor = Substitute.For(); - ScriptConventions = new ScriptConventions(FileSystem); + ScriptConventions = new ScriptConventions(FileSystem, AssemblyLoader, runtime, referenceAssemblyResolver); var context = Substitute.For(); context.Environment.Returns(c => Environment); @@ -65,7 +74,8 @@ public ScriptRunnerFixture(string fileName = "/Working/build.cake") public ScriptRunner CreateScriptRunner() { return new ScriptRunner(Environment, Log, Configuration, Engine, - AliasFinder, ScriptAnalyzer, ScriptProcessor, ScriptConventions); + AliasFinder, ScriptAnalyzer, ScriptProcessor, + ScriptConventions, AssemblyLoader); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Fixtures/ToolResolutionStrategyFixture.cs b/src/Cake.Core.Tests/Fixtures/ToolResolutionStrategyFixture.cs index 90843c3429..c9dbb34326 100644 --- a/src/Cake.Core.Tests/Fixtures/ToolResolutionStrategyFixture.cs +++ b/src/Cake.Core.Tests/Fixtures/ToolResolutionStrategyFixture.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.Diagnostics; using Cake.Core.IO; using Cake.Core.Tooling; using Cake.Testing; @@ -15,9 +18,9 @@ internal sealed class ToolResolutionStrategyFixture public FakeConfiguration Configuration { get; set; } public IToolRepository Repository { get; set; } - public ToolResolutionStrategyFixture() + public ToolResolutionStrategyFixture(FakeEnvironment environment = null) { - Environment = FakeEnvironment.CreateUnixEnvironment(); + Environment = environment ?? FakeEnvironment.CreateUnixEnvironment(); FileSystem = new FakeFileSystem(Environment); Globber = new Globber(FileSystem, Environment); Configuration = new FakeConfiguration(); @@ -26,8 +29,14 @@ public ToolResolutionStrategyFixture() public FilePath Resolve(string name) { - var strategy = new ToolResolutionStrategy(FileSystem, Environment, Globber, Configuration); + var strategy = new ToolResolutionStrategy(FileSystem, Environment, Globber, Configuration, new NullLog()); return strategy.Resolve(Repository, name); } + + public FilePath Resolve(IEnumerable toolExeNames) + { + var strategy = new ToolResolutionStrategy(FileSystem, Environment, Globber, Configuration, new NullLog()); + return strategy.Resolve(Repository, toolExeNames); + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Properties/AssemblyInfo.cs b/src/Cake.Core.Tests/Properties/AssemblyInfo.cs index 2795ad1352..3951826175 100644 --- a/src/Cake.Core.Tests/Properties/AssemblyInfo.cs +++ b/src/Cake.Core.Tests/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Reflection; using System.Runtime.InteropServices; @@ -8,9 +9,8 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Cake.Tests")] -[assembly: AssemblyDescription("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/src/Cake.Core.Tests/Properties/Resources.Designer.cs b/src/Cake.Core.Tests/Properties/Resources.Designer.cs index c98becfa03..87946bc36e 100644 --- a/src/Cake.Core.Tests/Properties/Resources.Designer.cs +++ b/src/Cake.Core.Tests/Properties/Resources.Designer.cs @@ -10,8 +10,9 @@ namespace Cake.Core.Tests.Properties { using System; - - + using System.Reflection; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -39,7 +40,7 @@ internal Resources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Cake.Core.Tests.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Cake.Core.Tests.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/Cake.Core.Tests/Stubs/DummySettings.cs b/src/Cake.Core.Tests/Stubs/DummySettings.cs index adb5a35991..48e52d73e6 100644 --- a/src/Cake.Core.Tests/Stubs/DummySettings.cs +++ b/src/Cake.Core.Tests/Stubs/DummySettings.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tooling; namespace Cake.Core.Tests.Stubs @@ -8,4 +9,4 @@ namespace Cake.Core.Tests.Stubs public sealed class DummySettings : ToolSettings { } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Stubs/DummyTool.cs b/src/Cake.Core.Tests/Stubs/DummyTool.cs index 7d13b951b2..697403baeb 100644 --- a/src/Cake.Core.Tests/Stubs/DummyTool.cs +++ b/src/Cake.Core.Tests/Stubs/DummyTool.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using Cake.Core.IO; using Cake.Core.Tooling; @@ -9,17 +11,31 @@ namespace Cake.Core.Tests.Stubs { public sealed class DummyTool : Tool { + private readonly Action _exitCodeValidation; + public DummyTool( IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, - IToolLocator tools) : base(fileSystem, environment, processRunner, tools) + IToolLocator tools, + Action exitCodeValidation) : base(fileSystem, environment, processRunner, tools) { + _exitCodeValidation = exitCodeValidation; } public void Run(DummySettings settings) { - Run(settings, new ProcessArgumentBuilder().Append("--foo")); + Run(settings, new ProcessArgumentBuilder().Append("--foo"), new ProcessSettings(), null); + } + + protected override void ProcessExitCode(int exitCode) + { + if (_exitCodeValidation == null) + { + base.ProcessExitCode(exitCode); + return; + } + _exitCodeValidation(exitCode); } protected override string GetToolName() @@ -32,4 +48,4 @@ protected override IEnumerable GetToolExecutableNames() return new[] { "dummy.exe" }; } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/ActionTaskTests.cs b/src/Cake.Core.Tests/Unit/ActionTaskTests.cs deleted file mode 100644 index b4d10422da..0000000000 --- a/src/Cake.Core.Tests/Unit/ActionTaskTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using Xunit; - -namespace Cake.Core.Tests.Unit -{ - public sealed class ActionTaskTests - { - public sealed class TheAddActionMethod - { - [Fact] - public void Should_Throw_If_Action_Is_Null() - { - // Given - var task = new ActionTask("task"); - - // When - var result = Record.Exception(() => task.AddAction(null)); - - // Then - Assert.IsArgumentNullException(result, "action"); - } - - [Fact] - public void Should_Add_Action_To_Task() - { - // Given - var task = new ActionTask("task"); - - // When - task.AddAction(c => { }); - - // Then - Assert.Equal(1, task.Actions.Count); - } - } - } -} diff --git a/src/Cake.Core.Tests/Unit/Annotations/CakeAliasCategoryAttributeTests.cs b/src/Cake.Core.Tests/Unit/Annotations/CakeAliasCategoryAttributeTests.cs index 50cf60a912..f1cd3af396 100644 --- a/src/Cake.Core.Tests/Unit/Annotations/CakeAliasCategoryAttributeTests.cs +++ b/src/Cake.Core.Tests/Unit/Annotations/CakeAliasCategoryAttributeTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Annotations; using Xunit; @@ -17,8 +18,8 @@ public void Should_Throw_If_Category_Name_Is_Null() var result = Record.Exception(() => new CakeAliasCategoryAttribute(null)); // Then - Assert.IsArgumentNullException(result, "name"); + AssertEx.IsArgumentNullException(result, "name"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Annotations/CakeModuleAttributeTests.cs b/src/Cake.Core.Tests/Unit/Annotations/CakeModuleAttributeTests.cs index f4825b3c80..affd7aecf9 100644 --- a/src/Cake.Core.Tests/Unit/Annotations/CakeModuleAttributeTests.cs +++ b/src/Cake.Core.Tests/Unit/Annotations/CakeModuleAttributeTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Annotations; using Xunit; @@ -15,7 +16,7 @@ public void Should_Throw_If_Module_Type_Is_Null() var result = Record.Exception(() => new CakeModuleAttribute(null)); // Then - Assert.IsArgumentNullException(result, "moduleType"); + AssertEx.IsArgumentNullException(result, "moduleType"); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Annotations/CakeNamespaceImportAttributeTests.cs b/src/Cake.Core.Tests/Unit/Annotations/CakeNamespaceImportAttributeTests.cs index 52288e677e..73d40e1842 100644 --- a/src/Cake.Core.Tests/Unit/Annotations/CakeNamespaceImportAttributeTests.cs +++ b/src/Cake.Core.Tests/Unit/Annotations/CakeNamespaceImportAttributeTests.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Reflection; using Cake.Core.Annotations; using Xunit; @@ -17,8 +20,40 @@ public void Should_Throw_If_Namespace_Is_Null() var result = Record.Exception(() => new CakeNamespaceImportAttribute(null)); // Then - Assert.IsArgumentNullException(result, "namespace"); + AssertEx.IsArgumentNullException(result, "@namespace"); + } + } + + public sealed class TheType + { + private readonly AttributeUsageAttribute _attributeUsage; + + public TheType() + { + // Given, When + _attributeUsage = (AttributeUsageAttribute)typeof(CakeNamespaceImportAttribute).GetTypeInfo().GetCustomAttribute(typeof(AttributeUsageAttribute)); + } + + [Fact] + public void Should_Be_Able_To_Target_Method() + { + // Then + Assert.True(_attributeUsage.ValidOn.HasFlag(AttributeTargets.Method)); + } + + [Fact] + public void Should_Be_Able_To_Target_Class() + { + // Then + Assert.True(_attributeUsage.ValidOn.HasFlag(AttributeTargets.Class)); + } + + [Fact] + public void Should_Be_Able_To_Target_Assembly() + { + // Then + Assert.True(_attributeUsage.ValidOn.HasFlag(AttributeTargets.Assembly)); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Annotations/CakePropertyAliasAttributeTests.cs b/src/Cake.Core.Tests/Unit/Annotations/CakePropertyAliasAttributeTests.cs index f76804a339..a4dcef530a 100644 --- a/src/Cake.Core.Tests/Unit/Annotations/CakePropertyAliasAttributeTests.cs +++ b/src/Cake.Core.Tests/Unit/Annotations/CakePropertyAliasAttributeTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Annotations; using Xunit; @@ -21,4 +22,4 @@ public void Should_Default_Cache_Property_To_False() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeArgumentsTests.cs b/src/Cake.Core.Tests/Unit/CakeArgumentsTests.cs new file mode 100644 index 0000000000..a21ce52fea --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeArgumentsTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class CakeArgumentsTests + { + [Fact] + public void Should_Return_Null_If_Argument_Is_Missing() + { + // Given + var arguments = new CakeArguments(new List> + { + Tuple.Create("FOO", "BAR"), + Tuple.Create("BAZ", "CORGI"), + }.ToLookup(x => x.Item1, x => x.Item2)); + + // When + var result = arguments.GetArgument("WALDO"); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Return_Argument_If_One_With_The_Specified_Name_Exist() + { + // Given + var arguments = new CakeArguments(new List> + { + Tuple.Create("FOO", "BAR"), + Tuple.Create("BAZ", "CORGI"), + }.ToLookup(x => x.Item1, x => x.Item2)); + + // When + var result = arguments.GetArgument("BAZ"); + + // Then + Assert.Equal("CORGI", result); + } + + [Fact] + public void Should_Return_The_Last_Argument_If_Multiple_Ones_With_The_Same_Name_Exist() + { + // Given + var arguments = new CakeArguments(new List> + { + Tuple.Create("FOO", "BAR"), + Tuple.Create("FOO", "BAZ"), + }.ToLookup(x => x.Item1, x => x.Item2)); + + // When + var result = arguments.GetArgument("FOO"); + + // Then + Assert.Equal("BAZ", result); + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeContextTests.cs b/src/Cake.Core.Tests/Unit/CakeContextTests.cs index d0debc6c5d..1a88260a04 100644 --- a/src/Cake.Core.Tests/Unit/CakeContextTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeContextTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tests.Fixtures; using Xunit; @@ -21,7 +22,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -35,7 +36,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -49,7 +50,7 @@ public void Should_Throw_If_Globber_Is_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "globber"); + AssertEx.IsArgumentNullException(result, "globber"); } [Fact] @@ -63,7 +64,7 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "log"); } [Fact] @@ -77,7 +78,7 @@ public void Should_Throw_If_Arguments_Are_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "arguments"); + AssertEx.IsArgumentNullException(result, "arguments"); } [Fact] @@ -91,7 +92,21 @@ public void Should_Throw_If_Process_Runner_Is_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "processRunner"); + AssertEx.IsArgumentNullException(result, "processRunner"); + } + + [Fact] + public void Should_Throw_If_Registry_Is_Null() + { + // Given + var fixture = new CakeContextFixture(); + fixture.Registry = null; + + // When + var result = Record.Exception(() => fixture.CreateContext()); + + // Then + AssertEx.IsArgumentNullException(result, "registry"); } [Fact] @@ -105,7 +120,21 @@ public void Should_Throw_If_Tools_Are_Null() var result = Record.Exception(() => fixture.CreateContext()); // Then - Assert.IsArgumentNullException(result, "tools"); + AssertEx.IsArgumentNullException(result, "tools"); + } + + [Fact] + public void Should_Throw_If_Configuration_Is_Null() + { + // Given + var fixture = new CakeContextFixture(); + fixture.Configuration = null; + + // When + var result = Record.Exception(() => fixture.CreateContext()); + + // Then + AssertEx.IsArgumentNullException(result, "configuration"); } } @@ -194,6 +223,20 @@ public void Should_Return_Provided_Process_Runner() // Then Assert.Same(fixture.ProcessRunner, processRunner); } + + [Fact] + public void Should_Return_Provided_Configuration() + { + // Given + var fixture = new CakeContextFixture(); + var context = fixture.CreateContext(); + + // When + var configuration = context.Configuration; + + // Then + Assert.Same(fixture.Configuration, configuration); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeEngineActionsTests.cs b/src/Cake.Core.Tests/Unit/CakeEngineActionsTests.cs new file mode 100644 index 0000000000..c54d6b00c6 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeEngineActionsTests.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class CakeEngineActionsTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Data_Service_Is_Null() + { + // Given, When + var result = Record.Exception(() => new CakeEngineActions(null)); + + // Then + AssertEx.IsArgumentNullException(result, "data"); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/CakeEngineTests.cs b/src/Cake.Core.Tests/Unit/CakeEngineTests.cs index a4221edfa3..b8245ded69 100644 --- a/src/Cake.Core.Tests/Unit/CakeEngineTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeEngineTests.cs @@ -1,12 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; using Cake.Core.Tests.Fixtures; using Xunit; -using NSubstitute; namespace Cake.Core.Tests.Unit { @@ -14,6 +16,19 @@ public sealed class CakeEngineTests { public sealed class TheConstructor { + [Fact] + public void Should_Throw_If_Data_Service_Is_Null() + { + // Given + var fixture = new CakeEngineFixture { DataService = null }; + + // When + var result = Record.Exception(() => fixture.CreateEngine()); + + // Then + AssertEx.IsArgumentNullException(result, "data"); + } + [Fact] public void Should_Throw_If_Log_Is_Null() { @@ -24,7 +39,7 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => fixture.CreateEngine()); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "log"); } } @@ -41,7 +56,7 @@ public void Should_Return_A_New_Task() // Then Assert.NotNull(result); - Assert.Equal("task", result.Task.Name); + Assert.Equal("task", result.Target.Name); } [Fact] @@ -54,7 +69,7 @@ public void Should_Register_Created_Task() var result = engine.RegisterTask("task"); // Then - Assert.True(engine.Tasks.Contains(result.Task)); + Assert.Contains(result.Target, engine.Tasks); } [Fact] @@ -69,7 +84,7 @@ public void Should_Throw_If_Another_Task_With_The_Same_Name_Already_Been_Added() // Then Assert.IsType(result); - Assert.Equal("Another task with the name 'task' has already been added.", result.Message); + Assert.Equal("Another task with the name 'task' has already been added.", result?.Message); } [Fact] @@ -84,683 +99,1782 @@ public void Should_Throw_If_Another_Task_With_The_Same_Name_Already_Been_Added_R // Then Assert.IsType(result); - Assert.Equal("Another task with the name 'TASK' has already been added.", result.Message); + Assert.Equal("Another task with the name 'TASK' has already been added.", result?.Message); } } - public sealed class TheRunTargetMethod + public sealed class TheRunTargetAsyncMethod { - public sealed class WithTarget + [Fact] + public async Task Should_Throw_If_Execution_Settings_Is_Null() { - [Fact] - public void Should_Throw_If_Target_Is_Null() - { - // Given - var fixture = new CakeEngineFixture(); - var engine = fixture.CreateEngine(); + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); - // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, null)); + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, null)); - // Then - Assert.IsArgumentNullException(result, "target"); - } + // Then + AssertEx.IsArgumentNullException(result, "settings"); } - public sealed class WithExecutionStrategy + [Fact] + public async Task Should_Throw_If_Execution_Strategy_Is_Null() { - [Fact] - public void Should_Throw_If_Target_Is_Null() - { - // Given - var fixture = new CakeEngineFixture(); - var engine = fixture.CreateEngine(); + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); - // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, null)); + // When + var result = await Record.ExceptionAsync(() => engine.RunTargetAsync(fixture.Context, null, settings)); - // Then - Assert.IsArgumentNullException(result, "target"); - } + // Then + AssertEx.IsArgumentNullException(result, "strategy"); + } - [Fact] - public void Should_Throw_If_Execution_Strategy_Is_Null() - { - // Given - var fixture = new CakeEngineFixture(); - var engine = fixture.CreateEngine(); + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t ")] + public async Task Should_Throw_If_Target_Is_Not_Specified(string target) + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget(target); - // When - var result = Record.Exception(() => engine.RunTarget(fixture.Context, null, "A")); + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); - // Then - Assert.IsArgumentNullException(result, "strategy"); - } + // Then + AssertEx.IsArgumentException(result, "settings", "No target specified."); } [Fact] - public void Should_Execute_Tasks_In_Order() + public async Task Should_Execute_Tasks_In_Order() { // Given var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("E"); var engine = fixture.CreateEngine(); engine.RegisterTask("A").Does(() => result.Add("A")); engine.RegisterTask("B").IsDependentOn("A").Does(() => result.Add("B")); engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); + engine.RegisterTask("D").IsDependentOn("C").IsDependeeOf("E").Does(() => { result.Add("D"); }); + engine.RegisterTask("E").Does(() => { result.Add("E"); }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(3, result.Count); + Assert.Equal(5, result.Count); Assert.Equal("A", result[0]); Assert.Equal("B", result[1]); Assert.Equal("C", result[2]); + Assert.Equal("D", result[3]); + Assert.Equal("E", result[4]); } [Fact] - public void Should_Skip_Tasks_Where_Boolean_Criterias_Are_Not_Fulfilled() + public async Task Should_Only_Execute_Target_Task_In_Exclusive_Mode() { // Given var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C").UseExclusiveTarget(); var engine = fixture.CreateEngine(); engine.RegisterTask("A").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => false).Does(() => result.Add("B")); + engine.RegisterTask("B").IsDependentOn("A").Does(() => result.Add("B")); engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); + engine.RegisterTask("D").IsDependentOn("C").IsDependeeOf("E").Does(() => { result.Add("D"); }); + engine.RegisterTask("E").Does(() => { result.Add("E"); }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(2, result.Count); - Assert.Equal("A", result[0]); - Assert.Equal("C", result[1]); + Assert.Single(result); + Assert.Equal("C", result[0]); } [Fact] - public void Should_Not_Skip_Tasks_Where_Boolean_Criterias_Are_Fulfilled() + public async Task Should_Throw_If_Target_Task_Is_Skipped() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => true).Does(() => result.Add("B")); - engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); + engine.RegisterTask("A").WithCriteria(() => false).Does(() => { }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(3, result.Count); - Assert.Equal("A", result[0]); - Assert.Equal("B", result[1]); - Assert.Equal("C", result[2]); + Assert.IsType(result); + Assert.Equal("Could not reach target 'A' since it was skipped due to a criteria.", result?.Message); } [Fact] - public void Should_Skip_Tasks_Where_CakeContext_Criterias_Are_Not_Fulfilled() + public async Task Should_Skip_Tasks_Where_Boolean_Criterias_Are_Not_Fulfilled() { // Given var result = new List(); var fixture = new CakeEngineFixture(); - fixture.Context.Environment.IsUnix().Returns(false); - + var settings = new ExecutionSettings().SetTarget("C"); var engine = fixture.CreateEngine(); engine.RegisterTask("A").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("A").WithCriteria(context => context.Environment.IsUnix()).Does(() => result.Add("B")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => false).Does(() => result.Add("B")); engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then Assert.Equal(2, result.Count); Assert.Equal("A", result[0]); Assert.Equal("C", result[1]); + Assert.Contains(fixture.Log.Entries, e => e.Message == "Skipping task: B"); } [Fact] - public void Should_Not_Skip_Tasks_Where_CakeContext_Criterias_Are_Fulfilled() + public async Task Should_Only_Write_Single_Skipped_Entry_To_Report_If_Multiple_Boolean_Criterias_Evaluated_To_False() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); - fixture.Context.Environment.IsUnix().Returns(true); - var engine = fixture.CreateEngine(); - engine.RegisterTask("A").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("A").WithCriteria(context => context.Environment.IsUnix()).Does(() => result.Add("B")); - engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); + var settings = new ExecutionSettings().SetTarget("Default"); + + engine.RegisterTask("Default").IsDependentOn("A"); + engine.RegisterTask("A") + .WithCriteria(() => false) + .WithCriteria(() => false, "Foo") + .WithCriteria(context => false) + .WithCriteria(context => false, "Bar") + .WithCriteria((context, data) => false) + .WithCriteria((context, data) => false, "Baz"); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + var result = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + var entries = result.ToList(); // Then - Assert.Equal(3, result.Count); - Assert.Equal("A", result[0]); - Assert.Equal("B", result[1]); - Assert.Equal("C", result[2]); + Assert.Equal(2, entries.Count); + Assert.Equal("A", entries[0].TaskName); + Assert.Equal(CakeTaskExecutionStatus.Skipped, entries[0].ExecutionStatus); + Assert.Equal("Default", entries[1].TaskName); + Assert.Equal(CakeTaskExecutionStatus.Delegated, entries[1].ExecutionStatus); } [Fact] - public void Should_Throw_If_Target_Was_Not_Found() + public async Task Should_Skip_Tasks_Where_Boolean_Criterias_Are_Not_Fulfilled_And_Write_Reason_To_Log() { // Given + var result = new List(); var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("C"); + + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => false, "Foo").Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "Run-Some-Tests")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.IsType(result); - Assert.Equal("The target 'Run-Some-Tests' was not found.", result.Message); + Assert.Equal(2, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("C", result[1]); + Assert.Contains(fixture.Log.Entries, e => e.Message == "Skipping task: Foo"); } [Fact] - public void Should_Not_Catch_Exceptions_From_Task_If_ContinueOnError_Is_Not_Set() + public async Task Should_Skip_Tasks_Where_Boolean_Criterias_Are_Not_Fulfilled_Async() { // Given + var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Whoopsie"); }); + engine.RegisterTask("A").Does(() => + { + result.Add("A"); + return Task.CompletedTask; + }); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => false).Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.IsType(result); - Assert.Equal("Whoopsie", result.Message); + Assert.Equal(2, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("C", result[1]); + Assert.Contains(fixture.Log.Entries, e => e.Message == "Skipping task: B"); } [Fact] - public void Should_Swallow_Exceptions_If_ContinueOnError_Is_Set() + public async Task Should_Not_Skip_Tasks_Where_Boolean_Criterias_Are_Fulfilled() { // Given + var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").ContinueOnError().Does(() => { throw new InvalidOperationException(); }); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(() => true).Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); - // When, Then - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(3, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("B", result[1]); + Assert.Equal("C", result[2]); } [Fact] - public void Should_Invoke_Task_Error_Handler_If_Exception_Is_Thrown() + public async Task Should_Skip_Tasks_Where_CakeContext_Criterias_Are_Not_Fulfilled() { // Given - var invoked = false; + var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); + var engine = fixture.CreateEngine(); - engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Whoopsie"); }) - .OnError(exception => { invoked = true; }); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(context => false).Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.True(invoked); + Assert.Equal(2, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("C", result[1]); + Assert.Contains(fixture.Log.Entries, e => e.Message == "Skipping task: B"); } [Fact] - public void Should_Propagate_Exception_From_Error_Handler() + public async Task Should_Skip_Tasks_Where_CakeContext_Criterias_Are_Not_Fulfilled_And_Write_Reason_To_Log() { // Given + var result = new List(); var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Whoopsie"); }) - .OnError(exception => { throw new InvalidOperationException("Totally my fault"); }); + var settings = new ExecutionSettings().SetTarget("C"); + + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(context => false, "Foo").Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.IsType(result); - Assert.Equal("Totally my fault", result.Message); + Assert.Equal(2, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("C", result[1]); + Assert.Contains(fixture.Log.Entries, e => e.Message == "Skipping task: Foo"); } [Fact] - public void Should_Log_Exception_Handled_By_Error_Handler() + public async Task Should_Not_Skip_Tasks_Where_CakeContext_Criterias_Are_Fulfilled() { // Given + var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); + var engine = fixture.CreateEngine(); - engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Whoops"); }) - .OnError(exception => { throw new InvalidOperationException("Totally my fault"); }); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(context => true).Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); // When - Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.True(fixture.Log.Entries.Any(x => x.Message == "Error: Whoops")); + Assert.Equal(3, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("B", result[1]); + Assert.Equal("C", result[2]); } [Fact] - public void Should_Throw_If_Target_Cannot_Be_Reached_Due_To_Constraint() + public async Task Should_Throw_If_Target_Was_Not_Found() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("Run-Some-Tests"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A"); - engine.RegisterTask("B").IsDependentOn("A").WithCriteria(false); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "B")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then Assert.IsType(result); - Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result.Message); + Assert.Equal("The target 'Run-Some-Tests' was not found.", result?.Message); } [Fact] - public void Should_Run_Setup_Before_First_Task() + public async Task Should_Not_Catch_Exceptions_From_Task_If_ContinueOnError_Is_Not_Set() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterSetupAction(context => result.Add("Setup")); - engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Whoopsie"); }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(2, result.Count); - Assert.Equal("Setup", result[0]); + Assert.IsType(result); + Assert.Equal("Whoopsie", result?.Message); } [Fact] - public void Should_Not_Run_Tasks_If_Setup_Failed() + public async Task Should_Swallow_Exceptions_If_ContinueOnError_Is_Set() { // Given - var runTask = false; var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); + engine.RegisterTask("A").ContinueOnError().Does(() => { throw new InvalidOperationException(); }); - engine.RegisterSetupAction(context => { throw new InvalidOperationException("Fail"); }); - engine.RegisterTask("A").Does(() => runTask = true); + // When, Then + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + } + + [Fact] + public void Should_Not_Throw_If_More_Than_One_Setup_Action_Is_Registered() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + engine.RegisterSetupAction(context => { }); // When - Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Record.Exception(() => engine.RegisterSetupAction(context => { })); // Then - Assert.False(runTask, "Task A was executed although it shouldn't have been."); - Assert.True(fixture.Log.Entries.Any(x => x.Message == "Executing custom setup action...")); + Assert.Null(result); } [Fact] - public void Should_Run_Teardown_After_Last_Running_Task() + public void Should_Throw_If_More_Than_One_Setup_Action_With_Same_Data_Type_Is_Registered() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - - engine.RegisterSetupAction(context => result.Add("Setup")); - engine.RegisterTeardownAction(context => result.Add("Teardown")); - engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterSetupAction(context => "foo"); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + var result = Record.Exception(() => engine.RegisterSetupAction(context => "bar")); // Then - Assert.Equal(3, result.Count); - Assert.Equal("Teardown", result[2]); + Assert.IsType(result); + Assert.Equal("More than one setup action have been registered that accepts data of type 'System.String'.", result.Message); } [Fact] - public void Should_Run_Teardown_After_Last_Running_Task_Even_If_Task_Failed() + public void Should_Not_Throw_If_More_Than_One_Teardown_Action_Is_Registered() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - - engine.RegisterSetupAction(context => { }); engine.RegisterTeardownAction(context => { }); - engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Fail"); }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Record.Exception(() => engine.RegisterTeardownAction(context => { })); // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Fail", result.Message); - Assert.True(fixture.Log.Entries.Any(x => x.Message == "Executing custom teardown action...")); + Assert.Null(result); } [Fact] - public void Should_Run_Teardown_If_Setup_Failed() + public void Should_Throw_If_More_Than_One_Task_Setup_Action_Is_Registered() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - - engine.RegisterSetupAction(context => { throw new InvalidOperationException("Fail"); }); - engine.RegisterTeardownAction(context => { }); - engine.RegisterTask("A").Does(() => { }); + engine.RegisterTaskSetupAction(context => { }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Record.Exception(() => engine.RegisterTaskSetupAction(context => { })); // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Fail", result.Message); - Assert.True(fixture.Log.Entries.Any(x => x.Message == "Executing custom teardown action...")); + Assert.IsType(result); + Assert.Equal("More than one task setup action have been registered.", result.Message); } [Fact] - public void Should_Throw_Exception_Thrown_From_Setup_Action_If_Both_Setup_And_Teardown_Actions_Throw() + public async Task Should_Throw_If_Registering_Teardown_With_Non_Registered_Data_Type() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); - engine.RegisterSetupAction(context => { throw new InvalidOperationException("Setup"); }); - engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => { }); + engine.RegisterTeardownAction((context, data) => { }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Setup", result.Message); + Assert.IsType(result); + Assert.Equal("Trying to register a teardown action that accepts data of " + + "type 'System.String', but no such data has been setup.", result.Message); } [Fact] - public void Should_Throw_Exception_Occuring_In_Teardown_If_No_Previous_Exception_Was_Thrown() + public async Task Should_Throw_If_Registering_Task_Setup_With_Non_Registered_Data_Type() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - var expected = new InvalidOperationException("Teardown"); + var settings = new ExecutionSettings().SetTarget("A"); - engine.RegisterTeardownAction(context => { throw expected; }); engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => { }); + engine.RegisterTaskSetupAction((context, data) => { }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(expected, result); + Assert.IsType(result); + Assert.Equal("Trying to register a task setup action that accepts data of " + + "type 'System.String', but no such data has been setup.", result.Message); } [Fact] - public void Should_Log_Teardown_Exception_If_Both_Setup_And_Teardown_Actions_Throw() + public async Task Should_Throw_If_Registering_Task_Teardown_With_Non_Registered_Data_Type() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); - engine.RegisterSetupAction(context => { throw new InvalidOperationException("Setup"); }); - engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => { }); + engine.RegisterTaskTeardownAction((context, data) => { }); // When - Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.True(fixture.Log.Entries.Any(x => x.Message.StartsWith("Teardown error:"))); + Assert.IsType(result); + Assert.Equal("Trying to register a task teardown action that accepts data of " + + "type 'System.String', but no such data has been setup.", result.Message); } [Fact] - public void Should_Exception_Thrown_From_Task_If_Both_Task_And_Teardown_Actions_Throw() + public async Task Should_Throw_If_Registering_Teardown_Action_With_Wrong_Registered_Data_Type() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); - engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); - engine.RegisterTask("A").Does(context => { throw new InvalidOperationException("Task"); }); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => string.Empty); + engine.RegisterTeardownAction((context, data) => { }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Task", result.Message); + Assert.IsType(result); + Assert.IsType(result); + Assert.Equal("Trying to register a teardown action that accepts data of " + + "type 'Cake.Core.CakeTask', but no such data has been setup.", result.Message); } [Fact] - public void Should_Log_Teardown_Exception_If_Both_Task_And_Teardown_Actions_Throw() + public async Task Should_Throw_If_Registering_Task_Setup_Action_With_Wrong_Registered_Data_Type() { // Given var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); - engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); - engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Task"); }); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => string.Empty); + engine.RegisterTaskSetupAction((context, data) => { }); // When - Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.True(fixture.Log.Entries.Any(x => x.Message.StartsWith("Teardown error:"))); + Assert.IsType(result); + Assert.Equal("Trying to register a task setup action that accepts data of " + + "type 'Cake.Core.CakeTask', but no such data has been setup.", result.Message); } [Fact] - public void Should_Execute_Finally_Handler_If_Task_Succeeds() + public async Task Should_Throw_If_Registering_Task_Teardown_Action_With_Wrong_Registered_Data_Type() { // Given - var invoked = false; var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - engine.RegisterTask("A") - .Finally(() => invoked = true); + var settings = new ExecutionSettings().SetTarget("A"); + + engine.RegisterTask("A").Does(() => { }); + engine.RegisterSetupAction(context => string.Empty); + engine.RegisterTaskTeardownAction((context, data) => { }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.True(invoked); + Assert.IsType(result); + Assert.Equal("Trying to register a task teardown action that accepts data of " + + "type 'Cake.Core.CakeTask', but no such data has been setup.", result.Message); } [Fact] - public void Should_Execute_Finally_Handler_If_Task_Throws() + public void Should_Throw_If_More_Than_One_Task_Teardown_Action_Is_Registered() { // Given - var invoked = false; var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - - engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Whoopsie"); }) - .ContinueOnError() - .Finally(() => invoked = true); + engine.RegisterTaskTeardownAction(context => { }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + var result = Record.Exception(() => engine.RegisterTaskTeardownAction(context => { })); // Then - Assert.True(invoked); + Assert.IsType(result); + Assert.Equal("More than one task teardown action have been registered.", result.Message); } [Fact] - public void Should_Execute_Finally_Handler_After_Error_Handler_If_Task_Succeeds() + public async Task Should_Invoke_Task_Error_Handler_If_Exception_Is_Thrown() { // Given - var result = new List(); + var invoked = false; var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); engine.RegisterTask("A") .Does(() => { throw new InvalidOperationException("Whoopsie"); }) - .OnError(() => result.Add("Error")) - .Finally(() => result.Add("Finally")); + .OnError(exception => { invoked = true; }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + await Record.ExceptionAsync(() => engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(2, result.Count); - Assert.Equal("Error", result[0]); + Assert.True(invoked); } [Fact] - public void Should_Execute_Error_Reporter_Before_Error_Handler_If_Task_Succeeds() + public async Task Should_Propagate_Exception_From_Error_Handler() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); engine.RegisterTask("A") .Does(() => { throw new InvalidOperationException("Whoopsie"); }) - .OnError(ex => result.Add("Error")) - .ReportError(ex => result.Add("Report")); + .OnError(exception => { throw new InvalidOperationException("Totally my fault"); }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(2, result.Count); - Assert.Equal("Report", result[0]); + Assert.IsType(result); + Assert.Equal("Totally my fault", result?.Message); } [Fact] - public void Should_Swallow_Exceptions_Thrown_In_Error_Reporter() + public async Task Should_Log_Exception_Handled_By_Error_Handler() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Task"); }) - .ReportError(ex => { throw new InvalidOperationException("Report"); }); + .Does(() => { throw new InvalidOperationException("Whoops"); }) + .OnError(exception => { throw new InvalidOperationException("Totally my fault"); }); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal("Task", result.Message); + Assert.Contains(fixture.Log.Entries, x => x.Message == "Error: Whoops"); } [Fact] - public void Should_Execute_Error_Handler_Even_If_Exception_Was_Thrown_In_Error_Reporter() + public async Task Should_Throw_If_Target_Cannot_Be_Reached_Due_To_Constraint() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A") - .Does(() => { throw new InvalidOperationException("Task"); }) - .OnError(() => { throw new InvalidOperationException("Error"); }) - .ReportError(ex => { throw new InvalidOperationException("Report"); }); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A").WithCriteria(false); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal("Error", result.Message); + Assert.IsType(result); + Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message); } [Fact] - public void Should_Run_Task_Setup_Before_Each_Task() + public async Task Should_Run_Setup_Before_First_Task() { // Given var result = new List(); + var settings = new ExecutionSettings().SetTarget("A"); var fixture = new CakeEngineFixture(); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => result.Add("TASK_SETUP:" + sc.Task.Name)); - engine.RegisterTask("A").Does(()=>result.Add("Executing A")); - engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + engine.RegisterSetupAction(context => result.Add("Setup")); + engine.RegisterTask("A").Does(() => result.Add("A")); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "B"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(new List { "TASK_SETUP:A", "Executing A", "TASK_SETUP:B", "Executing B" }, result); + Assert.Equal(2, result.Count); + Assert.Equal("Setup", result[0]); } [Fact] - public void Should_Not_Run_Task_If_Task_Setup_Failed() + public async Task Should_Not_Run_Tasks_If_Setup_Failed() { // Given - var result = new List(); + var runTask = false; var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => { throw new Exception("fake exception"); }); - engine.RegisterTask("A").Does(() => result.Add("Executing A")); - engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + + engine.RegisterSetupAction(context => { throw new InvalidOperationException("Fail"); }); + engine.RegisterTask("A").Does(() => runTask = true); // When - Record.Exception(() => engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "B")); + await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(new List(), result); - - Assert.True(fixture.Log.Entries.Any(x => x.Message == "Executing custom task setup action (A)...")); - Assert.False(fixture.Log.Entries.Any(x => x.Message == "Executing custom task setup action (B)...")); + Assert.False(runTask, "Task A was executed although it shouldn't have been."); + Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom setup action..."); } [Fact] - public void Should_Run_Task_Teardown_After_Each_Running_Task() + public async Task Should_Run_Teardown_After_Last_Running_Task() { // Given var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => result.Add("TASK_SETUP:" + sc.Task.Name)); - engine.RegisterTaskTeardownAction((cc, sc) => result.Add("TASK_TEARDOWN:" + sc.Task.Name)); - engine.RegisterTask("A").Does(() => result.Add("Executing A")); - engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + + engine.RegisterSetupAction(context => result.Add("Setup")); + engine.RegisterTeardownAction(context => result.Add("Teardown")); + engine.RegisterTask("A").Does(() => result.Add("A")); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "B"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(new List { "TASK_SETUP:A", "Executing A", "TASK_TEARDOWN:A", "TASK_SETUP:B", "Executing B", "TASK_TEARDOWN:B" }, result); + Assert.Equal(3, result.Count); + Assert.Equal("Teardown", result[2]); } [Fact] - public void Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_Is_Skipped() + public async Task Should_Run_Teardown_After_Last_Running_Task_Even_If_Task_Failed() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => result.Add("TASK_SETUP:" + sc.Task.Name)); - engine.RegisterTaskTeardownAction((cc, sc) => result.Add("TASK_TEARDOWN:" + sc.Task.Name)); - engine.RegisterTask("A").Does(() => result.Add("Executing A")); - engine.RegisterTask("B").Does(() => result.Add("Executing B")).WithCriteria(() => false).IsDependentOn("A"); - engine.RegisterTask("C").Does(() => result.Add("Executing C")).IsDependentOn("B"); + + engine.RegisterSetupAction(context => { }); + engine.RegisterTeardownAction(context => { }); + engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Fail"); }); // When - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal( + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Fail", result?.Message); + Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action..."); + } + + [Fact] + public async Task Should_Run_Teardown_If_Setup_Failed() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterSetupAction(context => { throw new InvalidOperationException("Fail"); }); + engine.RegisterTeardownAction(context => { }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Fail", result?.Message); + Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom teardown action..."); + } + + [Fact] + public async Task Should_Throw_Exception_Thrown_From_Setup_Action_If_Both_Setup_And_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterSetupAction(context => { throw new InvalidOperationException("Setup"); }); + engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Setup", result?.Message); + } + + [Fact] + public async Task Should_Throw_Exception_Occurring_In_Teardown_If_No_Previous_Exception_Was_Thrown() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + var expected = new InvalidOperationException("Teardown"); + + engine.RegisterTeardownAction(context => { throw expected; }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.Equal(expected, result); + } + + [Fact] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public async Task Should_Throw_Aggregated_Exception_If_Multiple_Teardown_Methods_Throw_And_If_No_Previous_Exception_Was_Thrown() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); + + engine.RegisterTeardownAction(context => throw new InvalidOperationException("Foo")); + engine.RegisterTeardownAction(context => throw new InvalidOperationException("Bar")); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + var ex = Assert.IsType(result); + Assert.Equal(2, ex.InnerExceptions.Count); + Assert.Equal("Foo", ex.InnerExceptions[0].Message); + Assert.Equal("Bar", ex.InnerExceptions[1].Message); + } + + [Fact] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public async Task Should_Throw_Single_Exception_If_Only_One_Of_Multiple_Teardown_Methods_Throw_And_If_No_Previous_Exception_Was_Thrown() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); + + engine.RegisterTeardownAction(context => { }); + engine.RegisterTeardownAction(context => throw new InvalidOperationException("Foo")); + engine.RegisterTeardownAction(context => { }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + var ex = Assert.IsType(result); + Assert.Equal("Foo", ex.Message); + } + + [Fact] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public async Task Should_Execute_All_Teardown_Methods_Even_If_One_Or_More_Throws() + { + // Given + var captured = new List(); + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); + + engine.RegisterTeardownAction(context => { captured.Add("First"); }); + engine.RegisterTeardownAction(context => throw new InvalidOperationException("Foo")); + engine.RegisterTeardownAction(context => { captured.Add("Third"); }); + engine.RegisterTeardownAction(context => throw new InvalidOperationException("Bar")); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.Contains("First", captured); + Assert.Contains("Third", captured); + } + + [Fact] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public async Task Should_Log_Teardown_Exception_If_Both_Setup_And_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterSetupAction(context => throw new InvalidOperationException("Setup")); + engine.RegisterTeardownAction(context => throw new CakeException("Teardown #1")); + engine.RegisterTeardownAction(context => throw new CakeException("Teardown #2")); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #1")); + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #2")); + } + + [Fact] + public async Task Should_Throw_Exception_Thrown_From_Task_If_Both_Task_And_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTeardownAction(context => { throw new InvalidOperationException("Teardown"); }); + engine.RegisterTask("A").Does(context => { throw new InvalidOperationException("Task"); }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Task", result?.Message); + } + + [Fact] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public async Task Should_Log_Teardown_Exception_If_Both_Task_And_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTarget("A"); + + engine.RegisterTask("A").Does(() => throw new InvalidOperationException("Task")); + engine.RegisterTeardownAction(context => throw new CakeException("Teardown #1")); + engine.RegisterTeardownAction(context => throw new CakeException("Teardown #2")); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #1")); + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Teardown error: Teardown #2")); + } + + [Fact] + public async Task Should_Execute_Finally_Handler_If_Task_Succeeds() + { + // Given + var invoked = false; + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A") + .Finally(() => invoked = true); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.True(invoked); + } + + [Fact] + public async Task Should_Execute_Finally_Handler_If_Task_Throws() + { + // Given + var invoked = false; + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTask("A") + .Does(() => { throw new InvalidOperationException("Whoopsie"); }) + .ContinueOnError() + .Finally(() => invoked = true); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.True(invoked); + } + + [Fact] + public async Task Should_Execute_Finally_Handler_After_Error_Handler_If_Task_Succeeds() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A") + .Does(() => { throw new InvalidOperationException("Whoopsie"); }) + .OnError(() => result.Add("Error")) + .Finally(() => result.Add("Finally")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, result.Count); + Assert.Equal("Error", result[0]); + } + + [Fact] + public async Task Should_Execute_Error_Reporter_Before_Error_Handler_If_Task_Succeeds() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A") + .Does(() => { throw new InvalidOperationException("Whoopsie"); }) + .OnError(ex => result.Add("Error")) + .ReportError(ex => result.Add("Report")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, result.Count); + Assert.Equal("Report", result[0]); + } + + [Fact] + public async Task Should_Swallow_Exceptions_Thrown_In_Error_Reporter() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A") + .Does(() => { throw new InvalidOperationException("Task"); }) + .ReportError(ex => { throw new InvalidOperationException("Report"); }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.Equal("Task", result?.Message); + } + + [Fact] + public async Task Should_Execute_Error_Handler_Even_If_Exception_Was_Thrown_In_Error_Reporter() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A") + .Does(() => { throw new InvalidOperationException("Task"); }) + .OnError(() => { throw new InvalidOperationException("Error"); }) + .ReportError(ex => { throw new InvalidOperationException("Report"); }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.Equal("Error", result?.Message); + } + + [Fact] + public async Task Should_Run_Task_Setup_Before_Each_Task() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_SETUP:A", + "Executing A", + "TASK_SETUP:B", + "Executing B" + }, result); + } + + [Fact] + public async Task Should_Not_Run_Task_If_Task_Setup_Failed() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => { throw new Exception("fake exception"); }); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + + // When + await Record.ExceptionAsync(() => engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.Equal(new List(), result); + + Assert.Contains(fixture.Log.Entries, x => x.Message == "Executing custom task setup action (A)..."); + Assert.DoesNotContain(fixture.Log.Entries, x => x.Message == "Executing custom task setup action (B)..."); + } + + [Fact] + public async Task Should_Run_Task_Teardown_After_Each_Running_Task() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTaskTeardownAction(context => result.Add("TASK_TEARDOWN:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_SETUP:A", + "Executing A", + "TASK_TEARDOWN:A", + "TASK_SETUP:B", + "Executing B", + "TASK_TEARDOWN:B" + }, result); + } + + [Fact] + public async Task Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_Is_Skipped() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTaskTeardownAction(context => result.Add("TASK_TEARDOWN:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B") + .Does(() => result.Add("Executing B")) + .WithCriteria(() => false) + .IsDependentOn("A"); + engine.RegisterTask("C").Does(() => result.Add("Executing C")).IsDependentOn("B"); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_SETUP:A", + "Executing A", + "TASK_TEARDOWN:A", + "TASK_SETUP:B", + "TASK_TEARDOWN:B", + "TASK_SETUP:C", + "Executing C", + "TASK_TEARDOWN:C" + }, result); + } + + [Fact] + public async Task Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_Failed() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTaskTeardownAction(context => result.Add("TASK_TEARDOWN:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => + { + result.Add("FAILING (A)"); + throw new InvalidOperationException("Fail"); + }); + + // When + var exception = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(exception); + Assert.IsType(exception); + Assert.Equal("Fail", exception?.Message); + Assert.Equal( + new List + { + "TASK_SETUP:A", + "FAILING (A)", + "TASK_TEARDOWN:A" + }, result); + } + + [Fact] + public async Task Should_Run_Task_Teardown_If_Task_Setup_Failed() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => + { + throw new InvalidOperationException("Fail"); + }); + engine.RegisterTaskTeardownAction(context => result.Add("TASK_TEARDOWN:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => + { + result.Add("Executing A"); + }); + + // When + var exception = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(exception); + Assert.IsType(exception); + Assert.Equal("Fail", exception?.Message); + Assert.Equal( + new List + { + "TASK_TEARDOWN:A" + }, result); + } + + [Fact] + public async Task Should_Throw_Exception_Thrown_From_Task_Setup_Action_If_Both_Task_Setup_And_Task_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTaskSetupAction( + context => { throw new InvalidOperationException("Task Setup: " + context.Task.Name); }); + engine.RegisterTaskTeardownAction( + context => { throw new InvalidOperationException("Task Teardown: " + context.Task.Name); }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Task Setup: A", result?.Message); + } + + [Fact] + public async Task Should_Throw_Exception_Occurring_In_Task_Teardown_If_No_Previous_Exception_Was_Thrown() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTaskTeardownAction( + context => { throw new InvalidOperationException("Task Teardown: " + context.Task.Name); }); + engine.RegisterTask("A"); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Task Teardown: A", result?.Message); + } + + [Fact] + public async Task Should_Log_Task_Teardown_Exception_If_Both_Task_Setup_And_Task_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTaskSetupAction( + context => { throw new InvalidOperationException("Task Setup: " + context.Task.Name); }); + engine.RegisterTaskTeardownAction( + context => { throw new InvalidOperationException("Task Teardown: " + context.Task.Name); }); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Task Setup: A", result?.Message); + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Task Teardown error (A):")); + } + + [Fact] + public async Task Should_Log_Exception_Thrown_From_Task_If_Both_Task_And_Task_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTaskTeardownAction( + context => { throw new InvalidOperationException("Task Teardown: " + context.Task.Name); }); + engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Task: A"); }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Task: A", result?.Message); + } + + [Fact] + public async Task Should_Log_Task_Teardown_Exception_If_Both_Task_And_Task_Teardown_Actions_Throw() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + + engine.RegisterTaskTeardownAction( + context => { throw new InvalidOperationException("Task Teardown: " + context.Task.Name); }); + engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Task: A"); }); + + // When + await Record.ExceptionAsync(() => engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.Contains(fixture.Log.Entries, x => x.Message.StartsWith("Task Teardown error (A):")); + } + + [Fact] + public async Task Should_Return_Report_That_Contains_Entry_For_Setup_First() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterSetupAction(x => { }); + engine.RegisterTask("A"); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, report.Count()); + Assert.Equal("Setup", report.ElementAt(0).TaskName); + Assert.Equal(CakeReportEntryCategory.Setup, report.ElementAt(0).Category); + Assert.Equal("A", report.ElementAt(1).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(1).Category); + } + + [Fact] + public async Task Should_Return_Report_That_Contains_Entry_For_Teardown_Last() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTeardownAction(x => { }); + engine.RegisterTask("A"); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, report.Count()); + Assert.Equal("A", report.ElementAt(0).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(0).Category); + Assert.Equal("Teardown", report.ElementAt(1).TaskName); + Assert.Equal(CakeReportEntryCategory.Teardown, report.ElementAt(1).Category); + } + + [Fact] + public async Task Should_Return_Report_That_Contains_Executed_Tasks_In_Order() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A"); + engine.RegisterTask("C").IsDependentOn("B"); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(3, report.Count()); + Assert.Equal("A", report.ElementAt(0).TaskName); + Assert.Equal("B", report.ElementAt(1).TaskName); + Assert.Equal("C", report.ElementAt(2).TaskName); + } + + [Fact] + public async Task Should_Return_Report_That_Marks_Executed_Tasks_As_Executed() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").IsDependentOn("B").Does(() => { }); + engine.RegisterTask("B").IsDependentOn("C"); + engine.RegisterTask("C").WithCriteria(() => false).Does(() => { }); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(CakeTaskExecutionStatus.Executed, report.First(e => e.TaskName == "A").ExecutionStatus); + } + + [Fact] + public async Task Should_Return_Report_That_Marks_Skipped_Tasks_As_Skipped() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").IsDependentOn("B"); + engine.RegisterTask("B").IsDependentOn("C"); + engine.RegisterTask("C").WithCriteria(() => false); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(CakeTaskExecutionStatus.Skipped, report.First(e => e.TaskName == "C").ExecutionStatus); + } + + [Fact] + public async Task Should_Return_Report_That_Marks_Delegated_Tasks_As_Delegated() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").IsDependentOn("B"); + engine.RegisterTask("B").IsDependentOn("C"); + engine.RegisterTask("C").WithCriteria(() => false); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(CakeTaskExecutionStatus.Delegated, report.First(e => e.TaskName == "B").ExecutionStatus); + } + + [Fact] + public async Task Should_Return_Report_That_Marks_Failed_Tasks_As_Failed() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").IsDependentOn("B"); + engine.RegisterTask("B").ContinueOnError().Does(() => throw new Exception("error")); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(CakeTaskExecutionStatus.Delegated, report.First(e => e.TaskName == "A").ExecutionStatus); + Assert.Equal(CakeTaskExecutionStatus.Failed, report.First(e => e.TaskName == "B").ExecutionStatus); + } + } + + public sealed class TheRunTargetAsyncMethod_MultipleTargets + { + [Fact] + public async Task Should_Throw_If_Targets_Is_Null() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTargets(null); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + AssertEx.IsArgumentException(result, "settings", "No target specified."); + } + + [Fact] + public async Task Should_Throw_If_Targets_Is_Empty() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTargets(Array.Empty()); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + AssertEx.IsArgumentException(result, "settings", "No target specified."); + } + + [Fact] + public async Task Should_Throw_If_Targets_Is_WhiteSpace() + { + // Given + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + var settings = new ExecutionSettings().SetTargets(new string[] { " ", " " }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + AssertEx.IsArgumentException(result, "settings", "No target specified."); + } + + [Fact] + public async Task Should_Execute_Tasks_In_Order() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").Does(() => result.Add("B")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("B", result[1]); + } + + [Fact] + public async Task Should_Only_Execute_Target_Tasks_In_Exclusive_Mode() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "D" }).UseExclusiveTarget(); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("B").Does(() => result.Add("C")); + engine.RegisterTask("D").IsDependentOn("C").IsDependeeOf("E").Does(() => { result.Add("D"); }); + engine.RegisterTask("E").Does(() => { result.Add("E"); }); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, result.Count); + Assert.Equal("B", result[0]); + Assert.Equal("D", result[1]); + } + + [Fact] + public async Task Should_Throw_If_Any_Target_Task_Is_Skipped() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterTask("B").WithCriteria(() => false).Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Equal("Could not reach target 'B' since it was skipped due to a criteria.", result?.Message); + } + + [Fact] + public async Task Should_Throw_If_Target_Was_Not_Found() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "Run-Some-Tests" }); + var engine = fixture.CreateEngine(); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Equal("The target 'Run-Some-Tests' was not found.", result?.Message); + } + + [Fact] + public async Task Should_Throw_If_All_Targets_Were_Not_Found() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "Run-Some-Tests", "Run-Some-More-Tests" }); + var engine = fixture.CreateEngine(); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Equal("The targets 'Run-Some-Tests', 'Run-Some-More-Tests' were not found.", result?.Message); + } + + [Fact] + public async Task Should_Throw_If_Any_Targets_Were_Not_Found() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "Run-Some-Tests", "A", "Run-Some-More-Tests" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => { }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Equal("The targets 'Run-Some-Tests', 'Run-Some-More-Tests' were not found.", result?.Message); + } + + [Fact] + public async Task Should_Not_Catch_Exceptions_From_Task_If_ContinueOnError_Is_Not_Set() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterTask("B").Does(() => { throw new InvalidOperationException("Whoopsie"); }); + + // When + var result = await Record.ExceptionAsync(() => + engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result); + Assert.Equal("Whoopsie", result?.Message); + } + + [Fact] + public async Task Should_Swallow_Exceptions_If_ContinueOnError_Is_Set() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterTask("B").ContinueOnError().Does(() => { throw new InvalidOperationException(); }); + + // When, Then + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + } + + [Fact] + public async Task Should_Run_Setup_Before_First_Task() + { + // Given + var result = new List(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var fixture = new CakeEngineFixture(); + var engine = fixture.CreateEngine(); + engine.RegisterSetupAction(context => result.Add("Setup")); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").Does(() => result.Add("B")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(3, result.Count); + Assert.Equal("Setup", result[0]); + } + + [Fact] + public async Task Should_Run_Teardown_After_Last_Running_Task() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "A", "B" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").Does(() => result.Add("B")); + engine.RegisterTeardownAction(context => result.Add("Teardown")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(3, result.Count); + Assert.Equal("Teardown", result[2]); + } + + [Fact] + public async Task Should_Run_Task_Setup_Before_Each_Task() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "C" }); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + engine.RegisterTask("C").Does(() => result.Add("Executing C")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_SETUP:A", + "Executing A", + "TASK_SETUP:B", + "Executing B", + "TASK_SETUP:C", + "Executing C" + }, result); + } + + [Fact] + public async Task Should_Run_Task_Teardown_After_Each_Running_Task() + { + // Given + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "C" }); + var engine = fixture.CreateEngine(); + engine.RegisterTaskSetupAction(context => result.Add("TASK_SETUP:" + context.Task.Name)); + engine.RegisterTaskTeardownAction(context => result.Add("TASK_TEARDOWN:" + context.Task.Name)); + engine.RegisterTask("A").Does(() => result.Add("Executing A")); + engine.RegisterTask("B").Does(() => result.Add("Executing B")).IsDependentOn("A"); + engine.RegisterTask("C").Does(() => result.Add("Executing C")); + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( new List { "TASK_SETUP:A", "Executing A", "TASK_TEARDOWN:A", "TASK_SETUP:B", + "Executing B", "TASK_TEARDOWN:B", "TASK_SETUP:C", "Executing C", @@ -769,242 +1883,607 @@ public void Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_Is_Ski } [Fact] - public void Should_Run_Task_Teardown_After_Each_Running_Task_Even_If_Task_Failed() + public async Task Should_Return_Report_That_Marks_Skipped_Tasks_As_Skipped() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "C", "D" }); + var engine = fixture.CreateEngine(); + engine.RegisterSetupAction(x => { }); + engine.RegisterTask("A"); + engine.RegisterTask("B").WithCriteria(() => false).IsDependentOn("A"); + engine.RegisterTask("C").IsDependentOn("B"); + engine.RegisterTask("D").ContinueOnError().Does(() => throw new Exception("error")); + engine.RegisterTeardownAction(x => { }); + + // When + var report = await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(6, report.Count()); + + Assert.Equal("Setup", report.ElementAt(0).TaskName); + Assert.Equal(CakeReportEntryCategory.Setup, report.ElementAt(0).Category); + Assert.Equal(CakeTaskExecutionStatus.Executed, report.ElementAt(0).ExecutionStatus); + + Assert.Equal("A", report.ElementAt(1).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(1).Category); + Assert.Equal(CakeTaskExecutionStatus.Delegated, report.ElementAt(1).ExecutionStatus); + + Assert.Equal("B", report.ElementAt(2).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(2).Category); + Assert.Equal(CakeTaskExecutionStatus.Skipped, report.ElementAt(2).ExecutionStatus); + + Assert.Equal("C", report.ElementAt(3).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(3).Category); + Assert.Equal(CakeTaskExecutionStatus.Delegated, report.ElementAt(3).ExecutionStatus); + + Assert.Equal("D", report.ElementAt(4).TaskName); + Assert.Equal(CakeReportEntryCategory.Task, report.ElementAt(4).Category); + Assert.Equal(CakeTaskExecutionStatus.Failed, report.ElementAt(4).ExecutionStatus); + + Assert.Equal("Teardown", report.ElementAt(5).TaskName); + Assert.Equal(CakeReportEntryCategory.Teardown, report.ElementAt(5).Category); + } + + [Fact] + public async Task Should_Execute_Shared_Dependency_Only_Once_When_UnifiedDependencyGraph_Is_Enabled() { - // Given + // Reproduces issue #4324: with opt-in, common dependent tasks run once. var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings() + .SetTargets(new string[] { "B", "C" }) + .UseUnifiedDependencyGraphForMultipleTargets(); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => result.Add("TASK_SETUP:" + sc.Task.Name)); - engine.RegisterTaskTeardownAction((cc, sc) => result.Add("TASK_TEARDOWN:" + sc.Task.Name)); - engine.RegisterTask("A").Does(() => + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("A").Does(() => result.Add("C")); + + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + Assert.Equal(3, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("B", result[1]); + Assert.Equal("C", result[2]); + Assert.Single(result, x => x == "A"); + } + + [Fact] + public async Task Should_Execute_Shared_Dependency_Twice_When_UnifiedDependencyGraph_Is_Disabled() + { + // Legacy behavior: without opt-in, each target is traversed separately. + var result = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "C" }); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A").Does(() => result.Add("A")); + engine.RegisterTask("B").IsDependentOn("A").Does(() => result.Add("B")); + engine.RegisterTask("C").IsDependentOn("A").Does(() => result.Add("C")); + + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + Assert.Equal(4, result.Count); + Assert.Equal("A", result[0]); + Assert.Equal("B", result[1]); + Assert.Equal("A", result[2]); + Assert.Equal("C", result[3]); + } + + [Fact] + public async Task Should_Expose_All_Tasks_To_Execute_In_Setup_When_UnifiedDependencyGraph_Is_Enabled() + { + // Reproduces issue #4066: with opt-in, SetupContext.TasksToExecute lists all tasks. + IReadOnlyCollection capturedTasks = null; + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings() + .SetTargets(new string[] { "A", "B" }) + .UseUnifiedDependencyGraphForMultipleTargets(); + var engine = fixture.CreateEngine(); + engine.RegisterSetupAction(ctx => capturedTasks = ctx.TasksToExecute); + engine.RegisterTask("A").Does(() => { }); + engine.RegisterTask("B").Does(() => { }); + + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + Assert.NotNull(capturedTasks); + Assert.Equal(2, capturedTasks.Count); + var names = capturedTasks.Select(t => t.Name).OrderBy(n => n).ToList(); + Assert.Equal(new[] { "A", "B" }, names); + } + } + + public sealed class TheSetupEvent + { + [Fact] + public void Should_Raise_Setup_Event() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + + // When + var result = Assert.Raises( + handler => engine.BeforeSetup += handler, + handler => engine.BeforeSetup -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.Equal(engine, result.Sender); + Assert.Equal(fixture.Context, result.Arguments.Context); + } + + [Fact] + public void Should_Raise_AfterSetup_Event() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + + // When + var result = Assert.Raises( + handler => engine.AfterSetup += handler, + handler => engine.AfterSetup -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.Equal(engine, result.Sender); + Assert.Equal(fixture.Context, result.Arguments.Context); + } + + [Fact] + public async Task Should_Invoke_All_Handlers() + { + // Given + var list = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.BeforeSetup += (sender, args) => { - result.Add("FAILING (A)"); - throw new InvalidOperationException("Fail"); - }); + list.Add("HANDLER_1"); + }; + engine.BeforeSetup += (sender, args) => + { + list.Add("HANDLER_2"); + }; // When - var exception = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal(2, list.Count); + Assert.Contains(list, s => s == "HANDLER_1"); + Assert.Contains(list, s => s == "HANDLER_2"); + } + + [Fact] + public async Task Should_Raise_The_Setup_Event_Only_Once() + { + // Given + var list = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A"); + engine.RegisterTask("C").IsDependentOn("B"); + engine.BeforeSetup += (sender, args) => + { + list.Add("SETUP_EVENT"); + }; + + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.NotNull(exception); - Assert.IsType(exception); - Assert.Equal("Fail", exception.Message); Assert.Equal( new List { - "TASK_SETUP:A", - "FAILING (A)", - "TASK_TEARDOWN:A" - }, result); + "SETUP_EVENT" + }, list); } + } + public sealed class TheTaskSetupEvent + { [Fact] - public void Should_Run_Task_Teardown_If_Task_Setup_Failed() + public void Should_Raise_Task_Setup_Event() { // Given - var result = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + + // When + var result = Assert.Raises( + handler => engine.BeforeTaskSetup += handler, + handler => engine.BeforeTaskSetup -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.Equal(engine, result.Sender); + } + + [Fact] + public void Should_Raise_Task_AfterSetup_Event() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + + // When + var result = Assert.Raises( + handler => engine.AfterTaskSetup += handler, + handler => engine.AfterTaskSetup -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.NotNull(result); + Assert.Equal(engine, result.Sender); + } + + [Fact] + public void Should_Raise_Task_Setup_Event_With_Task_Context() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + + // When + var result = Assert.Raises( + handler => engine.BeforeTaskSetup += handler, + handler => engine.BeforeTaskSetup -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result.Arguments); + Assert.NotNull(result.Arguments.TaskSetupContext.Task); + Assert.Equal("A", result.Arguments.TaskSetupContext.Task.Name); + } + + [Fact] + public async Task Should_Raise_Task_Setup_Event_After_Setup_Event() + { + // Given + var list = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTaskSetupAction((cc, sc) => + engine.RegisterTask("A"); + engine.BeforeSetup += (sender, args) => { - throw new InvalidOperationException("Fail"); - }); - engine.RegisterTaskTeardownAction((cc, sc) => result.Add("TASK_TEARDOWN:" + sc.Task.Name)); - engine.RegisterTask("A").Does(() => + list.Add("SETUP_EVENT"); + }; + engine.BeforeTaskSetup += (sender, args) => { - result.Add("Executing A"); - }); + list.Add("TASK_SETUP_EVENT_1"); + }; + engine.BeforeTaskSetup += (sender, args) => + { + list.Add("TASK_SETUP_EVENT_2"); + }; // When - var exception = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.NotNull(exception); - Assert.IsType(exception); - Assert.Equal("Fail", exception.Message); Assert.Equal( new List { - "TASK_TEARDOWN:A" - }, result); + "SETUP_EVENT", + "TASK_SETUP_EVENT_1", + "TASK_SETUP_EVENT_2" + }, list); } [Fact] - public void Should_Throw_Exception_Thrown_From_Task_Setup_Action_If_Both_Task_Setup_And_Task_Teardown_Actions_Throw() + public async Task Should_Raise_Task_Setup_Event_For_All_Tasks() { // Given + var list = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A"); + engine.BeforeTaskSetup += (sender, args) => + { + list.Add("TASK_SETUP_EVENT_" + args.TaskSetupContext.Task.Name); + }; - engine.RegisterTaskSetupAction((cc,sc) => { throw new InvalidOperationException("Task Setup: " + sc.Task.Name); }); - engine.RegisterTaskTeardownAction((cc, tc) => { throw new InvalidOperationException("Task Teardown: " + tc.Task.Name); }); - engine.RegisterTask("A").Does(() => { }); + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_SETUP_EVENT_A", + "TASK_SETUP_EVENT_B" + }, list); + } + } + + public sealed class TheTaskTeardownEvent + { + [Fact] + public void Should_Raise_Task_Teardown_Event() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Assert.Raises( + handler => engine.BeforeTaskTeardown += handler, + handler => engine.BeforeTaskTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Task Setup: A", result.Message); + Assert.Equal(engine, result.Sender); } [Fact] - public void Should_Throw_Exception_Occuring_In_Task_Teardown_If_No_Previous_Exception_Was_Thrown() + public void Should_Raise_Task_AfterTeardown_Event() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - - engine.RegisterTaskTeardownAction((cc, tc) => { throw new InvalidOperationException("Task Teardown: " + tc.Task.Name); }); engine.RegisterTask("A"); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Assert.Raises( + handler => engine.AfterTaskTeardown += handler, + handler => engine.AfterTaskTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Task Teardown: A", result.Message); + Assert.Equal(engine, result.Sender); } [Fact] - public void Should_Log_Task_Teardown_Exception_If_Both_Task_Setup_And_Task_Teardown_Actions_Throw() + public void Should_Raise_Task_Teardown_Event_With_Task_Context() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); - engine.RegisterTaskSetupAction((cc, sc) => { throw new InvalidOperationException("Task Setup: " + sc.Task.Name); }); - engine.RegisterTaskTeardownAction((cc, tc) => { throw new InvalidOperationException("Task Teardown: " + tc.Task.Name); }); - engine.RegisterTask("A").Does(() => { }); + // When + var result = Assert.Raises( + handler => engine.BeforeTaskTeardown += handler, + handler => engine.BeforeTaskTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); + + // Then + Assert.IsType(result.Arguments); + Assert.NotNull(result.Arguments.TaskTeardownContext.Task); + Assert.Equal("A", result.Arguments.TaskTeardownContext.Task.Name); + } + + [Fact] + public async Task Should_Raise_Task_Teardown_Event_After_Task_Setup_Event() + { + // Given + var list = new List(); + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.BeforeTaskSetup += (sender, args) => + { + list.Add("TASK_SETUP_EVENT"); + }; + engine.BeforeTaskTeardown += (sender, args) => + { + list.Add("TASK_TEARDOWN_EVENT"); + }; // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Task Setup: A", result.Message); - Assert.True(fixture.Log.Entries.Any(x => x.Message.StartsWith("Task Teardown error (A):"))); + Assert.Equal( + new List + { + "TASK_SETUP_EVENT", + "TASK_TEARDOWN_EVENT" + }, list); } [Fact] - public void Should_Log_Exception_Thrown_From_Task_If_Both_Task_And_Task_Teardown_Actions_Throw() + public async Task Should_Raise_Task_Teardown_Event_For_All_Tasks() { // Given + var list = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("B"); var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A"); + engine.BeforeTaskTeardown += (sender, args) => + { + list.Add("TASK_TEARDOWN_EVENT_" + args.TaskTeardownContext.Task.Name); + }; - engine.RegisterTaskTeardownAction((cc, tc) => { throw new InvalidOperationException("Task Teardown: " + tc.Task.Name); }); - engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Task: A"); }); + // When + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); + + // Then + Assert.Equal( + new List + { + "TASK_TEARDOWN_EVENT_A", + "TASK_TEARDOWN_EVENT_B" + }, list); + } + } + + public sealed class TheTeardownEvent + { + [Fact] + public void Should_Raise_Teardown_Event() + { + // Given + var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); + var engine = fixture.CreateEngine(); + engine.RegisterTask("A"); // When - var result = Record.Exception(() => - engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Assert.Raises( + handler => engine.BeforeTeardown += handler, + handler => engine.BeforeTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Task: A", result.Message); + Assert.Equal(engine, result.Sender); } [Fact] - public void Should_Log_Task_Teardown_Exception_If_Both_Task_And_Task_Teardown_Actions_Throw() + public void Should_Raise_AfterTeardown_Event() { // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - - engine.RegisterTaskTeardownAction((cc, tc) => { throw new InvalidOperationException("Task Teardown: " + tc.Task.Name); }); - engine.RegisterTask("A").Does(() => { throw new InvalidOperationException("Task: A"); }); + engine.RegisterTask("A"); // When - Record.Exception(() => engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A")); + var result = Assert.Raises( + handler => engine.AfterTeardown += handler, + handler => engine.AfterTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.True(fixture.Log.Entries.Any(x => x.Message.StartsWith("Task Teardown error (A):"))); + Assert.NotNull(result); + Assert.Equal(engine, result.Sender); } [Fact] - public void Should_Return_Report_That_Contains_Executed_Tasks_In_Order() + public void Should_Raise_Teardown_Event_With_Teardown_Context() { - // Given var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); engine.RegisterTask("A"); - engine.RegisterTask("B").IsDependentOn("A"); - engine.RegisterTask("C").IsDependentOn("B"); // When - var report = engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "C"); + var result = Assert.Raises( + handler => engine.BeforeTeardown += handler, + handler => engine.BeforeTeardown -= handler, + async () => await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings)); // Then - Assert.Equal(3, report.Count()); - Assert.Equal("A", report.ElementAt(0).TaskName); - Assert.Equal("B", report.ElementAt(1).TaskName); - Assert.Equal("C", report.ElementAt(2).TaskName); + Assert.NotNull(result); + Assert.Equal(fixture.Context.Environment, result.Arguments.TeardownContext.Environment); + Assert.True(result.Arguments.TeardownContext.Successful); } [Fact] - public void Should_Return_Report_That_Marks_Executed_Tasks_As_Executed() + public async Task Should_Invoke_All_Handlers() { // Given - var result = new List(); + var list = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").IsDependentOn("B").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("C"); - engine.RegisterTask("C").WithCriteria(() => false).Does(() => result.Add("C")); + engine.RegisterTask("A"); + engine.BeforeTeardown += (sender, args) => + { + list.Add("HANDLER_1"); + }; + engine.BeforeTeardown += (sender, args) => + { + list.Add("HANDLER_2"); + }; // When - var report = engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(CakeTaskExecutionStatus.Executed, report.First(e => e.TaskName == "A").ExecutionStatus); + Assert.Equal(2, list.Count); + Assert.Contains(list, s => s == "HANDLER_1"); + Assert.Contains(list, s => s == "HANDLER_2"); } [Fact] - public void Should_Return_Report_That_Marks_Skipped_Tasks_As_Skipped() + public async Task Should_Raise_The_Teardown_Event_Only_Once() { // Given - var result = new List(); + var list = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("C"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").IsDependentOn("B").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("C"); - engine.RegisterTask("C").WithCriteria(() => false).Does(() => result.Add("C")); + engine.RegisterTask("A"); + engine.RegisterTask("B").IsDependentOn("A"); + engine.RegisterTask("C").IsDependentOn("B"); + engine.BeforeTeardown += (sender, args) => + { + list.Add("TEARDOWN_EVENT"); + }; // When - var report = engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(CakeTaskExecutionStatus.Skipped, report.First(e => e.TaskName == "C").ExecutionStatus); + Assert.Equal( + new List + { + "TEARDOWN_EVENT" + }, list); } [Fact] - public void Should_Return_Report_That_Marks_Delegated_Tasks_As_Delegated() + public async Task Should_Raise_The_Teardown_Event_After_Task_Teardown_Event() { // Given - var result = new List(); + var list = new List(); var fixture = new CakeEngineFixture(); + var settings = new ExecutionSettings().SetTarget("A"); var engine = fixture.CreateEngine(); - engine.RegisterTask("A").IsDependentOn("B").Does(() => result.Add("A")); - engine.RegisterTask("B").IsDependentOn("C"); - engine.RegisterTask("C").WithCriteria(() => false).Does(() => result.Add("C")); + engine.RegisterTask("A"); + engine.BeforeTaskTeardown += (sender, args) => + { + list.Add("TASK_TEARDOWN_EVENT"); + }; + engine.BeforeTeardown += (sender, args) => + { + list.Add("TEARDOWN_EVENT"); + }; // When - var report = engine.RunTarget(fixture.Context, fixture.ExecutionStrategy, "A"); + await engine.RunTargetAsync(fixture.Context, fixture.ExecutionStrategy, settings); // Then - Assert.Equal(CakeTaskExecutionStatus.Delegated, report.First(e => e.TaskName == "B").ExecutionStatus); + Assert.Equal( + new List + { + "TASK_TEARDOWN_EVENT", + "TEARDOWN_EVENT" + }, list); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeEnvironmentTests.cs b/src/Cake.Core.Tests/Unit/CakeEnvironmentTests.cs new file mode 100644 index 0000000000..35926645d3 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeEnvironmentTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + /// + /// Tests for . + /// + public sealed class CakeEnvironmentTests + { + /// + /// Tests that ApplicationRoot is set to a valid rooted path when the environment is created. + /// When Assembly.Location is empty (e.g. single-file publish, AOT), ApplicationRoot falls back to AppContext.BaseDirectory. + /// + [Fact] + public void ApplicationRoot_Should_Be_Valid_Rooted_Path() + { + // Given + var platform = new CakePlatform(); + var runtime = new CakeRuntime(); + + // When + var environment = new CakeEnvironment(platform, runtime); + + // Then - ApplicationRoot must be set and rooted so script loading and tool resolution work + Assert.NotNull(environment.ApplicationRoot); + Assert.NotNull(environment.ApplicationRoot.FullPath); + Assert.False(string.IsNullOrWhiteSpace(environment.ApplicationRoot.FullPath)); + Assert.True(environment.ApplicationRoot.IsRelative == false, "ApplicationRoot should be an absolute path."); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/CakeReportPrinterTests.cs b/src/Cake.Core.Tests/Unit/CakeReportPrinterTests.cs new file mode 100644 index 0000000000..516a48762a --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeReportPrinterTests.cs @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Cake.Core.Diagnostics; +using Cake.Testing; +using NSubstitute; +using Xunit; +using static VerifyXunit.Verifier; + +namespace Cake.Core.Tests.Unit; + +public class CakeReportPrinterTests +{ + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Console_Is_Null() + { + // Given + var context = Substitute.For(); + // When + var result = Record.Exception(() => new CakeReportPrinter(null, context)); + // Then + AssertEx.IsArgumentNullException(result, "console"); + } + + [Fact] + public void Should_Throw_If_Context_Is_Null() + { + // Given + var console = new FakeConsole(); + // When + var result = Record.Exception(() => new CakeReportPrinter(console, null)); + // Then + AssertEx.IsArgumentNullException(result, "context"); + } + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task Write(Verbosity verbosity) + { + // Given + var log = Substitute.For(); + log.Verbosity.Returns(verbosity); + + var context = Substitute.For(); + context.Log.Returns(log); + + var console = new FakeConsole(); + + var reportPrinter = new CakeReportPrinter(console, context); + + var executionCategories = Enum + .GetValues() + .OrderBy( + category => category switch { + CakeReportEntryCategory.Setup => 0, + CakeReportEntryCategory.Task => 1, + CakeReportEntryCategory.Teardown => 9, + _ => 5 + }) + .ToArray(); + + var executionStatuses = Enum + .GetValues() + .OrderBy( + status => status switch { + CakeTaskExecutionStatus.Executed => 0, + CakeTaskExecutionStatus.Skipped => 1, + CakeTaskExecutionStatus.Failed => 2, + CakeTaskExecutionStatus.Delegated => 9, + _ => 5 + }) + .ToArray(); + + var report = executionCategories + .Aggregate( + new CakeReport(), + (report, category) => executionStatuses.Aggregate( + report, + (report, status) => + { + report.Add( + $"{category:F}{status:F}", + status == CakeTaskExecutionStatus.Skipped ? $"{status:F} reason" : null, + category, + TimeSpan.FromMilliseconds(((int)category + 1) * ((int)status + 1) * 111), + status); + return report; + }, + report => report), + report => report); + + // When + reportPrinter.Write(report); + + // Then + await Verify(console.Messages); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteStep(Verbosity verbosity) + { + // Given + var log = Substitute.For(); + log.Verbosity.Returns(verbosity); + + var context = Substitute.For(); + context.Log.Returns(log); + + var console = new FakeConsole(); + var reportPrinter = new CakeReportPrinter(console, context); + + // When + reportPrinter.WriteStep("Test Step", verbosity); + + // Then + await Verify(console.Messages); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteLifeCycleStep(Verbosity verbosity) + { + // Given + var log = Substitute.For(); + log.Verbosity.Returns(verbosity); + + var context = Substitute.For(); + context.Log.Returns(log); + + var console = new FakeConsole(); + var reportPrinter = new CakeReportPrinter(console, context); + + // When + reportPrinter.WriteLifeCycleStep("Setup", verbosity); + + // Then + await Verify(console.Messages); + } + + [Theory] + [InlineData(Verbosity.Quiet)] + [InlineData(Verbosity.Minimal)] + [InlineData(Verbosity.Normal)] + [InlineData(Verbosity.Verbose)] + [InlineData(Verbosity.Diagnostic)] + public async Task WriteSkippedStep(Verbosity verbosity) + { + // Given + var log = Substitute.For(); + log.Verbosity.Returns(verbosity); + + var context = Substitute.For(); + context.Log.Returns(log); + + var console = new FakeConsole(); + var reportPrinter = new CakeReportPrinter(console, context); + + // When + reportPrinter.WriteSkippedStep("Skipped Step", verbosity); + + // Then + await Verify(console.Messages); + } +} diff --git a/src/Cake.Core.Tests/Unit/CakeReportTests.cs b/src/Cake.Core.Tests/Unit/CakeReportTests.cs index 61e06a7b62..b8d5cb65b8 100644 --- a/src/Cake.Core.Tests/Unit/CakeReportTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeReportTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Cake.Core.Tests.Unit { using System; @@ -60,7 +61,7 @@ public void Should_Add_A_New_Task() var taskName = "task"; // When - report.AddSkipped(taskName); + report.AddSkipped(taskName, "This task was skipped for a great reason!"); // Then var firstTask = report.First(); @@ -74,12 +75,12 @@ public void Should_Add_To_End_Of_Sequence() { // Given var report = new CakeReport(); - report.AddSkipped("task 1"); + report.AddSkipped("task 1", "This task was skipped for a great reason!"); var taskName = "task 2"; // When - report.AddSkipped(taskName); + report.AddSkipped(taskName, "This task was skipped for a great reason!"); // Then var lastTask = report.Last(); @@ -114,7 +115,7 @@ public void Should_Add_To_End_Of_Sequence() { // Given var report = new CakeReport(); - report.AddSkipped("task 1"); + report.AddSkipped("task 1", "This task was skipped for a great reason!"); var taskName = "task 2"; var duration = TimeSpan.FromMilliseconds(100); @@ -129,5 +130,46 @@ public void Should_Add_To_End_Of_Sequence() Assert.Equal(CakeTaskExecutionStatus.Delegated, lastTask.ExecutionStatus); } } + + public sealed class TheAddFailedMethod + { + [Fact] + public void Should_Add_A_New_Task() + { + // Given + var report = new CakeReport(); + var taskName = "task"; + var duration = TimeSpan.FromMilliseconds(100); + + // When + report.AddFailed(taskName, duration); + + // Then + var firstTask = report.First(); + Assert.Equal(taskName, firstTask.TaskName); + Assert.Equal(duration, firstTask.Duration); + Assert.Equal(CakeTaskExecutionStatus.Failed, firstTask.ExecutionStatus); + } + + [Fact] + public void Should_Add_To_End_Of_Sequence() + { + // Given + var report = new CakeReport(); + report.AddSkipped("task 1", "This task was skipped for a great reason!"); + + var taskName = "task 2"; + var duration = TimeSpan.FromMilliseconds(100); + + // When + report.AddFailed(taskName, duration); + + // Then + var lastTask = report.Last(); + Assert.Equal(taskName, lastTask.TaskName); + Assert.Equal(duration, lastTask.Duration); + Assert.Equal(CakeTaskExecutionStatus.Failed, lastTask.ExecutionStatus); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeRuntimeTests.cs b/src/Cake.Core.Tests/Unit/CakeRuntimeTests.cs new file mode 100644 index 0000000000..4f710622b0 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeRuntimeTests.cs @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class CakeRuntimeTests + { + public sealed class TheBuiltFrameworkProperty + { + private ITestOutputHelper TestOutputHelper { get; } + + public TheBuiltFrameworkProperty(ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + } + + public void Should_Return_Correct_Result_For_CoreClr() + { + // Given + var runtime = new CakeRuntime(); + + // When + var framework = runtime.BuiltFramework; + + // Then +#if NETFRAMEWORK + Assert.Equal(".NETFramework,Version=v4.6.1", framework.FullName); +#elif !NETCOREAPP + Assert.Equal(".NETStandard,Version=v2.0", framework.FullName); +#else + var expect = string.Concat(".NETCoreApp,Version=v", +#if NET10_0 + "10.0"); +#elif NET9_0 + "9.0"); +#elif NET8_0 + "8.0"); +#elif NET7_0 + "7.0"); +#elif NET6_0 + "6.0"); +#elif NET5_0 + "5.0"); +#elif NETCOREAPP2_0 + "2.0"); +#elif NETCOREAPP2_1 + "2.1"); +#elif NETCOREAPP2_2 + "2.2"); +#elif NETCOREAPP3_0 + "3.0"); +#elif NETCOREAPP3_1 + "3.1"); +#endif + // ToDo: Enable mocking runtime resolution, temp work around for missing runtimes. + switch (expect) + { + case ".NETCoreApp,Version=v2.0": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v2.1": + case ".NETCoreApp,Version=v3.0": + case ".NETCoreApp,Version=v3.1": + case ".NETCoreApp,Version=v5.0": + case ".NETCoreApp,Version=v6.0": + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + case ".NETCoreApp,Version=v2.1": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v3.0": + case ".NETCoreApp,Version=v3.1": + case ".NETCoreApp,Version=v5.0": + case ".NETCoreApp,Version=v6.0": + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + case ".NETCoreApp,Version=v3.0": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v3.1": + case ".NETCoreApp,Version=v5.0": + case ".NETCoreApp,Version=v6.0": + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + case ".NETCoreApp,Version=v3.1": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v5.0": + case ".NETCoreApp,Version=v6.0": + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + case ".NETCoreApp,Version=v5.0": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v6.0": + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + case ".NETCoreApp,Version=v6.0": + { + switch (framework.FullName) + { + case ".NETCoreApp,Version=v7.0": + case ".NETCoreApp,Version=v8.0": + { + TestOutputHelper.WriteLine("Expect changed from {0} to {1}.", expect, framework.FullName); + expect = framework.FullName; + break; + } + } + break; + } + } + Assert.Equal(expect, framework.FullName); +#endif + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/CakeTaskBuilderExtensionsTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskBuilderExtensionsTests.cs index 091fff8af4..ed16431dc1 100644 --- a/src/Cake.Core.Tests/Unit/CakeTaskBuilderExtensionsTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeTaskBuilderExtensionsTests.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cake.Core.Tests.Fixtures; using Xunit; namespace Cake.Core.Tests.Unit @@ -14,14 +18,114 @@ public sealed class TheIsDependentOnMethod public void Should_Add_Dependency_To_Task() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.IsDependentOn("other"); // Then - Assert.Equal(1, task.Dependencies.Count); + Assert.Single(task.Dependencies); + } + + public sealed class OnMethodTaskBuilder + { + [Fact] + public void Should_Add_Dependency_To_Task() + { + // Given + var parentTask = new CakeTask("parent"); + var childTask = new CakeTask("child"); + var builder = new CakeTaskBuilder(parentTask); + var cakeTaskBuilder = new CakeTaskBuilder(childTask); + + // When + builder.IsDependentOn(cakeTaskBuilder); + + // Then + Assert.Single(parentTask.Dependencies); + } + + [Fact] + public void Should_Add_Dependency_To_Task_With_Correct_Name() + { + // Given + var parentTask = new CakeTask("parent"); + var childTask = new CakeTask("child"); + var builder = new CakeTaskBuilder(parentTask); + var childTaskBuilder = new CakeTaskBuilder(childTask); + + // When + builder.IsDependentOn(childTaskBuilder); + + // Then + Assert.Equal(parentTask.Dependencies[0].Name, childTaskBuilder.Target.Name); + } + + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + var childTask = new CakeTask("child"); + CakeTaskBuilder builder = null; + var childTaskBuilder = new CakeTaskBuilder(childTask); + + // When + var result = Record.Exception(() => builder.IsDependentOn(childTaskBuilder)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_OtherBuilder_Is_Null() + { + // Given + var parentTask = new CakeTask("parent"); + var builder = new CakeTaskBuilder(parentTask); + CakeTaskBuilder childTaskBuilder = null; + + // When + var result = Record.Exception(() => builder.IsDependentOn(childTaskBuilder)); + + // Then + AssertEx.IsArgumentNullException(result, "other"); + } + } + } + + public sealed class TheIsDependeeOfMethod + { + [Fact] + public void Should_Add_Dependee_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.IsDependeeOf("other"); + + // Then + Assert.Single(task.Dependees); + Assert.Equal("other", task.Dependees[0].Name); + } + + [Fact] + public void Should_Add_Dependee_To_Task_From_Other() + { + // Given + var task = new CakeTask("task"); + var other = new CakeTask("other"); + var builder = new CakeTaskBuilder(task); + var otherBuilder = new CakeTaskBuilder(other); + + // When + builder.IsDependeeOf(otherBuilder); + + // Then + Assert.Single(task.Dependees); + Assert.Equal("other", task.Dependees[0].Name); } } @@ -33,14 +137,29 @@ public sealed class ThatAcceptsBoolean public void Should_Add_Criteria_To_Task() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.WithCriteria(false); // Then - Assert.Equal(1, task.Criterias.Count); + Assert.Single(task.Criterias); + } + + [Fact] + public void Should_Add_Message_To_Criteria_If_Specified() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.WithCriteria(false, "Foo"); + + // Then + Assert.Single(task.Criterias); + Assert.Equal("Foo", task.Criterias[0].Message); } } @@ -50,100 +169,460 @@ public sealed class ThatAcceptsBooleanLambda public void Should_Add_Criteria_To_Task() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.WithCriteria(() => true); // Then - Assert.Equal(1, task.Criterias.Count); + Assert.Single(task.Criterias); } - } - public sealed class ThatAcceptsCakeContextToBooleanLambda - { [Fact] - public void Should_Add_Criteria_To_Task() + public void Should_Add_Message_To_Criteria_If_Specified() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When - builder.WithCriteria(context => true); + builder.WithCriteria(() => true, "Foo"); // Then - Assert.Equal(1, task.Criterias.Count); + Assert.Single(task.Criterias); + Assert.Equal("Foo", task.Criterias[0].Message); } } - } - public sealed class TheDoesMethod - { - public class WithoutContext + public sealed class ThatAcceptsCakeContextToBooleanLambda { [Fact] - public void Should_Throw_If_Action_Is_Null() + public void Should_Add_Criteria_To_Task() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When - var result = Record.Exception(() => builder.Does((Action)null)); + builder.WithCriteria(context => true); // Then - Assert.IsArgumentNullException(result, "action"); + Assert.Single(task.Criterias); } [Fact] - public void Should_Add_Action_To_Task() + public void Should_Add_Message_To_Criteria_If_Specified() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When - builder.Does(() => { }); + builder.WithCriteria(context => true, "Foo"); // Then - Assert.Equal(1, task.Actions.Count); + Assert.Single(task.Criterias); + Assert.Equal("Foo", task.Criterias[0].Message); } } + } - public class WithContext + public sealed class TheDoesMethod + { + public sealed class ThatIsAsynchronous { - [Fact] - public void Should_Add_Action_To_Task() + public sealed class WithData { - // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); - // When - builder.Does(c => { }); + // When + var result = Record.Exception(() => builder.Does((Func)null)); - // Then - Assert.Equal(1, task.Actions.Count); + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async (data, context) => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Func)null)); + + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async data => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Func)null)); + + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async c => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Func)null)); + + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async () => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + } + } + + public sealed class ThatIsSynchronous + { + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does((data, context) => { }); + + // Then + Assert.Single(task.Actions); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(data => { }); + + // Then + Assert.Single(task.Actions); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(c => { }); + + // Then + Assert.Single(task.Actions); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(() => { }); + + // Then + Assert.Single(task.Actions); + } + } } } } public sealed class TheOnErrorMethod { - [Fact] - public void Should_Set_The_Error_Handler() + public sealed class WithData { - // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + public sealed class WithContext + { + public sealed class WithException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); - // When - builder.OnError(exception => { }); + // When + builder.OnError((exception, context, data) => { }); - // Then - Assert.NotNull(builder.Task.ErrorHandler); + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + } + + public sealed class WithoutContext + { + public sealed class WithException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError((Exception exception, string data) => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + + public sealed class WithoutException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError(data => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + public sealed class WithException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError((exception, context) => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + } + + public sealed class WithoutContext + { + public sealed class WithException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError(exception => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + + public sealed class WithoutException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError(() => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + } } } @@ -153,14 +632,14 @@ public sealed class TheContinueOnErrorMethod public void Should_Set_The_Error_Handler() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.ContinueOnError(); // Then - Assert.NotNull(builder.Task.ErrorHandler); + Assert.NotNull(builder.Target.ErrorHandler); } } @@ -170,38 +649,66 @@ public sealed class TheFinallyMethod public void Should_Throw_If_Builder_Is_Null() { // Given, When - var result = Record.Exception(() => CakeTaskBuilderExtensions.Finally(null, () => { })); + var result = Record.Exception(() => CakeTaskBuilderExtensions.Finally(null, () => { })); // Then - Assert.IsArgumentNullException(result, "builder"); + AssertEx.IsArgumentNullException(result, "builder"); } [Fact] public void Should_Throw_If_Action_Is_Null() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When - var result = Record.Exception(() => CakeTaskBuilderExtensions.Finally(builder, null)); + var result = Record.Exception(() => CakeTaskBuilderExtensions.Finally(builder, default(Action))); // Then - Assert.IsArgumentNullException(result, "finallyHandler"); + AssertEx.IsArgumentNullException(result, "finallyHandler"); + } + + [Fact] + public void Should_Throw_If_Action_Context_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => CakeTaskBuilderExtensions.Finally(builder, default(Action))); + + // Then + AssertEx.IsArgumentNullException(result, "finallyHandler"); } [Fact] public void Should_Set_The_Finally_Handler() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.Finally(() => { }); // Then - Assert.NotNull(builder.Task.FinallyHandler); + Assert.NotNull(builder.Target.FinallyHandler); + } + + [Fact] + public void Should_Set_The_Finally_Context_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Finally(context => { }); + + // Then + Assert.NotNull(builder.Target.FinallyHandler); } } @@ -211,39 +718,1442 @@ public sealed class TheReportErrorMethod public void Should_Throw_If_Builder_Is_Null() { // Given, When - var result = Record.Exception(() => CakeTaskBuilderExtensions.ReportError(null, exception => { })); + var result = Record.Exception(() => + CakeTaskBuilderExtensions.ReportError(null, exception => { })); // Then - Assert.IsArgumentNullException(result, "builder"); + AssertEx.IsArgumentNullException(result, "builder"); } [Fact] public void Should_Throw_If_Action_Is_Null() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When var result = Record.Exception(() => CakeTaskBuilderExtensions.ReportError(builder, null)); // Then - Assert.IsArgumentNullException(result, "errorReporter"); + AssertEx.IsArgumentNullException(result, "errorReporter"); } [Fact] public void Should_Set_The_Finally_Handler() { // Given - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // When builder.ReportError(exception => { }); // Then - Assert.NotNull(builder.Task.ErrorReporter); + Assert.NotNull(builder.Target.ErrorReporter); + } + } + + public sealed class TheDoesForEachMethod + { + public sealed class ForDeferredItemsWithContext + { + private static readonly Func> FuncDeferredItemsWithContext = ctx => new[] { 1, 2, 3 }; + private static readonly Action DefaultFunc = (item) => { }; + private static readonly Action NullFunc = null; + private static readonly Action ExceptionFunc = (item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithContext = (item, ctx) => { }; + private static readonly Action NullFuncWithContext = null; + private static readonly Action ExceptionFuncWithContext = (item, ctx) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithData = (data, item) => { }; + private static readonly Action NullFuncWithData = null; + private static readonly Action ExceptionFuncWithData = (data, item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithDataAndContext = (data, item, ctx) => { }; + private static readonly Action NullFuncWithDataAndContext = null; + private static readonly Action ExceptionFuncWithDataAndContext = (data, item, ctx) => throw new NotImplementedException(); + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithContext, DefaultFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, NullFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, DefaultFuncWithDataAndContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, ExceptionFuncWithDataAndContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithContext, DefaultFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, NullFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, DefaultFuncWithData); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, ExceptionFuncWithData); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithContext, DefaultFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, NullFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, DefaultFuncWithContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, ExceptionFuncWithContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithContext, DefaultFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, NullFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, DefaultFunc); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithContext, ExceptionFunc); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForDeferredItemsWithData + { + private static readonly Func> FuncDeferredItemsWithData = data => new[] { 1, 2, 3 }; + private static readonly Action DefaultFunc = (item) => { }; + private static readonly Action NullFunc = null; + private static readonly Action ExceptionFunc = (item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithContext = (item, ctx) => { }; + private static readonly Action NullFuncWithContext = null; + private static readonly Action ExceptionFuncWithContext = (item, ctx) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithData = (data, item) => { }; + private static readonly Action NullFuncWithData = null; + private static readonly Action ExceptionFuncWithData = (data, item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithDataAndContext = (data, item, ctx) => { }; + private static readonly Action NullFuncWithDataAndContext = null; + private static readonly Action ExceptionFuncWithDataAndContext = (data, item, ctx) => throw new NotImplementedException(); + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithData, DefaultFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, NullFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, DefaultFuncWithDataAndContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, ExceptionFuncWithDataAndContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithData, DefaultFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, NullFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, DefaultFuncWithData); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, ExceptionFuncWithData); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithData, DefaultFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, NullFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, DefaultFuncWithContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, ExceptionFuncWithContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithData, DefaultFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, NullFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, DefaultFunc); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithData, ExceptionFunc); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForDeferredItemsWithDataAndContext + { + private static readonly Func> FuncDeferredItemsWithDataAndContext = (data, ctx) => new[] { 1, 2, 3 }; + private static readonly Action DefaultFunc = (item) => { }; + private static readonly Action NullFunc = null; + private static readonly Action ExceptionFunc = (item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithContext = (item, ctx) => { }; + private static readonly Action NullFuncWithContext = null; + private static readonly Action ExceptionFuncWithContext = (item, ctx) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithData = (data, item) => { }; + private static readonly Action NullFuncWithData = null; + private static readonly Action ExceptionFuncWithData = (data, item) => throw new NotImplementedException(); + private static readonly Action DefaultFuncWithDataAndContext = (data, item, ctx) => { }; + private static readonly Action NullFuncWithDataAndContext = null; + private static readonly Action ExceptionFuncWithDataAndContext = (data, item, ctx) => throw new NotImplementedException(); + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithDataAndContext, DefaultFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, NullFuncWithDataAndContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, DefaultFuncWithDataAndContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, ExceptionFuncWithDataAndContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithDataAndContext, DefaultFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, NullFuncWithData)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, DefaultFuncWithData); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, ExceptionFuncWithData); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithDataAndContext, DefaultFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, NullFuncWithContext)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, DefaultFuncWithContext); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, ExceptionFuncWithContext); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, FuncDeferredItemsWithDataAndContext, DefaultFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, NullFunc)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, DefaultFunc); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndContext, ExceptionFunc); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForDeferredItems + { + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, () => new[] { 1, 2, 3 }, (data, item, ctx) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (item, data, ctx) => { }); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (data, item, ctx) => throw new NotImplementedException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, () => new[] { 1, 2, 3 }, (data, item) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (data, item) => { }); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (data, item) => throw new NotImplementedException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, () => new[] { "a", "b", "c" }, (item, context) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { "a", "b", "c" }, (item, ctx) => { }); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, () => new[] { "a", "b", "c" }, item => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { "a", "b", "c" }, item => { }); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(context); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, () => new[] { "a", "b", "c" }, (item, c) => throw new NotImplementedException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForImmediateItems + { + public sealed class WithData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, new[] { 1, 2, 3 }, (data, item, context) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Actions_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (data, item, context) => { }); + + // Then + Assert.Equal(3, builder.Target.Actions.Count); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, new[] { 1, 2, 3 }, (data, item) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Actions_Foreach_Item() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (data, item) => { }); + + // Then + Assert.Equal(3, builder.Target.Actions.Count); + } + } + } + + public sealed class WithoutData + { + public sealed class WithContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, new[] { "a", "b", "c" }, (item, context) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Actions_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { "a", "b", "c" }, (item, context) => { }); + + // Then + Assert.Equal(3, builder.Target.Actions.Count); + } + } + + public sealed class WithoutContext + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(null, new string[0], item => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { 1, 2, 3 }, (Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Actions_Foreach_Item() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + CakeTaskBuilderExtensions.DoesForEach(builder, new[] { "a", "b", "c" }, item => { }); + + // Then + Assert.Equal(3, builder.Target.Actions.Count); + } + } + } + } + } + + public sealed class TheDeferOnErrorMethod + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => CakeTaskBuilderExtensions.DeferOnError(null)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + builder.Does(() => throw new NotImplementedException()); + builder.Does(() => throw new NotSupportedException()); + builder.Does(() => throw new OutOfMemoryException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + } + + [Fact] + public async Task Should_Aggregate_Exceptions_From_Actions() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + builder.Does(() => throw new NotImplementedException()); + builder.Does(() => throw new NotSupportedException()); + builder.Does(() => throw new OutOfMemoryException()); + builder.DeferOnError(); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); + var ex = result as AggregateException; + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotImplementedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotSupportedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(OutOfMemoryException)); + } + + [Fact] + public async Task Should_Only_Aggregate_Exceptions_When_There_Are_Many() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var context = new CakeContextFixture().CreateContext(); + + // When + builder.Does(() => throw new NotImplementedException()); + builder.DeferOnError(); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(context)); + + // Then + Assert.IsType(result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeTaskBuilderOfTExtensionsTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskBuilderOfTExtensionsTests.cs new file mode 100644 index 0000000000..8e21737f30 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeTaskBuilderOfTExtensionsTests.cs @@ -0,0 +1,673 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cake.Core.Tests.Fixtures; +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class CakeTaskBuilderOfTExtensionsTests + { + public sealed class TheIsDependentOnMethod + { + [Fact] + public void Should_Add_Dependency_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.IsDependentOn("other"); + + // Then + Assert.Single(task.Dependencies); + } + + public sealed class OnMethodTaskBuilder + { + [Fact] + public void Should_Add_Dependency_To_Task() + { + // Given + var parentTask = new CakeTask("parent"); + var childTask = new CakeTask("child"); + var builder = new CakeTaskBuilder(parentTask); + var cakeTaskBuilder = new CakeTaskBuilder(childTask); + + // When + builder.IsDependentOn(cakeTaskBuilder); + + // Then + Assert.Single(parentTask.Dependencies); + } + + [Fact] + public void Should_Add_Dependency_To_Task_With_Correct_Name() + { + // Given + var parentTask = new CakeTask("parent"); + var childTask = new CakeTask("child"); + var builder = new CakeTaskBuilder(parentTask); + var childTaskBuilder = new CakeTaskBuilder(childTask); + + // When + builder.IsDependentOn(childTaskBuilder); + + // Then + Assert.Equal(parentTask.Dependencies[0].Name, childTaskBuilder.Target.Name); + } + + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + var childTask = new CakeTask("child"); + CakeTaskBuilder builder = null; + var childTaskBuilder = new CakeTaskBuilder(childTask); + + // When + var result = Record.Exception(() => builder.IsDependentOn(childTaskBuilder)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_OtherBuilder_Is_Null() + { + // Given + var parentTask = new CakeTask("parent"); + var builder = new CakeTaskBuilder(parentTask); + CakeTaskBuilder childTaskBuilder = null; + + // When + var result = Record.Exception(() => builder.IsDependentOn(childTaskBuilder)); + + // Then + AssertEx.IsArgumentNullException(result, "other"); + } + } + } + + public sealed class TheIsDependeeOfMethod + { + [Fact] + public void Should_Add_Dependee_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.IsDependeeOf("other"); + + // Then + Assert.Single(task.Dependees); + Assert.Equal("other", task.Dependees[0].Name); + } + + [Fact] + public void Should_Add_Dependee_To_Task_From_Other() + { + // Given + var task = new CakeTask("task"); + var other = new CakeTask("other"); + var builder = new CakeTaskBuilder(task); + var otherBuilder = new CakeTaskBuilder(other); + + // When + builder.IsDependeeOf(otherBuilder); + + // Then + Assert.Single(task.Dependees); + Assert.Equal("other", task.Dependees[0].Name); + } + } + + public sealed class TheWithCriteriaMethod + { + public sealed class ThatAcceptsBoolean + { + [Fact] + public void Should_Add_Criteria_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.WithCriteria((ctx, data) => false); + + // Then + Assert.Single(task.Criterias); + } + + [Fact] + public void Should_Add_Message_To_Criteria_If_Specified() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.WithCriteria((ctx, data) => false, "Foo"); + + // Then + Assert.Single(task.Criterias); + Assert.Equal("Foo", task.Criterias[0].Message); + } + } + + public sealed class ThatAcceptsBooleanLambda + { + [Fact] + public void Should_Add_Criteria_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.WithCriteria((ctx, data) => true); + + // Then + Assert.Single(task.Criterias); + } + + [Fact] + public void Should_Add_Message_To_Criteria_If_Specified() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.WithCriteria((ctx, data) => true, "Foo"); + + // Then + Assert.Single(task.Criterias); + Assert.Equal("Foo", task.Criterias[0].Message); + } + } + } + + public sealed class TheDoesMethod + { + public sealed class ThatIsAsynchronous + { + public sealed class WithData + { + public sealed class Withctx + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Func)null)); + + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async (data, ctx) => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + + public sealed class Withoutctx + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Func)null)); + + // Then + AssertEx.IsArgumentNullException(result, "func"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does(async (ctx, data) => { await Task.Delay(0, TestContext.Current.CancellationToken); }); + + // Then + Assert.Single(task.Actions); + } + } + } + } + + public sealed class ThatIsSynchronous + { + public sealed class WithData + { + public sealed class Withctx + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => builder.Does((Action)null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Does((data, ctx) => { }); + + // Then + Assert.Single(task.Actions); + } + } + } + } + } + + public sealed class TheOnErrorMethod + { + public sealed class WithData + { + public sealed class Withctx + { + public sealed class WithException + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.OnError((exception, ctx, data) => { }); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + Assert.IsType>(builder.Target.ErrorHandler); + } + } + } + } + } + + public sealed class TheContinueOnErrorMethod + { + [Fact] + public void Should_Set_The_Error_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.ContinueOnError(); + + // Then + Assert.NotNull(builder.Target.ErrorHandler); + } + } + + public sealed class TheFinallyMethod + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + CakeTaskBuilder builder = null; + + // When + var result = Record.Exception(() => CakeTaskBuilderOfTExtensions.Finally(builder, (ctx, data) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => CakeTaskBuilderOfTExtensions.Finally(builder, default(Action))); + + // Then + AssertEx.IsArgumentNullException(result, "finallyHandler"); + } + + [Fact] + public void Should_Set_The_Finally_Handler() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.Finally((ctx, data) => { }); + + // Then + Assert.NotNull(builder.Target.FinallyHandler); + } + } + + public sealed class TheDoesForEachMethod + { + public sealed class ForDeferredItemsWithDataAndctx + { + private static readonly Func> FuncDeferredItemsWithDataAndctx = (data, ctx) => new[] { 1, 2, 3 }; + private static readonly Action DefaultFuncWithDataAndctx = (data, item, ctx) => { }; + private static readonly Action NullFuncWithDataAndctx = null; + private static readonly Action ExceptionFuncWithDataAndctx = (data, item, ctx) => throw new NotImplementedException(); + public sealed class WithData + { + public sealed class Withctx + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderOfTExtensions.DoesForEach(null, FuncDeferredItemsWithDataAndctx, DefaultFuncWithDataAndctx)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + CakeTaskBuilderOfTExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndctx, NullFuncWithDataAndctx)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderOfTExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndctx, DefaultFuncWithDataAndctx); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(ctx); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderOfTExtensions.DoesForEach(builder, FuncDeferredItemsWithDataAndctx, ExceptionFuncWithDataAndctx); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(ctx)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForDeferredItems + { + public sealed class WithData + { + public sealed class Withctx + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderOfTExtensions.DoesForEach(default(CakeTaskBuilder), (data, ctx) => new[] { 1, 2, 3 }, (data, item, ctx) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + builder.DoesForEach((data, ctx) => new[] { 1, 2, 3 }, null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public async Task Should_Add_Actions_To_Task_After_Execution() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + builder.DoesForEach((data, ctx) => new[] { 1, 2, 3 }, (item, data, ctx) => { }); + + // Then + Assert.Empty(builder.Target.Actions); + Assert.Single(builder.Target.DelayedActions); + + // When + await builder.Target.Execute(ctx); + + // Then + Assert.Empty(builder.Target.DelayedActions); + Assert.Equal(3, builder.Target.Actions.Count); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + CakeTaskBuilderOfTExtensions.DoesForEach(builder, (data, ctx) => new[] { 1, 2, 3 }, (data, item, ctx) => throw new NotImplementedException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(ctx)); + + // Then + Assert.IsType(result); + } + } + } + } + + public sealed class ForImmediateItems + { + public sealed class WithData + { + public sealed class Withctx + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given, When + var result = Record.Exception(() => + CakeTaskBuilderOfTExtensions.DoesForEach(default(CakeTaskBuilder), new[] { 1, 2, 3 }, (data, item, ctx) => { })); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + var result = Record.Exception(() => + builder.DoesForEach(new[] { 1, 2, 3 }, null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Actions_To_Task() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + + // When + builder.DoesForEach(new[] { 1, 2, 3 }, (data, item, ctx) => { }); + + // Then + Assert.Equal(3, builder.Target.Actions.Count); + } + } + } + } + } + + public sealed class TheDeferOnErrorMethod + { + [Fact] + public void Should_Throw_If_Builder_Is_Null() + { + // Given + CakeTaskBuilder builder = null; + + // When + var result = Record.Exception(() => CakeTaskBuilderOfTExtensions.DeferOnError(builder)); + + // Then + AssertEx.IsArgumentNullException(result, "builder"); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + builder.Does((ctx, data) => throw new NotImplementedException()); + builder.Does((ctx, data) => throw new NotSupportedException()); + builder.Does((ctx, data) => throw new OutOfMemoryException()); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(ctx)); + + // Then + Assert.IsType(result); + } + + [Fact] + public async Task Should_Aggregate_Exceptions_From_Actions() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + builder.Does((ctx, data) => throw new NotImplementedException()); + builder.Does((ctx, data) => throw new NotSupportedException()); + builder.Does((ctx, data) => throw new OutOfMemoryException()); + builder.DeferOnError(); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(ctx)); + + // Then + Assert.IsType(result); + var ex = result as AggregateException; + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotImplementedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotSupportedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(OutOfMemoryException)); + } + + [Fact] + public async Task Should_Only_Aggregate_Exceptions_When_There_Are_Many() + { + // Given + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); + var ctx = new CakeContextFixture().CreateContext(); + + // When + builder.Does((ctx, data) => throw new NotImplementedException()); + builder.DeferOnError(); + var result = await Record.ExceptionAsync(() => builder.Target.Execute(ctx)); + + // Then + Assert.IsType(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeTaskBuilderTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskBuilderTests.cs index 26922d855c..879d04d51a 100644 --- a/src/Cake.Core.Tests/Unit/CakeTaskBuilderTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeTaskBuilderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Xunit; namespace Cake.Core.Tests.Unit @@ -13,10 +14,10 @@ public sealed class TheConstructor public void Should_Throw_Is_Provided_Task_Is_Null() { // Given, When - var result = Record.Exception(() => new CakeTaskBuilder(null)); + var result = Record.Exception(() => new CakeTaskBuilder(null)); // Then - Assert.IsArgumentNullException(result, "task"); + AssertEx.IsArgumentNullException(result, "task"); } } @@ -26,12 +27,12 @@ public sealed class TheTaskProperty public void Should_Return_The_Task_Provided_To_The_Constructor() { // Given, When - var task = new ActionTask("task"); - var builder = new CakeTaskBuilder(task); + var task = new CakeTask("task"); + var builder = new CakeTaskBuilder(task); // Then - Assert.Equal(task, builder.Task); + Assert.Equal(task, builder.Target); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeTaskCriteriaTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskCriteriaTests.cs new file mode 100644 index 0000000000..b2f30d9a48 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/CakeTaskCriteriaTests.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class CakeTaskCriteriaTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Predicate_Is_Null() + { + // Given, When + var result = Record.Exception(() => new CakeTaskCriteria(null)); + + // Then + AssertEx.IsArgumentNullException(result, "predicate"); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/CakeTaskExtensionsTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskExtensionsTests.cs index 2f6e12244b..978651d5ba 100644 --- a/src/Cake.Core.Tests/Unit/CakeTaskExtensionsTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeTaskExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Xunit; namespace Cake.Core.Tests.Unit @@ -13,27 +14,27 @@ public sealed class TheAddCriteriaMethod public void Should_Throw_If_Criteria_Is_Null() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When var result = Record.Exception(() => task.AddCriteria(null)); // Then - Assert.IsArgumentNullException(result, "criteria"); + AssertEx.IsArgumentNullException(result, "predicate"); } [Fact] public void Should_Add_Criteria() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When - task.AddCriteria(() => true); + task.AddCriteria(context => true); // Then - Assert.Equal(1, task.Criterias.Count); + Assert.Single(task.Criterias); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/CakeTaskTests.cs b/src/Cake.Core.Tests/Unit/CakeTaskTests.cs index 64a051fb58..187df4dc6e 100644 --- a/src/Cake.Core.Tests/Unit/CakeTaskTests.cs +++ b/src/Cake.Core.Tests/Unit/CakeTaskTests.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Linq; +using System.Threading.Tasks; +using Cake.Core.Tests.Fixtures; using Xunit; namespace Cake.Core.Tests.Unit @@ -15,10 +18,10 @@ public sealed class TheConstructor public void Should_Throw_If_Name_Is_Null() { // Given, When - var result = Record.Exception(() => new ActionTask(null)); + var result = Record.Exception(() => new CakeTask(null)); // Then - Assert.IsArgumentNullException(result, "name"); + AssertEx.IsArgumentNullException(result, "name"); } [Theory] @@ -29,11 +32,11 @@ public void Should_Throw_If_Name_Is_Null() public void Should_Throw_If_Name_Is_Empty(string name) { // Given, When - var result = Record.Exception(() => new ActionTask(name)); + var result = Record.Exception(() => new CakeTask(name)); // Then Assert.IsType(result); - Assert.Equal("Task name cannot be empty.", result.Message); + Assert.Equal("Task name cannot be empty.", result?.Message); } } @@ -43,22 +46,21 @@ public sealed class TheAddDependencyMethod public void Should_Add_Dependency_If_Not_Already_Present() { // Given - var task = new ActionTask("task"); - + var task = new CakeTask("task"); // When task.AddDependency("other"); // Then - Assert.Equal(1, task.Dependencies.Count); - Assert.Equal("other", task.Dependencies[0]); + Assert.Single(task.Dependencies); + Assert.Equal("other", task.Dependencies[0].Name); } [Fact] public void Should_Throw_If_Dependency_Already_Exist() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); task.AddDependency("other"); // When @@ -66,7 +68,39 @@ public void Should_Throw_If_Dependency_Already_Exist() // Then Assert.IsType(result); - Assert.Equal("The task 'task' already have a dependency on 'other'.", result.Message); + Assert.Equal("The task 'task' already have a dependency on 'other'.", result?.Message); + } + } + + public sealed class TheAddReverseDependencyMethod + { + [Fact] + public void Should_Add_Dependency_If_Not_Already_Present() + { + // Given + var task = new CakeTask("task"); + + // When + task.AddDependee("other"); + + // Then + Assert.Single(task.Dependees); + Assert.Equal("other", task.Dependees[0].Name); + } + + [Fact] + public void Should_Throw_If_Dependency_Already_Exist() + { + // Given + var task = new CakeTask("task"); + task.AddDependee("other"); + + // When + var result = Record.Exception(() => task.AddDependee("other")); + + // Then + Assert.IsType(result); + Assert.Equal("The task 'task' already is a dependee of 'other'.", result?.Message); } } @@ -76,26 +110,26 @@ public sealed class TheAddCriteriaMethod public void Should_Throw_If_Criteria_Is_Null() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When var result = Record.Exception(() => task.AddCriteria(null)); // Then - Assert.IsArgumentNullException(result, "criteria"); + AssertEx.IsArgumentNullException(result, "predicate"); } [Fact] public void Should_Add_Criteria() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When task.AddCriteria(context => true); // Then - Assert.Equal(1, task.Criterias.Count); + Assert.Single(task.Criterias); } } @@ -105,41 +139,42 @@ public sealed class TheSetErrorHandlerMethod public void Should_Throw_If_Error_Handler_Is_Null() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When var result = Record.Exception(() => task.SetErrorHandler(null)); // Then - Assert.IsArgumentNullException(result, "errorHandler"); + AssertEx.IsArgumentNullException(result, "errorHandler"); } [Fact] public void Should_Set_Error_Handler() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When - task.SetErrorHandler(e => { }); + task.SetErrorHandler((e, c) => { }); // Then Assert.NotNull(task.ErrorHandler); + Assert.IsType>(task.ErrorHandler); } [Fact] public void Should_Throw_If_Setting_More_Than_One_Error_Handler() { // Given - var task = new ActionTask("task"); - task.SetErrorHandler(e => { }); + var task = new CakeTask("task"); + task.SetErrorHandler((e, c) => { }); // When - var result = Record.Exception(() => task.SetErrorHandler(e => { })); + var result = Record.Exception(() => task.SetErrorHandler((e, c) => { })); // Then Assert.IsType(result); - Assert.Equal("There can only be one error handler per task.", result.Message); + Assert.Equal("There can only be one error handler per task.", result?.Message); } } @@ -149,20 +184,33 @@ public sealed class TheSetFinallyHandlerMethod public void Should_Throw_If_Finally_Handler_Is_Null() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); + + // When + var result = Record.Exception(() => task.SetFinallyHandler(default(Action))); + + // Then + AssertEx.IsArgumentNullException(result, "finallyHandler"); + } + + [Fact] + public void Should_Throw_If_Finally_Context_Handler_Is_Null() + { + // Given + var task = new CakeTask("task"); // When - var result = Record.Exception(() => task.SetFinallyHandler(null)); + var result = Record.Exception(() => task.SetFinallyHandler(default(Action))); // Then - Assert.IsArgumentNullException(result, "finallyHandler"); + AssertEx.IsArgumentNullException(result, "finallyHandler"); } [Fact] public void Should_Set_Finally_Handler() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When task.SetFinallyHandler(() => { }); @@ -171,11 +219,24 @@ public void Should_Set_Finally_Handler() Assert.NotNull(task.FinallyHandler); } + [Fact] + public void Should_Set_Finally_Context_Handler() + { + // Given + var task = new CakeTask("task"); + + // When + task.SetFinallyHandler(context => { }); + + // Then + Assert.NotNull(task.FinallyHandler); + } + [Fact] public void Should_Throw_If_Setting_More_Than_One_Finally_Handler() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); task.SetFinallyHandler(() => { }); // When @@ -183,7 +244,7 @@ public void Should_Throw_If_Setting_More_Than_One_Finally_Handler() // Then Assert.IsType(result); - Assert.Equal("There can only be one finally handler per task.", result.Message); + Assert.Equal("There can only be one finally handler per task.", result?.Message); } } @@ -193,20 +254,20 @@ public sealed class TheSetErrorReportHandlerMethod public void Should_Throw_If_Error_Reporter_Is_Null() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When var result = Record.Exception(() => task.SetErrorReporter(null)); // Then - Assert.IsArgumentNullException(result, "errorReporter"); + AssertEx.IsArgumentNullException(result, "errorReporter"); } [Fact] public void Should_Set_Error_Reporter() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); // When task.SetErrorReporter(exception => { }); @@ -219,7 +280,7 @@ public void Should_Set_Error_Reporter() public void Should_Throw_If_Setting_More_Than_One_Error_Reporter() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); task.SetErrorReporter(error => { }); // When @@ -227,7 +288,91 @@ public void Should_Throw_If_Setting_More_Than_One_Error_Reporter() // Then Assert.IsType(result); - Assert.Equal("There can only be one error reporter per task.", result.Message); + Assert.Equal("There can only be one error reporter per task.", result?.Message); + } + } + + public sealed class TheAddActionMethod + { + [Fact] + public void Should_Throw_If_Action_Is_Null() + { + // Given + var task = new CakeTask("task"); + + // When + var result = Record.Exception(() => task.AddAction(null)); + + // Then + AssertEx.IsArgumentNullException(result, "action"); + } + + [Fact] + public void Should_Add_Action_To_Task() + { + // Given + var task = new CakeTask("task"); + + // When + task.AddAction(c => Task.CompletedTask); + + // Then + Assert.Single(task.Actions); + } + + [Fact] + public async Task Should_Throw_On_First_Failed_Action() + { + // Given + var task = new CakeTask("task"); + var context = new CakeContextFixture().CreateContext(); + + // When + task.Actions.Add((c) => throw new NotImplementedException()); + task.Actions.Add((c) => throw new NotSupportedException()); + task.Actions.Add((c) => throw new OutOfMemoryException()); + var result = await Record.ExceptionAsync(() => task.Execute(context)); + + // Then + Assert.IsType(result); + } + + [Fact] + public async Task Should_Aggregate_Exceptions_From_Actions() + { + // Given + var task = new CakeTask("task"); + var context = new CakeContextFixture().CreateContext(); + + // When + task.Actions.Add((c) => throw new NotImplementedException()); + task.Actions.Add((c) => throw new NotSupportedException()); + task.Actions.Add((c) => throw new OutOfMemoryException()); + task.SetDeferExceptions(true); + var result = await Record.ExceptionAsync(() => task.Execute(context)); + + // Then + Assert.IsType(result); + var ex = result as AggregateException; + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotImplementedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(NotSupportedException)); + Assert.Contains(ex.InnerExceptions, x => x.GetType() == typeof(OutOfMemoryException)); + } + + [Fact] + public async Task Should_Only_Aggregate_Exceptions_When_There_Are_Many() + { + // Given + var task = new CakeTask("task"); + var context = new CakeContextFixture().CreateContext(); + + // When + task.Actions.Add((c) => throw new NotImplementedException()); + task.SetDeferExceptions(true); + var result = await Record.ExceptionAsync(() => task.Execute(context)); + + // Then + Assert.IsType(result); } } @@ -235,7 +380,7 @@ public void Should_Throw_If_Setting_More_Than_One_Error_Reporter() public void Should_Implement_ICakeTaskInfo() { // Given - var task = new ActionTask("task"); + var task = new CakeTask("task"); task.AddDependency("dependency1"); task.AddDependency("dependency2"); task.Description = "my description"; @@ -247,7 +392,7 @@ public void Should_Implement_ICakeTaskInfo() Assert.IsAssignableFrom(task); Assert.Equal("task", result.Name); Assert.Equal("my description", result.Description); - Assert.Equal(new[] { "dependency1", "dependency2" }, result.Dependencies.ToArray()); + Assert.Equal(new[] { "dependency1", "dependency2" }, result.Dependencies.Select(x => x.Name).ToArray()); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Configuration/CakeConfigurationProviderTests.cs b/src/Cake.Core.Tests/Unit/Configuration/CakeConfigurationProviderTests.cs index cc4c0d4b0a..7fa261ed0a 100644 --- a/src/Cake.Core.Tests/Unit/Configuration/CakeConfigurationProviderTests.cs +++ b/src/Cake.Core.Tests/Unit/Configuration/CakeConfigurationProviderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tests.Fixtures; using Cake.Testing; using Xunit; @@ -22,7 +23,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.Create()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -36,7 +37,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.Create()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -53,7 +54,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => fixture.Create()); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -67,7 +68,7 @@ public void Should_Throw_If_Arguments_Are_Null() var result = Record.Exception(() => fixture.Create()); // Then - Assert.IsArgumentNullException(result, "arguments"); + AssertEx.IsArgumentNullException(result, "arguments"); } [Fact] @@ -232,4 +233,4 @@ public void Should_Use_Value_From_Argument_Over_Environment_Variable_And_Configu } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationParserTests.cs b/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationParserTests.cs index a3992112a0..b9938409ef 100644 --- a/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationParserTests.cs +++ b/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationParserTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; +using System.Collections.Generic; using System.IO; using Cake.Core.Configuration.Parser; using Cake.Core.Tests.Properties; @@ -27,8 +29,8 @@ public void Should_Throw_If_File_Do_Not_Exist() // Then Assert.IsType(result); - Assert.Equal("Unable to find the configuration file.", result.Message); - Assert.Equal("/Working/cake.config", ((FileNotFoundException)result).FileName); + Assert.Equal("Unable to find the configuration file.", result?.Message); + Assert.Equal("/Working/cake.config", ((FileNotFoundException)result)?.FileName); } [Fact] @@ -44,7 +46,7 @@ public void Should_Throw_If_Section_Contains_Whitespace() var result = Record.Exception(() => parser.Read("./cake.config")); // Then - Assert.IsExceptionWithMessage(result, "Sections cannot contain whitespace."); + AssertEx.IsExceptionWithMessage(result, "Sections cannot contain whitespace."); } [Fact] @@ -60,7 +62,7 @@ public void Should_Throw_If_Equals_Sign_Is_Missing_From_Key_And_Value_Pair() var result = Record.Exception(() => parser.Read("./cake.config")); // Then - Assert.IsExceptionWithMessage(result, "Expected to find '=' token."); + AssertEx.IsExceptionWithMessage(result, "Expected to find '=' token."); } [Fact] @@ -76,7 +78,7 @@ public void Should_Throw_If_Key_Contains_WhiteSpace() var result = Record.Exception(() => parser.Read("./cake.config")); // Then - Assert.IsExceptionWithMessage(result, "The key 'Hello World' contains whitespace."); + AssertEx.IsExceptionWithMessage(result, "The key 'Hello World' contains whitespace."); } [Fact] @@ -92,7 +94,7 @@ public void Should_Throw_If_KeyValue_Pair_Is_Not_Followed_By_Section_Or_Another_ var result = Record.Exception(() => parser.Read("./cake.config")); // Then - Assert.IsExceptionWithMessage(result, "Encountered unexpected token."); + AssertEx.IsExceptionWithMessage(result, "Encountered unexpected token."); } [Fact] @@ -113,6 +115,137 @@ public void Should_Parse_Ini_With_Sections_Correctly() Assert.True(result.ContainsKey("Section2_Baz")); Assert.Equal("Qux", result["Section2_Baz"]); } + + public sealed class EnvironmentVariableSubstitution + { + [Theory] + [MemberData(nameof(EnvironmentVariableSubstitutionTestData))] + public void Should_Substitute_Environment_Variables(EnvironmentVariableTestHarness harness) + { + // Given + var environment = harness.CreateEnvironment(); + var fileSystem = new FakeFileSystem(environment); + fileSystem.CreateFile("/Working/cake.config").SetContent(harness.IniFileContents); + var parser = new ConfigurationParser(fileSystem, environment); + + // When + var result = parser.Read("/Working/cake.config"); + + // Then + harness.Assert(result); + } + + public static IEnumerable EnvironmentVariableSubstitutionTestData() + { + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"Basic environment variable substitution", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment().AddEnvironmentVariable("VALUE", "world"), + IniFileContents = "[Section1]\nHello=%VALUE%", + Assert = result => Assert.Equal("world", result["Section1_Hello"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"Casing of substitution token should not matter", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment().AddEnvironmentVariable("VALUE", "world"), + IniFileContents = "[Section1]\nHello=%value%", + Assert = result => Assert.Equal("world", result["Section1_Hello"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"Should respect leading text", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment().AddEnvironmentVariable("VALUE", "world"), + IniFileContents = "[Section1]\nHello=it is a wonderful %VALUE%", + Assert = result => Assert.Equal("it is a wonderful world", result["Section1_Hello"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"Should respect trailing text", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment().AddEnvironmentVariable("VALUE", "John"), + IniFileContents = "[Section1]\nHello=%VALUE%, nice to meet you.", + Assert = result => Assert.Equal("John, nice to meet you.", result["Section1_Hello"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"No environment variable found for substitution token, should not substitute", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment(), + IniFileContents = "[Section1]\nHello=%VALUE%", + Assert = result => Assert.Equal("%VALUE%", result["Section1_Hello"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"Special characters should be allowed", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment().AddEnvironmentVariable("ProgramFiles(x86)", "PATH TO PROGRAM FILES"), + IniFileContents = "[Section1]\nSpecialPath=%ProgramFiles(x86)%", + Assert = result => Assert.Equal("PATH TO PROGRAM FILES", result["Section1_SpecialPath"]) + }, + }; + + yield return new object[] + { + new EnvironmentVariableTestHarness() + { + Testcase = $"More than one environment variable to substitute, should substitute all", + CreateEnvironment = () => FakeEnvironment.CreateUnixEnvironment() + .AddEnvironmentVariable("VARIABLE1", "Value1") + .AddEnvironmentVariable("VARIABLE2", "Value2"), + IniFileContents = "[Section1]\nValue1=%VARIABLE1%\nValue2=%VARIABLE2%", + Assert = result => + { + Assert.Equal("Value1", result["Section1_Value1"]); + Assert.Equal("Value2", result["Section1_Value2"]); + } + }, + }; + } + + public class EnvironmentVariableTestHarness + { + public string Testcase { get; set; } + + public Func CreateEnvironment { get; set; } + + public string IniFileContents { get; set; } + + public Action> Assert { get; set; } + + public override string ToString() + { + return $"Testcase={Testcase}"; + } + } + } + } + } + + internal static class FakeEnvironmentExtensions + { + internal static FakeEnvironment AddEnvironmentVariable(this FakeEnvironment environment, string variable, string value) + { + environment.SetEnvironmentVariable(variable, value); + return environment; } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationTokenizerTests.cs b/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationTokenizerTests.cs index cd3a4f7673..ab461e7247 100644 --- a/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationTokenizerTests.cs +++ b/src/Cake.Core.Tests/Unit/Configuration/Parser/ConfigurationTokenizerTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.Configuration.Parser; using Cake.Core.Tests.Properties; @@ -19,7 +20,7 @@ public void Should_Tokenize_Comment() var result = ConfigurationTokenizer.Tokenize("; Hello World"); // Then - Assert.Equal(0, result.Count); + Assert.Empty(result); } [Fact] @@ -29,7 +30,7 @@ public void Should_Tokenize_Section() var result = ConfigurationTokenizer.Tokenize("[TheSection]"); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal(ConfigurationTokenKind.Section, result[0].Kind); Assert.Equal("TheSection", result[0].Value); } @@ -43,7 +44,7 @@ public void Should_Throw_If_Section_Is_Malformed(string section) var result = Record.Exception(() => ConfigurationTokenizer.Tokenize(section)); // Then - Assert.IsExceptionWithMessage(result, "Encountered malformed section."); + AssertEx.IsExceptionWithMessage(result, "Encountered malformed section."); } [Fact] @@ -92,5 +93,4 @@ public void Should_Tokenize_Ini_File() } } } -} - +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/AnsiDetectorTests.cs b/src/Cake.Core.Tests/Unit/Diagnostics/AnsiDetectorTests.cs new file mode 100644 index 0000000000..42b09252bb --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Diagnostics/AnsiDetectorTests.cs @@ -0,0 +1,53 @@ +using Cake.Core.Diagnostics; +using Cake.Testing; +using Xunit; + +namespace Cake.Core.Tests.Unit.Diagnostics +{ + public sealed class AnsiDetectorTests + { + public sealed class The_SupportsAnsi_Method + { + public sealed class UsingTeamCity + { + [Theory] + [InlineData("2020.2")] + [InlineData("2020.1.5")] + [InlineData("2017.1")] + [InlineData("10.0.5")] + [InlineData("10.0")] + [InlineData("9.1.7")] + public void Should_Return_True_When_Running_TeamCity_9_1_or_2017_Or_Higher(string teamCityVersion) + { + // Given + var fakeEnvironment = FakeEnvironment.CreateUnixEnvironment(); + + // When + fakeEnvironment.SetEnvironmentVariable("TEAMCITY_VERSION", teamCityVersion); + + // Then + Assert.True(AnsiDetector.SupportsAnsi(fakeEnvironment)); + } + + [Theory] + [InlineData("9.0.5")] + [InlineData("9.0.1")] + [InlineData("8.1.5")] + [InlineData("7.1.5")] + [InlineData("7.0.1")] + [InlineData("6.0")] + public void Should_Return_False_When_Running_TeamCity_9_0_Or_Lower(string teamCityVersion) + { + // Given + var fakeEnvironment = FakeEnvironment.CreateUnixEnvironment(); + + // When + fakeEnvironment.SetEnvironmentVariable("TEAMCITY_VERSION", teamCityVersion); + + // Then + Assert.False(AnsiDetector.SupportsAnsi(fakeEnvironment)); + } + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/CakeBuildLogTests.cs b/src/Cake.Core.Tests/Unit/Diagnostics/CakeBuildLogTests.cs new file mode 100644 index 0000000000..e783430e90 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Diagnostics/CakeBuildLogTests.cs @@ -0,0 +1,292 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.Diagnostics; +using Cake.Testing; +using NSubstitute; +using Xunit; + +namespace Cake.Core.Tests.Unit.Diagnostics +{ + public sealed class CakeBuildLogTests + { + public sealed class TheWriteMethod + { + public sealed class UsingAnsiEscapeCodes + { + [Theory] + [InlineData(Verbosity.Quiet, Verbosity.Minimal)] + [InlineData(Verbosity.Minimal, Verbosity.Normal)] + [InlineData(Verbosity.Normal, Verbosity.Verbose)] + [InlineData(Verbosity.Verbose, Verbosity.Diagnostic)] + public void Should_Drop_Log_Messages_Written_With_A_Lower_Verbosity_Than_Allowed(Verbosity logVerbosity, Verbosity messageVerbosity) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, logVerbosity); + + // When + log.Write(messageVerbosity, LogLevel.Information, "Hello World"); + + // Then + Assert.Empty(console.Messages); + } + + [Theory] + [InlineData(Verbosity.Minimal, Verbosity.Quiet)] + [InlineData(Verbosity.Normal, Verbosity.Minimal)] + [InlineData(Verbosity.Verbose, Verbosity.Normal)] + [InlineData(Verbosity.Diagnostic, Verbosity.Verbose)] + public void Should_Write_Log_Messages_Written_With_A_Higher_Verbosity_Than_Allowed(Verbosity logVerbosity, Verbosity messageVerbosity) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, logVerbosity); + + // When + log.Write(messageVerbosity, LogLevel.Information, "Hello World"); + + // Then + Assert.Single(console.Messages); + } + + [Theory] + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Information)] + [InlineData(LogLevel.Verbose)] + [InlineData(LogLevel.Debug)] + public void Should_Write_Standard_Log_Messages_Written_With_A_Higher_Log_Level_Than_Error(LogLevel logLevel) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, logLevel, "Hello World"); + + // Then + Assert.Single(console.Messages); + } + + [Theory] + [InlineData(LogLevel.Fatal)] + [InlineData(LogLevel.Error)] + public void Should_Write_Error_Log_Messages_Written_With_A_Lower_Log_Level_Than_Warning(LogLevel logLevel) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, logLevel, "Hello World"); + + // Then + Assert.Single(console.ErrorMessages); + } + + [Fact] + public void Should_Not_Colorize_A_Log_Message_Containg_A_Single_Token() + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, LogLevel.Information, "{0}", "Hello World"); + + // Then + Assert.Single(console.Messages); + Assert.Equal("\u001b[37;1mHello World\u001b[0m", console.Messages[0]); + } + + [Theory] + [InlineData(LogLevel.Warning, "\u001b[33;1mHello, \u001b[0m\u001b[33;1mWorld\u001b[0m")] + [InlineData(LogLevel.Information, "\u001b[37;1mHello, \u001b[0m\u001b[44m\u001b[37;1mWorld\u001b[0m")] + [InlineData(LogLevel.Verbose, "\u001b[37mHello, \u001b[0m\u001b[37;1mWorld\u001b[0m")] + [InlineData(LogLevel.Debug, "\u001b[30;1mHello, \u001b[0m\u001b[37mWorld\u001b[0m")] + public void Should_Colorize_Tokens_Correctly(LogLevel level, string expected) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, level, "Hello, {0}", "World"); + + // Then + Assert.Single(console.Messages); + Assert.Equal(expected, console.Messages[0]); + } + + [Theory] + [InlineData(LogLevel.Fatal, "\u001b[45;1m\u001b[37;1mHello, \u001b[0m\u001b[45m\u001b[37;1mWorld\u001b[0m")] + [InlineData(LogLevel.Error, "\u001b[41m\u001b[37;1mHello, \u001b[0m\u001b[41;1m\u001b[37;1mWorld\u001b[0m")] + public void Should_Colorize_Error_Tokens_Correctly(LogLevel level, string expected) + { + // Given + var console = FakeConsole.CreateAnsiConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, level, "Hello, {0}", "World"); + + // Then + Assert.Single(console.ErrorMessages); + Assert.Equal(expected, console.ErrorMessages[0]); + } + } + + public sealed class UsingSystemConsole + { + [Theory] + [InlineData(Verbosity.Quiet, Verbosity.Minimal)] + [InlineData(Verbosity.Minimal, Verbosity.Normal)] + [InlineData(Verbosity.Normal, Verbosity.Verbose)] + [InlineData(Verbosity.Verbose, Verbosity.Diagnostic)] + public void Should_Drop_Log_Messages_Written_With_A_Lower_Verbosity_Than_Allowed(Verbosity logVerbosity, Verbosity messageVerbosity) + { + // Given + var console = new FakeConsole(); + var log = new CakeBuildLog(console, logVerbosity); + + // When + log.Write(messageVerbosity, LogLevel.Information, "Hello World"); + + // Then + Assert.Empty(console.Messages); + } + + [Theory] + [InlineData(Verbosity.Minimal, Verbosity.Quiet)] + [InlineData(Verbosity.Normal, Verbosity.Minimal)] + [InlineData(Verbosity.Verbose, Verbosity.Normal)] + [InlineData(Verbosity.Diagnostic, Verbosity.Verbose)] + public void Should_Write_Log_Messages_Written_With_A_Higher_Verbosity_Than_Allowed(Verbosity logVerbosity, Verbosity messageVerbosity) + { + // Given + var console = new FakeConsole(); + var log = new CakeBuildLog(console, logVerbosity); + + // When + log.Write(messageVerbosity, LogLevel.Information, "Hello World"); + + // Then + Assert.Single(console.Messages); + } + + [Theory] + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Information)] + [InlineData(LogLevel.Verbose)] + [InlineData(LogLevel.Debug)] + public void Should_Write_Standard_Log_Messages_Written_With_A_Higher_Log_Level_Than_Error(LogLevel logLevel) + { + // Given + var console = new FakeConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, logLevel, "Hello World"); + + // Then + Assert.Single(console.Messages); + } + + [Theory] + [InlineData(LogLevel.Fatal)] + [InlineData(LogLevel.Error)] + public void Should_Write_Error_Log_Messages_Written_With_A_Lower_Log_Level_Than_Warning(LogLevel logLevel) + { + // Given + var console = new FakeConsole(); + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, logLevel, "Hello World"); + + // Then + Assert.Single(console.ErrorMessages); + } + + [Fact] + public void Should_Not_Colorize_A_Log_Message_Containg_A_Single_Token() + { + // Given + var console = new FakeConsole(); + console.OutputConsoleColor = true; + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, LogLevel.Information, "{0}", "Hello World"); + + // Then + Assert.Single(console.Messages); + Assert.Equal("#[Black|White]Hello World[/]", console.Messages[0]); + } + + [Theory] + [InlineData(LogLevel.Warning, "#[Black|Yellow]Hello, [/]#[Black|Yellow]World[/]")] + [InlineData(LogLevel.Information, "#[Black|White]Hello, [/]#[DarkBlue|White]World[/]")] + [InlineData(LogLevel.Verbose, "#[Black|Gray]Hello, [/]#[Black|White]World[/]")] + [InlineData(LogLevel.Debug, "#[Black|DarkGray]Hello, [/]#[Black|Gray]World[/]")] + public void Should_Colorize_Tokens_Correctly(LogLevel level, string expected) + { + // Given + var console = new FakeConsole(); + console.OutputConsoleColor = true; + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, level, "Hello, {0}", "World"); + + // Then + Assert.Single(console.Messages); + Assert.Equal(expected, console.Messages[0]); + } + + [Theory] + [InlineData(LogLevel.Fatal, "#[Magenta|White]Hello, [/]#[DarkMagenta|White]World[/]")] + [InlineData(LogLevel.Error, "#[DarkRed|White]Hello, [/]#[Red|White]World[/]")] + public void Should_Colorize_Error_Tokens_Correctly(LogLevel level, string expected) + { + // Given + var console = new FakeConsole(); + console.OutputConsoleColor = true; + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Write(Verbosity.Diagnostic, level, "Hello, {0}", "World"); + + // Then + Assert.Single(console.ErrorMessages); + Assert.Equal(expected, console.ErrorMessages[0]); + } + + [Theory] + [InlineData(false, "#[Black|DarkGray]Executing: if ($LASTEXITCODE -gt 0) { throw \"script failed with exit code $LASTEXITCODE\" }[/]")] + [InlineData(true, "\u001b[30;1mExecuting: if ($LASTEXITCODE -gt 0) { throw \"script failed with exit code $LASTEXITCODE\" }\u001b[0m")] + public void Should_Output_Escaped_Tokens_Correctly(bool ansi, string expected) + { + // Given + var message = "Executing: if ($LASTEXITCODE -gt 0) {{ throw \"script failed with exit code $LASTEXITCODE\" }}"; + var console = ansi + ? FakeConsole.CreateAnsiConsole() + : new FakeConsole() + { + OutputConsoleColor = true + }; + var log = new CakeBuildLog(console, Verbosity.Diagnostic); + + // When + log.Debug(Verbosity.Normal, message); + + // Then + Assert.Single(console.Messages); + Assert.Equal(expected, console.Messages[0]); + } + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/FormatParserTests.cs b/src/Cake.Core.Tests/Unit/Diagnostics/FormatParserTests.cs new file mode 100644 index 0000000000..1acf8f3e9d --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Diagnostics/FormatParserTests.cs @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using Cake.Core.Diagnostics.Formatting; +using Xunit; + +namespace Cake.Core.Tests.Unit.Diagnostics +{ + public class FormatParserTests + { + public sealed class TheParseMethod : FormatParserTests + { + [Fact] + public void Return_No_Token_For_An_Empty_Format() + { + // Given, When + var result = FormatParser.Parse(string.Empty).ToArray(); + + // Then + Assert.Empty(result); + } + + [Fact] + public void Returns_Correct_Token_For_Message_With_No_Properties() + { + // Given, When + var result = FormatParser.Parse("Hello World!").ToArray(); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + Assert.Equal("Hello World!", ((LiteralToken)result[0]).Text); + } + + [Fact] + public void Returns_Correct_Token_For_Message_With_One_Property() + { + // Given, When + var result = FormatParser.Parse("{0}").ToArray(); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + Assert.Equal(0, ((PropertyToken)result[0]).Position); + } + + [Fact] + public void Should_Return_Literal_Tokens_For_Message_With_Nothing_But_Escaped_Braces() + { + // Given, When + var result = FormatParser.Parse("{{}}").ToArray(); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + Assert.Equal("{}", ((LiteralToken)result[0]).Text); + } + + [Fact] + public void Should_Return_Literal_Tokens_For_Message_With_Escaped_Braces_And_Properties() + { + // Given, When + var result = FormatParser.Parse("{{}} {0}").ToArray(); + + // Then + Assert.Equal(2, result.Length); + Assert.IsType(result[0]); + Assert.Equal("{} ", ((LiteralToken)result[0]).Text); + + Assert.IsType(result[1]); + Assert.Equal(0, ((PropertyToken)result[1]).Position); + Assert.Equal(null, ((PropertyToken)result[1]).Format); + } + + [Fact] + public void Should_Return_Literal_Token_For_Unbalanced_Escaped_Opening_Curly_Braces() + { + // Given, When + var result = FormatParser.Parse("{{ test {0}").ToArray(); + + // Then + Assert.Equal(2, result.Length); + Assert.IsType(result[0]); + Assert.Equal("{ test ", ((LiteralToken)result[0]).Text); + + Assert.IsType(result[1]); + Assert.Equal(0, ((PropertyToken)result[1]).Position); + Assert.Equal(null, ((PropertyToken)result[1]).Format); + } + + [Fact] + public void Should_Return_Literal_Tokens_For_Multiple_Unbalanced_Escaped_Opening_Curly_Braces() + { + // Given, When + var result = FormatParser.Parse("{{{{ test {0}").ToArray(); + + // Then + Assert.Equal(2, result.Length); + Assert.IsType(result[0]); + Assert.Equal("{{ test ", ((LiteralToken)result[0]).Text); + + Assert.IsType(result[1]); + Assert.Equal(0, ((PropertyToken)result[1]).Position); + Assert.Equal(null, ((PropertyToken)result[1]).Format); + } + + [Fact] + public void Should_Return_Literal_Token_For_Unbalanced_Escaped_Closing_Curly_Braces() + { + // Given, When + var result = FormatParser.Parse("test {0:d} }}").ToArray(); + + // Then + Assert.Equal(3, result.Length); + Assert.IsType(result[0]); + Assert.Equal("test ", ((LiteralToken)result[0]).Text); + + Assert.IsType(result[1]); + Assert.Equal(0, ((PropertyToken)result[1]).Position); + Assert.Equal("d", ((PropertyToken)result[1]).Format); + + Assert.IsType(result[2]); + Assert.Equal(" }", ((LiteralToken)result[2]).Text); + } + + [Fact] + public void Should_Return_Literal_Tokens_For_Message_With_Escaped_Braces() + { + // Given, When + var result = FormatParser.Parse("{{0}}").ToArray(); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + Assert.Equal("{0}", ((LiteralToken)result[0]).Text); + } + + [Fact] + public void Should_Return_Property_Token_With_Format_For_Property_With_Format() + { + // Given, When + var result = FormatParser.Parse("{0:yyyy-MM-dd}").ToArray(); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + Assert.Equal(0, ((PropertyToken)result[0]).Position); + Assert.Equal("yyyy-MM-dd", ((PropertyToken)result[0]).Format); + } + + [Fact] + public void Should_Throw_If_A_Format_Item_Is_Not_Positional() + { + // Given, When + var result = Record.Exception(() => FormatParser.Parse("{Hello}").ToArray()); + + // Then + Assert.IsType(result); + Assert.Equal("Input string was not in a correct format.", result?.Message); + } + + [Fact] + public void Should_Throw_If_A_Format_Item_Is_Not_Positional_Even_If_Other_Valid_Tokens_Are_Present() + { + // Given, When + var result = Record.Exception(() => FormatParser.Parse("{} {0}").ToArray()); + + // Then + Assert.IsType(result); + Assert.Equal("Input string was not in a correct format.", result?.Message); + } + + [Fact] + public void Should_Throw_If_A_Format_Item_With_Format_Is_Not_Positional() + { + // Given, When + var result = Record.Exception(() => FormatParser.Parse("{Hello:yyyy-MM-dd}").ToArray()); + + // Then + Assert.IsType(result); + Assert.Equal("Input string was not in a correct format.", result?.Message); + } + + [Fact] + public void Should_Return_Correct_Tokens_For_Message_Which_Mixes_Properties_And_Literals() + { + // Given, When + var result = FormatParser.Parse("Hello {0}! My name is {1}!").ToArray(); + + // Then + Assert.Equal(5, result.Length); + + Assert.IsType(result[0]); + + Assert.IsType(result[1]); + Assert.Equal(0, ((PropertyToken)result[1]).Position); + + Assert.IsType(result[2]); + + Assert.IsType(result[3]); + Assert.Equal(1, ((PropertyToken)result[3]).Position); + + Assert.IsType(result[4]); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/Formatting/PropertyTokenTests.cs b/src/Cake.Core.Tests/Unit/Diagnostics/Formatting/PropertyTokenTests.cs new file mode 100644 index 0000000000..e3b62c20b3 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Diagnostics/Formatting/PropertyTokenTests.cs @@ -0,0 +1,52 @@ +using System; +using Cake.Core.Diagnostics.Formatting; +using Xunit; + +namespace Cake.Core.Tests.Unit.Diagnostics.Formatting +{ + public sealed class PropertyTokenTests + { + public sealed class TheRenderMethod + { + [Fact] + public void Should_Throw_FormatException_When_Index_And_Args_Are_Mismatched() + { + // Given + var token = new PropertyToken(1, null); + + // When + var ex = Record.Exception(() => token.Render(new object[] { "test" })); + + // Then + Assert.IsType(ex); + Assert.Equal("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.", ex.Message); + } + + [Fact] + public void Should_Format_Argument_According_To_Formatting_Rules() + { + // Given + var token = new PropertyToken(0, "B"); + + // When + var result = token.Render(new object[] { new Guid("d6ed7358ef9645bf9245864025de28fa") }); + + // Then + Assert.Equal("{d6ed7358-ef96-45bf-9245-864025de28fa}", result); + } + + [Fact] + public void Should_Format_Argument_As_String_When_No_Formatting_Rules_Specified() + { + // Given + var token = new PropertyToken(0, null); + + // When + var result = token.Render(new object[] { new Guid("{d6ed7358-ef96-45bf-9245-864025de28fa}") }); + + // Then + Assert.Equal("d6ed7358-ef96-45bf-9245-864025de28fa", result); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.Formattable.cs b/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.Formattable.cs new file mode 100644 index 0000000000..598838c8d4 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.Formattable.cs @@ -0,0 +1,279 @@ +using System; +using Cake.Core.Diagnostics; +using Xunit; + +namespace Cake.Core.Tests.Unit.Diagnostics; + +public partial class LogExtensionsTests +{ + public class Formattable + { + private const string ExpectedMessage = "Hello World"; + private static readonly FormattableString HelloWorld = $"{"Hello"} {"World"}"; + + public class TheDebugMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Debug(null, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Debug(null, Verbosity.Normal, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Can_Write_Debug_Message_With_Default_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Debug(HelloWorld); + + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + Assert.Equal(LogLevel.Debug, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + + [Fact] + public void Can_Write_Debug_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Debug(Verbosity.Quiet, HelloWorld); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Debug, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + } + + public class TheVerboseMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Verbose(null, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Verbose(null, Verbosity.Normal, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Can_Write_Verbose_Message_With_Default_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Verbose(HelloWorld); + + // Then + Assert.Equal(Verbosity.Verbose, log.Verbosity); + Assert.Equal(LogLevel.Verbose, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + + [Fact] + public void Can_Write_Verbose_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Verbose(Verbosity.Quiet, HelloWorld); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Verbose, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + } + + public class TheInformationMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Information(null, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Information(null, Verbosity.Normal, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Can_Write_Informative_Message_With_Default_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Information(HelloWorld); + + // Then + Assert.Equal(Verbosity.Normal, log.Verbosity); + Assert.Equal(LogLevel.Information, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + + [Fact] + public void Can_Write_Informative_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Information(Verbosity.Quiet, HelloWorld); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Information, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + } + + public class TheWarningMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Warning(null, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Warning(null, Verbosity.Normal, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Can_Write_Warning_Message_With_Default_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Warning(HelloWorld); + + // Then + Assert.Equal(Verbosity.Minimal, log.Verbosity); + Assert.Equal(LogLevel.Warning, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + + [Fact] + public void Can_Write_Warning_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Warning(Verbosity.Quiet, HelloWorld); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Warning, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + } + + public class TheErrorMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Error(null, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Error(null, Verbosity.Normal, HelloWorld)); + + // Then + Assert.Null(result); + } + + [Fact] + public void Can_Write_Error_Message_With_Default_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Error(HelloWorld); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Error, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + + [Fact] + public void Can_Write_Error_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); + + // When + log.Error(Verbosity.Diagnostic, HelloWorld); + + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + Assert.Equal(LogLevel.Error, log.Level); + Assert.Equal(ExpectedMessage, log.Message); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.cs b/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.cs index 21ccde7027..bf5c1bc550 100644 --- a/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.cs +++ b/src/Cake.Core.Tests/Unit/Diagnostics/LogExtensionsTests.cs @@ -1,292 +1,514 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Diagnostics; using Xunit; -namespace Cake.Core.Tests.Unit.Diagnostics +namespace Cake.Core.Tests.Unit.Diagnostics; + +public partial class LogExtensionsTests { - public class LogExtensionsTests + private sealed class TestLog : ICakeLog { - private sealed class TestLog : ICakeLog + public Verbosity Verbosity { get; set; } + + public LogLevel Level { get; private set; } + + public string Message { get; private set; } + + public void Write(Verbosity verbosity, LogLevel level, string format, params object[] args) { - public Verbosity Verbosity { get; set; } + Verbosity = verbosity; + Level = level; + Message = string.Format(format, args); + } + } - public LogLevel Level { get; private set; } + public class TheDebugMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Debug(null, "Hello World")); - public string Message { get; private set; } + // Then + Assert.Null(result); + } - public void Write(Verbosity verbosity, LogLevel level, string format, params object[] args) - { - Verbosity = verbosity; - Level = level; - Message = string.Format(format, args); - } + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Debug(null, Verbosity.Normal, "Hello World")); + + // Then + Assert.Null(result); } - public class TheDebugMethod + [Fact] + public void Can_Write_Debug_Message_With_Default_Verbosity() { - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Debug(null, "Hello World")); + // Given + var log = new TestLog(); - // Then - Assert.Null(result); - } + // When + log.Debug("Hello World"); - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Debug(null, Verbosity.Normal, "Hello World")); + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + Assert.Equal(LogLevel.Debug, log.Level); + Assert.Equal("Hello World", log.Message); + } - // Then - Assert.Null(result); - } + [Fact] + public void Can_Write_Debug_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); - [Fact] - public void Can_Write_Debug_Message_With_Default_Verbosity() - { - // Given - var log = new TestLog(); + // When + log.Debug(Verbosity.Quiet, "Hello World"); - // When - log.Debug("Hello World"); + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Debug, log.Level); + Assert.Equal("Hello World", log.Message); + } + } - // Then - Assert.Equal(Verbosity.Diagnostic, log.Verbosity); - Assert.Equal(LogLevel.Debug, log.Level); - Assert.Equal("Hello World", log.Message); - } + public class TheVerboseMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Verbose(null, "Hello World")); - [Fact] - public void Can_Write_Debug_Message_With_Custom_Verbosity() - { - // Given - var log = new TestLog(); + // Then + Assert.Null(result); + } - // When - log.Debug(Verbosity.Quiet, "Hello World"); + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Verbose(null, Verbosity.Normal, "Hello World")); - // Then - Assert.Equal(Verbosity.Quiet, log.Verbosity); - Assert.Equal(LogLevel.Debug, log.Level); - Assert.Equal("Hello World", log.Message); - } + // Then + Assert.Null(result); } - public class TheVerboseMethod + [Fact] + public void Can_Write_Verbose_Message_With_Default_Verbosity() { - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Verbose(null, "Hello World")); + // Given + var log = new TestLog(); - // Then - Assert.Null(result); - } + // When + log.Verbose("Hello World"); - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Verbose(null, Verbosity.Normal, "Hello World")); + // Then + Assert.Equal(Verbosity.Verbose, log.Verbosity); + Assert.Equal(LogLevel.Verbose, log.Level); + Assert.Equal("Hello World", log.Message); + } - // Then - Assert.Null(result); - } + [Fact] + public void Can_Write_Verbose_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); - [Fact] - public void Can_Write_Verbose_Message_With_Default_Verbosity() - { - // Given - var log = new TestLog(); + // When + log.Verbose(Verbosity.Quiet, "Hello World"); - // When - log.Verbose("Hello World"); + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Verbose, log.Level); + Assert.Equal("Hello World", log.Message); + } + } - // Then - Assert.Equal(Verbosity.Verbose, log.Verbosity); - Assert.Equal(LogLevel.Verbose, log.Level); - Assert.Equal("Hello World", log.Message); - } + public class TheInformationMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Information(null, "Hello World")); - [Fact] - public void Can_Write_Verbose_Message_With_Custom_Verbosity() - { - // Given - var log = new TestLog(); + // Then + Assert.Null(result); + } - // When - log.Verbose(Verbosity.Quiet, "Hello World"); + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Information(null, Verbosity.Normal, "Hello World")); - // Then - Assert.Equal(Verbosity.Quiet, log.Verbosity); - Assert.Equal(LogLevel.Verbose, log.Level); - Assert.Equal("Hello World", log.Message); - } + // Then + Assert.Null(result); } - public class TheInformationMethod + [Fact] + public void Can_Write_Informative_Message_With_Default_Verbosity() { - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Information(null, "Hello World")); + // Given + var log = new TestLog(); - // Then - Assert.Null(result); - } + // When + log.Information("Hello World"); - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Information(null, Verbosity.Normal, "Hello World")); + // Then + Assert.Equal(Verbosity.Normal, log.Verbosity); + Assert.Equal(LogLevel.Information, log.Level); + Assert.Equal("Hello World", log.Message); + } - // Then - Assert.Null(result); - } + [Fact] + public void Can_Write_Informative_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); - [Fact] - public void Can_Write_Informative_Message_With_Default_Verbosity() - { - // Given - var log = new TestLog(); + // When + log.Information(Verbosity.Quiet, "Hello World"); - // When - log.Information("Hello World"); + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Information, log.Level); + Assert.Equal("Hello World", log.Message); + } + } - // Then - Assert.Equal(Verbosity.Normal, log.Verbosity); - Assert.Equal(LogLevel.Information, log.Level); - Assert.Equal("Hello World", log.Message); - } + public class TheWarningMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Warning(null, "Hello World")); - [Fact] - public void Can_Write_Informative_Message_With_Custom_Verbosity() - { - // Given - var log = new TestLog(); + // Then + Assert.Null(result); + } - // When - log.Information(Verbosity.Quiet, "Hello World"); + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Warning(null, Verbosity.Normal, "Hello World")); - // Then - Assert.Equal(Verbosity.Quiet, log.Verbosity); - Assert.Equal(LogLevel.Information, log.Level); - Assert.Equal("Hello World", log.Message); - } + // Then + Assert.Null(result); } - public class TheWarningMethod + [Fact] + public void Can_Write_Warning_Message_With_Default_Verbosity() { - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Warning(null, "Hello World")); + // Given + var log = new TestLog(); - // Then - Assert.Null(result); - } + // When + log.Warning("Hello World"); - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Warning(null, Verbosity.Normal, "Hello World")); + // Then + Assert.Equal(Verbosity.Minimal, log.Verbosity); + Assert.Equal(LogLevel.Warning, log.Level); + Assert.Equal("Hello World", log.Message); + } - // Then - Assert.Null(result); - } + [Fact] + public void Can_Write_Warning_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); - [Fact] - public void Can_Write_Warning_Message_With_Default_Verbosity() - { - // Given - var log = new TestLog(); + // When + log.Warning(Verbosity.Quiet, "Hello World"); - // When - log.Warning("Hello World"); + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Warning, log.Level); + Assert.Equal("Hello World", log.Message); + } + } - // Then - Assert.Equal(Verbosity.Minimal, log.Verbosity); - Assert.Equal(LogLevel.Warning, log.Level); - Assert.Equal("Hello World", log.Message); - } + public class TheErrorMethod + { + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Error(null, "Hello World")); - [Fact] - public void Can_Write_Warning_Message_With_Custom_Verbosity() - { - // Given - var log = new TestLog(); + // Then + Assert.Null(result); + } - // When - log.Warning(Verbosity.Quiet, "Hello World"); + [Fact] + public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + { + // Given, When + var result = Record.Exception(() => LogExtensions.Error(null, Verbosity.Normal, "Hello World")); - // Then - Assert.Equal(Verbosity.Quiet, log.Verbosity); - Assert.Equal(LogLevel.Warning, log.Level); - Assert.Equal("Hello World", log.Message); - } + // Then + Assert.Null(result); } - public class TheErrorMethod + [Fact] + public void Can_Write_Error_Message_With_Default_Verbosity() { - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Default_Verbosity() - { - // Given, When - var result = Record.Exception(() => LogExtensions.Error(null, "Hello World")); + // Given + var log = new TestLog(); - // Then - Assert.Null(result); - } + // When + log.Error("Hello World"); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + Assert.Equal(LogLevel.Error, log.Level); + Assert.Equal("Hello World", log.Message); + } + + [Fact] + public void Can_Write_Error_Message_With_Custom_Verbosity() + { + // Given + var log = new TestLog(); - [Fact] - public void Should_Not_Throw_If_Log_Is_Null_When_Logging_With_Custom_Verbosity() + // When + log.Error(Verbosity.Diagnostic, "Hello World"); + + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + Assert.Equal(LogLevel.Error, log.Level); + Assert.Equal("Hello World", log.Message); + } + } + + public sealed class TheQuietVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.QuietVerbosity(null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_To_Quiet() + { + // When + var log = new TestLog { Verbosity = Verbosity.Verbose }; + log.QuietVerbosity(); + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Verbose }; + using (log.QuietVerbosity()) { - // Given, When - var result = Record.Exception(() => LogExtensions.Error(null, Verbosity.Normal, "Hello World")); + } + + // Then + Assert.Equal(Verbosity.Verbose, log.Verbosity); + } + } + + public sealed class TheMinimalVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.MinimalVerbosity(null)); - // Then - Assert.Null(result); + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_To_Minimal() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + log.MinimalVerbosity(); + + // Then + Assert.Equal(Verbosity.Minimal, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + using (log.MinimalVerbosity()) + { } - [Fact] - public void Can_Write_Error_Message_With_Default_Verbosity() + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + } + } + + public sealed class TheNormalVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.NormalVerbosity(null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_To_Normal() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + log.NormalVerbosity(); + + // Then + Assert.Equal(Verbosity.Normal, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + using (log.NormalVerbosity()) { - // Given - var log = new TestLog(); + } + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + } + } - // When - log.Error("Hello World"); + public sealed class TheVerboseVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.VerboseVerbosity(null)); - // Then - Assert.Equal(Verbosity.Quiet, log.Verbosity); - Assert.Equal(LogLevel.Error, log.Level); - Assert.Equal("Hello World", log.Message); + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_To_Verbose() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + log.VerboseVerbosity(); + + // Then + Assert.Equal(Verbosity.Verbose, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + using (log.VerboseVerbosity()) + { } - [Fact] - public void Can_Write_Error_Message_With_Custom_Verbosity() + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + } + } + + public sealed class TheDiagnosticVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.DiagnosticVerbosity(null)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_To_Diagnostic() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + log.DiagnosticVerbosity(); + + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + using (log.DiagnosticVerbosity()) { - // Given - var log = new TestLog(); + } - // When - log.Error(Verbosity.Diagnostic, "Hello World"); + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); + } + } - // Then - Assert.Equal(Verbosity.Diagnostic, log.Verbosity); - Assert.Equal(LogLevel.Error, log.Level); - Assert.Equal("Hello World", log.Message); + public sealed class TheWithVerbosityMethod + { + [Fact] + public void Should_Throw_If_Log_Null() + { + // When + var result = Record.Exception(() => LogExtensions.WithVerbosity(null, Verbosity.Diagnostic)); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Set_Log_Verbosity_As_Specified() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + log.WithVerbosity(Verbosity.Diagnostic); + + // Then + Assert.Equal(Verbosity.Diagnostic, log.Verbosity); + } + + [Fact] + public void Should_Return_Disposable_That_Restores_Log_Verbosity() + { + // When + var log = new TestLog { Verbosity = Verbosity.Quiet }; + using (log.WithVerbosity(Verbosity.Diagnostic)) + { } + + // Then + Assert.Equal(Verbosity.Quiet, log.Verbosity); } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/DisposableTests.cs b/src/Cake.Core.Tests/Unit/DisposableTests.cs new file mode 100644 index 0000000000..253097ed4a --- /dev/null +++ b/src/Cake.Core.Tests/Unit/DisposableTests.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class DisposableTests + { + public sealed class TheCreateMethod + { + [Fact] + public void Should_Throw_If_Disposer_Null() + { + // When + var result = Record.Exception(() => Disposable.Create(null)); + + // Then + AssertEx.IsArgumentNullException(result, "disposer"); + } + + [Fact] + public void Should_Return_Disposable_That_Invokes_Disposer_Once_Only() + { + // When + var disposed = 0; + var disposable = Disposable.Create(() => disposed++); + using (disposable) + { + } + disposable.Dispose(); + + // Then + Assert.Equal(1, disposed); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/ExecutionSettingsTests.cs b/src/Cake.Core.Tests/Unit/ExecutionSettingsTests.cs new file mode 100644 index 0000000000..09edb16679 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/ExecutionSettingsTests.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Cake.Core.Tests.Unit +{ + public sealed class ExecutionSettingsTests + { + [Fact] + public void Should_Have_Zero_Targets_Initially() + { + // Given + var settings = new ExecutionSettings(); + + // When + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + public void Should_Not_Allow_Empty_Targets(string actual) + { + // Given + var settings = new ExecutionSettings().SetTarget(actual); + + // When + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Fact] + public void Should_Not_Allow_Empty_Targets_2() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { "" }); + + // When + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Fact] + public void Should_Not_Allow_Empty_Targets_3() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { " " }); + + // When + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Fact] + public void Should_Not_Allow_Empty_Targets_4() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { " ", " " }); + + // When + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Fact] + public void Should_Not_Allow_Empty_Targets_5() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { " ", " ", "A" }); + + // When + + // Then + Assert.Equal(new string[] { "A" }, settings.Targets); + } + + [Fact] + public void Should_Clear_Existing_Targets_When_Setting_Targets_1() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "C" }); + + // When + settings.SetTarget("A"); + + // Then + Assert.Equal(new string[] { "A" }, settings.Targets); + } + + [Fact] + public void Should_Clear_Existing_Targets_When_Setting_Targets_2() + { + // Given + var settings = new ExecutionSettings().SetTargets(new string[] { "B", "C" }); + + // When + settings.SetTarget(""); + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + + [Fact] + public void Should_Clear_Existing_Targets_When_Setting_Targets_3() + { + // Given + var settings = new ExecutionSettings().SetTarget("A"); + + // When + settings.SetTargets(new string[] { "B", "C" }); + + // Then + Assert.Equal(new string[] { "B", "C" }, settings.Targets); + } + + [Fact] + public void Should_Clear_Existing_Targets_When_Setting_Targets_4() + { + // Given + var settings = new ExecutionSettings().SetTarget("A"); + + // When + settings.SetTarget(""); + + // Then + Assert.Equal(Array.Empty(), settings.Targets); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Extensions/ByteArrayExtensionsTests.cs b/src/Cake.Core.Tests/Unit/Extensions/ByteArrayExtensionsTests.cs new file mode 100644 index 0000000000..2bd43603d5 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Extensions/ByteArrayExtensionsTests.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace Cake.Core.Tests.Unit.Extensions +{ + public class ByteArrayExtensionsTests + { + public class TheStartsWithMethod + { + [Fact] + public void Should_Throw_When_Value_Is_Null() + { + var result = Record.Exception(() => ByteArrayExtensions.StartsWith(null, new byte[] { })); + + AssertEx.IsArgumentNullException(result, "value"); + } + + [Fact] + public void Should_Throw_When_Prefix_Is_Null() + { + var value = new byte[] { 0x6E, 0x75, 0x6C, 0x6C }; + + var result = Record.Exception(() => value.StartsWith(null)); + + AssertEx.IsArgumentNullException(result, "prefix"); + } + + [Fact] + public void Should_Return_False_When_Value_Is_Shorter_Than_Prefix() + { + var value = new byte[] { 0xEF, 0xBB }; + var prefix = new byte[] { 0xEF, 0xBB, 0xBF }; + + var result = value.StartsWith(prefix); + + Assert.False(result); + } + + [Fact] + public void Should_Return_False_When_Value_Does_Not_Start_With_Prefix() + { + var value = new byte[] { 0x45, 0x61, 0x74 }; + var prefix = new byte[] { 0xEF, 0xBB, 0xBF }; + + var result = value.StartsWith(prefix); + + Assert.False(result); + } + + [Fact] + public void Should_Return_True_When_Value_Starts_With_Prefix() + { + var value = new byte[] { 0xEF, 0xBB, 0xBF, 0x43, 0x61, 0x6B, 0x65 }; + var prefix = new byte[] { 0xEF, 0xBB, 0xBF }; + + var result = value.StartsWith(prefix); + + Assert.True(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Extensions/ProcessArgumentListExtensionsTests.cs b/src/Cake.Core.Tests/Unit/Extensions/ProcessArgumentListExtensionsTests.cs index ae70949431..40dba71e71 100644 --- a/src/Cake.Core.Tests/Unit/Extensions/ProcessArgumentListExtensionsTests.cs +++ b/src/Cake.Core.Tests/Unit/Extensions/ProcessArgumentListExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.IO.Arguments; using Xunit; @@ -31,6 +32,30 @@ public void ShouldAppendFormattedTextArgument() Assert.Equal("/arg1:Value1 /arg2:Value2", result); } } + public class ThePrependMethods + { + [Fact] + public void ShouldPrependTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("string arg") + .Prepend("first") + .RenderSafe(); + + Assert.Equal("first string arg", result); + } + + [Fact] + public void ShouldPrependFormattedTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("string arg") + .Prepend("/arg1:{0} /arg2:{1}", "Value1", "Value2") + .RenderSafe(); + + Assert.Equal("/arg1:Value1 /arg2:Value2 string arg", result); + } + } public class TheAppendQuotedMethods { [Fact] @@ -64,6 +89,42 @@ public void ShouldAppendFormattedTextArgument() } } + public class ThePrependQuotedMethods + { + [Fact] + public void ShouldPrependTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuoted("string arg") + .RenderSafe(); + + Assert.Equal("\"string arg\" last", result); + } + + [Fact] + public void ShouldPrependProcessArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuoted(new TextArgument("text arg")) + .RenderSafe(); + + Assert.Equal("\"text arg\" last", result); + } + + [Fact] + public void ShouldPrependFormattedTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuoted("/arg1:{0}", "Value1") + .RenderSafe(); + + Assert.Equal("\"/arg1:Value1\" last", result); + } + } + public class TheAppendSecretMethods { [Fact] @@ -97,6 +158,42 @@ public void ShouldAppendFormattedTextArgument() } } + public class ThePrependSecretMethods + { + [Fact] + public void ShouldPrependTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependSecret("string arg") + .Render(); + + Assert.Equal("string arg last", result); + } + + [Fact] + public void ShouldPrependProcessArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependSecret(new TextArgument("text arg")) + .Render(); + + Assert.Equal("text arg last", result); + } + + [Fact] + public void ShouldPrependFormattedTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependSecret("/arg1:{0}", "Value1") + .Render(); + + Assert.Equal("/arg1:Value1 last", result); + } + } + public class TheAppendQuotedSecretMethods { [Fact] @@ -123,11 +220,47 @@ public void ShouldAppendProcessArgument() public void ShouldAppendFormattedTextArgument() { var result = new ProcessArgumentBuilder() - .AppendQuotedSecret("/arg1:{0} /arg2:{1}", "Value1","Value2") + .AppendQuotedSecret("/arg1:{0} /arg2:{1}", "Value1", "Value2") .Render(); Assert.Equal("\"/arg1:Value1 /arg2:Value2\"", result); } } + + public class ThePrependQuotedSecretMethods + { + [Fact] + public void ShouldPrependTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuotedSecret("string arg") + .Render(); + + Assert.Equal("\"string arg\" last", result); + } + + [Fact] + public void ShouldPrependProcessArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuotedSecret(new TextArgument("text arg")) + .Render(); + + Assert.Equal("\"text arg\" last", result); + } + + [Fact] + public void ShouldPrependFormattedTextArgument() + { + var result = new ProcessArgumentBuilder() + .Append("last") + .PrependQuotedSecret("/arg1:{0} /arg2:{1}", "Value1", "Value2") + .Render(); + + Assert.Equal("\"/arg1:Value1 /arg2:Value2\" last", result); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Graph/CakeGraphBuilderTests.cs b/src/Cake.Core.Tests/Unit/Graph/CakeGraphBuilderTests.cs index 92e3a52fea..61ce4680cf 100644 --- a/src/Cake.Core.Tests/Unit/Graph/CakeGraphBuilderTests.cs +++ b/src/Cake.Core.Tests/Unit/Graph/CakeGraphBuilderTests.cs @@ -1,10 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Cake.Core.Graph; using Xunit; +using static VerifyXunit.Verifier; namespace Cake.Core.Tests.Unit.Graph { @@ -13,51 +16,118 @@ public sealed class CakeGraphBuilderTests public sealed class TheBuildMethod { [Fact] - public void Should_Add_All_Tasks_As_Nodes_In_Graph() + public async Task Should_Add_All_Tasks_As_Nodes_In_Graph() { // Given, When - var tasks = new List { new ActionTask("A"), new ActionTask("B") }; - var graph = CakeGraphBuilder.Build(tasks); + var graph = CakeGraphBuilder.Build( + [ + new CakeTask("A"), + new CakeTask("B") + ]); // Then - Assert.Equal(2, graph.Nodes.Count); + await Verify(graph.Nodes); } [Fact] - public void Should_Create_Edges_Between_Dependencies() + public async Task Should_Create_Edges_Between_Dependencies() { // Given - var task1 = new ActionTask("A"); - var task2 = new ActionTask("B"); + var task1 = new CakeTask("A"); + var task2 = new CakeTask("B"); task2.AddDependency("A"); - var tasks = new List - { - task1, task2 - }; - var graph = CakeGraphBuilder.Build(tasks); + var graph = CakeGraphBuilder.Build( + [ + task1, + task2 + ]); // When - var result = graph.Edges.SingleOrDefault(x => x.Start == "A" && x.End == "B"); + var result = graph.Edges.SingleOrDefault(); // Then - Assert.NotNull(result); + await Verify(result); } [Fact] - public void Should_Throw_Exception_When_Depending_On_Task_That_Does_Not_Exist() + public async Task Should_Create_Edges_Between_Reversed_Dependencies() { // Given - var task = new ActionTask("A"); + var task1 = new CakeTask("A"); + var task2 = new CakeTask("B"); + task2.AddDependee("A"); + + var graph = CakeGraphBuilder.Build( + [ + task1, + task2 + ]); + + // When + var result = graph.Edges.SingleOrDefault(); + + // Then + await Verify(result); + } + + [Fact] + public void Should_Throw_When_Depending_On_Task_That_Does_Not_Exist() + { + // Given + var task = new CakeTask("A"); task.AddDependency("C"); - var tasks = new List { task }; // When - var result = Assert.Throws(() => CakeGraphBuilder.Build(tasks)); + var result = Record.Exception(() => CakeGraphBuilder.Build([task])); + + // Then + Assert.NotNull(result); + Assert.Equal("Task 'A' is dependent on task 'C' which does not exist.", result.Message); + } + + [Fact] + public void Should_Not_Throw_When_Depending_On_Optional_Task_That_Does_Not_Exist() + { + // Given + var task = new CakeTask("A"); + task.AddDependency("C", false); + + // When + var result = Record.Exception(() => CakeGraphBuilder.Build([task])); + + // Then + Assert.Null(result); + } + + [Fact] + public void Should_Throw_When_Reverse_Dependency_Is_Depending_On_Task_That_Does_Not_Exist() + { + // Given + var task = new CakeTask("A"); + task.AddDependee("C"); + + // When + var result = Record.Exception(() => CakeGraphBuilder.Build([task])); + + // Then + Assert.NotNull(result); + Assert.Equal("Task 'A' has specified that it's a dependency for task 'C' which does not exist.", result.Message); + } + + [Fact] + public void Should_Not_Throw_When_An_Reverse_Dependency_Is_Depending_On_An_Optional_Task_That_Does_Not_Exist() + { + // Given + var task = new CakeTask("A"); + task.AddDependee("C", required: false); + + // When + var result = Record.Exception(() => CakeGraphBuilder.Build([task])); // Then - Assert.Equal("Task 'A' is dependent on task 'C' which do not exist.", result.Message); + Assert.Null(result); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs b/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs index 6af94220d5..a329305da6 100644 --- a/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs +++ b/src/Cake.Core.Tests/Unit/Graph/CakeGraphTests.cs @@ -1,9 +1,13 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; using System.Linq; +using System.Threading.Tasks; using Cake.Core.Graph; using Xunit; +using static VerifyXunit.Verifier; namespace Cake.Core.Tests.Unit.Graph { @@ -21,7 +25,7 @@ public void Should_Throw_If_Provided_Node_Is_Null() var result = Record.Exception(() => graph.Add(null)); // Then - Assert.IsArgumentNullException(result, "node"); + AssertEx.IsArgumentNullException(result, "node"); } [Fact] @@ -34,7 +38,7 @@ public void Should_Add_Node_To_Graph() graph.Add("start"); // Then - Assert.Equal(1, graph.Nodes.Count); + Assert.Single(graph.Nodes); } [Fact] @@ -49,7 +53,7 @@ public void Should_Throw_If_Node_Already_Is_Present_In_Graph() // Then Assert.IsType(result); - Assert.Equal("Node has already been added to graph.", result.Message); + Assert.Equal("Node has already been added to graph.", result?.Message); } } @@ -108,7 +112,7 @@ public void Should_Not_Create_Edge_Between_Connected_Nodes_If_An_Edge_Already_Ex graph.Connect("start", "end"); // Then - Assert.Equal(1, graph.Edges.Count); + Assert.Single(graph.Edges); } [Fact] @@ -122,7 +126,7 @@ public void Should_Not_Create_Edge_Between_Connected_Nodes_If_An_Edge_Already_Ex graph.Connect("START", "END"); // Then - Assert.Equal(1, graph.Edges.Count); + Assert.Single(graph.Edges); } [Fact] @@ -136,7 +140,7 @@ public void Should_Throw_If_Edge_Is_Reflexive() // Then Assert.IsType(result); - Assert.Equal("Reflexive edges in graph are not allowed.", result.Message); + Assert.Equal("Reflexive edges in graph are not allowed.", result?.Message); } [Fact] @@ -151,7 +155,7 @@ public void Should_Throw_If_Edge_Is_Unidirectional() // Then Assert.IsType(result); - Assert.Equal("Unidirectional edges in graph are not allowed.", result.Message); + Assert.Equal($"Unidirectional edges in graph are not allowed.{Environment.NewLine}\"start\" and \"end\" cannot depend on each other.", result?.Message); } [Fact] @@ -166,7 +170,7 @@ public void Should_Throw_If_Edge_Is_Unidirectional_Regardless_Of_Casing() // Then Assert.IsType(result); - Assert.Equal("Unidirectional edges in graph are not allowed.", result.Message); + Assert.Equal($"Unidirectional edges in graph are not allowed.{Environment.NewLine}\"start\" and \"end\" cannot depend on each other.", result?.Message); } } @@ -221,11 +225,11 @@ public void Should_Return_Empty_Collection_Of_Nodes_If_Target_Was_Not_Found() var result = graph.Traverse("E").ToArray(); // Then - Assert.Equal(0, result.Length); + Assert.Empty(result); } [Fact] - public void Should_Traverse_Graph_In_Correct_Order() + public async Task Should_Traverse_Graph_In_Correct_Order() { // Given var graph = new CakeGraph(); @@ -237,15 +241,11 @@ public void Should_Traverse_Graph_In_Correct_Order() var result = graph.Traverse("D").ToArray(); // Then - Assert.Equal(4, result.Length); - Assert.Equal("A", result[0]); - Assert.Equal("B", result[1]); - Assert.Equal("C", result[2]); - Assert.Equal("D", result[3]); + await Verify(result); } [Fact] - public void Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root() + public async Task Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root() { // Given var graph = new CakeGraph(); @@ -257,15 +257,11 @@ public void Should_Traverse_Graph_In_Correct_Order_Regardless_Of_Casing_Of_Root( var result = graph.Traverse("d").ToArray(); // Then - Assert.Equal(4, result.Length); - Assert.Equal("A", result[0]); - Assert.Equal("B", result[1]); - Assert.Equal("C", result[2]); - Assert.Equal("d", result[3]); + await Verify(result); } [Fact] - public void Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target() + public async Task Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target() { // Given var graph = new CakeGraph(); @@ -278,11 +274,7 @@ public void Should_Skip_Nodes_That_Are_Not_On_The_Way_To_The_Target() var result = graph.Traverse("E").ToArray(); // Then - Assert.Equal(4, result.Length); - Assert.Equal("A", result[0]); - Assert.Equal("B", result[1]); - Assert.Equal("D", result[2]); - Assert.Equal("E", result[3]); + await Verify(result); } [Fact] @@ -296,8 +288,48 @@ public void Should_Throw_If_Encountering_Circular_Reference() var result = Record.Exception(() => graph.Traverse("C")); Assert.IsType(result); - Assert.Equal("Graph contains circular references.", result.Message); + Assert.Equal("Graph contains circular references.", result?.Message); + } + } + + public sealed class TheTraverseMultipleMethod + { + [Fact] + public void Should_Return_Empty_When_No_Targets() + { + var graph = new CakeGraph(); + graph.Connect("A", "B"); + + var result = graph.Traverse(Array.Empty()).ToArray(); + + Assert.Empty(result); + } + + [Fact] + public async Task Should_Return_Union_Of_All_Targets_With_Shared_Dependencies_Once() + { + var graph = new CakeGraph(); + graph.Connect("A", "B"); + graph.Connect("A", "C"); + + var result = graph.Traverse(["B", "C"]).ToArray(); + + await Verify(result); + } + + [Fact] + public void Should_Throw_When_Circular_Reference_In_Combined_Graph() + { + var graph = new CakeGraph(); + graph.Connect("A", "B"); + graph.Connect("B", "C"); + graph.Connect("C", "A"); + + var result = Record.Exception(() => graph.Traverse(["B", "C"]).ToArray()); + + Assert.IsType(result); + Assert.Equal("Graph contains circular references.", result?.Message); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/Arguments/QuotedArgumentTests.cs b/src/Cake.Core.Tests/Unit/IO/Arguments/QuotedArgumentTests.cs index 75ac9ace3a..5f8d5e56fb 100644 --- a/src/Cake.Core.Tests/Unit/IO/Arguments/QuotedArgumentTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/Arguments/QuotedArgumentTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.IO.Arguments; using Xunit; @@ -88,4 +89,4 @@ public void Should_Call_Child_Arguments_RenderSafe_Method() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/Arguments/SecretArgumentTests.cs b/src/Cake.Core.Tests/Unit/IO/Arguments/SecretArgumentTests.cs index e7eff1e7ca..8671a22c34 100644 --- a/src/Cake.Core.Tests/Unit/IO/Arguments/SecretArgumentTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/Arguments/SecretArgumentTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.IO.Arguments; using Xunit; @@ -90,4 +91,4 @@ public void Should_Not_Call_Child_Arguments_RenderSafe_Method() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/Arguments/TextArgumentTests.cs b/src/Cake.Core.Tests/Unit/IO/Arguments/TextArgumentTests.cs index 6e312a2e2e..dc40a28175 100644 --- a/src/Cake.Core.Tests/Unit/IO/Arguments/TextArgumentTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/Arguments/TextArgumentTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO.Arguments; using Xunit; @@ -48,4 +49,4 @@ public void Should_Render_The_Provided_Text_As_Normal(string text, string expect } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/DirectoryPathCollectionTests.cs b/src/Cake.Core.Tests/Unit/IO/DirectoryPathCollectionTests.cs index 3c95df028a..3de9653a0b 100644 --- a/src/Cake.Core.Tests/Unit/IO/DirectoryPathCollectionTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/DirectoryPathCollectionTests.cs @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; + +using System.Collections.Generic; using Cake.Core.IO; using Xunit; @@ -12,13 +13,13 @@ public sealed class DirectoryPathCollectionTests public sealed class TheConstructor { [Fact] - public void Should_Throw_If_Comparer_Is_Null() + public void Should_Use_PathComparer_Default_If_Comparer_Is_Null() { - // Given, When - var result = Record.Exception(() => new DirectoryPathCollection(Enumerable.Empty(), null)); + // Given + var collection = new DirectoryPathCollection(); // Then - Assert.IsArgumentNullException(result, "comparer"); + Assert.Equal(PathComparer.Default, collection.Comparer); } } @@ -28,9 +29,7 @@ public sealed class TheCountProperty public void Should_Return_The_Number_Of_Paths_In_The_Collection() { // Given - var collection = new DirectoryPathCollection( - new[] { new DirectoryPath("A.txt"), new DirectoryPath("B.txt") }, - new PathComparer(false)); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); // When, Then Assert.Equal(2, collection.Count); @@ -45,11 +44,10 @@ public sealed class WithSinglePath public void Should_Add_Path_If_Not_Already_Present() { // Given - var collection = new DirectoryPathCollection(new PathComparer(false)); - collection.Add(new DirectoryPath("B")); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); // When - collection.Add(new DirectoryPath("A")); + collection.Add(new DirectoryPath("B")); // Then Assert.Equal(2, collection.Count); @@ -61,8 +59,7 @@ public void Should_Add_Path_If_Not_Already_Present() public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new DirectoryPathCollection(new PathComparer(caseSensitive)); - collection.Add(new DirectoryPath("A")); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); // When collection.Add(new DirectoryPath("a")); @@ -74,6 +71,19 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path(bool ca public sealed class WithMultiplePaths { + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); + + // When + var result = Record.Exception(() => collection.Add((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } + [Fact] public void Should_Add_Paths_That_Are_Not_Present() { @@ -114,8 +124,7 @@ public sealed class WithSinglePath public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new DirectoryPathCollection(new PathComparer(caseSensitive)); - collection.Add(new DirectoryPath("A")); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); // When collection.Remove(new DirectoryPath("a")); @@ -127,6 +136,19 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Path(bool public sealed class WithMultiplePaths { + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); + + // When + var result = Record.Exception(() => collection.Remove((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } + [Theory] [InlineData(true, 2)] [InlineData(false, 0)] @@ -149,27 +171,38 @@ public sealed class ThePlusOperator public sealed class WithSinglePath { [Fact] - public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path() + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (DirectoryPathCollection)null + new DirectoryPath("a")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new DirectoryPathCollection(new PathComparer(false)); - collection.Add("B"); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); // When - var result = collection + new DirectoryPath("A"); + var result = collection + new DirectoryPath("a"); // Then - Assert.Equal(2, result.Count); + Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Adding_Path() { // Given - var collection = new DirectoryPathCollection(new PathComparer(false)); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); // When - var result = collection + new DirectoryPath("A"); + var result = collection + new DirectoryPath("B"); // Then Assert.False(ReferenceEquals(result, collection)); @@ -179,30 +212,38 @@ public void Should_Return_New_Collection() public sealed class WithMultiplePaths { [Fact] - public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Paths() + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (DirectoryPathCollection)null + new DirectoryPath[] { "a" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Paths(bool caseSensitive, int expectedCount) { // Given - var comparer = new PathComparer(false); - var collection = new DirectoryPathCollection(comparer); - var second = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, comparer); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); // When - var result = collection + second; + var result = collection + new DirectoryPath[] { "a", "b", "c" }; // Then - Assert.Equal(2, result.Count); + Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Adding_Paths() { // Given - var comparer = new PathComparer(false); - var collection = new DirectoryPathCollection(comparer); - var second = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, comparer); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); // When - var result = collection + second; + var result = collection + new DirectoryPath[] { "C", "D" }; // Then Assert.False(ReferenceEquals(result, collection)); @@ -214,16 +255,23 @@ public sealed class TheMinusOperator { public sealed class WithSinglePath { + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (DirectoryPathCollection)null - new DirectoryPath("a")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + [Theory] [InlineData(true, 2)] [InlineData(false, 1)] - public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool caseSensitive, int expectedCount) + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Path(bool caseSensitive, int expectedCount) { // Given - var comparer = new PathComparer(caseSensitive); - var collection = new DirectoryPathCollection(comparer); - collection.Add("A"); - collection.Add("B"); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); // When var result = collection - new DirectoryPath("a"); @@ -233,12 +281,10 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Removing_Path() { // Given - var collection = new DirectoryPathCollection(new PathComparer(false)); - collection.Add("A"); - collection.Add("B"); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); // When var result = collection - new DirectoryPath("A"); @@ -250,35 +296,39 @@ public void Should_Return_New_Collection() public sealed class WithMultiplePaths { + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (DirectoryPathCollection)null - new DirectoryPath[] { "a" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + [Theory] [InlineData(true, 3)] [InlineData(false, 1)] public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool caseSensitive, int expectedCount) { // Given - var collection = new DirectoryPathCollection(new PathComparer(caseSensitive)); - collection.Add("A"); - collection.Add("B"); - collection.Add("C"); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B", "C" }, new PathComparer(caseSensitive)); // When - var result = collection - new[] { new DirectoryPath("b"), new DirectoryPath("c") }; + var result = collection - new DirectoryPath[] { "b", "c" }; // Then Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Removing_Paths() { // Given - var collection = new DirectoryPathCollection(new PathComparer(false)); - collection.Add("A"); - collection.Add("B"); - collection.Add("C"); + var collection = new DirectoryPathCollection(new DirectoryPath[] { "A", "B", "C" }, new PathComparer(false)); // When - var result = collection - new[] { new DirectoryPath("B"), new DirectoryPath("C") }; + var result = collection - new DirectoryPath[] { "B", "C" }; // Then Assert.False(ReferenceEquals(result, collection)); @@ -286,4 +336,4 @@ public void Should_Return_New_Collection() } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/DirectoryPathConverterTests.cs b/src/Cake.Core.Tests/Unit/IO/DirectoryPathConverterTests.cs new file mode 100644 index 0000000000..fc32875fc7 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/DirectoryPathConverterTests.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO +{ + public sealed class DirectoryPathConverterTests + { + public sealed class TheCanConvertFromMethod + { + [Fact] + public void Should_Return_True_When_Source_Type_Is_String() + { + var converter = new DirectoryPathConverter(); + + var result = converter.CanConvertFrom(typeof(string)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_Source_Type_Is_Not_String() + { + var converter = new DirectoryPathConverter(); + + var result = converter.CanConvertFrom(typeof(DateTime)); + + Assert.False(result); + } + } + + public sealed class TheConvertFromMethod + { + [Fact] + public void Should_Convert_String_Value_To_Directory_Path() + { + var converter = new DirectoryPathConverter(); + + var result = converter.ConvertFrom("c:/data/work"); + + Assert.IsType(result); + Assert.Equal("c:/data/work", ((DirectoryPath)result).FullPath); + } + + [Fact] + public void Should_Throw_NotSupportedException_When_Value_Is_Not_A_Valid_Directory_Path() + { + var converter = new DirectoryPathConverter(); + + var result = Record.Exception(() => converter.ConvertFrom(DateTime.Now)); + + Assert.IsType(result); + } + } + + public sealed class TheCanConvertToMethod + { + [Fact] + public void Should_Return_True_When_Destination_Type_Is_String() + { + var converter = new DirectoryPathConverter(); + + var result = converter.CanConvertTo(typeof(string)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_True_When_Destination_Type_Is_DirectoryPath() + { + var converter = new DirectoryPathConverter(); + + var result = converter.CanConvertTo(typeof(DirectoryPath)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_Source_Type_Is_Not_DirectoryPath() + { + var converter = new DirectoryPathConverter(); + + var result = converter.CanConvertTo(typeof(DateTime)); + + Assert.False(result); + } + } + + public sealed class TheConvertToMethod + { + [Fact] + public void Should_Convert_Directory_Path_To_String_Value_Using_FullPath() + { + var converter = new DirectoryPathConverter(); + + var result = converter.ConvertTo(DirectoryPath.FromString("c:/data/work"), typeof(string)); + + Assert.IsType(result); + Assert.Equal("c:/data/work", result); + } + + [Fact] + public void Should_Throw_NotSupportedException_When_Destination_Type_Is_Not_String() + { + var converter = new DirectoryPathConverter(); + + var result = Record.Exception(() => + converter.ConvertTo(DirectoryPath.FromString("c:/data/work"), typeof(DateTime))); + + Assert.IsType(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/DirectoryPathTests.cs b/src/Cake.Core.Tests/Unit/IO/DirectoryPathTests.cs index a6b0aee1d7..8ba9a9ea81 100644 --- a/src/Cake.Core.Tests/Unit/IO/DirectoryPathTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/DirectoryPathTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; using Cake.Testing.Xunit; @@ -11,12 +12,29 @@ namespace Cake.Core.Tests.Unit.IO { public sealed class DirectoryPathTests { + public sealed class TheConversionFromStringOperator + { + [Fact] + public void Null_String_Converts_To_Null_DirectoryPath() + { + // Given + const string nullString = null; + + // When + var path = (DirectoryPath)nullString; + + // Then + Assert.Null(path); + } + } + public sealed class TheGetDirectoryNameMethod { - [Theory] + [WindowsTheory] [InlineData("C:/Data", "Data")] [InlineData("C:/Data/Work", "Work")] [InlineData("C:/Data/Work/file.txt", "file.txt")] + [InlineData(@"\\Data\Work", "Work")] public void Should_Return_Directory_Name(string directoryPath, string name) { // Given @@ -42,22 +60,22 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => path.GetFilePath(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Theory] -#if !UNIX [InlineData("c:/assets/shaders/", "simple.frag", "c:/assets/shaders/simple.frag")] [InlineData("c:/", "simple.frag", "c:/simple.frag")] [InlineData("c:/assets/shaders/", "test/simple.frag", "c:/assets/shaders/simple.frag")] [InlineData("c:/", "test/simple.frag", "c:/simple.frag")] -#endif [InlineData("assets/shaders", "simple.frag", "assets/shaders/simple.frag")] [InlineData("assets/shaders/", "simple.frag", "assets/shaders/simple.frag")] [InlineData("/assets/shaders/", "simple.frag", "/assets/shaders/simple.frag")] [InlineData("assets/shaders", "test/simple.frag", "assets/shaders/simple.frag")] [InlineData("assets/shaders/", "test/simple.frag", "assets/shaders/simple.frag")] [InlineData("/assets/shaders/", "test/simple.frag", "/assets/shaders/simple.frag")] + [InlineData(@"\\foo\bar", "qux.txt", @"\\foo\bar\qux.txt")] + [InlineData(@"\\foo\bar", "baz/qux.txt", @"\\foo\bar\qux.txt")] public void Should_Combine_Paths(string first, string second, string expected) { // Given @@ -83,22 +101,17 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => path.CombineWithFilePath(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Theory] -#if !UNIX - [InlineData("c:/assets/shaders/", "simple.frag", "c:/assets/shaders/simple.frag")] - [InlineData("c:/", "simple.frag", "c:/simple.frag")] - [InlineData("c:/assets/shaders/", "test/simple.frag", "c:/assets/shaders/test/simple.frag")] - [InlineData("c:/", "test/simple.frag", "c:/test/simple.frag")] -#endif [InlineData("assets/shaders", "simple.frag", "assets/shaders/simple.frag")] [InlineData("assets/shaders/", "simple.frag", "assets/shaders/simple.frag")] [InlineData("/assets/shaders/", "simple.frag", "/assets/shaders/simple.frag")] [InlineData("assets/shaders", "test/simple.frag", "assets/shaders/test/simple.frag")] [InlineData("assets/shaders/", "test/simple.frag", "assets/shaders/test/simple.frag")] [InlineData("/assets/shaders/", "test/simple.frag", "/assets/shaders/test/simple.frag")] + [InlineData("/", "test/simple.frag", "/test/simple.frag")] public void Should_Combine_Paths(string first, string second, string expected) { // Given @@ -111,28 +124,57 @@ public void Should_Combine_Paths(string first, string second, string expected) Assert.Equal(expected, result.FullPath); } + [WindowsTheory] + [InlineData("c:/assets/shaders/", "simple.frag", "c:/assets/shaders/simple.frag")] + [InlineData("c:/", "simple.frag", "c:/simple.frag")] + [InlineData("c:/assets/shaders/", "test/simple.frag", "c:/assets/shaders/test/simple.frag")] + [InlineData("c:/", "test/simple.frag", "c:/test/simple.frag")] + [InlineData(@"\\", "qux.txt", @"\\qux.txt")] + [InlineData(@"\\foo\bar", "qux.txt", @"\\foo\bar\qux.txt")] + [InlineData(@"\\", "baz/qux.txt", @"\\baz\qux.txt")] + [InlineData(@"\\foo\bar", "baz/qux.txt", @"\\foo\bar\baz\qux.txt")] + public void Should_Combine_Windows_Paths(string first, string second, string expected) + { + // Given + var path = new DirectoryPath(first); + + // When + var result = path.CombineWithFilePath(new FilePath(second)); + + // Then + Assert.Equal(expected, result.FullPath); + } + [Fact] public void Can_Not_Combine_Directory_Path_With_Absolute_File_Path() { // Given - var path = new DirectoryPath("assets"); + var path = new DirectoryPath("foo"); + + // When + var result = Record.Exception(() => path.CombineWithFilePath(new FilePath("/other/asset.txt"))); + + // Then + Assert.IsType(result); + } + + [WindowsFact] + public void Can_Not_Combine_Directory_Path_With_Absolute_UNC_File_Path() + { + // Given + var path = new DirectoryPath(@"\\foo"); // When var result = Record.Exception(() => path.CombineWithFilePath(new FilePath("/other/asset.txt"))); // Then Assert.IsType(result); - Assert.Equal("Cannot combine a directory path with an absolute file path.", result.Message); } } - public sealed class TheCombineWithDirectoryPathMethod + public sealed class TheCombineMethod { [Theory] -#if !UNIX - [InlineData("c:/assets/shaders/", "simple", "c:/assets/shaders/simple")] - [InlineData("c:/", "simple", "c:/simple")] -#endif [InlineData("assets/shaders", "simple", "assets/shaders/simple")] [InlineData("assets/shaders/", "simple", "assets/shaders/simple")] [InlineData("/assets/shaders/", "simple", "/assets/shaders/simple")] @@ -148,6 +190,27 @@ public void Should_Combine_Paths(string first, string second, string expected) Assert.Equal(expected, result.FullPath); } + [WindowsTheory] + [InlineData("c:/assets/shaders/", "simple", "c:/assets/shaders/simple")] + [InlineData("c:/", "simple", "c:/simple")] + [InlineData(@"\\", "foo", @"\\foo")] + [InlineData(@"\\", "foo/", @"\\foo")] + [InlineData(@"\\", "foo\\", @"\\foo")] + [InlineData(@"\\foo", "bar", @"\\foo\bar")] + [InlineData(@"\\foo", "bar/", @"\\foo\bar")] + [InlineData(@"\\foo", "bar\\", @"\\foo\bar")] + public void Should_Combine_Windows_Paths(string first, string second, string expected) + { + // Given + var path = new DirectoryPath(first); + + // When + var result = path.Combine(new DirectoryPath(second)); + + // Then + Assert.Equal(expected, result.FullPath); + } + [Fact] public void Should_Throw_If_Path_Is_Null() { @@ -158,7 +221,7 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => path.Combine(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -172,7 +235,156 @@ public void Can_Not_Combine_Directory_Path_With_Absolute_Directory_Path() // Then Assert.IsType(result); - Assert.Equal("Cannot combine a directory path with an absolute directory path.", result.Message); + Assert.Equal("Cannot combine a directory path with an absolute directory path.", result?.Message); + } + + [WindowsTheory] + [InlineData("C:/foo/bar")] + [InlineData(@"\\foo\bar")] + public void Can_Not_Combine_Directory_Path_With_Absolute_Windows_Directory_Path(string input) + { + // Given + var path = new DirectoryPath("assets"); + + // When + var result = Record.Exception(() => path.Combine(new DirectoryPath(input))); + + // Then + Assert.IsType(result); + Assert.Equal("Cannot combine a directory path with an absolute directory path.", result?.Message); + } + } + + public sealed class TheGetParentMethod + { + public sealed class InUncFormat + { + [Theory] + [InlineData(@"\\server\share\folder", @"\\server\share")] + public void Should_Return_Parent_Directory(string directoryPath, string parentPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(parentPath, result.FullPath); + } + + [Theory] + [InlineData(@"\\Server\")] + [InlineData(@"\\Server")] + [InlineData(@"\\Server\Share")] + public void Should_Return_Null_If_No_Parent(string directoryPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(null, result); + } + } + public sealed class InRelativeFormat + { + [Theory] + [InlineData("foo\\bar", "foo")] + [InlineData("foo\\bar\\baz\\..\\..\\Work", "foo")] + [InlineData("foo/bar/baz/../../Work", "foo")] + [InlineData("foo/bar", "foo")] + [InlineData("Data\\Work\\..\\foo", "Data")] + [InlineData("Data/Work/../foo", "Data")] + [InlineData("someFolder", ".")] + [InlineData("..", ".")] // a bit unexpected, but due to the way "Collapse" works. + [InlineData("./", ".")] // a bit unexpected, but due to the way "Collapse" works. + public void Should_Return_Parent_Directory(string directoryPath, string parentPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(parentPath, result.FullPath); + } + } + + public sealed class InWindowsFormat + { + [WindowsTheory] + [InlineData("C:/Data", "C:/")] + [InlineData("C:/Data/Work", "C:/Data")] + [InlineData("C:/Data/Work/file.txt", "C:/Data/Work")] + [InlineData("C:\\folder\\foo\\..", "C:/")] + public void Should_Return_Parent_Directory(string directoryPath, string parentPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(parentPath, result.FullPath); + } + + [WindowsTheory] + [InlineData("C:/")] + [InlineData("C:")] + [InlineData("C:/..")] + public void Should_Return_Null_If_No_Parent(string directoryPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(null, result); + } + } + + public sealed class InUnixFormat + { + [NonWindowsTheory] + [InlineData("/C", "/")] + [InlineData("/C/", "/")] + [InlineData("/C/Data", "/C")] + [InlineData("/C/Data/Work", "/C/Data")] + [InlineData("/C/Data/Work/file.txt", "/C/Data/Work")] + [InlineData("/folder/foo/..", "/")] + public void Should_Return_Parent_Directory(string directoryPath, string parentPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(parentPath, result.FullPath); + } + + [NonWindowsTheory] + [InlineData("/")] + [InlineData("/..")] + public void Should_Return_Null_If_No_Parent(string directoryPath) + { + // Given + var path = new DirectoryPath(directoryPath); + + // When + var result = path.GetParent(); + + // Then + Assert.Equal(null, result); + } } } @@ -188,10 +400,10 @@ public void Should_Throw_If_Provided_Environment_Is_Null() // When var result = Record.Exception( - () => path.MakeAbsolute((ICakeEnvironment) null)); + () => path.MakeAbsolute((ICakeEnvironment)null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -222,6 +434,23 @@ public void Should_Create_New_Absolute_Path_Identical_To_The_Path() // Then Assert.Equal("/assets", result.FullPath); } + + [WindowsTheory] + [InlineData("C:/foo")] + [InlineData(@"\\foo")] + public void Should_Create_New_Absolute_Windows_Path_Identical_To_The_Path(string fullPath) + { + // Given + var environment = Substitute.For(); + var path = new DirectoryPath(fullPath); + + // When + var result = path.MakeAbsolute(environment); + + // Then + Assert.Equal(fullPath, result.FullPath); + Assert.NotSame(path, result); + } } public sealed class ThatTakesAnotherDirectoryPath @@ -237,7 +466,7 @@ public void Should_Throw_If_Provided_Path_Is_Null() () => path.MakeAbsolute((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -251,7 +480,7 @@ public void Should_Throw_If_Provided_Path_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("The provided path cannot be relative.", result.Message); + Assert.Equal("The provided path cannot be relative.", result?.Message); } [Fact] @@ -295,6 +524,12 @@ public sealed class InWindowsFormat [InlineData("C:/A/B/C", "C:/", "../../..")] [InlineData("C:/A/B/C/D/E/F", "C:/A/B/C", "../../..")] [InlineData("C:/A/B/C", "C:/A/B/C/D/E/F", "D/E/F")] + [InlineData(@"\\A\B\C", @"\\A\B\C", ".")] + [InlineData(@"\\", @"\\", ".")] + [InlineData(@"\\A\B\C", @"\\A\D\E", "../../D/E")] + [InlineData(@"\\A\B\C", @"\\", "../../..")] + [InlineData(@"\\A\B\C/D/E/F", @"\\A\B\C", "../../..")] + [InlineData(@"\\A\B\C", @"\\A\B\C\D\E\F", "D/E/F")] public void Should_Returns_Relative_Path_Between_Paths(string from, string to, string expected) { // Given @@ -311,6 +546,9 @@ public void Should_Returns_Relative_Path_Between_Paths(string from, string to, s [InlineData("C:/A/B/C", "D:/A/B/C")] [InlineData("C:/A/B", "D:/E/")] [InlineData("C:/", "B:/")] + [InlineData(@"\\A\B\C", "D:/A/B/C")] + [InlineData(@"\\A\B", "D:/E/")] + [InlineData(@"\\", "B:/")] public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to) { // Given @@ -321,7 +559,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [WindowsFact] @@ -334,7 +572,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [WindowsFact] @@ -348,21 +586,23 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } - [WindowsFact] - public void Should_Throw_If_Target_DirectoryPath_Is_Relative() + [WindowsTheory] + [InlineData("C:/A/B/C")] + [InlineData(@"\\A\B\C")] + public void Should_Throw_If_Target_DirectoryPath_Is_Relative(string input) { // Given - var path = new DirectoryPath("C:/A/B/C"); + var path = new DirectoryPath(input); // When var result = Record.Exception(() => path.GetRelativePath(new DirectoryPath("D"))); // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } @@ -401,7 +641,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [Fact] @@ -414,7 +654,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [Fact] @@ -428,7 +668,7 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } [Fact] @@ -442,7 +682,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } } @@ -458,6 +698,11 @@ public sealed class InWindowsFormat [InlineData("C:/A/B/C", "C:/hello.txt", "../../../hello.txt")] [InlineData("C:/A/B/C/D/E/F", "C:/A/B/C/hello.txt", "../../../hello.txt")] [InlineData("C:/A/B/C", "C:/A/B/C/D/E/F/hello.txt", "D/E/F/hello.txt")] + [InlineData(@"\\A\B\C", @"\\A\B\C\D\E\F\hello.txt", "D/E/F/hello.txt")] + [InlineData(@"\\", @"\\hello.txt", "hello.txt")] + [InlineData(@"\\A\B\C", @"\\A\D\E\hello.txt", "../../D/E/hello.txt")] + [InlineData(@"\\A\B\C", @"\\hello.txt", "../../../hello.txt")] + [InlineData(@"\\A\B\C\D\E\F", @"\\A\B\C\hello.txt", "../../../hello.txt")] public void Should_Returns_Relative_Path_Between_Paths(string from, string to, string expected) { // Given @@ -474,6 +719,9 @@ public void Should_Returns_Relative_Path_Between_Paths(string from, string to, s [InlineData("C:/A/B/C", "D:/A/B/C/hello.txt")] [InlineData("C:/A/B", "D:/E/hello.txt")] [InlineData("C:/", "B:/hello.txt")] + [InlineData(@"\\A\B\C", "D:/A/B/C/hello.txt")] + [InlineData(@"\\A\B", "D:/E/hello.txt")] + [InlineData(@"\\", "B:/hello.txt")] public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to) { // Given @@ -484,7 +732,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [WindowsFact] @@ -497,7 +745,7 @@ public void Should_Throw_If_Target_FilePath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((FilePath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [WindowsFact] @@ -511,21 +759,23 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } - [WindowsFact] - public void Should_Throw_If_Target_FilePath_Is_Relative() + [WindowsTheory] + [InlineData("C:/A/B/C")] + [InlineData(@"\\A\B\C")] + public void Should_Throw_If_Target_FilePath_Is_Relative(string input) { // Given - var path = new DirectoryPath("C:/A/B/C"); + var path = new DirectoryPath(input); // When var result = Record.Exception(() => path.GetRelativePath(new FilePath("D/hello.txt"))); // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } @@ -564,7 +814,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [Fact] @@ -577,7 +827,7 @@ public void Should_Throw_If_Target_FilePath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((FilePath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [Fact] @@ -591,7 +841,7 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } [Fact] @@ -605,10 +855,10 @@ public void Should_Throw_If_Target_FilePath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/FileExtensionsTests.cs b/src/Cake.Core.Tests/Unit/IO/FileExtensionsTests.cs index eaa174a865..4a0fc5f3f3 100644 --- a/src/Cake.Core.Tests/Unit/IO/FileExtensionsTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/FileExtensionsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.IO; using System.Linq; using System.Text; @@ -24,7 +25,7 @@ public void Should_Throw_If_File_Is_Null() var result = Record.Exception(() => FileExtensions.Open(null, FileMode.Create)); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } [Theory] @@ -56,7 +57,7 @@ public void Should_Throw_If_File_Is_Null() var result = Record.Exception(() => FileExtensions.Open(null, FileMode.Create, FileAccess.Write)); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } [Theory] @@ -89,7 +90,7 @@ public void Should_Throw_If_File_Is_Null() var result = Record.Exception(() => FileExtensions.OpenRead(null)); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } [Fact] @@ -115,7 +116,7 @@ public void Should_Throw_If_File_Is_Null() var result = Record.Exception(() => FileExtensions.OpenWrite(null)); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } [Fact] @@ -141,7 +142,7 @@ public void Should_Throw_If_File_Is_Null() var result = Record.Exception(() => FileExtensions.ReadLines(null, Encoding.UTF8)); // Then - Assert.IsArgumentNullException(result, "file"); + AssertEx.IsArgumentNullException(result, "file"); } [Fact] @@ -156,7 +157,7 @@ public void Should_Return_Empty_List_If_File_Contains_No_Lines() var result = file.ReadLines(Encoding.UTF8).ToList(); // Then - Assert.Equal(0, result.Count); + Assert.Empty(result); } [Fact] @@ -171,7 +172,7 @@ public void Should_Read_File_With_Single_Line_Correctly() var result = file.ReadLines(Encoding.UTF8).ToList(); // Then - Assert.Equal(1, result.Count); + Assert.Single(result); } [Fact] @@ -196,5 +197,121 @@ public void Should_Read_File_With_Multiple_Lines_Correctly() Assert.Equal("3", result[2]); } } + + public sealed class TheIsClrAssemblyMethod + { + private static readonly byte[] _validPeAndClrAssemblyBytes = + { + 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x03, 0x00, 0x8a, 0x54, 0x4a, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x22, 0x20, 0x0b, 0x01, 0x30, 0x00, 0x00, 0x84, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xa2, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x60, 0x85, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0xa2, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x88, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x94, 0xa1, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x48, + }; + + private static readonly byte[] _validPeButNotClrAssemblyBytes = + { + 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, + 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xab, 0x04, 0x28, 0x30, 0xef, 0x65, 0x46, 0x63, 0xef, 0x65, 0x46, 0x63, 0xef, 0x65, 0x46, 0x63, 0x71, 0xc5, 0x81, 0x63, 0xea, 0x65, 0x46, 0x63, 0xa9, 0x34, 0xa7, 0x63, 0xc2, 0x65, 0x46, 0x63, + 0xa9, 0x34, 0xa6, 0x63, 0x71, 0x65, 0x46, 0x63, 0xa9, 0x34, 0x99, 0x63, 0xe4, 0x65, 0x46, 0x63, 0x32, 0x9a, 0x88, 0x63, 0xed, 0x65, 0x46, 0x63, 0x32, 0x9a, 0x8d, 0x63, 0xe0, 0x65, 0x46, 0x63, + 0xef, 0x65, 0x47, 0x63, 0x43, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0xa2, 0x63, 0x4d, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0x9a, 0x63, 0xee, 0x65, 0x46, 0x63, 0xe2, 0x37, 0x9d, 0x63, 0xee, 0x65, 0x46, 0x63, + 0xef, 0x65, 0xd1, 0x63, 0xee, 0x65, 0x46, 0x63, 0x5a, 0xfb, 0x98, 0x63, 0xee, 0x65, 0x46, 0x63, 0x52, 0x69, 0x63, 0x68, 0xef, 0x65, 0x46, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x06, 0x00, 0xe2, 0xac, 0x47, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x22, 0x20, + 0x0b, 0x02, 0x0c, 0x00, 0x00, 0x6e, 0x0d, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x87, 0x0c, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x11, 0x00, 0x00, 0x04, 0x00, 0x00, + 0xfe, 0x24, 0x12, 0x00, 0x03, 0x00, 0x60, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x0f, 0x00, 0x8a, 0x62, 0x00, 0x00, 0x8c, 0x3b, 0x10, 0x00, 0xa0, 0x00, 0x00, 0x00, + 0x00, 0x90, 0x11, 0x00, 0xb8, 0x05, 0x00, 0x00, 0x00, 0xb0, 0x10, 0x00, 0xb4, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x00, 0x3c, 0x09, 0x00, 0x00, + 0xa0, 0x85, 0x0d, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0xcd, 0x0e, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0d, 0x00, 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + private static readonly byte[] _invalidPeBytes = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + [Fact] + public void Should_Return_True_When_File_Is_Valid_PE_And_Is_Clr_Assembly() + { + // Given + var file = Substitute.For(); + + // When + file.Exists.Returns(true); + file.Length.Returns(_validPeAndClrAssemblyBytes.Length); + file.OpenRead().Returns(new MemoryStream(_validPeAndClrAssemblyBytes)); + + // Then + Assert.True(FileExtensions.IsClrAssembly(file)); + } + + [Fact] + public void Should_Return_False_When_File_Is_Valid_PE_But_Not_A_Clr_Assembly() + { + // Given + var file = Substitute.For(); + + // When + file.Exists.Returns(true); + file.Length.Returns(_validPeButNotClrAssemblyBytes.Length); + file.OpenRead().Returns(new MemoryStream(_validPeButNotClrAssemblyBytes)); + + // Then + Assert.False(FileExtensions.IsClrAssembly(file)); + } + + [Fact] + public void Should_Return_False_When_File_Is_Not_A_Valid_PE() + { + // Given + var file = Substitute.For(); + + // When + file.Exists.Returns(true); + file.Length.Returns(_invalidPeBytes.Length); + file.OpenRead().Returns(new MemoryStream(_invalidPeBytes)); + + // Then + Assert.False(FileExtensions.IsClrAssembly(file)); + } + + [Fact] + public void Should_Return_False_When_File_Is_MacOS_MachO_dylib() + { + // Given + var file = Substitute.For(); + + // When + file.Exists.Returns(true); + file.Path.Returns(new string("fullname.dylib")); + file.Length.Returns(_invalidPeBytes.Length); + + // Then + Assert.False(FileExtensions.IsClrAssembly(file)); + } + } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/FilePathCollectionTests.cs b/src/Cake.Core.Tests/Unit/IO/FilePathCollectionTests.cs index d641151b8e..e5448d1ee3 100644 --- a/src/Cake.Core.Tests/Unit/IO/FilePathCollectionTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/FilePathCollectionTests.cs @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; + +using System.Collections.Generic; using Cake.Core.IO; using Xunit; @@ -12,13 +13,13 @@ public sealed class FilePathCollectionTests public sealed class TheConstructor { [Fact] - public void Should_Throw_If_Comparer_Is_Null() + public void Should_Use_PathComparer_Default_If_Comparer_Is_Null() { - // Given, When - var result = Record.Exception(() => new FilePathCollection(Enumerable.Empty(), null)); + // Given + var collection = new FilePathCollection(); // Then - Assert.IsArgumentNullException(result, "comparer"); + Assert.Equal(PathComparer.Default, collection.Comparer); } } @@ -28,9 +29,7 @@ public sealed class TheCountProperty public void Should_Return_The_Number_Of_Paths_In_The_Collection() { // Given - var collection = new FilePathCollection( - new[] { new FilePath("A.txt"), new FilePath("B.txt") }, - new PathComparer(false)); + var collection = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); // When, Then Assert.Equal(2, collection.Count); @@ -45,11 +44,10 @@ public sealed class WithSinglePath public void Should_Add_Path_If_Not_Already_Present() { // Given - var collection = new FilePathCollection(new PathComparer(false)); - collection.Add(new FilePath("B.txt")); + var collection = new FilePathCollection(new FilePath[] { "A.txt" }, new PathComparer(false)); // When - collection.Add(new FilePath("A.txt")); + collection.Add(new FilePath("B.txt")); // Then Assert.Equal(2, collection.Count); @@ -61,8 +59,7 @@ public void Should_Add_Path_If_Not_Already_Present() public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new FilePathCollection(new PathComparer(caseSensitive)); - collection.Add(new FilePath("A.TXT")); + var collection = new FilePathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); // When collection.Add(new FilePath("a.txt")); @@ -78,10 +75,10 @@ public sealed class WithMultiplePaths public void Should_Add_Paths_That_Are_Not_Present() { // Given - var collection = new FilePathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(false)); + var collection = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); // When - collection.Add(new FilePath[] { "A.TXT", "B.TXT", "C.TXT" }); + collection.Add(new FilePath[] { "A.txt", "B.txt", "C.txt" }); // Then Assert.Equal(3, collection.Count); @@ -101,6 +98,19 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Paths(bool c // Then Assert.Equal(expectedCount, collection.Count); } + + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new FilePathCollection(); + + // When + var result = Record.Exception(() => collection.Add((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } } } @@ -114,8 +124,7 @@ public sealed class WithSinglePath public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new FilePathCollection(new PathComparer(caseSensitive)); - collection.Add(new FilePath("A.TXT")); + var collection = new FilePathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); // When collection.Remove(new FilePath("a.txt")); @@ -141,6 +150,19 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool // Then Assert.Equal(expectedCount, collection.Count); } + + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new FilePathCollection(); + + // When + var result = Record.Exception(() => collection.Remove((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } } } @@ -148,65 +170,84 @@ public sealed class ThePlusOperator { public sealed class WithSinglePath { - [Fact] - public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path() + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Path(bool caseSensitive, int expectedCount) { // Given - var collection = new FilePathCollection(new PathComparer(false)); - collection.Add("B.txt"); + var collection = new FilePathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); // When - var result = collection + new FilePath("A.txt"); + var result = collection + new FilePath("a.txt"); // Then - Assert.Equal(2, result.Count); + Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Adding_Path() { // Given - var collection = new FilePathCollection(new PathComparer(false)); + var collection = new FilePathCollection(new FilePath[] { "A.txt" }, new PathComparer(false)); // When - var result = collection + new FilePath("A.txt"); + var result = collection + new FilePath("B.txt"); // Then Assert.False(ReferenceEquals(result, collection)); } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (FilePathCollection)null + new FilePath("A.txt")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } } public sealed class WithMultiplePaths { - [Fact] - public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Paths() + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_Paths(bool caseSensitive, int expectedCount) { // Given - var comparer = new PathComparer(false); - var collection = new FilePathCollection(comparer); - var second = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, comparer); + var collection = new FilePathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); // When - var result = collection + second; + var result = collection + new FilePath[] { "a.txt", "b.txt", "c.txt" }; // Then - Assert.Equal(2, result.Count); + Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Adding_Paths() { // Given - var comparer = new PathComparer(false); - var collection = new FilePathCollection(comparer); - var second = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, comparer); + var collection = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); // When - var result = collection + second; + var result = collection + new FilePath[] { "C.txt", "D.txt" }; // Then Assert.False(ReferenceEquals(result, collection)); } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (FilePathCollection)null + new FilePath[] { "A.txt" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } } } @@ -217,13 +258,10 @@ public sealed class WithSinglePath [Theory] [InlineData(true, 2)] [InlineData(false, 1)] - public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool caseSensitive, int expectedCount) + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Path(bool caseSensitive, int expectedCount) { // Given - var comparer = new PathComparer(caseSensitive); - var collection = new FilePathCollection(comparer); - collection.Add("A.txt"); - collection.Add("B.txt"); + var collection = new FilePathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); // When var result = collection - new FilePath("a.txt"); @@ -233,12 +271,10 @@ public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Removing_Path() { // Given - var collection = new FilePathCollection(new PathComparer(false)); - collection.Add("A.txt"); - collection.Add("B.txt"); + var collection = new FilePathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); // When var result = collection - new FilePath("A.txt"); @@ -246,6 +282,16 @@ public void Should_Return_New_Collection() // Then Assert.False(ReferenceEquals(result, collection)); } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (FilePathCollection)null - new FilePath("A.txt")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } } public sealed class WithMultiplePaths @@ -256,34 +302,38 @@ public sealed class WithMultiplePaths public void Should_Respect_File_System_Case_Sensitivity_When_Removing_Paths(bool caseSensitive, int expectedCount) { // Given - var collection = new FilePathCollection(new PathComparer(caseSensitive)); - collection.Add("A.txt"); - collection.Add("B.txt"); - collection.Add("C.txt"); + var collection = new FilePathCollection(new FilePath[] { "A.TXT", "B.TXT", "C.TXT" }, new PathComparer(caseSensitive)); // When - var result = collection - new[] { new FilePath("b.txt"), new FilePath("c.txt") }; + var result = collection - new FilePath[] { "b.txt", "c.txt" }; // Then Assert.Equal(expectedCount, result.Count); } [Fact] - public void Should_Return_New_Collection() + public void Should_Return_New_Collection_When_Removing_Paths() { // Given - var collection = new FilePathCollection(new PathComparer(false)); - collection.Add("A.txt"); - collection.Add("B.txt"); - collection.Add("C.txt"); + var collection = new FilePathCollection(new FilePath[] { "A.txt", "B.txt", "C.txt" }, new PathComparer(false)); // When - var result = collection - new[] { new FilePath("B.txt"), new FilePath("C.txt") }; + var result = collection - new FilePath[] { "B.txt", "C.txt" }; // Then Assert.False(ReferenceEquals(result, collection)); } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (FilePathCollection)null - new FilePath[] { "A.txt" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/FilePathConverterTests.cs b/src/Cake.Core.Tests/Unit/IO/FilePathConverterTests.cs new file mode 100644 index 0000000000..80e7cb8eac --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/FilePathConverterTests.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO +{ + public sealed class FilePathConverterTests + { + public sealed class TheCanConvertFromMethod + { + [Fact] + public void Should_Return_True_When_Source_Type_Is_String() + { + var converter = new FilePathConverter(); + + var result = converter.CanConvertFrom(typeof(string)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_Source_Type_Is_Not_String() + { + var converter = new FilePathConverter(); + + var result = converter.CanConvertFrom(typeof(DateTime)); + + Assert.False(result); + } + } + + public sealed class TheConvertFromMethod + { + [Fact] + public void Should_Convert_String_Value_To_File_Path() + { + var converter = new FilePathConverter(); + + var result = converter.ConvertFrom("c:/data/work/file.txt"); + + Assert.IsType(result); + Assert.Equal("c:/data/work/file.txt", ((FilePath)result).FullPath); + } + + [Fact] + public void Should_Throw_NotSupportedException_When_Value_Is_Not_A_Valid_File_Path() + { + var converter = new FilePathConverter(); + + var result = Record.Exception(() => converter.ConvertFrom(DateTime.Now)); + + Assert.IsType(result); + } + } + + public sealed class TheCanConvertToMethod + { + [Fact] + public void Should_Return_True_When_Destination_Type_Is_String() + { + var converter = new FilePathConverter(); + + var result = converter.CanConvertTo(typeof(string)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_True_When_Destination_Type_Is_FilePath() + { + var converter = new FilePathConverter(); + + var result = converter.CanConvertTo(typeof(FilePath)); + + Assert.True(result); + } + + [Fact] + public void Should_Return_False_When_Source_Type_Is_Not_FilePath() + { + var converter = new FilePathConverter(); + + var result = converter.CanConvertTo(typeof(DateTime)); + + Assert.False(result); + } + } + + public sealed class TheConvertToMethod + { + [Fact] + public void Should_Convert_File_Path_To_String_Value_Using_FullPath() + { + var converter = new FilePathConverter(); + + var result = converter.ConvertTo(FilePath.FromString("c:/data/work/file.txt"), typeof(string)); + + Assert.IsType(result); + Assert.Equal("c:/data/work/file.txt", result); + } + + [Fact] + public void Should_Throw_NotSupportedException_When_Destination_Type_Is_Not_String() + { + var converter = new FilePathConverter(); + + var result = Record.Exception(() => + converter.ConvertTo(FilePath.FromString("c:/data/work/file.txt"), typeof(DateTime))); + + Assert.IsType(result); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/FilePathTests.cs b/src/Cake.Core.Tests/Unit/IO/FilePathTests.cs index 981b73f2a3..4c477c9ab8 100644 --- a/src/Cake.Core.Tests/Unit/IO/FilePathTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/FilePathTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using Cake.Core.IO; using Cake.Testing.Xunit; @@ -11,12 +12,30 @@ namespace Cake.Core.Tests.Unit.IO { public sealed class FilePathTests { + public sealed class TheConversionFromStringOperator + { + [Fact] + public void Null_String_Converts_To_Null_FilePath() + { + // Given + const string nullString = null; + + // When + var path = (FilePath)nullString; + + // Then + Assert.Null(path); + } + } + public sealed class TheHasExtensionProperty { [Theory] [InlineData("assets/shaders/basic.txt", true)] [InlineData("assets/shaders/basic", false)] + [InlineData("assets/shad.ers/basic", false)] [InlineData("assets/shaders/basic/", false)] + [InlineData("assets/shad.ers/basic/", false)] public void Can_See_If_A_Path_Has_An_Extension(string fullPath, bool expected) { // Given, When @@ -25,6 +44,24 @@ public void Can_See_If_A_Path_Has_An_Extension(string fullPath, bool expected) // Then Assert.Equal(expected, path.HasExtension); } + + [WindowsTheory] + [InlineData("C:/foo/bar/baz.txt", true)] + [InlineData("C:/foo/bar/baz", false)] + [InlineData("C:/foo/bar.baz/qux", false)] + [InlineData("C:/foo/bar/baz/", false)] + [InlineData(@"\\foo\bar\baz.txt", true)] + [InlineData(@"\\foo\bar\baz", false)] + [InlineData(@"\\foo\bar.baz\qux", false)] + [InlineData(@"\\foo\bar\baz\", false)] + public void Can_See_If_A_Windows_Path_Has_An_Extension(string fullPath, bool expected) + { + // Given, When + var path = new FilePath(fullPath); + + // Then + Assert.Equal(expected, path.HasExtension); + } } public sealed class TheGetExtensionMethod @@ -32,57 +69,156 @@ public sealed class TheGetExtensionMethod [Theory] [InlineData("assets/shaders/basic.frag", ".frag")] [InlineData("assets/shaders/basic.frag/test.vert", ".vert")] + [InlineData("assets/shaders/basic.frag/test.foo.vert", ".vert")] [InlineData("assets/shaders/basic", null)] [InlineData("assets/shaders/basic.frag/test", null)] public void Can_Get_Extension(string fullPath, string expected) + { + // Given + var path = new FilePath(fullPath); + + // When + var result = path.GetExtension(); + + // Then + Assert.Equal(expected, result); + } + + [WindowsTheory] + [InlineData("C:/foo/bar/baz.txt", ".txt")] + [InlineData("C:/foo/bar/baz.txt/qux.md", ".md")] + [InlineData("C:/foo/bar/baz.txt/qux.md.rs", ".rs")] + [InlineData("C:/foo/bar/baz", null)] + [InlineData("C:/foo/bar/baz.txt/qux", null)] + [InlineData(@"\\foo\bar\baz.txt", ".txt")] + [InlineData(@"\\foo\bar\baz.txt\qux.md", ".md")] + [InlineData(@"\\foo\bar\baz.txt\qux.md.rs", ".rs")] + [InlineData(@"\\foo\bar\baz", null)] + [InlineData(@"\\foo\bar\baz.txt\qux", null)] + public void Can_Get_Windows_Extension(string fullPath, string expected) { // Given, When - var result = new FilePath(fullPath); - var extension = result.GetExtension(); + var path = new FilePath(fullPath); + + // When + var result = path.GetExtension(); // Then - Assert.Equal(expected, extension); + Assert.Equal(expected, result); } } public sealed class TheGetDirectoryMethod { - [Fact] - public void Can_Get_Directory_For_File_Path() + [Theory] + [InlineData("temp/hello.txt", "temp")] + public void Can_Get_Directory_For_File_Path(string input, string expected) { - // Given, When - var path = new FilePath("temp/hello.txt"); - var directory = path.GetDirectory(); + // Given + var path = new FilePath(input); + + // When + var result = path.GetDirectory(); + + // Then + Assert.Equal(expected, result.FullPath); + } + + [WindowsTheory] + [InlineData("C:/temp/hello.txt", "C:/temp")] + [InlineData(@"\\temp\hello.txt", @"\\temp")] + public void Can_Get_Directory_For_Windows_File_Path(string fullPath, string expected) + { + // Given + var path = new FilePath(fullPath); + + // When + var result = path.GetDirectory(); // Then - Assert.Equal("temp", directory.FullPath); + Assert.Equal(expected, result.FullPath); } [Fact] - public void Can_Get_Directory_For_File_Path_In_Root() + public void Can_Get_Directory_For_Relative_File_Path_In_Root() { - // Given, When + // Given var path = new FilePath("hello.txt"); - var directory = path.GetDirectory(); + + // When + var result = path.GetDirectory(); + + // Then + Assert.Equal(string.Empty, result.FullPath); + } + + [Fact] + public void Can_Get_Directory_For_Absolute_File_Path_In_Root() + { + // Given + var path = new FilePath("/hello.txt"); + + // When + var result = path.GetDirectory(); + + // Then + Assert.Equal("/", result.FullPath); + } + + [WindowsTheory] + [InlineData("C:/hello.txt", "C:/")] + [InlineData(@"\\hello.txt", @"\\")] + public void Can_Get_Directory_For_Absolute_File_Path_In_Windows_Root(string fullPath, string expected) + { + // Given + var path = new FilePath(fullPath); + + // When + var result = path.GetDirectory(); // Then - Assert.Equal(string.Empty, directory.FullPath); + Assert.Equal(expected, result.FullPath); } } public sealed class TheChangeExtensionMethod { - [Fact] - public void Can_Change_Extension_Of_Path() + [Theory] + [InlineData("temp/hello.txt", ".dat", "temp/hello.dat")] + [InlineData("temp/hello", ".dat", "temp/hello.dat")] + [InlineData("./", ".dat", "")] + [InlineData("temp/hello.txt", null, "temp/hello")] + [InlineData("temp/hello.txt", "", "temp/hello.")] + [InlineData("temp/hello.txt", ".", "temp/hello.")] + public void Can_Change_Extension_Of_Path(string input, string extension, string expected) { // Given - var path = new FilePath("temp/hello.txt"); + var path = new FilePath(input); // When - path = path.ChangeExtension(".dat"); + path = path.ChangeExtension(extension); // Then - Assert.Equal("temp/hello.dat", path.ToString()); + Assert.Equal(expected, path.ToString()); + } + + [WindowsTheory] + [InlineData("C:/temp/hello.txt", ".dat", "C:/temp/hello.dat")] + [InlineData("C:/temp/hello", ".dat", "C:/temp/hello.dat")] + [InlineData("C:/", ".dat", "C:/.dat")] + [InlineData("C:/temp/hello.txt", null, "C:/temp/hello")] + [InlineData("C:/temp/hello.txt", "", "C:/temp/hello.")] + [InlineData("C:/temp/hello.txt", ".", "C:/temp/hello.")] + public void Can_Change_Extension_Of_Windows_Path(string input, string extension, string expected) + { + // Given + var path = new FilePath(input); + + // When + path = path.ChangeExtension(extension); + + // Then + Assert.Equal(expected, path.ToString()); } } @@ -98,7 +234,7 @@ public void Should_Throw_If_Extension_Is_Null() var result = Record.Exception(() => path.AppendExtension(null)); // Then - Assert.IsArgumentNullException(result, "extension"); + AssertEx.IsArgumentNullException(result, "extension"); } [Theory] @@ -115,21 +251,70 @@ public void Can_Append_Extension_To_Path(string extension, string expected) // Then Assert.Equal(expected, path.ToString()); } + + [WindowsTheory] + [InlineData("C:/temp/hello.txt", ".dat", "C:/temp/hello.txt.dat")] + [InlineData(@"\\temp\hello.txt", ".dat", @"\\temp\hello.txt.dat")] + public void Can_Append_Extension_To_Windows_Path(string fullPath, string extension, string expected) + { + // Given + var path = new FilePath(fullPath); + + // When + path = path.AppendExtension(extension); + + // Then + Assert.Equal(expected, path.ToString()); + } } public sealed class TheGetFilenameMethod { - [Fact] - public void Can_Get_Filename_From_Path() + [Theory] + [InlineData("/input/test.txt", "test.txt")] + [InlineData("/input/test.foo.txt", "test.foo.txt")] + [InlineData("/input/test", "test")] + [InlineData("/test.txt", "test.txt")] + [InlineData("/test.foo.txt", "test.foo.txt")] + [InlineData("./test.txt", "test.txt")] + [InlineData("./test.foo.txt", "test.foo.txt")] + [InlineData("./", "")] + [InlineData("/", "")] + public void Can_Get_Filename_From_Path(string input, string expected) { // Given - var path = new FilePath("/input/test.txt"); + var path = new FilePath(input); // When var result = path.GetFilename(); // Then - Assert.Equal("test.txt", result.FullPath); + Assert.Equal(expected, result.FullPath); + } + + [WindowsTheory] + [InlineData("C:/input/test.txt", "test.txt")] + [InlineData("C:/input/test.foo.txt", "test.foo.txt")] + [InlineData("C:/input/test", "test")] + [InlineData("C:/test.txt", "test.txt")] + [InlineData("C:/test.foo.txt", "test.foo.txt")] + [InlineData("C:/", "")] + [InlineData(@"\\input\test.txt", "test.txt")] + [InlineData(@"\\input\test.foo.txt", "test.foo.txt")] + [InlineData(@"\\input\test", "test")] + [InlineData(@"\\test.txt", "test.txt")] + [InlineData(@"\\test.foo.txt", "test.foo.txt")] + [InlineData(@"\\", "")] + public void Can_Get_Filename_From_Windows_Path(string input, string expected) + { + // Given + var path = new FilePath(input); + + // When + var result = path.GetFilename(); + + // Then + Assert.Equal(expected, result.FullPath); } } @@ -137,7 +322,13 @@ public sealed class TheGetFilenameWithoutExtensionMethod { [Theory] [InlineData("/input/test.txt", "test")] + [InlineData("/input/test.foo.txt", "test.foo")] [InlineData("/input/test", "test")] + [InlineData("/test.txt", "test")] + [InlineData("/test.foo.txt", "test.foo")] + [InlineData("./test.txt", "test")] + [InlineData("./test.foo.txt", "test.foo")] + [InlineData("./", "")] public void Should_Return_Filename_Without_Extension_From_Path(string fullPath, string expected) { // Given @@ -149,6 +340,31 @@ public void Should_Return_Filename_Without_Extension_From_Path(string fullPath, // Then Assert.Equal(expected, result.FullPath); } + + [WindowsTheory] + [InlineData("C:/input/test.txt", "test")] + [InlineData("C:/input/test.foo.txt", "test.foo")] + [InlineData("C:/input/test", "test")] + [InlineData("C:/test.txt", "test")] + [InlineData("C:/test.foo.txt", "test.foo")] + [InlineData("C:/", "")] + [InlineData(@"\\input\test.txt", "test")] + [InlineData(@"\\input\test.foo.txt", "test.foo")] + [InlineData(@"\\input\test", "test")] + [InlineData(@"\\test.txt", "test")] + [InlineData(@"\\test.foo.txt", "test.foo")] + [InlineData(@"\\", "")] + public void Should_Return_Filename_Without_Extension_From_Windows_Path(string fullPath, string expected) + { + // Given + var path = new FilePath(fullPath); + + // When + var result = path.GetFilenameWithoutExtension(); + + // Then + Assert.Equal(expected, result.FullPath); + } } public sealed class TheMakeAbsoluteMethod @@ -162,10 +378,10 @@ public void Should_Throw_If_Environment_Is_Null() var path = new FilePath("temp/hello.txt"); // When - var result = Record.Exception(() => path.MakeAbsolute((ICakeEnvironment) null)); + var result = Record.Exception(() => path.MakeAbsolute((ICakeEnvironment)null)); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -183,11 +399,12 @@ public void Should_Return_A_Absolute_File_Path_If_File_Path_Is_Relative() Assert.Equal("/absolute/test.txt", result.FullPath); } - [Fact] - public void Should_Return_Same_File_Path_If_File_Path_Is_Absolute() + [Theory] + [InlineData("/test.txt")] + public void Should_Return_Same_File_Path_If_File_Path_Is_Absolute(string fullPath) { // Given - var path = new FilePath("/test.txt"); + var path = new FilePath(fullPath); var environment = Substitute.For(); environment.WorkingDirectory.Returns(new DirectoryPath("/absolute")); @@ -195,7 +412,25 @@ public void Should_Return_Same_File_Path_If_File_Path_Is_Absolute() var result = path.MakeAbsolute(environment); // Then - Assert.Equal("/test.txt", result.FullPath); + Assert.Equal(fullPath, result.FullPath); + } + + [WindowsTheory] + [InlineData("C:/foo/bar.txt")] + [InlineData(@"\\foo\bar.txt")] + public void Should_Create_New_Absolute_Windows_Path_Identical_To_The_Path(string fullPath) + { + // Given + var path = new FilePath(fullPath); + var environment = Substitute.For(); + environment.WorkingDirectory.Returns(new DirectoryPath("/absolute")); + + // When + var result = path.MakeAbsolute(environment); + + // Then + Assert.Equal(fullPath, result.FullPath); + Assert.NotSame(path, result); } } @@ -208,10 +443,10 @@ public void Should_Throw_If_Provided_Directory_Is_Null() var path = new FilePath("./test.txt"); // When - var result = Record.Exception(() => path.MakeAbsolute((DirectoryPath) null)); + var result = Record.Exception(() => path.MakeAbsolute((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] @@ -226,7 +461,7 @@ public void Should_Throw_If_Provided_Directory_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Cannot make a file path absolute with a relative directory path.", result.Message); + Assert.Equal("Cannot make a file path absolute with a relative directory path.", result?.Message); } [Fact] @@ -272,6 +507,13 @@ public sealed class InWindowsFormat [InlineData("C:/A/B/C/hello.txt", "C:/", "../../..")] [InlineData("C:/A/B/C/D/E/F/hello.txt", "C:/A/B/C", "../../..")] [InlineData("C:/A/B/C/hello.txt", "C:/A/B/C/D/E/F", "D/E/F")] + + [InlineData(@"\\A\B\C\hello.txt", @"\\A\B\C", ".")] + [InlineData(@"\\hello.txt", @"\\", ".")] + [InlineData(@"\\A\B\C\hello.txt", @"\\A\D\E", @"../../D/E")] + [InlineData(@"\\A\B\C\hello.txt", @"\\", @"../../..")] + [InlineData(@"\\A\B\C\D\E\F\hello.txt", @"\\A\B\C", @"../../..")] + [InlineData(@"\\A\B\C\hello.txt", @"\\A\B\C\D\E\F", @"D/E/F")] public void Should_Returns_Relative_Path_Between_Paths(string from, string to, string expected) { // Given @@ -288,6 +530,9 @@ public void Should_Returns_Relative_Path_Between_Paths(string from, string to, s [InlineData("C:/A/B/C/hello.txt", "D:/A/B/C")] [InlineData("C:/A/B/hello.txt", "D:/E/")] [InlineData("C:/hello.txt", "B:/")] + [InlineData(@"\\A\B\C\hello.txt", "D:/A/B/C")] + [InlineData(@"\\A\B\hello.txt", "D:/E/")] + [InlineData(@"\\hello.txt", "B:/")] public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to) { // Given @@ -298,7 +543,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [WindowsFact] @@ -311,7 +556,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [WindowsFact] @@ -325,21 +570,23 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } - [WindowsFact] - public void Should_Throw_If_Target_DirectoryPath_Is_Relative() + [WindowsTheory] + [InlineData("C:/A/B/C/hello.txt")] + [InlineData(@"\\A\B\C\hello.txt")] + public void Should_Throw_If_Target_DirectoryPath_Is_Relative(string input) { // Given - var path = new FilePath("C:/A/B/C/hello.txt"); + var path = new FilePath(input); // When var result = Record.Exception(() => path.GetRelativePath(new DirectoryPath("D"))); // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } @@ -378,7 +625,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [Fact] @@ -391,7 +638,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((DirectoryPath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [Fact] @@ -405,7 +652,7 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } [Fact] @@ -419,7 +666,7 @@ public void Should_Throw_If_Target_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } } @@ -436,6 +683,13 @@ public sealed class InWindowsFormat [InlineData("C:/A/B/C/hello.txt", "C:/hello.txt", "../../../hello.txt")] [InlineData("C:/A/B/C/D/E/F/hello.txt", "C:/A/B/C/hello.txt", "../../../hello.txt")] [InlineData("C:/A/B/C/hello.txt", "C:/A/B/C/D/E/F/hello.txt", "D/E/F/hello.txt")] + [InlineData(@"\\A\B\C\hello.txt", @"\\A\B\C\hello.txt", "hello.txt")] + [InlineData(@"\\hello.txt", @"\\hello.txt", "hello.txt")] + [InlineData(@"\\hello.txt", @"\\world.txt", "world.txt")] + [InlineData(@"\\A\B\C\hello.txt", @"\\A\D\E\hello.txt", "../../D/E/hello.txt")] + [InlineData(@"\\A\B\C\hello.txt", @"\\hello.txt", "../../../hello.txt")] + [InlineData(@"\\A\B\C\D\E\F\hello.txt", @"\\A\B\C\hello.txt", "../../../hello.txt")] + [InlineData(@"\\A\B\C\hello.txt", @"\\A\B\C\D\E\F\hello.txt", "D/E/F/hello.txt")] public void Should_Returns_Relative_Path_Between_Paths(string from, string to, string expected) { // Given @@ -452,6 +706,9 @@ public void Should_Returns_Relative_Path_Between_Paths(string from, string to, s [InlineData("C:/A/B/C/hello.txt", "D:/A/B/C/hello.txt")] [InlineData("C:/A/B/hello.txt", "D:/E/hello.txt")] [InlineData("C:/hello.txt", "B:/hello.txt")] + [InlineData(@"\\A\B\C\hello.txt", "D:/A/B/C/hello.txt")] + [InlineData(@"\\A\B\hello.txt", "D:/E/hello.txt")] + [InlineData(@"\\hello.txt", "B:/hello.txt")] public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to) { // Given @@ -462,7 +719,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [WindowsFact] @@ -475,7 +732,7 @@ public void Should_Throw_If_Target_FilePath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((FilePath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [WindowsFact] @@ -489,21 +746,23 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } - [WindowsFact] - public void Should_Throw_If_Target_FilePath_Is_Relative() + [WindowsTheory] + [InlineData("C:/A/B/C/hello.txt")] + [InlineData(@"\\A\B\C\hello.txt")] + public void Should_Throw_If_Target_FilePath_Is_Relative(string input) { // Given - var path = new FilePath("C:/A/B/C/hello.txt"); + var path = new FilePath(input); // When var result = Record.Exception(() => path.GetRelativePath(new FilePath("D/hello.txt"))); // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } @@ -543,7 +802,7 @@ public void Should_Throw_If_No_Relative_Path_Can_Be_Found(string from, string to // Then Assert.IsType(result); - Assert.Equal("Paths must share a common prefix.", result.Message); + Assert.Equal("Paths must share a common prefix.", result?.Message); } [Fact] @@ -556,7 +815,7 @@ public void Should_Throw_If_Target_FilePath_Is_Null() var result = Record.Exception(() => path.GetRelativePath((FilePath)null)); // Then - Assert.IsArgumentNullException(result, "to"); + AssertEx.IsArgumentNullException(result, "to"); } [Fact] @@ -570,7 +829,7 @@ public void Should_Throw_If_Source_DirectoryPath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Source path must be an absolute path.", result.Message); + Assert.Equal("Source path must be an absolute path.", result?.Message); } [Fact] @@ -584,10 +843,10 @@ public void Should_Throw_If_Target_FilePath_Is_Relative() // Then Assert.IsType(result); - Assert.Equal("Target path must be an absolute path.", result.Message); + Assert.Equal("Target path must be an absolute path.", result?.Message); } } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/FileSystemExtensionsTest.cs b/src/Cake.Core.Tests/Unit/IO/FileSystemExtensionsTest.cs index 8fe4c0df5d..031fa66909 100644 --- a/src/Cake.Core.Tests/Unit/IO/FileSystemExtensionsTest.cs +++ b/src/Cake.Core.Tests/Unit/IO/FileSystemExtensionsTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using NSubstitute; using Xunit; @@ -58,6 +59,16 @@ public void Should_Return_True_If_File_Exist() // Then Assert.True(result); } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given, When + var result = Record.Exception(() => FileSystemExtensions.Exist(null, (FilePath)"file.txt")); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } } public sealed class WithDirectoryPath @@ -107,7 +118,17 @@ public void Should_Return_True_If_Directory_Exist() // Then Assert.True(result); } + + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given, When + var result = Record.Exception(() => FileSystemExtensions.Exist(null, (DirectoryPath)"/Target")); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/GlobberTests.cs b/src/Cake.Core.Tests/Unit/IO/GlobberTests.cs deleted file mode 100644 index deaf7b9860..0000000000 --- a/src/Cake.Core.Tests/Unit/IO/GlobberTests.cs +++ /dev/null @@ -1,512 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using Cake.Core.IO; -using Cake.Core.Tests.Fixtures; -using Cake.Testing.Xunit; -using NSubstitute; -using Xunit; - -namespace Cake.Core.Tests.Unit.IO -{ - public sealed class GlobberTests - { - public sealed class TheConstructor - { - [Fact] - public void Should_Throw_If_File_System_Is_Null() - { - // Given, When - var environment = Substitute.For(); - var result = Record.Exception(() => new Globber(null, environment)); - - // Then - Assert.IsArgumentNullException(result, "fileSystem"); - } - - [Fact] - public void Should_Throw_If_Environment_Is_Null() - { - // Given - var fileSystem = Substitute.For(); - - // When - var result = Record.Exception(() => new Globber(fileSystem, null)); - - // Then - Assert.IsArgumentNullException(result, "environment"); - } - } - - public sealed class TheMatchMethod - { - public sealed class WindowsSpecific - { - [WindowsFact] - public void Will_Fix_Root_If_Drive_Is_Missing_By_Using_The_Drive_From_The_Working_Directory() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = fixture.Match("/Working/Foo/Bar/Qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "C:/Working/Foo/Bar/Qux.c"); - } - - [WindowsFact] - public void Should_Throw_If_Unc_Root_Was_Encountered() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = Record.Exception(() => fixture.Match("//Foo/Bar/Qux.c")); - - // Then - Assert.IsType(result); - Assert.Equal("UNC paths are not supported.", result.Message); - } - - [WindowsFact] - public void Should_Ignore_Case_Sensitivity_On_Case_Insensitive_Operative_System() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = fixture.Match("C:/Working/**/qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.IsType(result[0]); - Assert.ContainsFilePath(result, "C:/Working/Foo/Bar/Qux.c"); - } - - [WindowsFact] - public void Should_Parse_Glob_Expressions_With_Parenthesis_In_Them() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = fixture.Match("C:/Program Files (x86)/Foo.*"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "C:/Program Files (x86)/Foo.c"); - } - - [WindowsFact] - public void Should_Parse_Glob_Expressions_With_Ampersand_In_Them() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = fixture.Match("C:/Tools & Services/*.dll"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "C:/Tools & Services/MyTool.dll"); - } - - [WindowsFact] - public void Should_Parse_Glob_Expressions_With_Plus_In_Them() - { - // Given - var fixture = new GlobberFixture(windows: true); - - // When - var result = fixture.Match("C:/Tools + Services/*.dll"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "C:/Tools + Services/MyTool.dll"); - } - } - - public sealed class WithPredicate - { - [Fact] - public void Should_Return_Paths_Not_Affected_By_Walker_Hints() - { - // Given - var fixture = new GlobberFixture(); - var predicate = new Func(i => - i.Path.FullPath != "/Working/Bar"); - - // When - var result = fixture.Match("./**/Qux.h", predicate); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.h"); - } - - [Fact] - public void Should_Not_Return_Path_If_Walker_Hint_Matches_Part_Of_Pattern() - { - // Given - var fixture = new GlobberFixture(); - var predicate = new Func(i => - i.Path.FullPath != "/Working/Bar"); - - // When - var result = fixture.Match("/Working/Bar/Qux.h", predicate); - - // Then - Assert.Equal(0, result.Length); - } - - [Fact] - public void Should_Not_Return_Path_If_Walker_Hint_Exactly_Match_Pattern() - { - // Given - var fixture = new GlobberFixture(); - var predicate = new Func(i => - i.Path.FullPath != "/Working/Bar"); - - // When - var result = fixture.Match("/Working/Bar", predicate); - - // Then - Assert.Equal(0, result.Length); - } - } - - [Fact] - public void Should_Throw_If_Pattern_Is_Null() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = Record.Exception(() => fixture.Match(null)); - - // Then - Assert.IsArgumentNullException(result, "pattern"); - } - - [Fact] - public void Should_Return_Empty_Result_If_Pattern_Is_Empty() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match(string.Empty); - - // Then - Assert.Equal(0, result.Length); - } - - [Fact] - public void Can_Traverse_Recursively() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/**/*.c"); - - // Then - Assert.Equal(5, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Bar/Qux.c"); - } - - [Fact] - public void Will_Append_Relative_Root_With_Implicit_Working_Directory() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("Foo/Bar/Qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - } - - [Fact] - public void Should_Be_Able_To_Visit_Parent_Using_Double_Dots() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/../Foo/Bar/Qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.IsType(result[0]); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - } - - [Fact] - public void Should_Throw_If_Visiting_Parent_That_Is_Recursive_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = Record.Exception(() => fixture.Match("/Working/Foo/**/../Foo/Bar/Qux.c")); - - // Then - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Visiting a parent that is a recursive wildcard is not supported.", result.Message); - } - - [Fact] - public void Should_Return_Single_Path_For_Absolute_File_Path_Without_Glob_Pattern() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/Bar/Qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.IsType(result[0]); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - } - - [Fact] - public void Should_Return_Single_Path_For_Absolute_Directory_Path_Without_Glob_Pattern() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/Bar"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar"); - } - - [Fact] - public void Should_Return_Single_Path_For_Relative_File_Path_Without_Glob_Pattern() - { - // Given - var fixture = new GlobberFixture(); - fixture.SetWorkingDirectory("/Working/Foo"); - - // When - var result = fixture.Match("./Bar/Qux.c"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - } - - [Fact] - public void Should_Return_Single_Path_For_Relative_Directory_Path_Without_Glob_Pattern() - { - // Given - var fixture = new GlobberFixture(); - fixture.SetWorkingDirectory("/Working/Foo"); - - // When - var result = fixture.Match("./Bar"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar"); - } - - [Fact] - public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/**/*"); - - // Then - Assert.Equal(15, result.Length); - Assert.ContainsDirectoryPath(result, "/Working/Foo"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Baz"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar/Baz"); - Assert.ContainsDirectoryPath(result, "/Working/Bar"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.h"); - Assert.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo.Bar.Test.dll"); - Assert.ContainsFilePath(result, "/Working/Bar.Qux.Test.dll"); - Assert.ContainsFilePath(result, "/Working/Quz.FooTest.dll"); - Assert.ContainsFilePath(result, "/Working/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Bar/Qux.h"); - } - - [Fact] - public void Should_Return_Files_And_Folders_For_Pattern_Containing_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/*/Qux.c"); - - // Then - Assert.Equal(2, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); - } - - [Fact] - public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Character_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/Bar/Q?x.c"); - - // Then - Assert.Equal(2, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); - } - - [Fact] - public void Should_Return_Files_And_Folders_For_Pattern_Containing_Character_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/Foo/Ba?/Qux.c"); - - // Then - Assert.Equal(2, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); - } - - [Fact] - public void Should_Return_Files_For_Pattern_Ending_With_Character_Wildcard_And_Dot() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/*.Test.dll"); - - // Then - Assert.Equal(2, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo.Bar.Test.dll"); - Assert.ContainsFilePath(result, "/Working/Bar.Qux.Test.dll"); - } - - [WindowsFact] - public void Should_Return_Files_For_Pattern_Ending_With_Character_Wildcard_And_Dot_On_Windows() - { - // Given - var fixture = new GlobberFixture(true); - - // When - var result = fixture.Match("C:/Working/*.Test.dll"); - - // Then - Assert.Equal(2, result.Length); - Assert.ContainsFilePath(result, "C:/Working/Project.A.Test.dll"); - Assert.ContainsFilePath(result, "C:/Working/Project.B.Test.dll"); - } - - - [Fact] - public void Should_Return_File_For_Recursive_Wildcard_Pattern_Ending_With_Wildcard_Regex() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/**/*.c"); - - // Then - Assert.Equal(5, result.Length); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); - Assert.ContainsFilePath(result, "/Working/Bar/Qux.c"); - } - - [Fact] - public void Should_Return_Only_Folders_For_Pattern_Ending_With_Recursive_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Working/**"); - - // Then - Assert.Equal(6, result.Length); - Assert.ContainsDirectoryPath(result, "/Working"); - Assert.ContainsDirectoryPath(result, "/Working/Foo"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Baz"); - Assert.ContainsDirectoryPath(result, "/Working/Foo/Bar/Baz"); - Assert.ContainsDirectoryPath(result, "/Working/Bar"); - } - - [Fact] - public void Should_Include_Files_In_Root_Folder_When_Using_Recursive_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Foo/**/Bar.baz"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "/Foo/Bar.baz"); - } - - [Fact] - public void Should_Include_Folder_In_Root_Folder_When_Using_Recursive_Wildcard() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Foo/**/Bar"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsDirectoryPath(result, "/Foo/Bar"); - } - - [Fact] - public void Should_Parse_Glob_Expressions_With_Parenthesis_In_Them() - { - // Given - var fixture = new GlobberFixture(); - - // When - var result = fixture.Match("/Foo (Bar)/Baz.*"); - - // Then - Assert.Equal(1, result.Length); - Assert.ContainsFilePath(result, "/Foo (Bar)/Baz.c"); - } - } - } -} diff --git a/src/Cake.Core.Tests/Unit/IO/Globbing/GlobberTests.cs b/src/Cake.Core.Tests/Unit/IO/Globbing/GlobberTests.cs new file mode 100644 index 0000000000..3846f14237 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/Globbing/GlobberTests.cs @@ -0,0 +1,641 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using NSubstitute; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO.Globbing +{ + public sealed class GlobberTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_File_System_Is_Null() + { + // Given, When + var environment = Substitute.For(); + var result = Record.Exception(() => new Globber(null, environment)); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var fileSystem = Substitute.For(); + + // When + var result = Record.Exception(() => new Globber(fileSystem, null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + } + + public sealed class TheMatchMethod + { + public sealed class WithDirectoryPredicate + { + [Fact] + public void Should_Return_Paths_Not_Affected_By_Walker_Hints() + { + // Given + var fixture = GlobberFixture.UnixLike(); + var predicate = new Func(i => + i.Path.FullPath != "/Working/Bar"); + + // When + var result = fixture.Match("./**/Qux.h", predicate); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.h"); + } + + [Fact] + public void Should_Not_Return_Path_If_Walker_Hint_Matches_Part_Of_Pattern() + { + // Given + var fixture = GlobberFixture.UnixLike(); + var predicate = new Func(i => + i.Path.FullPath != "/Working/Bar"); + + // When + var result = fixture.Match("/Working/Bar/Qux.h", predicate); + + // Then + Assert.Empty(result); + } + + [Fact] + public void Should_Not_Return_Path_If_Walker_Hint_Exactly_Match_Pattern() + { + // Given + var fixture = GlobberFixture.UnixLike(); + var predicate = new Func(i => + i.Path.FullPath != "/Working/Bar"); + + // When + var result = fixture.Match("/Working/Bar", predicate); + + // Then + Assert.Empty(result); + } + } + + public sealed class WithFilePredicate + { + [Fact] + public void Should_Return_Only_Files_Matching_Predicate() + { + // Given + var fixture = GlobberFixture.UnixLike(); + var predicate = new Func(i => i.Path.FullPath.EndsWith(".c")); + + // When + var result = fixture.Match("/Working/**/*.*", null, predicate); + + // Then + Assert.Equal(5, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.c"); + } + } + + public sealed class WithDirectoryAndFilePredicate + { + [Fact] + public void Should_Return_Only_Files_Matching_Predicate() + { + // Given + var fixture = GlobberFixture.UnixLike(); + var directoryPredicate = new Func(i => i.Path.FullPath.Contains("/Working")); + var filePredicate = new Func(i => !i.Path.FullPath.EndsWith(".dll")); + + // When + var result = fixture.Match("./**/*.*", directoryPredicate, filePredicate); + + // Then + Assert.Equal(14, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.h"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.h"); + AssertEx.ContainsFilePath(result, "/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobaz.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobax.rs"); + AssertEx.ContainsFilePath(result, "/Working/Project/package.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/package-lock.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/tsconfig.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/.npmrc"); + } + } + + [Fact] + public void Should_Throw_If_Pattern_Is_Null() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = Record.Exception(() => fixture.Match(null)); + + // Then + AssertEx.IsArgumentNullException(result, "pattern"); + } + + [Fact] + public void Should_Return_Empty_Result_If_Pattern_Is_Empty() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(string.Empty); + + // Then + Assert.Empty(result); + } + + [Fact] + public void Should_Return_Empty_Result_If_Pattern_Is_Invalid() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("pattern/"); + + // Then + Assert.Empty(result); + } + + [Fact] + public void Can_Traverse_Recursively() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/**/*.c"); + + // Then + Assert.Equal(5, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.c"); + } + + [Fact] + public void Will_Append_Relative_Root_With_Implicit_Working_Directory() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("Foo/Bar/Qux.c"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + } + + [Fact] + public void Should_Be_Able_To_Visit_Parent_Using_Double_Dots() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Foo/../Foo/Bar/Qux.c"); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + } + + [Fact] + public void Should_Throw_If_Visiting_Parent_That_Is_Recursive_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = Record.Exception(() => fixture.Match("/Working/Foo/**/../Foo/Bar/Qux.c")); + + // Then + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Visiting a parent that is a recursive wildcard is not supported.", result?.Message); + } + + [Theory] + [InlineData("/RootFile.sh")] + [InlineData("/Working/Foo/Bar/Qux.c")] + public void Should_Return_Single_Path_For_Absolute_File_Path_Without_Glob_Pattern(string pattern) + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(pattern); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + AssertEx.ContainsFilePath(result, pattern); + } + + [Theory] + [InlineData("/RootDir")] + [InlineData("/Working/Foo/Bar")] + public void Should_Return_Single_Path_For_Absolute_Directory_Path_Without_Glob_Pattern(string pattern) + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(pattern); + + // Then + Assert.Single(result); + AssertEx.ContainsDirectoryPath(result, pattern); + } + + [Fact] + public void Should_Return_Single_Path_For_Relative_File_Path_Without_Glob_Pattern() + { + // Given + var fixture = GlobberFixture.UnixLike(); + fixture.SetWorkingDirectory("/Working/Foo"); + + // When + var result = fixture.Match("./Bar/Qux.c"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + } + + [Fact] + public void Should_Return_Single_Path_For_Relative_Directory_Path_Without_Glob_Pattern() + { + // Given + var fixture = GlobberFixture.UnixLike(); + fixture.SetWorkingDirectory("/Working/Foo"); + + // When + var result = fixture.Match("./Bar"); + + // Then + Assert.Single(result); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Bar"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/**/*"); + + // Then + Assert.Equal(23, result.Length); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Bar"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Baz"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Bar/Baz"); + AssertEx.ContainsDirectoryPath(result, "/Working/Bar"); + AssertEx.ContainsDirectoryPath(result, "/Working/Project"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.h"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, "/Working/Bar.Qux.Test.dll"); + AssertEx.ContainsFilePath(result, "/Working/Quz.FooTest.dll"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.h"); + AssertEx.ContainsFilePath(result, "/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobaz.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobax.rs"); + AssertEx.ContainsFilePath(result, "/Working/Project/package.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/package-lock.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/tsconfig.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/.npmrc"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Foo/*/Qux.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Character_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Foo/Bar/Q?x.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Character_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Foo/Ba?/Qux.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + } + + [Fact] + public void Should_Return_Files_For_Pattern_Ending_With_Character_Wildcard_And_Dot() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/*.Test.dll"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, "/Working/Bar.Qux.Test.dll"); + } + + [Fact] + public void Should_Return_File_For_Recursive_Wildcard_Pattern_Ending_With_Wildcard_Regex() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/**/*.c"); + + // Then + Assert.Equal(5, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Qex.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Foo/Bar/Baz/Qux.c"); + AssertEx.ContainsFilePath(result, "/Working/Bar/Qux.c"); + } + + [Fact] + public void Should_Return_Only_Folders_For_Pattern_Ending_With_Recursive_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/**"); + + // Then + Assert.Equal(7, result.Length); + AssertEx.ContainsDirectoryPath(result, "/Working"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Bar"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Baz"); + AssertEx.ContainsDirectoryPath(result, "/Working/Foo/Bar/Baz"); + AssertEx.ContainsDirectoryPath(result, "/Working/Bar"); + AssertEx.ContainsDirectoryPath(result, "/Working/Project"); + } + + [Theory] + [InlineData("/*.sh", "/RootFile.sh")] + [InlineData("/Foo/*.baz", "/Foo/Bar.baz")] + public void Should_Include_Files_In_Root_Folder_When_Using_Wildcard(string pattern, string file) + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(pattern); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, file); + } + + [Theory] + [InlineData("/**/RootFile.sh", "/RootFile.sh")] + [InlineData("/Foo/**/Bar.baz", "/Foo/Bar.baz")] + public void Should_Include_Files_In_Root_Folder_When_Using_Recursive_Wildcard(string pattern, string file) + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(pattern); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, file); + } + + [Theory] + [InlineData("/**/RootDir", "/RootDir")] + [InlineData("/Foo/**/Bar", "/Foo/Bar")] + public void Should_Include_Folder_In_Root_Folder_When_Using_Recursive_Wildcard(string pattern, string folder) + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match(pattern); + + // Then + Assert.Single(result); + AssertEx.ContainsDirectoryPath(result, folder); + } + + [Fact] + public void Should_Parse_Glob_Expressions_With_Parenthesis_In_Them() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Foo (Bar)/Baz.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Foo (Bar)/Baz.c"); + } + + [Fact] + public void Should_Parse_Glob_Expressions_With_AtSign_In_Them() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Foo@Bar/Baz.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Foo@Bar/Baz.c"); + } + + [Fact] + public void Should_Parse_Glob_Expressions_With_Relative_Directory_Not_At_The_Beginning() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/./*.Test.dll"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, "/Working/Bar.Qux.Test.dll"); + } + + [Fact] + public void Should_Parse_Glob_Expressions_With_Unicode_Characters_And_Ending_With_Identifier() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/嵌套/**/文件.延期"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/嵌套/目录/文件.延期"); + } + + [Fact] + public void Should_Parse_Glob_Expressions_With_Unicode_Characters_And_Not_Ending_With_Identifier() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/嵌套/**/文件.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/嵌套/目录/文件.延期"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Bracket_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/fooba[rz].rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobaz.rs"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Brace_Expansion() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/foo{bar,bax}.rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobax.rs"); + } + + [Fact] + public void Should_Return_File_When_Brace_Expansion_Is_Only_Segment_Single_Alternative() + { + // Given (reproduces GitHub issue #2666: GetFiles with glob curly braces gave empty result) + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Project/{package.json}"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "/Working/Project/package.json"); + } + + [Fact] + public void Should_Return_Files_When_Brace_Expansion_Is_Only_Segment_Multiple_Alternatives() + { + // Given (reproduces GitHub issue #2666: GetFiles with glob curly braces gave empty result) + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/Project/{package.json,package-lock.json,tsconfig.json,.npmrc}"); + + // Then + Assert.Equal(4, result.Length); + AssertEx.ContainsFilePath(result, "/Working/Project/package.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/package-lock.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/tsconfig.json"); + AssertEx.ContainsFilePath(result, "/Working/Project/.npmrc"); + } + + [Fact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Negated_Bracket_Wildcard() + { + // Given + var fixture = GlobberFixture.UnixLike(); + + // When + var result = fixture.Match("/Working/fooba[!x].rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "/Working/foobaz.rs"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/Globbing/UncGlobberTests.cs b/src/Cake.Core.Tests/Unit/IO/Globbing/UncGlobberTests.cs new file mode 100644 index 0000000000..416e71d6aa --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/Globbing/UncGlobberTests.cs @@ -0,0 +1,332 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO.Globbing +{ + public sealed class UncGlobberTests + { + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\**\*"); + + // Then + Assert.Equal(15, result.Length); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Bar"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Baz"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Bar\Baz"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Bar"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qex.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.h"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Baz\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Baz\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar.Qux.Test.dll"); + AssertEx.ContainsFilePath(result, @"\\Server\Quz.FooTest.dll"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar\Qux.h"); + } + + [WindowsFact] + public void Should_Throw_If_No_Share_Name_Has_Been_Specified() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = Record.Exception(() => fixture.Match(@"\\")); + + // Then + Assert.NotNull(result); + Assert.Equal(@"The pattern '\\' has no server part specified.", result.Message); + } + + [WindowsTheory] + [InlineData(@"\\fo?")] + [InlineData(@"\\fo*")] + [InlineData(@"\\fo?\bar")] + [InlineData(@"\\fo*\bar")] + public void Should_Throw_If_Invalid_Share_Name_Has_Been_Specified(string input) + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = Record.Exception(() => fixture.Match(input)); + + // Then + Assert.NotNull(result); + Assert.Equal($"The pattern '{input}' has an invalid server part specified.", result.Message); + } + + [WindowsFact] + public void Can_Traverse_Recursively() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\**\*.c"); + + // Then + Assert.Equal(5, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Baz\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qex.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Baz/Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar\Qux.c"); + } + + [WindowsFact] + public void Should_Be_Able_To_Visit_Parent_Using_Double_Dots() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\..\Foo\Bar\Qux.c"); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + } + + [WindowsFact] + public void Should_Return_Single_Path_For_Absolute_File_Path_Without_Glob_Pattern() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\Bar\Qux.c"); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + } + + [WindowsFact] + public void Should_Return_Single_Path_For_Absolute_Directory_Path_Without_Glob_Pattern() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\Bar"); + + // Then + Assert.Single(result); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Bar"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\*\Qux.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Baz\Qux.c"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Ending_With_Character_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\Bar\Q?x.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qex.c"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Character_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\Foo\Ba?\Qux.c"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Baz\Qux.c"); + } + + [WindowsFact] + public void Should_Return_Files_For_Pattern_Ending_With_Character_Wildcard_And_Dot() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\*.Test.dll"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar.Qux.Test.dll"); + } + + [WindowsFact] + public void Should_Return_File_For_Recursive_Wildcard_Pattern_Ending_With_Wildcard_Regex() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\**\*.c"); + + // Then + Assert.Equal(5, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Qex.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Baz\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Foo\Bar\Baz\Qux.c"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar\Qux.c"); + } + + [WindowsFact] + public void Should_Return_Only_Folders_For_Pattern_Ending_With_Recursive_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\**"); + + // Then + Assert.Equal(6, result.Length); + AssertEx.ContainsDirectoryPath(result, @"\\Server"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Bar"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Baz"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Foo\Bar\Baz"); + AssertEx.ContainsDirectoryPath(result, @"\\Server\Bar"); + } + + [WindowsFact] + public void Should_Include_Files_In_Root_Folder_When_Using_Recursive_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Foo\**\Bar.baz"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, @"\\Foo\Bar.baz"); + } + + [WindowsFact] + public void Should_Include_Folder_In_Root_Folder_When_Using_Recursive_Wildcard() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Foo\**\Bar"); + + // Then + Assert.Single(result); + AssertEx.ContainsDirectoryPath(result, @"\\Foo\Bar"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Parenthesis_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Foo (Bar)\Baz.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, @"\\Foo (Bar)\Baz.c"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_AtSign_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Foo@Bar\Baz.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, @"\\Foo@Bar\Baz.c"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Relative_Directory_Not_At_The_Beginning() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\Server\.\*.Test.dll"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, @"\\Server\Foo.Bar.Test.dll"); + AssertEx.ContainsFilePath(result, @"\\Server\Bar.Qux.Test.dll"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Unicode_Characters_And_Ending_With_Identifier() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\嵌套\**\文件.延期"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, @"\\嵌套\目录\文件.延期"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Unicode_Characters_And_Not_Ending_With_Identifier() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match(@"\\嵌套\**\文件.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, @"\\嵌套\目录\文件.延期"); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/IO/Globbing/WindowsGlobberTests.cs b/src/Cake.Core.Tests/Unit/IO/Globbing/WindowsGlobberTests.cs new file mode 100644 index 0000000000..e88f4a2c36 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/Globbing/WindowsGlobberTests.cs @@ -0,0 +1,190 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO.Globbing +{ + public sealed class WindowsGlobberTests + { + public sealed class TheMatchMethod + { + [WindowsFact] + public void Will_Fix_Root_If_Drive_Is_Missing_By_Using_The_Drive_From_The_Working_Directory() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("/Working/Foo/Bar/Qux.c"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Working/Foo/Bar/Qux.c"); + } + + [WindowsFact] + public void Should_Ignore_Case_Sensitivity_On_Case_Insensitive_Operative_System() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Working/**/qux.c"); + + // Then + Assert.Single(result); + Assert.IsType(result[0]); + AssertEx.ContainsFilePath(result, "C:/Working/Foo/Bar/Qux.c"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Parenthesis_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Program Files (x86)/Foo.*"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Program Files (x86)/Foo.c"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Ampersand_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Tools & Services/*.dll"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Tools & Services/MyTool.dll"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Plus_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Tools + Services/*.dll"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Tools + Services/MyTool.dll"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Percent_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Some %2F Directory/*.dll"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Some %2F Directory/MyTool.dll"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_Exclamation_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Some ! Directory/*.dll"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Some ! Directory/MyTool.dll"); + } + + [WindowsFact] + public void Should_Parse_Glob_Expressions_With_AtSign_In_Them() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Some@Directory/*.dll"); + + // Then + Assert.Single(result); + AssertEx.ContainsFilePath(result, "C:/Some@Directory/MyTool.dll"); + } + + [WindowsFact] + public void Should_Return_Files_For_Pattern_Ending_With_Character_Wildcard_And_Dot_On_Windows() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Working/*.Test.dll"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "C:/Working/Project.A.Test.dll"); + AssertEx.ContainsFilePath(result, "C:/Working/Project.B.Test.dll"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Bracket_Wildcard_On_Windows() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Working/fooba[rz].rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "C:/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "C:/Working/foobaz.rs"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Brace_Expansion_On_Windows() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Working/foo{bar,bax}.rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "C:/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "C:/Working/foobax.rs"); + } + + [WindowsFact] + public void Should_Return_Files_And_Folders_For_Pattern_Containing_Negated_Bracket_Wildcard_On_Windows() + { + // Given + var fixture = GlobberFixture.Windows(); + + // When + var result = fixture.Match("C:/Working/fooba[!x].rs"); + + // Then + Assert.Equal(2, result.Length); + AssertEx.ContainsFilePath(result, "C:/Working/foobar.rs"); + AssertEx.ContainsFilePath(result, "C:/Working/foobaz.rs"); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/IO/NuGet/NuGetToolResolverTests.cs b/src/Cake.Core.Tests/Unit/IO/NuGet/NuGetToolResolverTests.cs index 8ba8b80d59..fa414e011d 100644 --- a/src/Cake.Core.Tests/Unit/IO/NuGet/NuGetToolResolverTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/NuGet/NuGetToolResolverTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Tests.Fixtures; using Cake.Testing; using Cake.Testing.Xunit; @@ -23,7 +24,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -37,7 +38,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.Resolve()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } } @@ -53,7 +54,7 @@ public void Should_Throw_If_NuGet_Exe_Could_Not_Be_Found() var result = Record.Exception(() => fixture.Resolve()); // Assert - Assert.IsCakeException(result, "Could not locate nuget.exe."); + AssertEx.IsCakeException(result, "Could not locate nuget.exe."); } [Fact] @@ -116,6 +117,7 @@ public void Should_Be_Able_To_Resolve_Path_Via_NuGet_Environment_Variable() } [Theory] + [InlineData("/Library/Frameworks/Mono.framework/Versions/Current/Commands/nuget")] [InlineData("/usr/local/bin/nuget")] [InlineData("/usr/bin/nuget")] public void Should_Be_Able_To_Resolve_Path_Via_Special_Unix_Paths(string path) @@ -132,4 +134,4 @@ public void Should_Be_Able_To_Resolve_Path_Via_Special_Unix_Paths(string path) } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/PathCollapserTests.cs b/src/Cake.Core.Tests/Unit/IO/PathCollapserTests.cs index 043a6d7655..f5ef71dd09 100644 --- a/src/Cake.Core.Tests/Unit/IO/PathCollapserTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/PathCollapserTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Testing.Xunit; using Xunit; @@ -18,107 +19,181 @@ public void Should_Throw_If_Path_Is_Null() var result = Record.Exception(() => PathCollapser.Collapse(null)); // Then - Assert.IsArgumentNullException(result, "path"); - } - - [Fact] - public void Should_Collapse_Relative_Path() - { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("hello/temp/test/../../world")); - - // Then - Assert.Equal("hello/world", path); - } - - [Fact] - public void Should_Collapse_Path_With_Separated_Ellipsis() - { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("hello/temp/../temp2/../world")); - - // Then - Assert.Equal("hello/world", path); - } - - [WindowsFact] - public void Should_Collapse_Path_With_Windows_Root() - { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("c:/hello/temp/test/../../world")); - - // Then - Assert.Equal("c:/hello/world", path); - } - - [Fact] - public void Should_Collapse_Path_With_Non_Windows_Root() - { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("/hello/temp/test/../../world")); - - // Then - Assert.Equal("/hello/world", path); - } - - [WindowsFact] - public void Should_Stop_Collapsing_When_Windows_Root_Is_Reached() - { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("c:/../../../../../../temp")); - - // Then - Assert.Equal("c:/temp", path); + AssertEx.IsArgumentNullException(result, "path"); } - [Fact] - public void Should_Stop_Collapsing_When_Root_Is_Reached() + public sealed class WithPathsInRelativeFormat { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("/hello/../../../../../../temp")); - - // Then - Assert.Equal("/temp", path); + [Fact] + public void Should_Collapse_Relative_Path() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("hello/temp/test/../../world")); + + // Then + Assert.Equal("hello/world", path); + } + + [Fact] + public void Should_Collapse_Path_With_Separated_Ellipsis() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("hello/temp/../temp2/../world")); + + // Then + Assert.Equal("hello/world", path); + } + + [Theory] + [InlineData("./foo/..", ".")] + [InlineData("foo/..", ".")] + public void Should_Collapse_To_Dot_When_Only_One_Folder_Is_Followed_By_Ellipsis(string input, + string expected) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(input)); + + // Then + Assert.Equal(expected, path); + } + + [Theory] + [InlineData(".")] + [InlineData("./")] + [InlineData("")] + public void Should_Collapse_Single_Dot_To_Single_Dot(string uncollapsedPath) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(uncollapsedPath)); + + // Then + Assert.Equal(".", path); + } + + [Fact] + public void Should_Collapse_Single_Dot_With_Ellipsis() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("./..")); + + // Then + Assert.Equal(".", path); + } + + [Theory] + [InlineData("./a", "a")] + [InlineData("a/./b", "a/b")] + [InlineData("a/b/.", "a/b")] + public void Should_Collapse_Single_Dot(string uncollapsedPath, string collapsedPath) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(uncollapsedPath)); + + // Then + Assert.Equal(collapsedPath, path); + } } - [Theory] - [InlineData(".")] - [InlineData("./")] - [InlineData("/.")] - public void Should_Collapse_Single_Dot_To_Single_Dot(string uncollapsedPath) + public sealed class WithPathsInUncFormat { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath(uncollapsedPath)); - - // Then - Assert.Equal(".", path); + [Theory] + [InlineData(@"\\server\share\folder\..", @"\\server\share")] + [InlineData(@"\\server\share\folder\..\..\..\..", @"\\server")] + [InlineData(@"\\server\share\folder\..\..\..\..\foo", @"\\server\foo")] + public void Should_Collapse_Ellipsis(string input, + string expected) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(input)); + + // Then + Assert.Equal(expected, path); + } } - [Fact] - public void Should_Collapse_Single_Dot_With_Ellipsis() + public sealed class WithPathsInNonWindowsFormat { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath("./..")); - - // Then - Assert.Equal(".", path); + [Fact] + public void Should_Collapse_Path_With_Non_Windows_Root() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("/hello/temp/test/../../world")); + + // Then + Assert.Equal("/hello/world", path); + } + + [NonWindowsFact] + public void Should_Stop_Collapsing_When_Root_Is_Reached() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("/hello/../../../../../../temp")); + + // Then + Assert.Equal("/temp", path); + } + + [NonWindowsTheory] + [InlineData("/foo/..", "/")] + [InlineData("/..", "/")] + public void Should_Collapse_To_Root_When_Only_One_Folder_Is_Followed_By_Ellipsis(string input, + string expected) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(input)); + + // Then + Assert.Equal(expected, path); + } + + [Theory] + [InlineData("/a/./b", "/a/b")] + [InlineData("/a/b/.", "/a/b")] + [InlineData("/./a/b", "/a/b")] + public void Should_Collapse_Single_Dot(string uncollapsedPath, string collapsedPath) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(uncollapsedPath)); + + // Then + Assert.Equal(collapsedPath, path); + } } - [Theory] - [InlineData("./a", "a")] - [InlineData("a/./b", "a/b")] - [InlineData("/a/./b", "/a/b")] - [InlineData("a/b/.", "a/b")] - [InlineData("/a/b/.", "/a/b")] - [InlineData("/./a/b", "/a/b")] - public void Should_Collapse_Single_Dot(string uncollapsedPath, string collapsedPath) + public sealed class WithPathsInWindowsFormat { - // Given, When - var path = PathCollapser.Collapse(new DirectoryPath(uncollapsedPath)); - - // Then - Assert.Equal(collapsedPath, path); + [Fact] + public void Should_Collapse_Path_With_Windows_Root() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("c:/hello/temp/test/../../world")); + + // Then + Assert.Equal("c:/hello/world", path); + } + + [WindowsFact] + public void Should_Stop_Collapsing_When_Windows_Root_Is_Reached() + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath("c:/../../../../../../temp")); + + // Then + Assert.Equal("c:/temp", path); + } + + [Theory] + [InlineData("C:/foo/..", "C:")] + public void Should_Collapse_To_Root_When_Only_One_Folder_Is_Followed_By_Ellipsis(string input, + string expected) + { + // Given, When + var path = PathCollapser.Collapse(new DirectoryPath(input)); + + // Then + Assert.Equal(expected, path); + } } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/PathCollectionTests.cs b/src/Cake.Core.Tests/Unit/IO/PathCollectionTests.cs new file mode 100644 index 0000000000..3ea1aa4984 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/PathCollectionTests.cs @@ -0,0 +1,539 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Cake.Core.IO; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO +{ + public sealed class PathCollectionTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Use_PathComparer_Default_If_Comparer_Is_Null() + { + // Given + var collection = new PathCollection(); + + // Then + Assert.Equal(PathComparer.Default, collection.Comparer); + } + } + + public sealed class TheCountProperty + { + [Fact] + public void Should_Return_The_Number_Of_Paths_In_The_Collection() + { + // Given + var collection = new PathCollection( + new Path[] { new DirectoryPath("A"), new FilePath("A.txt"), new DirectoryPath("B"), new FilePath("B.txt") }, + new PathComparer(false)); + + // When, Then + Assert.Equal(4, collection.Count); + } + } + + public sealed class TheAddMethod + { + public sealed class WithSinglePath + { + [Fact] + public void Should_Add_DirectoryPath_If_Not_Already_Present() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); + + // When + collection.Add(new DirectoryPath("B")); + + // Then + Assert.Equal(2, collection.Count); + } + + [Fact] + public void Should_Add_FilePath_If_Not_Already_Present() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt" }, new PathComparer(false)); + + // When + collection.Add(new FilePath("B.txt")); + + // Then + Assert.Equal(2, collection.Count); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_DirectoryPath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); + + // When + collection.Add(new DirectoryPath("a")); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_FilePath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); + + // When + collection.Add(new FilePath("a.txt")); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + } + + public sealed class WithMultiplePaths + { + [Fact] + public void Should_Add_DirectoryPaths_That_Are_Not_Present() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); + + // When + collection.Add(new DirectoryPath[] { "A", "B", "C" }); + + // Then + Assert.Equal(3, collection.Count); + } + + [Fact] + public void Should_Add_FilePaths_That_Are_Not_Present() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); + + // When + collection.Add(new FilePath[] { "A.txt", "B.txt", "C.txt" }); + + // Then + Assert.Equal(3, collection.Count); + } + + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_DirectoryPaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); + + // When + collection.Add(new DirectoryPath[] { "a", "b", "c" }); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_FilePaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); + + // When + collection.Add(new FilePath[] { "a.txt", "b.txt", "c.txt" }); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new PathCollection(); + + // When + var result = Record.Exception(() => collection.Add((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } + } + } + + public sealed class TheRemoveMethod + { + public sealed class WithSinglePath + { + [Theory] + [InlineData(true, 1)] + [InlineData(false, 0)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_DirectoryPath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); + + // When + collection.Remove(new DirectoryPath("a")); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Theory] + [InlineData(true, 1)] + [InlineData(false, 0)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_FilePath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); + + // When + collection.Remove(new FilePath("a.txt")); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + } + + public sealed class WithMultiplePaths + { + [Theory] + [InlineData(true, 2)] + [InlineData(false, 0)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_DirectoryPaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); + + // When + collection.Remove(new DirectoryPath[] { "a", "b", "c" }); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 0)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_FilePaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); + + // When + collection.Remove(new FilePath[] { "a.txt", "b.txt", "c.txt" }); + + // Then + Assert.Equal(expectedCount, collection.Count); + } + + [Fact] + public void Should_Throw_If_Paths_Is_Null() + { + // Given + var collection = new PathCollection(); + + // When + var result = Record.Exception(() => collection.Remove((IEnumerable)null)); + + // Then + AssertEx.IsArgumentNullException(result, "paths"); + } + } + } + + public sealed class ThePlusOperator + { + public sealed class WithSinglePath + { + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_DirectoryPath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A" }, new PathComparer(caseSensitive)); + + // When + var result = collection + new DirectoryPath("a"); + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_FilePath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT" }, new PathComparer(caseSensitive)); + + // When + var result = collection + new FilePath("a.txt"); + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Fact] + public void Should_Return_New_Collection_When_Adding_DirectoryPath() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A" }, new PathComparer(false)); + + // When + var result = collection + new DirectoryPath("B"); + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Return_New_Collection_When_Adding_FilePath() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt" }, new PathComparer(false)); + + // When + var result = collection + new FilePath("B.txt"); + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (PathCollection)null + new FilePath("A.txt")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + } + + public sealed class WithMultiplePaths + { + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_DirectoryPaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); + + // When + var result = collection + new DirectoryPath[] { "a", "b", "c" }; + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Theory] + [InlineData(true, 5)] + [InlineData(false, 3)] + public void Should_Respect_File_System_Case_Sensitivity_When_Adding_FilePaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); + + // When + var result = collection + new FilePath[] { "a.txt", "b.txt", "c.txt" }; + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Fact] + public void Should_Return_New_Collection_When_Adding_DirectoryPaths() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); + + // When + var result = collection + new DirectoryPath[] { "C", "D" }; + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Return_New_Collection_When_Adding_FilePaths() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); + + // When + var result = collection + new FilePath[] { "C.txt", "D.txt" }; + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (PathCollection)null + new FilePath[] { "A.txt" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + } + } + + public sealed class TheMinusOperator + { + public sealed class WithSinglePath + { + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_DirectoryPath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(caseSensitive)); + + // When + var result = collection - new DirectoryPath("a"); + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Theory] + [InlineData(true, 2)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_FilePath(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT", "B.TXT" }, new PathComparer(caseSensitive)); + + // When + var result = collection - new FilePath("a.txt"); + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Fact] + public void Should_Return_New_Collection_When_Removing_DirectoryPathPath() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B" }, new PathComparer(false)); + + // When + var result = collection - new DirectoryPath("A"); + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Return_New_Collection_When_Removing_FilePath() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt", "B.txt" }, new PathComparer(false)); + + // When + var result = collection - new FilePath("A.txt"); + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (PathCollection)null - new FilePath("A.txt")); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + } + + public sealed class WithMultiplePaths + { + [Theory] + [InlineData(true, 3)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_DirectoryPaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B", "C" }, new PathComparer(caseSensitive)); + + // When + var result = collection - new DirectoryPath[] { "b", "c" }; + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Theory] + [InlineData(true, 3)] + [InlineData(false, 1)] + public void Should_Respect_File_System_Case_Sensitivity_When_Removing_FilePaths(bool caseSensitive, int expectedCount) + { + // Given + var collection = new PathCollection(new FilePath[] { "A.TXT", "B.TXT", "C.TXT" }, new PathComparer(caseSensitive)); + + // When + var result = collection - new FilePath[] { "b.txt", "c.txt" }; + + // Then + Assert.Equal(expectedCount, result.Count); + } + + [Fact] + public void Should_Return_New_Collection_When_Removing_DirectoryPaths() + { + // Given + var collection = new PathCollection(new DirectoryPath[] { "A", "B", "C" }, new PathComparer(false)); + + // When + var result = collection - new DirectoryPath[] { "B", "C" }; + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Return_New_Collection_When_Removing_FilePaths() + { + // Given + var collection = new PathCollection(new FilePath[] { "A.txt", "B.txt", "C.txt" }, new PathComparer(false)); + + // When + var result = collection - new FilePath[] { "B.txt", "C.txt" }; + + // Then + Assert.False(ReferenceEquals(result, collection)); + } + + [Fact] + public void Should_Throw_If_Collection_Is_Null() + { + // Given, When + var result = Record.Exception(() => (PathCollection)null - new FilePath[] { "A.txt" }); + + // Then + AssertEx.IsArgumentNullException(result, "collection"); + } + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/PathComparerTests.cs b/src/Cake.Core.Tests/Unit/IO/PathComparerTests.cs index aada39a7a0..c0c26f6b30 100644 --- a/src/Cake.Core.Tests/Unit/IO/PathComparerTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/PathComparerTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; +using Cake.Core.Polyfill; using Xunit; namespace Cake.Core.Tests.Unit.IO @@ -53,7 +55,6 @@ public void Paths_Are_Considered_Inequal_If_Any_Is_Null(bool isCaseSensitive) Assert.False(result); } - [Theory] [InlineData(true)] [InlineData(false)] @@ -112,7 +113,7 @@ public void Should_Throw_If_Other_Path_Is_Null() var result = Record.Exception(() => comparer.GetHashCode(null)); // Then - Assert.IsArgumentNullException(result, "obj"); + AssertEx.IsArgumentNullException(result, "obj"); } [Theory] @@ -164,7 +165,7 @@ public sealed class TheDefaultProperty public void Should_Return_Correct_Comparer_Depending_On_Operative_System() { // Given - var expected = Machine.IsUnix(); + var expected = EnvironmentHelper.IsUnix(); // When var instance = PathComparer.Default; @@ -189,4 +190,4 @@ public void Should_Return_Whether_Or_Not_The_Comparer_Is_Case_Sensitive(bool isC } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/PathExtensionsTests.cs b/src/Cake.Core.Tests/Unit/IO/PathExtensionsTests.cs new file mode 100644 index 0000000000..e6c5dd23f9 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/PathExtensionsTests.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; +using Cake.Testing; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO +{ + public sealed class PathExtensionsTests + { + public sealed class TheExpandEnvironmentVariablesMethod + { + public sealed class ThatTakesAFilePath + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var path = new FilePath("/%FOO%/baz.qux"); + + // When + var result = Record.Exception(() => path.ExpandEnvironmentVariables(null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Expand_Existing_Environment_Variables() + { + // Given + var environment = FakeEnvironment.CreateWindowsEnvironment(); + environment.SetEnvironmentVariable("FOO", "bar"); + var path = new FilePath("/%FOO%/baz.qux"); + + // When + var result = path.ExpandEnvironmentVariables(environment); + + // Then + Assert.Equal("/bar/baz.qux", result.FullPath); + } + } + + public sealed class ThatTakesADirectoryPath + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given + var path = new DirectoryPath("/%FOO%/baz"); + + // When + var result = Record.Exception(() => path.ExpandEnvironmentVariables(null)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Expand_Existing_Environment_Variables() + { + // Given + var environment = FakeEnvironment.CreateWindowsEnvironment(); + environment.SetEnvironmentVariable("FOO", "bar"); + var path = new DirectoryPath("/%FOO%/baz"); + + // When + var result = path.ExpandEnvironmentVariables(environment); + + // Then + Assert.Equal("/bar/baz", result.FullPath); + } + } + } + + public sealed class TheExpandShortPathMethod + { + [Theory] + [InlineData("C:/Program Files/cake-build/addins", "C:/Program Files/cake-build/addins")] + [InlineData("C:/PROGRA~1/cake-build/addins", "C:/Program Files/cake-build/addins")] + public void Will_Normalize_Short_Paths_File(string input, string expected) + { + // Given, When + var path = new FilePath(input); + + path = path.ExpandShortPath(); + + // Then + if (OperatingSystem.IsWindows()) + { + Assert.Equal(expected, path.FullPath); + } + } + + [Theory] + [InlineData("C:/Program Files/cake-build/addins", "C:/Program Files/cake-build/addins")] + [InlineData("C:/PROGRA~1/cake-build/addins", "C:/Program Files/cake-build/addins")] + public void Will_Normalize_Short_Paths_Directory(string input, string expected) + { + // Given, When + var path = new DirectoryPath(input); + + path = path.ExpandShortPath(); + + // Then + if (OperatingSystem.IsWindows()) + { + Assert.Equal(expected, path.FullPath); + } + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/IO/PathTests.cs b/src/Cake.Core.Tests/Unit/IO/PathTests.cs index a09dae0cae..62ebac533a 100644 --- a/src/Cake.Core.Tests/Unit/IO/PathTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/PathTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; + using Cake.Core.IO; using Cake.Testing.Xunit; using Xunit; @@ -24,30 +24,6 @@ public TestingPath(string path) public sealed class TheConstructor { - [Fact] - public void Should_Throw_If_Path_Is_Null() - { - // Given, When - var result = Record.Exception(() => new TestingPath(null)); - - // Then - Assert.IsArgumentNullException(result, "path"); - } - - [Theory] - [InlineData("")] - [InlineData("\t ")] - public void Should_Throw_If_Path_Is_Empty(string fullPath) - { - // Given, When - var result = Record.Exception(() => new TestingPath(fullPath)); - - // Then - Assert.IsType(result); - Assert.Equal("path", ((ArgumentException)result).ParamName); - Assert.Equal(string.Format("Path cannot be empty.{0}Parameter name: path", Environment.NewLine), result.Message); - } - [Fact] public void Current_Directory_Returns_Empty_Path() { @@ -69,35 +45,37 @@ public void Will_Normalize_Path_Separators() } [Fact] - public void Will_Trim_WhiteSpace_From_Path() + public void Will_Normalize_UNC_Path_Separators() { // Given, When - var path = new TestingPath(" shaders/basic "); + var path = new TestingPath(@"\\foo/bar\qux"); // Then - Assert.Equal("shaders/basic", path.FullPath); + Assert.Equal(@"\\foo\bar\qux", path.FullPath); } - [Fact] - public void Will_Not_Remove_WhiteSpace_Within_Path() + [Theory] + [InlineData(" foo/bar ", "foo/bar")] + [InlineData(@" \\foo\bar ", @"\\foo\bar")] + public void Will_Trim_WhiteSpace_From_Path(string input, string expected) { // Given, When - var path = new TestingPath("my awesome shaders/basic"); + var path = new TestingPath(input); // Then - Assert.Equal("my awesome shaders/basic", path.FullPath); + Assert.Equal(expected, path.FullPath); } - [Fact] - public void Should_Throw_If_Path_Contains_Illegal_Characters() + [Theory] + [InlineData("foo bar/qux")] + [InlineData(@"\\foo bar\qux")] + public void Will_Not_Remove_WhiteSpace_Within_Path(string fullPath) { - // Given - var result = Record.Exception(() => new TestingPath("hello/**/world.txt")); + // Given, When + var path = new TestingPath(fullPath); // Then - Assert.IsType(result); - Assert.Equal("path", ((ArgumentException)result).ParamName); - Assert.Equal(string.Format("Illegal characters in directory path (*).{0}Parameter name: path", Environment.NewLine), result.Message); + Assert.Equal(fullPath, path.FullPath); } [Theory] @@ -107,6 +85,8 @@ public void Should_Throw_If_Path_Contains_Illegal_Characters() [InlineData("file.txt\\", "file.txt")] [InlineData("Temp/file.txt/", "Temp/file.txt")] [InlineData("Temp\\file.txt\\", "Temp/file.txt")] + [InlineData(@"\\foo\bar\", @"\\foo\bar")] + [InlineData(@"\\foo\bar/", @"\\foo\bar")] public void Should_Remove_Trailing_Slashes(string value, string expected) { // Given, When @@ -115,6 +95,45 @@ public void Should_Remove_Trailing_Slashes(string value, string expected) // Then Assert.Equal(expected, path.FullPath); } + + [Fact] + public void Should_Not_Remove_Trailing_Slash_For_Root() + { + // Given, When + var path = new TestingPath("/"); + + // Then + Assert.Equal("/", path.FullPath); + } + + [Fact] + public void Should_Not_Remove_Trailing_Slash_For_UNC_Root() + { + // Given, When + var path = new TestingPath(@"\\"); + + // Then + Assert.Equal(@"\\", path.FullPath); + } + } + + public sealed class TheIsUNCProperty + { + [Theory] + [InlineData(@"\\Hello\World", true)] + [InlineData("Hello/World", false)] + [InlineData("./Hello/World", false)] + public void Should_Return_Whether_Or_Not_A_Path_Is_An_UNC_Path(string pathName, bool expected) + { + // Given + var path = new TestingPath(pathName); + + // When + var result = path.IsUNC; + + // Then + Assert.Equal(expected, result); + } } public sealed class TheSegmentsProperty @@ -133,6 +152,20 @@ public void Should_Return_Segments_Of_Path(string pathName) Assert.Equal("World", path.Segments[1]); } + [Theory] + [InlineData(@"\\Hello\World")] + public void Should_Return_Segments_Of_UNC_Path(string pathName) + { + // Given + var path = new TestingPath(pathName); + + // When, Then + Assert.Equal(3, path.Segments.Length); + Assert.Equal(@"\\", path.Segments[0]); + Assert.Equal("Hello", path.Segments[1]); + Assert.Equal("World", path.Segments[2]); + } + [Theory] [InlineData("/Hello/World")] [InlineData("/Hello/World/")] @@ -154,11 +187,10 @@ public sealed class TheFullPathProperty public void Should_Return_Full_Path() { // Given, When - const string expected = "shaders/basic"; - var path = new TestingPath(expected); + var path = new TestingPath("shaders/basic"); // Then - Assert.Equal(expected, path.FullPath); + Assert.Equal("shaders/basic", path.FullPath); } } @@ -178,6 +210,16 @@ public void Should_Return_Whether_Or_Not_A_Path_Is_Relative(string fullPath, boo Assert.Equal(expected, path.IsRelative); } + [Fact] + public void An_UNC_Path_Is_Always_Considered_To_Be_Absolute() + { + // Given, When + var path = new TestingPath(@"\\foo\bar"); + + // Then + Assert.False(path.IsRelative); + } + [WindowsTheory] [InlineData("c:/assets/shaders", false)] [InlineData("c:/assets/shaders/basic.frag", false)] @@ -195,15 +237,17 @@ public void Should_Return_Whether_Or_Not_A_Path_Is_Relative_On_Windows(string fu public sealed class TheToStringMethod { - [Fact] - public void Should_Return_The_Full_Path() + [Theory] + [InlineData("foo/bar")] + [InlineData(@"\\foo\bar")] + public void Should_Return_The_Full_Path(string fullPath) { // Given, When - var path = new TestingPath("temp/hello"); + var path = new TestingPath(fullPath); // Then - Assert.Equal("temp/hello", path.ToString()); + Assert.Equal(fullPath, path.ToString()); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/ProcessArgumentBuilderTests.cs b/src/Cake.Core.Tests/Unit/IO/ProcessArgumentBuilderTests.cs index 2f4b4d54a5..28401598ea 100644 --- a/src/Cake.Core.Tests/Unit/IO/ProcessArgumentBuilderTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/ProcessArgumentBuilderTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.IO.Arguments; using Xunit; @@ -22,7 +23,33 @@ public void Should_Remove_All_Arguments() builder.Clear(); // Then - Assert.Empty(builder.Render()); + Assert.Empty(builder); + } + } + + public sealed class TheFromStringsMethod + { + [Fact] + public void Should_Return_Empty_Builder_When_Values_Is_Null() + { + // Given, When + var builder = ProcessArgumentBuilder.FromStrings(null); + + // Then + Assert.Empty(builder); + } + } + + public sealed class TheFromStringsQuotedMethod + { + [Fact] + public void Should_Return_Empty_Builder_When_Values_Is_Null() + { + // Given, When + var builder = ProcessArgumentBuilder.FromStringsQuoted(null); + + // Then + Assert.Empty(builder); } } @@ -46,4 +73,4 @@ public void Should_Return_Builder_With_Correct_Content(string value, string expe } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/ProcessRunnerTests.cs b/src/Cake.Core.Tests/Unit/IO/ProcessRunnerTests.cs index 16ea280384..500d6d6ac8 100644 --- a/src/Cake.Core.Tests/Unit/IO/ProcessRunnerTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/ProcessRunnerTests.cs @@ -1,8 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.Diagnostics; using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using Cake.Core.Tooling; +using Cake.Testing.Xunit; using NSubstitute; using Xunit; @@ -12,30 +16,60 @@ public sealed class ProcessRunnerTests { public sealed class TheConstructor { + [Fact] + public void Should_Throw_If_FileSystem_Is_Null() + { + // Given + var fixture = new ProcessRunnerFixture(); + fixture.FileSystem = null; + + // Given, When + var result = Record.Exception(() => fixture.CreateProcessRunner()); + + // Then + AssertEx.IsArgumentNullException(result, "fileSystem"); + } + [Fact] public void Should_Throw_If_Environment_Is_Null() { // Given - var log = Substitute.For(); + var fixture = new ProcessRunnerFixture(); + fixture.Environment = null; // Given, When - var result = Record.Exception(() => new ProcessRunner(null, log)); + var result = Record.Exception(() => fixture.CreateProcessRunner()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] public void Should_Throw_If_Log_Is_Null() + { + // Given; + var fixture = new ProcessRunnerFixture(); + fixture.Log = null; + + // Given, When + var result = Record.Exception(() => fixture.CreateProcessRunner()); + + // Then + AssertEx.IsArgumentNullException(result, "log"); + } + + [Fact] + public void Should_Throw_If_Tools_Is_Null() { // Given - var environment = Substitute.For(); + var fixture = new ProcessRunnerFixture(); + fixture.Tools = null; // Given, When - var result = Record.Exception(() => new ProcessRunner(environment, null)); + var result = Record.Exception(() => fixture.CreateProcessRunner()); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "tools"); } } @@ -45,32 +79,150 @@ public sealed class TheStartMethod public void Should_Throw_If_Process_Settings_Are_Null() { // Given - var environment = Substitute.For(); - var log = Substitute.For(); - var runner = new ProcessRunner(environment, log); + var fixture = new ProcessRunnerFixture(); + fixture.ProcessSettings = null; // When - var result = Record.Exception(() => runner.Start("./app.exe", null)); + var result = Record.Exception(() => fixture.Start()); // Then - Assert.IsArgumentNullException(result, "settings"); + AssertEx.IsArgumentNullException(result, "settings"); } [Fact] public void Should_Throw_If_Filename_Is_Null() { // Given - var environment = Substitute.For(); - var log = Substitute.For(); - var runner = new ProcessRunner(environment, log); - var info = new ProcessSettings(); + var fixture = new ProcessRunnerFixture(); + fixture.ProcessFilePath = null; + + // When + var result = Record.Exception(() => fixture.Start()); + + // Then + AssertEx.IsArgumentNullException(result, "filePath"); + } + } + + public sealed class TheGetProcessStartInfoMethod + { + [Fact] + public void Should_Not_Quote_FileName_On_Unix() + { + // Given + var fixture = new ProcessRunnerFixture(windows: false); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + Assert.Equal("/Program Files/Cake.exe", result.FileName); + } + + [Fact] + public void Should_Quote_FileName_On_Windows() + { + // Given + var fixture = new ProcessRunnerFixture(windows: true); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + Assert.Equal("\"/Program Files/Cake.exe\"", result.FileName); + } + + [Fact] + public void Should_Not_Log_If_Setting_Silent() + { + // Given + var fixture = new ProcessRunnerFixture(windows: true); + fixture.ProcessSettings.Silent = true; + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + fixture.Log + .Received(0); + } + + [Fact] + public void Should_Not_Log_Secret_Arguments() + { + // Given + var fixture = new ProcessRunnerFixture(windows: true); + fixture.GivenSecretArgument(); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + fixture.Log + .Received(1) + .Verbose(Verbosity.Diagnostic, "Executing: {0}", "\"/Program Files/Cake.exe\" [REDACTED]"); + } + + public void Should_Coerse_Mono_On_Unix_And_CoreClr() + { + // Given + var fixture = new ProcessRunnerFixture(windows: false); + fixture.GivenIsCoreClr(); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + Assert.Equal("/Program Files/mono.exe", result.FileName); + Assert.Equal("\"/Program Files/Cake.exe\"", result.Arguments); + fixture.Log + .Received(1) + .Write(Verbosity.Diagnostic, LogLevel.Verbose, "{0} is a .NET Framework executable, will try execute using Mono.", "/Program Files/Cake.exe"); + } + + public void Should_Not_Coerse_Mono_On_Windows_And_CoreClr() + { + // Given + var fixture = new ProcessRunnerFixture(windows: true); + fixture.GivenIsCoreClr(); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + Assert.Equal("\"/Program Files/Cake.exe\"", result.FileName); + } + + public void Should_Not_Coerse_Mono_On_Unix_And_CoreClr_With_Config_NoMonoCoersion() + { + // Given + var fixture = new ProcessRunnerFixture(windows: false); + fixture.GivenIsCoreClr(); + fixture.GivenConfigNoMonoCoersion(); + + // When + var result = fixture.GetProcessStartInfo(); + + // Then + Assert.Equal("/Program Files/Cake.exe", result.FileName); + } + + public void Should_Not_Coerse_Mono_On_Unix_And_CoreClr_If_Mono_Not_Resolved() + { + // Given + var fixture = new ProcessRunnerFixture(windows: false); + fixture.GivenIsCoreClr(); + fixture.GivenMonoNotResolved(); // When - var result = Record.Exception(() => runner.Start(null, info)); + var result = fixture.GetProcessStartInfo(); // Then - Assert.IsArgumentNullException(result, "filePath"); + Assert.Equal("/Program Files/Cake.exe", result.FileName); + fixture.Log + .Received(1) + .Write(Verbosity.Diagnostic, LogLevel.Verbose, "{0} is a .NET Framework executable, you might need to install Mono for it to execute successfully.", "/Program Files/Cake.exe"); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/ProcessSettingsTests.cs b/src/Cake.Core.Tests/Unit/IO/ProcessSettingsTests.cs index 38d8207804..2b0c4633a6 100644 --- a/src/Cake.Core.Tests/Unit/IO/ProcessSettingsTests.cs +++ b/src/Cake.Core.Tests/Unit/IO/ProcessSettingsTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Xunit; @@ -35,13 +36,23 @@ public void Should_Return_Settings_With_Correct_Directory(string value, string e [Theory] [InlineData(true, true)] [InlineData(false, false)] - public void Should_Return_Settings_With_Correct_Output(bool value, bool expected) + public void Should_Return_Settings_With_Correct_StandardOutput(bool value, bool expected) { var settings = new ProcessSettings().SetRedirectStandardOutput(value); Assert.Equal(expected, settings.RedirectStandardOutput); } + [Theory] + [InlineData(true, true)] + [InlineData(false, false)] + public void Should_Return_Settings_With_Correct_StandardError(bool value, bool expected) + { + var settings = new ProcessSettings().SetRedirectStandardError(value); + + Assert.Equal(expected, settings.RedirectStandardError); + } + [Theory] [InlineData(0, 0)] [InlineData(5000, 5000)] @@ -54,4 +65,4 @@ public void Should_Return_Settings_With_Correct_Timeout(int value, int expected) } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/IO/ProcessWrapperTests.cs b/src/Cake.Core.Tests/Unit/IO/ProcessWrapperTests.cs new file mode 100644 index 0000000000..d033e545f0 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/IO/ProcessWrapperTests.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using Cake.Core.Diagnostics; +using Cake.Core.IO; +using Cake.Core.Tests.Fixtures; +using NSubstitute; +using Xunit; + +namespace Cake.Core.Tests.Unit.IO +{ + public sealed class ProcessWrapperTests + { + public sealed class The_StandardOutputReceived_Method + { + [Fact] + public void Should_Pass_StandardOutput_Through_Handler() + { + // Given + var receivedMessage = string.Empty; + var fixture = new ProcessWrapperFixture(); + fixture.StandartOutputHandler = (s) => receivedMessage = s; + var wrapper = fixture.CreateProcessWrapper(); + + // When + wrapper.StandardOutputReceived("message"); + + // Then + Assert.Equal("message", receivedMessage); + } + } + + public sealed class The_StandardErrorReceived_Method + { + [Fact] + public void Should_Pass_StandardError_Through_Handler() + { + // Given + var receivedMessage = string.Empty; + var fixture = new ProcessWrapperFixture(); + fixture.StandardErrorHandler = (s) => receivedMessage = s; + var wrapper = fixture.CreateProcessWrapper(); + + // When + wrapper.StandardErrorReceived("message"); + + // Then + Assert.Equal("message", receivedMessage); + } + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Packaging/PackageReferenceTests.cs b/src/Cake.Core.Tests/Unit/Packaging/PackageReferenceTests.cs index 24572f9dfd..1027360407 100644 --- a/src/Cake.Core.Tests/Unit/Packaging/PackageReferenceTests.cs +++ b/src/Cake.Core.Tests/Unit/Packaging/PackageReferenceTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Linq; using Cake.Core.Packaging; using Xunit; @@ -17,7 +19,7 @@ public void Should_Throw_If_Package_Not_Provided_In_Uri() var result = Record.Exception(() => new PackageReference("nuget:?version=1.2.3")); // Then - Assert.IsArgumentException(result, "uri", "Query string parameter 'package' is missing in package reference."); + AssertEx.IsArgumentException(result, "uri", "Query string parameter 'package' is missing in package reference."); } } @@ -75,6 +77,7 @@ public sealed class TheParametersProperty { [Theory] [InlineData("nuget:?package=Cake.Foo&version=1.2.3")] + [InlineData("nuget:?PacKagE=Cake.Foo&VerSIon=1.2.3")] [InlineData("nuget:https://nuget.org/?package=Cake.Foo&version=1.2.3")] [InlineData("nuget:https://user:pass@myget.org/f/Cake?package=Cake.Foo&version=1.2.3")] public void Should_Return_Correct_Address(string uri) @@ -87,8 +90,8 @@ public void Should_Return_Correct_Address(string uri) // Then Assert.Equal(2, result.Count); - Assert.Equal("Cake.Foo", result["package"]); - Assert.Equal("1.2.3", result["version"]); + Assert.Equal("Cake.Foo", result["package"].First()); + Assert.Equal("1.2.3", result["version"].First()); } } @@ -127,6 +130,5 @@ public void Should_Return_Uri_Provided_To_Constructor(string uri) Assert.Equal(uri, result); } } - } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerResultTests.cs b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerResultTests.cs index df133e0f84..f6f7d23434 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerResultTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerResultTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using Cake.Core.Packaging; using Cake.Core.Scripting.Analysis; @@ -106,6 +107,71 @@ public void Should_Populate_Tools_From_Provided_Script() Assert.Contains(result.Tools, package => package.OriginalString == "nuget:?package=First.Package"); Assert.Contains(result.Tools, package => package.OriginalString == "nuget:?package=Second.Package"); } + + [Fact] + public void Should_Set_Succeeded_To_False_If_Any_Errors() + { + // Given + var script = new ScriptInformation("./build.cake"); + var error = new ScriptAnalyzerError("./build.cake", 1, "error message"); + + // When + var result = new ScriptAnalyzerResult(script, new List(), new List { error }); + + // Then + Assert.False(result.Succeeded); + } + + [Fact] + public void Should_Set_Succeeded_To_True_If_Not_Any_Errors() + { + // Given + var script = new ScriptInformation("./build.cake"); + + // When + var result = new ScriptAnalyzerResult(script, new List(), new List()); + + // Then + Assert.True(result.Succeeded); + } + + [Fact] + public void Should_Populate_Using_Static_From_Provided_Script() + { + // Given + var script = new ScriptInformation("./build.cake"); + script.UsingStaticDirectives.Add("using static System.Math;"); + var other = new ScriptInformation("./other.cake"); + other.UsingStaticDirectives.Add("using static System.Console;"); + script.Includes.Add(other); + + // When + var result = new ScriptAnalyzerResult(script, new List()); + + // Then + Assert.Equal(2, result.UsingStaticDirectives.Count); + Assert.Contains("using static System.Math;", result.UsingStaticDirectives); + Assert.Contains("using static System.Console;", result.UsingStaticDirectives); + } + + [Fact] + public void Should_Populate_Defines_From_Provided_Script() + { + // Given + var script = new ScriptInformation("./build.cake"); + script.Defines.Add("#define FOO"); + var other = new ScriptInformation("./other.cake"); + other.Defines.Add("#define BAR"); + script.Includes.Add(other); + + // When + var result = new ScriptAnalyzerResult(script, new List()); + + // Then + Assert.Equal(2, result.Defines.Count); + Assert.Contains("#define FOO", result.Defines); + Assert.Contains("#define BAR", result.Defines); + } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs index a7af417d3c..5aaa3ec9f7 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/Analysis/ScriptAnalyzerTests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Linq; +using Cake.Core.Scripting.Processors.Loading; using Cake.Core.Tests.Fixtures; using Xunit; @@ -22,7 +24,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.CreateAnalyzer()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -36,7 +38,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.CreateAnalyzer()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -50,7 +52,7 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => fixture.CreateAnalyzer()); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "log"); } } @@ -66,21 +68,22 @@ public void Should_Throw_If_File_Path_Is_Null() var result = Record.Exception(() => fixture.Analyze(null)); // Then - Assert.IsArgumentNullException(result, "path"); + AssertEx.IsArgumentNullException(result, "path"); } [Fact] - public void Should_Throw_If_Script_Was_Not_Found() + public void Should_Return_Error_If_Script_Was_Not_Found() { // Given var fixture = new ScriptAnalyzerFixture(); // When - var result = Record.Exception(() => fixture.Analyze("/Working/notfound.cake")); + var result = fixture.Analyze("/Working/notfound.cake"); // Then - Assert.IsType(result); - Assert.Equal("Could not find script '/Working/notfound.cake'.", result.Message); + Assert.False(result.Succeeded); + Assert.Single(result.Errors); + Assert.Equal("Could not find script '/Working/notfound.cake'.", result.Errors[0].Message); } [Fact] @@ -112,14 +115,14 @@ public void Should_Return_Single_Assembly_Reference_Found_In_Source(string sourc var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.References.Count); + Assert.Single(result.Script.References); Assert.Equal("hello.dll", result.Script.References.ElementAt(0)); } [Theory] - [InlineData("#r \"hello.dll\"\n#r \"world.dll\"")] - [InlineData("#r \"hello.dll\"\nConsole.WriteLine();\n#r \"world.dll\"")] - public void Should_Return_Multiple_Assembly_References_Found_In_Source(string source) + [InlineData("#r \"hello world.dll\"")] + [InlineData("#reference \"hello world.dll\"")] + public void Should_Return_Single_Assembly_Reference_With_Space_In_File_Name_Found_In_Source(string source) { // Given var fixture = new ScriptAnalyzerFixture(); @@ -129,47 +132,26 @@ public void Should_Return_Multiple_Assembly_References_Found_In_Source(string so var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(2, result.Script.References.Count); - Assert.Equal("hello.dll", result.Script.References.ElementAt(0)); - Assert.Equal("world.dll", result.Script.References.ElementAt(1)); + Assert.Single(result.Script.References); + Assert.Equal("hello world.dll", result.Script.References.ElementAt(0)); } [Theory] - [InlineData("#l \"utils.cake\"")] - [InlineData("#load \"utils.cake\"")] - public void Should_Process_Single_Script_Reference_Found_In_Source(string source) - { - // Given - var fixture = new ScriptAnalyzerFixture(); - fixture.GivenScriptExist("/Working/script.cake", source); - fixture.GivenScriptExist("/Working/utils.cake", "Console.WriteLine();"); - - // When - var result = fixture.Analyze("/Working/script.cake"); - - // Then - Assert.Equal(1, result.Script.Includes.Count); - Assert.Equal("/Working/utils.cake", result.Script.Includes[0].Path.FullPath); - } - - [Theory] - [InlineData("#l \"utils.cake\"\n#l \"other.cake\"")] - [InlineData("#load \"utils.cake\"\n#load \"other.cake\"")] - public void Should_Process_Multiple_Script_References_Found_In_Source(string source) + [InlineData("#r \"hello.dll\"\n#r \"world.dll\"")] + [InlineData("#r \"hello.dll\"\nConsole.WriteLine();\n#r \"world.dll\"")] + public void Should_Return_Multiple_Assembly_References_Found_In_Source(string source) { // Given var fixture = new ScriptAnalyzerFixture(); fixture.GivenScriptExist("/Working/script.cake", source); - fixture.GivenScriptExist("/Working/utils.cake", "Console.WriteLine();"); - fixture.GivenScriptExist("/Working/other.cake", "Console.WriteLine();"); // When var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(2, result.Script.Includes.Count); - Assert.Equal("/Working/utils.cake", result.Script.Includes[0].Path.FullPath); - Assert.Equal("/Working/other.cake", result.Script.Includes[1].Path.FullPath); + Assert.Equal(2, result.Script.References.Count); + Assert.Equal("hello.dll", result.Script.References.ElementAt(0)); + Assert.Equal("world.dll", result.Script.References.ElementAt(1)); } [Fact] @@ -183,7 +165,7 @@ public void Should_Process_Using_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Namespaces.Count); + Assert.Single(result.Script.Namespaces); Assert.Equal("Cake.Core", result.Script.Namespaces.ElementAt(0)); } @@ -198,7 +180,7 @@ public void Should_Process_Using_Alias_Directives() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.UsingAliases.Count); + Assert.Single(result.Script.UsingAliases); Assert.Equal("using Core = Cake.Core;", result.Script.UsingAliases.ElementAt(0)); } @@ -207,17 +189,17 @@ public void Should_Keep_Using_Block() { // Given var fixture = new ScriptAnalyzerFixture(); - fixture.GivenScriptExist("/Working/script.cake", "using(new Temp())\n{\n}"); + fixture.GivenScriptExist("/Working/script.cake", "using (new Temp())\n{\n}"); // When var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(0, result.Script.UsingAliases.Count); - Assert.Equal(0, result.Script.Namespaces.Count); + Assert.Empty(result.Script.UsingAliases); + Assert.Empty(result.Script.Namespaces); Assert.Equal(4, result.Lines.Count); Assert.Equal(result.Lines[0], "#line 1 \"/Working/script.cake\""); - Assert.Equal(result.Lines[1], "using(new Temp())"); + Assert.Equal(result.Lines[1], "using (new Temp())"); Assert.Equal(result.Lines[2], "{"); Assert.Equal(result.Lines[3], "}"); } @@ -250,7 +232,7 @@ public void Should_Process_Addin_Directive_Without_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("nuget:?package=Hello.World", result.Script.Addins.ElementAt(0).OriginalString); } @@ -265,7 +247,7 @@ public void Should_Process_Addin_Directive_Using_Uri() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("npm:?package=node", result.Script.Addins.ElementAt(0).OriginalString); } @@ -280,7 +262,7 @@ public void Should_Process_Addin_Directive_With_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Addins.Count); + Assert.Single(result.Script.Addins); Assert.Equal("nuget:http://source/?package=Hello.World", result.Script.Addins.ElementAt(0).OriginalString); } @@ -295,7 +277,7 @@ public void Should_Process_Tool_Directive_Without_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("nuget:?package=Hello.World", result.Script.Tools.ElementAt(0).OriginalString); } @@ -310,7 +292,7 @@ public void Should_Process_Tool_Directive_With_Source() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("nuget:http://source/?package=Hello.World", result.Script.Tools.ElementAt(0).OriginalString); } @@ -325,54 +307,98 @@ public void Should_Process_Tool_Directive_Using_Uri() var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(1, result.Script.Tools.Count); + Assert.Single(result.Script.Tools); Assert.Equal("npm:?package=node", result.Script.Tools.ElementAt(0).OriginalString); } [Fact] - public void Should_Insert_Line_Directives_When_Processing_Load_Directives() + public void Should_Process_Break_Directive() { // Given var fixture = new ScriptAnalyzerFixture(); - fixture.GivenScriptExist("/Working/a.cake", "int x=0;\n#l b.cake\nint y=2;"); - fixture.GivenScriptExist("/Working/b.cake", "int z=1;\n#l c.cake\nint p=4;"); - fixture.GivenScriptExist("/Working/c.cake", "int o=3;\n#r d.dll"); + fixture.GivenScriptExist("/Working/script.cake", "#break"); // When - var result = fixture.Analyze("/Working/a.cake"); + var result = fixture.Analyze("/Working/script.cake"); // Then - Assert.Equal(13, result.Lines.Count); - Assert.Equal(result.Lines[0], "#line 1 \"/Working/a.cake\""); - Assert.Equal(result.Lines[1], "int x=0;"); - Assert.Equal(result.Lines[2], "#line 1 \"/Working/b.cake\""); - Assert.Equal(result.Lines[3], "int z=1;"); - Assert.Equal(result.Lines[4], "#line 1 \"/Working/c.cake\""); - Assert.Equal(result.Lines[5], "int o=3;"); - Assert.Equal(result.Lines[6], "// #r d.dll"); - Assert.Equal(result.Lines[7], "#line 2 \"/Working/b.cake\""); - Assert.Equal(result.Lines[8], "// #l c.cake"); - Assert.Equal(result.Lines[9], "int p=4;"); - Assert.Equal(result.Lines[10], "#line 2 \"/Working/a.cake\""); - Assert.Equal(result.Lines[11], "// #l b.cake"); - Assert.Equal(result.Lines[12], "int y=2;"); + Assert.Equal(2, result.Lines.Count); + Assert.Equal(result.Lines[0], "#line 1 \"/Working/script.cake\""); + Assert.Equal(result.Lines[1], @"if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); }"); } [Fact] - public void Should_Process_Break_Directive() + public void Should_Log_Error_Location() { // Given var fixture = new ScriptAnalyzerFixture(); - fixture.GivenScriptExist("/Working/script.cake", "#break"); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", "#load \"local:?pat\""); // When var result = fixture.Analyze("/Working/script.cake"); // Then Assert.Equal(2, result.Lines.Count); - Assert.Equal(result.Lines[0], "#line 1 \"/Working/script.cake\""); - Assert.Equal(result.Lines[1], @"if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); }"); + Assert.Equal("#line 1 \"/Working/script.cake\"", result.Lines[0]); + Assert.Equal("// #load \"local:?pat\"", result.Lines[1]); + Assert.False(result.Succeeded); + Assert.Single(result.Errors); + Assert.Equal("/Working/script.cake", result.Errors[0].File.FullPath); + Assert.Equal(1, result.Errors[0].Line); + Assert.Equal("Query string for #load is missing parameter 'path'.", result.Errors[0].Message); + } + + [Fact] + public void Should_Log_Error_Location_In_Loaded_Script() + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", "#load \"local:?path=script2.cake\""); + fixture.GivenScriptExist("/Working/script2.cake", "\n#load \"local:?path=1&path=2\""); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Equal(6, result.Lines.Count); + Assert.False(result.Succeeded); + Assert.Single(result.Errors); + Assert.Equal("/Working/script2.cake", result.Errors[0].File.FullPath); + Assert.Equal(2, result.Errors[0].Line); + Assert.Equal("Query string for #load contains more than one parameter 'path'.", result.Errors[0].Message); + } + + [Fact] + public void Should_Process_Using_Static_Directives() + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.GivenScriptExist("/Working/script.cake", "using static System.Math;"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.UsingStaticDirectives); + Assert.Equal("using static System.Math;", result.Script.UsingStaticDirectives.ElementAt(0)); + } + + [Fact] + public void Should_Process_Define_Directives() + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.GivenScriptExist("/Working/script.cake", "#define FOO"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Defines); + Assert.Equal("#define FOO", result.Script.Defines.ElementAt(0)); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethod b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethod deleted file mode 100644 index d63e8d9166..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethod +++ /dev/null @@ -1,4 +0,0 @@ -public void Generic_ExtensionMethod() -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethod(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithGenericReturnValue b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithGenericReturnValue deleted file mode 100644 index 1ff0b652ca..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithGenericReturnValue +++ /dev/null @@ -1,4 +0,0 @@ -public TTest Generic_ExtensionMethodWithGenericReturnValue(TTest value) -{ - return Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethodWithGenericReturnValue(Context, value); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithParameter b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithParameter deleted file mode 100644 index 44b10e97a5..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Generic_ExtensionMethodWithParameter +++ /dev/null @@ -1,4 +0,0 @@ -public void Generic_ExtensionMethodWithParameter(TTest value) -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.Generic_ExtensionMethodWithParameter(Context, value); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithGenericParameter b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithGenericParameter deleted file mode 100644 index e8e6fb042c..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithGenericParameter +++ /dev/null @@ -1,4 +0,0 @@ -public void NonGeneric_ExtensionMethodWithGenericParameter(System.Action value) -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithGenericParameter(Context, value); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithNoParameters b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithNoParameters deleted file mode 100644 index b93f98b555..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithNoParameters +++ /dev/null @@ -1,4 +0,0 @@ -public void NonGeneric_ExtensionMethodWithNoParameters() -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithNoParameters(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameter b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameter deleted file mode 100644 index c637d53846..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameter +++ /dev/null @@ -1,4 +0,0 @@ -public void NonGeneric_ExtensionMethodWithParameter(System.Int32 value) -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithParameter(Context, value); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameterArray b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameterArray deleted file mode 100644 index 79998303c7..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithParameterArray +++ /dev/null @@ -1,4 +0,0 @@ -public void NonGeneric_ExtensionMethodWithParameterArray(params System.Int32[] values) -{ - Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithParameterArray(Context, values); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithReturnValue b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithReturnValue deleted file mode 100644 index 06b202e16d..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/NonGeneric_ExtensionMethodWithReturnValue +++ /dev/null @@ -1,4 +0,0 @@ -public System.String NonGeneric_ExtensionMethodWithReturnValue() -{ - return Cake.Core.Tests.Data.MethodAliasGeneratorData.NonGeneric_ExtensionMethodWithReturnValue(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitError_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitError_WithMessage deleted file mode 100644 index 69ad78324c..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitError_WithMessage +++ /dev/null @@ -1,4 +0,0 @@ -public void Obsolete_ExplicitError_WithMessage() -{ - throw new Cake.Core.CakeException("The alias Obsolete_ExplicitError_WithMessage has been made obsolete. Please use Foo.Bar instead."); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitWarning_WithMessage deleted file mode 100644 index 3a91acf3e9..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ExplicitWarning_WithMessage +++ /dev/null @@ -1,5 +0,0 @@ -public void Obsolete_ExplicitWarning_WithMessage() -{ - Context.Log.Warning("Warning: The alias Obsolete_ExplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ExplicitWarning_WithMessage(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_NoMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_NoMessage deleted file mode 100644 index 9590953498..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_NoMessage +++ /dev/null @@ -1,5 +0,0 @@ -public void Obsolete_ImplicitWarning_NoMessage() -{ - Context.Log.Warning("Warning: The alias Obsolete_ImplicitWarning_NoMessage has been made obsolete."); - Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ImplicitWarning_NoMessage(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_WithMessage deleted file mode 100644 index 2102f9f920..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Methods/Obsolete_ImplicitWarning_WithMessage +++ /dev/null @@ -1,5 +0,0 @@ -public void Obsolete_ImplicitWarning_WithMessage() -{ - Context.Log.Warning("Warning: The alias Obsolete_ImplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - Cake.Core.Tests.Data.MethodAliasGeneratorData.Obsolete_ImplicitWarning_WithMessage(Context); -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitError_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitError_WithMessage deleted file mode 100644 index 57c6caf94f..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitError_WithMessage +++ /dev/null @@ -1,7 +0,0 @@ -public System.Int32 Cached_Obsolete_ExplicitError_WithMessage -{ - get - { - throw new Cake.Core.CakeException("The alias Cached_Obsolete_ExplicitError_WithMessage has been made obsolete. Please use Foo.Bar instead."); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitWarning_WithMessage deleted file mode 100644 index 35ba5058e1..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ExplicitWarning_WithMessage +++ /dev/null @@ -1,13 +0,0 @@ -private System.Int32? _Cached_Obsolete_ExplicitWarning_WithMessage; -public System.Int32 Cached_Obsolete_ExplicitWarning_WithMessage -{ - get - { - Context.Log.Warning("Warning: The alias Cached_Obsolete_ExplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - if (_Cached_Obsolete_ExplicitWarning_WithMessage==null) - { - _Cached_Obsolete_ExplicitWarning_WithMessage = Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ExplicitWarning_WithMessage(Context); - } - return _Cached_Obsolete_ExplicitWarning_WithMessage.Value; - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_NoMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_NoMessage deleted file mode 100644 index 3453f79af9..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_NoMessage +++ /dev/null @@ -1,13 +0,0 @@ -private System.Int32? _Cached_Obsolete_ImplicitWarning_NoMessage; -public System.Int32 Cached_Obsolete_ImplicitWarning_NoMessage -{ - get - { - Context.Log.Warning("Warning: The alias Cached_Obsolete_ImplicitWarning_NoMessage has been made obsolete."); - if (_Cached_Obsolete_ImplicitWarning_NoMessage==null) - { - _Cached_Obsolete_ImplicitWarning_NoMessage = Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ImplicitWarning_NoMessage(Context); - } - return _Cached_Obsolete_ImplicitWarning_NoMessage.Value; - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_WithMessage deleted file mode 100644 index 5ec05949f3..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Obsolete_ImplicitWarning_WithMessage +++ /dev/null @@ -1,13 +0,0 @@ -private System.Int32? _Cached_Obsolete_ImplicitWarning_WithMessage; -public System.Int32 Cached_Obsolete_ImplicitWarning_WithMessage -{ - get - { - Context.Log.Warning("Warning: The alias Cached_Obsolete_ImplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - if (_Cached_Obsolete_ImplicitWarning_WithMessage==null) - { - _Cached_Obsolete_ImplicitWarning_WithMessage = Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Obsolete_ImplicitWarning_WithMessage(Context); - } - return _Cached_Obsolete_ImplicitWarning_WithMessage.Value; - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Reference_Type b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Reference_Type deleted file mode 100644 index 1df2421f9f..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Reference_Type +++ /dev/null @@ -1,12 +0,0 @@ -private System.String _Cached_Reference_Type; -public System.String Cached_Reference_Type -{ - get - { - if (_Cached_Reference_Type==null) - { - _Cached_Reference_Type = Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Reference_Type(Context); - } - return _Cached_Reference_Type; - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Value_Type b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Value_Type deleted file mode 100644 index 68bb776893..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/Cached_Value_Type +++ /dev/null @@ -1,12 +0,0 @@ -private System.Boolean? _Cached_Value_Type; -public System.Boolean Cached_Value_Type -{ - get - { - if (_Cached_Value_Type==null) - { - _Cached_Value_Type = Cake.Core.Tests.Data.PropertyAliasGeneratorData.Cached_Value_Type(Context); - } - return _Cached_Value_Type.Value; - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitError_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitError_WithMessage deleted file mode 100644 index 4eed195dcc..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitError_WithMessage +++ /dev/null @@ -1,7 +0,0 @@ -public System.Int32 NonCached_Obsolete_ExplicitError_WithMessage -{ - get - { - throw new Cake.Core.CakeException("The alias NonCached_Obsolete_ExplicitError_WithMessage has been made obsolete. Please use Foo.Bar instead."); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitWarning_WithMessage deleted file mode 100644 index 102bc209f0..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ExplicitWarning_WithMessage +++ /dev/null @@ -1,8 +0,0 @@ -public System.Int32 NonCached_Obsolete_ExplicitWarning_WithMessage -{ - get - { - Context.Log.Warning("Warning: The alias NonCached_Obsolete_ExplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - return Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ExplicitWarning_WithMessage(Context); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_NoMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_NoMessage deleted file mode 100644 index be258ce45a..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_NoMessage +++ /dev/null @@ -1,8 +0,0 @@ -public System.Int32 NonCached_Obsolete_ImplicitWarning_NoMessage -{ - get - { - Context.Log.Warning("Warning: The alias NonCached_Obsolete_ImplicitWarning_NoMessage has been made obsolete."); - return Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ImplicitWarning_NoMessage(Context); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_WithMessage b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_WithMessage deleted file mode 100644 index d82977bc0d..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Obsolete_ImplicitWarning_WithMessage +++ /dev/null @@ -1,8 +0,0 @@ -public System.Int32 NonCached_Obsolete_ImplicitWarning_WithMessage -{ - get - { - Context.Log.Warning("Warning: The alias NonCached_Obsolete_ImplicitWarning_WithMessage has been made obsolete. Please use Foo.Bar instead."); - return Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Obsolete_ImplicitWarning_WithMessage(Context); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Value_Type b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Value_Type deleted file mode 100644 index cdfe390809..0000000000 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/Expected/Properties/NonCached_Value_Type +++ /dev/null @@ -1,7 +0,0 @@ -public System.Int32 NonCached_Value_Type -{ - get - { - return Cake.Core.Tests.Data.PropertyAliasGeneratorData.NonCached_Value_Type(Context); - } -} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericConstraintFakes.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericConstraintFakes.cs new file mode 100644 index 0000000000..6cbef9cf55 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericConstraintFakes.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Core.Tests.Unit.Scripting.CodeGen +{ + /// + /// Provides test/marker classes for tests in . + /// + internal static class GenericConstraintFakes + { + internal class FakeClass + { + } + + internal interface IFakeInterface + { + } + } +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericParameterConstraintEmitterTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericParameterConstraintEmitterTests.cs new file mode 100644 index 0000000000..606b4c3c52 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/GenericParameterConstraintEmitterTests.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Cake.Core.Scripting.CodeGen; +using Xunit; +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedParameter.Local +namespace Cake.Core.Tests.Unit.Scripting.CodeGen +{ + public sealed class GenericParameterConstraintEmitterTests + { + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeClass + { + } + + private interface IFakeInterface + { + } + + private static class GenericParameterConstraintEmitterFixture + { + public static TResult Test_TResult_No_Constraints() + { + throw new NotImplementedException(); + } + + public static TResult Test_TResult_class() where TResult : class + { + throw new NotImplementedException(); + } + + public static TResult Test_TResult_class_new() where TResult : class, new() + { + throw new NotImplementedException(); + } + + public static TResult Test_TResult_struct() where TResult : struct + { + throw new NotImplementedException(); + } + + public static TResult Test_TResult_struct_and_IFakeInterface() where TResult : struct, IFakeInterface + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_NoConstraints(TIn obj) + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_FakeClass(TIn obj) where TIn : FakeClass + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_IFakeInterface(TIn obj) where TIn : IFakeInterface + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_IFakeInterface_and_DefaultCtor(TIn obj) where TIn : IFakeInterface, new() + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_FakeClass_and_DefaultCtor(TIn obj) where TIn : FakeClass, new() + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface(TIn obj) where TIn : FakeClass, IFakeInterface + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface_and_DefaultCtor(TIn obj) where TIn : FakeClass, IFakeInterface, new() + { + throw new NotImplementedException(); + } + + public static TOut Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface_and_TOut_IsIFakeInterface(TIn obj) + where TIn : FakeClass, IFakeInterface + where TOut : IFakeInterface + { + throw new NotImplementedException(); + } + + public static void Test_TIn_TIn_is_NestedFakeClass_and_NestedFakeInterface(TIn obj) where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericConstraintFakes.FakeClass, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericConstraintFakes.IFakeInterface + { + throw new NotImplementedException(); + } + + public static void Test_TIn_TIn_Is_IEnumerable_int(TIn obj) + where TIn : IEnumerable + { + throw new NotImplementedException(); + } + } + + [Theory] + [InlineData("Test_TResult_No_Constraints", "")] + [InlineData("Test_TResult_class", "where TResult : class")] + [InlineData("Test_TResult_class_new", "where TResult : class, new()")] + [InlineData("Test_TResult_struct", "where TResult : struct")] + [InlineData("Test_TResult_struct_and_IFakeInterface", "where TResult : struct, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface")] + [InlineData("Test_TOut_TIn_NoConstraints", "")] + [InlineData("Test_TOut_TIn_TIn_is_FakeClass", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.FakeClass")] + [InlineData("Test_TOut_TIn_TIn_is_IFakeInterface_and_DefaultCtor", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface, new()")] + [InlineData("Test_TOut_TIn_TIn_is_FakeClass_and_DefaultCtor", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.FakeClass, new()")] + [InlineData("Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.FakeClass, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface")] + [InlineData("Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface_and_DefaultCtor", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.FakeClass, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface, new()")] + [InlineData("Test_TOut_TIn_TIn_is_FakeClass_and_IFakeInterface_and_TOut_IsIFakeInterface", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.FakeClass, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface\r\nwhere TOut : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericParameterConstraintEmitterTests.IFakeInterface")] + [InlineData("Test_TIn_TIn_is_NestedFakeClass_and_NestedFakeInterface", "where TIn : Cake.Core.Tests.Unit.Scripting.CodeGen.GenericConstraintFakes.FakeClass, Cake.Core.Tests.Unit.Scripting.CodeGen.GenericConstraintFakes.IFakeInterface")] + [InlineData("Test_TIn_TIn_Is_IEnumerable_int", "where TIn : System.Collections.Generic.IEnumerable")] + public void Should_Return_Correct_Generated_Code_For_Generic_Method_Parameter_Constraints(string methodName, string expected) + { + // Given + var method = typeof(GenericParameterConstraintEmitterFixture).GetMethod(methodName, BindingFlags.Static | BindingFlags.Public); + + // When + var result = GenericParameterConstraintEmitter.Emit(method); + + // Then + Assert.Equal(expected.NormalizeLineEndings(), result.NormalizeLineEndings()); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs index 40b1e6d1ec..6e32e2a19e 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/MethodAliasGeneratorTests.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; using Cake.Core.Scripting.CodeGen; using Cake.Core.Tests.Fixtures; +using VerifyXunit; using Xunit; +using static Cake.Core.Tests.VerifyConfig; namespace Cake.Core.Tests.Unit.Scripting.CodeGen { @@ -25,41 +29,61 @@ public void Should_Throw_If_Method_Is_Null() var result = Record.Exception(() => MethodAliasGenerator.Generate(null)); // Then - Assert.IsArgumentNullException(result, "method"); + AssertEx.IsArgumentNullException(result, "method"); } [Theory] - [InlineData("NonGeneric_ExtensionMethodWithNoParameters")] - [InlineData("NonGeneric_ExtensionMethodWithParameter")] - [InlineData("NonGeneric_ExtensionMethodWithGenericParameter")] - [InlineData("NonGeneric_ExtensionMethodWithReturnValue")] - [InlineData("NonGeneric_ExtensionMethodWithParameterArray")] - public void Should_Return_Correct_Generated_Code_For_Non_Generic_Methods(string name) + [InlineData("ExtensionMethodWithNoParameters")] + [InlineData("ExtensionMethodWithParameter")] + [InlineData("ExtensionMethodWithGenericParameter")] + [InlineData("ExtensionMethodWithGenericExpressionParameter")] + [InlineData("ExtensionMethodWithGenericExpressionArrayParameter")] + [InlineData("ExtensionMethodWithGenericExpressionParamsArrayParameter")] + [InlineData("ExtensionMethodWithReturnValue")] + [InlineData("ExtensionMethodWithParameterArray")] + [InlineData("ExtensionMethodWithOptionalObjectParameter")] + [InlineData("ExtensionMethodWithOptionalBooleanParameter")] + [InlineData("ExtensionMethodWithOptionalStringParameter")] + [InlineData("ExtensionMethodWithOptionalEnumParameter")] + [InlineData("ExtensionMethodWithOptionalCharParameter")] + [InlineData("ExtensionMethodWithOptionalDecimalParameter")] + [InlineData("ExtensionMethodWithOptionalNullableTParameter")] + [InlineData("ExtensionMethodWithOptionalNullableBooleanParameter")] + [InlineData("ExtensionMethodWithOptionalNullableCharParameter")] + [InlineData("ExtensionMethodWithOptionalNullableEnumParameter")] + [InlineData("ExtensionMethodWithOptionalNullableDecimalParameter")] + [InlineData("ExtensionMethodWithOptionalNullableLongParameter")] + [InlineData("ExtensionMethodWithOptionalNullableDoubleParameter")] + [InlineData("ExtensionMethodWithReservedKeywordParameter")] + [InlineData("ExtensionMethodWithOutputParameter")] + [InlineData("ExtensionMethodWithGenericCollectionOfNestedType")] + [InlineData("ExtensionMethodWithParameterAttributes")] + [InlineData("ExtensionMethodWithDynamicReturnValue")] + [InlineData("ExtensionMethodWithNullableParameter")] + [InlineData("ExtensionMethodWithNullableReturnValue")] + public Task Should_Return_Correct_Generated_Code_For_Non_Generic_Methods(string name) { - // Given - var expected = _fixture.GetExpectedCode(name); - - // When - var result = _fixture.Generate(name); + // Given / When + var result = _fixture.Generate("NonGeneric_" + name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } [Theory] [InlineData("Generic_ExtensionMethod")] [InlineData("Generic_ExtensionMethodWithParameter")] [InlineData("Generic_ExtensionMethodWithGenericReturnValue")] - public void Should_Return_Correct_Generated_Code_For_Generic_Methods(string name) + [InlineData("Generic_ExtensionMethodWithGenericReturnValueAndTypeParamConstraints")] + public Task Should_Return_Correct_Generated_Code_For_Generic_Methods(string name) { - // Given - var expected = _fixture.GetExpectedCode(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } [Theory] @@ -67,17 +91,15 @@ public void Should_Return_Correct_Generated_Code_For_Generic_Methods(string name [InlineData("Obsolete_ImplicitWarning_WithMessage")] [InlineData("Obsolete_ExplicitWarning_WithMessage")] [InlineData("Obsolete_ExplicitError_WithMessage")] - public void Should_Return_Correct_Generated_Code_For_Obsolete_Methods(string name) + public Task Should_Return_Correct_Generated_Code_For_Obsolete_Methods(string name) { - // Given - var expected = _fixture.GetExpectedCode(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterEmitterTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterEmitterTests.cs new file mode 100644 index 0000000000..7c47dd7c23 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterEmitterTests.cs @@ -0,0 +1,548 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Cake.Core.Scripting.CodeGen; +using Xunit; +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedParameter.Local +namespace Cake.Core.Tests.Unit.Scripting.CodeGen +{ + public sealed class ParameterEmitterTests + { + public enum TestEnum + { + None, + Some, + All + } + + [AttributeUsageAttribute(AttributeTargets.Parameter)] + public sealed class TestParameterAttribute : Attribute + { + public TestParameterAttribute(string name) + { + } + + public TestParameterAttribute(int number) + { + } + + public TestParameterAttribute(TestEnum value) + { + } + + public TestParameterAttribute(TestEnum[] values) + { + } + + public TestParameterAttribute() + { + } + + public string StringProperty { get; set; } + + public int Int32Property { get; set; } + + public TestEnum EnumProperty { get; set; } + } + + private static class ParameterFixture + { + public static void RequiredInt(int arg) + { + } + + public static void RequiredDouble(double arg) + { + } + + public static void RequiredDecimal(decimal arg) + { + } + + public static void RequiredLong(long arg) + { + } + + public static void RequiredShort(short arg) + { + } + + public static void RequiredSByte(sbyte arg) + { + } + + public static void RequiredByte(byte arg) + { + } + + public static void RequiredULong(ulong arg) + { + } + + public static void RequiredUShort(ushort arg) + { + } + + public static void RequiredUInt(uint arg) + { + } + + public static void RequiredFloat(float arg) + { + } + + public static void RequiredEnum(TestEnum arg) + { + } + + public static void RequiredBool(bool arg) + { + } + + public static void RequiredString(string arg) + { + } + + public static void RequiredObject(object arg) + { + } + + public static void RequiredDateTime(DateTime arg) + { + } + + public static void RequiredInterface(IDisposable arg) + { + } + + public static void RequiredClass(CakeContext arg) + { + } + + public static void RequiredType(Type type) + { + } + + public static void RequiredGenericEnumerable(System.Collections.Generic.IEnumerable items) + { + } + + public static void OptionalInt(int arg = 1) + { + } + + public static void OptionalDouble(double arg = 1) + { + } + + public static void OptionalDecimal(decimal arg = 1) + { + } + + public static void OptionalLong(long arg = 1) + { + } + + public static void OptionalShort(short arg = 1) + { + } + + public static void OptionalSByte(sbyte arg = 1) + { + } + + public static void OptionalByte(byte arg = 1) + { + } + + public static void OptionalULong(ulong arg = 1) + { + } + + public static void OptionalUShort(ushort arg = 1) + { + } + + public static void OptionalUInt(uint arg = 1) + { + } + + public static void OptionalFloat(float arg = 1) + { + } + + public static void OptionalEnum(TestEnum arg = TestEnum.All) + { + } + + public static void OptionalBool(bool arg = true) + { + } + + public static void OptionalString(string arg = "value") + { + } + + public static void OptionalObject(object arg = null) + { + } + + public static void OptionalInterface(IDisposable arg = null) + { + } + + public static void OptionalClass(CakeContext arg = null) + { + } + + public static void OptionalType(Type type = null) + { + } + + public static void OptionalGenericEnumerable(System.Collections.Generic.IEnumerable items = null) + { + } + + public static void RequiredNullableInt(int? arg) + { + } + + public static void RequiredNullableDouble(double? arg) + { + } + + public static void RequiredNullableDecimal(decimal? arg) + { + } + + public static void RequiredNullableLong(long? arg) + { + } + + public static void RequiredNullableShort(short? arg) + { + } + + public static void RequiredNullableSByte(sbyte? arg) + { + } + + public static void RequiredNullableByte(byte? arg) + { + } + + public static void RequiredNullableULong(ulong? arg) + { + } + + public static void RequiredNullableUShort(ushort? arg) + { + } + + public static void RequiredNullableUInt(uint? arg) + { + } + + public static void RequiredNullableFloat(float? arg) + { + } + + public static void RequiredNullableEnum(TestEnum? arg) + { + } + + public static void RequiredNullableBool(bool? arg) + { + } + + public static void RequiredNullableDateTime(DateTime? arg) + { + } + + public static void OptionalNullableIntWithNullDefault(int? arg = null) + { + } + + public static void OptionalNullableDoubleWithNullDefault(double? arg = null) + { + } + + public static void OptionalNullableDecimalWithNullDefault(decimal? arg = null) + { + } + + public static void OptionalNullableLongWithNullDefault(long? arg = null) + { + } + + public static void OptionalNullableShortWithNullDefault(short? arg = null) + { + } + + public static void OptionalNullableSByteWithNullDefault(sbyte? arg = null) + { + } + + public static void OptionalNullableByteWithNullDefault(byte? arg = null) + { + } + + public static void OptionalNullableULongWithNullDefault(ulong? arg = null) + { + } + + public static void OptionalNullableUShortWithNullDefault(ushort? arg = null) + { + } + + public static void OptionalNullableUIntWithNullDefault(uint? arg = null) + { + } + + public static void OptionalNullableFloatWithNullDefault(float? arg = null) + { + } + + public static void OptionalNullableEnumWithNullDefault(TestEnum? arg = null) + { + } + + public static void OptionalNullableBoolWithNullDefault(bool? arg = null) + { + } + + public static void OptionalNullableDateTimeWithNullDefault(DateTime? arg = null) + { + } + + public static void OptionalNullableIntWithNonNullDefault(int? arg = 1) + { + } + + public static void OptionalNullableDoubleWithNonNullDefault(double? arg = 1) + { + } + + public static void OptionalNullableDecimalWithNonNullDefault(decimal? arg = 1) + { + } + + public static void OptionalNullableLongWithNonNullDefault(long? arg = 1) + { + } + + public static void OptionalNullableShortWithNonNullDefault(short? arg = 1) + { + } + + public static void OptionalNullableSByteWithNonNullDefault(sbyte? arg = 1) + { + } + + public static void OptionalNullableByteWithNonNullDefault(byte? arg = 1) + { + } + + public static void OptionalNullableULongWithNonNullDefault(ulong? arg = 1) + { + } + + public static void OptionalNullableUShortWithNonNullDefault(ushort? arg = 1) + { + } + + public static void OptionalNullableUIntWithNonNullDefault(uint? arg = 1) + { + } + + public static void OptionalNullableFloatWithNonNullDefault(float? arg = 1) + { + } + + public static void OptionalNullableEnumWithNonNullDefault(TestEnum? arg = TestEnum.Some) + { + } + + public static void OptionalNullableBoolWithNonNullDefault(bool? arg = true) + { + } + + public static void RequiredIntKeyword(int @new) + { + } + + public static void RequiredNullableIntKeyword(int? @new) + { + } + + public static void OptionalIntKeywordWithNonNullDefault(int @new = 1) + { + } + + public static void OptionalNullableIntKeywordWithNullDefault(int? @new = null) + { + } + + public static void OutputParameterInterface(out IDisposable arg) + { + arg = null; + } + + public static void OutputParameterInt32(out int arg) + { + arg = 0; + } + + public static void OptionalStringWithAttribute([CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + } + + public static void OptionalInt32WithAttribute([CallerLineNumber] int sourceLineNumber = 0) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithInt32CtorParameter([TestParameter(19)] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithStringCtorParameter([TestParameter("test")] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithEnumCtorParameter([TestParameter(TestEnum.All)] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithArrayCtorParameter([TestParameter(new[] { TestEnum.All, TestEnum.Some })] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithInt32NamedArgument([TestParameter(Int32Property = 19)] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithStringNamedArgument([TestParameter(StringProperty = "test")] string value) + { + } + + public static void RequiredStringWithCustomAttributeCalledWithEnumNamedArgument([TestParameter(EnumProperty = TestEnum.All)] string value) + { + } + } + + [Theory] + [InlineData("RequiredInt", "System.Int32 arg")] + [InlineData("RequiredDouble", "System.Double arg")] + [InlineData("RequiredDecimal", "System.Decimal arg")] + [InlineData("RequiredLong", "System.Int64 arg")] + [InlineData("RequiredShort", "System.Int16 arg")] + [InlineData("RequiredSByte", "System.SByte arg")] + [InlineData("RequiredByte", "System.Byte arg")] + [InlineData("RequiredULong", "System.UInt64 arg")] + [InlineData("RequiredUShort", "System.UInt16 arg")] + [InlineData("RequiredUInt", "System.UInt32 arg")] + [InlineData("RequiredFloat", "System.Single arg")] + [InlineData("RequiredEnum", "Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum arg")] + [InlineData("RequiredBool", "System.Boolean arg")] + [InlineData("RequiredString", "System.String arg")] + [InlineData("RequiredObject", "System.Object arg")] + [InlineData("RequiredDateTime", "System.DateTime arg")] + [InlineData("RequiredInterface", "System.IDisposable arg")] + [InlineData("RequiredClass", "Cake.Core.CakeContext arg")] + [InlineData("RequiredType", "System.Type type")] + [InlineData("RequiredGenericEnumerable", "System.Collections.Generic.IEnumerable items")] + [InlineData("OptionalInt", "System.Int32 arg = (System.Int32)1")] + [InlineData("OptionalDouble", "System.Double arg = (System.Double)1")] + [InlineData("OptionalDecimal", "System.Decimal arg = (System.Decimal)1")] + [InlineData("OptionalLong", "System.Int64 arg = (System.Int64)1")] + [InlineData("OptionalShort", "System.Int16 arg = (System.Int16)1")] + [InlineData("OptionalSByte", "System.SByte arg = (System.SByte)1")] + [InlineData("OptionalByte", "System.Byte arg = (System.Byte)1")] + [InlineData("OptionalULong", "System.UInt64 arg = (System.UInt64)1")] + [InlineData("OptionalUShort", "System.UInt16 arg = (System.UInt16)1")] + [InlineData("OptionalUInt", "System.UInt32 arg = (System.UInt32)1")] + [InlineData("OptionalFloat", "System.Single arg = (System.Single)1")] + [InlineData("OptionalEnum", "Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum arg = (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2")] + [InlineData("OptionalBool", "System.Boolean arg = true")] + [InlineData("OptionalString", "System.String arg = \"value\"")] + [InlineData("OptionalObject", "System.Object arg = null")] + [InlineData("OptionalInterface", "System.IDisposable arg = null")] + [InlineData("OptionalClass", "Cake.Core.CakeContext arg = null")] + [InlineData("OptionalType", "System.Type type = null")] + [InlineData("OptionalGenericEnumerable", "System.Collections.Generic.IEnumerable items = null")] + [InlineData("RequiredNullableInt", "System.Nullable arg")] + [InlineData("RequiredNullableDouble", "System.Nullable arg")] + [InlineData("RequiredNullableDecimal", "System.Nullable arg")] + [InlineData("RequiredNullableLong", "System.Nullable arg")] + [InlineData("RequiredNullableShort", "System.Nullable arg")] + [InlineData("RequiredNullableSByte", "System.Nullable arg")] + [InlineData("RequiredNullableByte", "System.Nullable arg")] + [InlineData("RequiredNullableULong", "System.Nullable arg")] + [InlineData("RequiredNullableUShort", "System.Nullable arg")] + [InlineData("RequiredNullableUInt", "System.Nullable arg")] + [InlineData("RequiredNullableFloat", "System.Nullable arg")] + [InlineData("RequiredNullableEnum", "System.Nullable arg")] + [InlineData("RequiredNullableBool", "System.Nullable arg")] + [InlineData("RequiredNullableDateTime", "System.Nullable arg")] + [InlineData("OptionalNullableIntWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableDoubleWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableDecimalWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableLongWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableShortWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableSByteWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableByteWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableULongWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableUShortWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableUIntWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableFloatWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableEnumWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableBoolWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableDateTimeWithNullDefault", "System.Nullable arg = null")] + [InlineData("OptionalNullableIntWithNonNullDefault", "System.Nullable arg = (System.Int32)1")] + [InlineData("OptionalNullableDoubleWithNonNullDefault", "System.Nullable arg = (System.Double)1")] + [InlineData("OptionalNullableDecimalWithNonNullDefault", "System.Nullable arg = (System.Decimal)1")] + [InlineData("OptionalNullableLongWithNonNullDefault", "System.Nullable arg = (System.Int64)1")] + [InlineData("OptionalNullableShortWithNonNullDefault", "System.Nullable arg = (System.Int16)1")] + [InlineData("OptionalNullableSByteWithNonNullDefault", "System.Nullable arg = (System.SByte)1")] + [InlineData("OptionalNullableByteWithNonNullDefault", "System.Nullable arg = (System.Byte)1")] + [InlineData("OptionalNullableULongWithNonNullDefault", "System.Nullable arg = (System.UInt64)1")] + [InlineData("OptionalNullableUShortWithNonNullDefault", "System.Nullable arg = (System.UInt16)1")] + [InlineData("OptionalNullableUIntWithNonNullDefault", "System.Nullable arg = (System.UInt32)1")] + [InlineData("OptionalNullableFloatWithNonNullDefault", "System.Nullable arg = (System.Single)1")] + [InlineData("OptionalNullableEnumWithNonNullDefault", "System.Nullable arg = (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)1")] + [InlineData("OptionalNullableBoolWithNonNullDefault", "System.Nullable arg = (System.Boolean)true")] + [InlineData("RequiredIntKeyword", "System.Int32 @new")] + [InlineData("RequiredNullableIntKeyword", "System.Nullable @new")] + [InlineData("OptionalIntKeywordWithNonNullDefault", "System.Int32 @new = (System.Int32)1")] + [InlineData("OptionalNullableIntKeywordWithNullDefault", "System.Nullable @new = null")] + [InlineData("OutputParameterInterface", "out System.IDisposable arg")] + [InlineData("OutputParameterInt32", "out System.Int32 arg")] + [InlineData("OptionalStringWithAttribute", "[System.Runtime.CompilerServices.CallerMemberName] System.String memberName = \"\"")] + [InlineData("OptionalInt32WithAttribute", "[System.Runtime.CompilerServices.CallerLineNumber] System.Int32 sourceLineNumber = (System.Int32)0")] + [InlineData("RequiredStringWithCustomAttributeCalledWithInt32CtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter((System.Int32)19)] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithStringCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(\"test\")] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithEnumCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter((Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2)] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithArrayCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(new Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum[2] { (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2, (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)1 })] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithInt32NamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(Int32Property = (System.Int32)19)] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithStringNamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(StringProperty = \"test\")] System.String value")] + [InlineData("RequiredStringWithCustomAttributeCalledWithEnumNamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(EnumProperty = (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2)] System.String value")] + public void Should_Return_Correct_Generated_Code_For_Method_Parameters(string methodName, string expected) + { + // Given + var method = typeof(ParameterFixture).GetMethod(methodName, BindingFlags.Static | BindingFlags.Public); + var parameter = method.GetParameters().FirstOrDefault(); + + // When + var result = ParameterEmitter.Emit(parameter, true); + + // Then + Assert.Equal(expected, result); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterFormatterTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterFormatterTests.cs new file mode 100644 index 0000000000..35e1b7ad3f --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/ParameterFormatterTests.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Cake.Core.Scripting.CodeGen; +using Xunit; + +namespace Cake.Core.Tests.Unit.Scripting.CodeGen +{ + public sealed class ParameterFormatterTests + { + private readonly ParameterFormatter _parameterFormatter = new ParameterFormatter(); + + [Fact] + public void Should_Throw_If_Parameter_Info_Is_Null() + { + // When + var result = Record.Exception(() => _parameterFormatter.FormatName((ParameterInfo)null)); + + // Then + AssertEx.IsArgumentNullException(result, "parameterInfo"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("\t")] + [InlineData(" ")] + public void Should_Throw_If_Parameter_Name_Is_Null_Or_Whitespace(string arg) + { + // When + var result = Record.Exception(() => _parameterFormatter.FormatName(arg)); + + // Then + AssertEx.IsArgumentException(result, "parameterName", "Parameter name cannot be null or whitespace"); + } + + [Theory] + [InlineData("abstract", "@abstract")] + [InlineData("as", "@as")] + [InlineData("base", "@base")] + [InlineData("bool", "@bool")] + [InlineData("break", "@break")] + [InlineData("byte", "@byte")] + [InlineData("case", "@case")] + [InlineData("catch", "@catch")] + [InlineData("char", "@char")] + [InlineData("checked", "@checked")] + [InlineData("class", "@class")] + [InlineData("const", "@const")] + [InlineData("continue", "@continue")] + [InlineData("decimal", "@decimal")] + [InlineData("default", "@default")] + [InlineData("delegate", "@delegate")] + [InlineData("do", "@do")] + [InlineData("double", "@double")] + [InlineData("else", "@else")] + [InlineData("enum", "@enum")] + [InlineData("event", "@event")] + [InlineData("explicit", "@explicit")] + [InlineData("extern", "@extern")] + [InlineData("false", "@false")] + [InlineData("finally", "@finally")] + [InlineData("fixed", "@fixed")] + [InlineData("float", "@float")] + [InlineData("for", "@for")] + [InlineData("foreach", "@foreach")] + [InlineData("goto", "@goto")] + [InlineData("if", "@if")] + [InlineData("implicit", "@implicit")] + [InlineData("in", "@in")] + [InlineData("int", "@int")] + [InlineData("interface", "@interface")] + [InlineData("internal", "@internal")] + [InlineData("is", "@is")] + [InlineData("lock", "@lock")] + [InlineData("long", "@long")] + [InlineData("namespace", "@namespace")] + [InlineData("new", "@new")] + [InlineData("null", "@null")] + [InlineData("object", "@object")] + [InlineData("operator", "@operator")] + [InlineData("out", "@out")] + [InlineData("override", "@override")] + [InlineData("params", "@params")] + [InlineData("private", "@private")] + [InlineData("protected", "@protected")] + [InlineData("public", "@public")] + [InlineData("readonly", "@readonly")] + [InlineData("ref", "@ref")] + [InlineData("return", "@return")] + [InlineData("sbyte", "@sbyte")] + [InlineData("sealed", "@sealed")] + [InlineData("short", "@short")] + [InlineData("sizeof", "@sizeof")] + [InlineData("stackalloc", "@stackalloc")] + [InlineData("static", "@static")] + [InlineData("string", "@string")] + [InlineData("struct", "@struct")] + [InlineData("switch", "@switch")] + [InlineData("this", "@this")] + [InlineData("throw", "@throw")] + [InlineData("true", "@true")] + [InlineData("try", "@try")] + [InlineData("typeof", "@typeof")] + [InlineData("uint", "@uint")] + [InlineData("ulong", "@ulong")] + [InlineData("unchecked", "@unchecked")] + [InlineData("unsafe", "@unsafe")] + [InlineData("ushort", "@ushort")] + [InlineData("using", "@using")] + [InlineData("virtual", "@virtual")] + [InlineData("void", "@void")] + [InlineData("volatile", "@volatile")] + [InlineData("while", "@while")] + public void Should_Format_Reserved_Keywords_Correctly(string parameterName, string expectedParameterName) + { + // When + var result = _parameterFormatter.FormatName(parameterName); + + // Then + Assert.Equal(expectedParameterName, result); + } + + [Theory] + [InlineData("testParameter", "testParameter")] + public void Should_Format_Variable_Names_Correctly(string parameterName, string expectedParameterName) + { + // When + var result = _parameterFormatter.FormatName(parameterName); + + // Then + Assert.Equal(expectedParameterName, result); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/PropertyAliasGeneratorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/PropertyAliasGeneratorTests.cs index 3137d22b5b..88ab1435f4 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/CodeGen/PropertyAliasGeneratorTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/CodeGen/PropertyAliasGeneratorTests.cs @@ -1,9 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Threading.Tasks; using Cake.Core.Scripting.CodeGen; using Cake.Core.Tests.Fixtures; +using VerifyXunit; using Xunit; +using static Cake.Core.Tests.VerifyConfig; namespace Cake.Core.Tests.Unit.Scripting.CodeGen { @@ -25,21 +30,21 @@ public void Should_Throw_If_Method_Is_Null() var result = Record.Exception(() => PropertyAliasGenerator.Generate(null)); // Then - Assert.IsArgumentNullException(result, "method"); + AssertEx.IsArgumentNullException(result, "method"); } [Fact] public void Should_Throw_If_Declaring_Type_Is_Not_Static() { // Given - var method = GetType().GetMethod("Should_Throw_If_Declaring_Type_Is_Not_Static"); + var method = GetType().GetTypeInfo().GetMethod("Should_Throw_If_Declaring_Type_Is_Not_Static"); // When var result = Record.Exception(() => PropertyAliasGenerator.Generate(method)); // Then Assert.IsType(result); - Assert.Equal("The type 'Cake.Core.Tests.Unit.Scripting.CodeGen.PropertyAliasGeneratorTests+TheGenerateMethod' is not static.", result.Message); + Assert.Equal("The type 'Cake.Core.Tests.Unit.Scripting.CodeGen.PropertyAliasGeneratorTests+TheGenerateMethod' is not static.", result?.Message); } [Fact] @@ -50,7 +55,7 @@ public void Should_Throw_If_Method_Is_Not_An_Extension_Method() // Then Assert.IsType(result); - Assert.Equal("The method 'NotAnExtensionMethod' is not an extension method.", result.Message); + Assert.Equal("The method 'NotAnExtensionMethod' is not an extension method.", result?.Message); } [Fact] @@ -61,7 +66,7 @@ public void Should_Throw_If_Method_Is_Not_An_Cake_Property_Alias() // Then Assert.IsType(result); - Assert.Equal("The method 'NotAScriptMethod' is not a property alias.", result.Message); + Assert.Equal("The method 'NotAScriptMethod' is not a property alias.", result?.Message); } [Fact] @@ -72,7 +77,7 @@ public void Should_Throw_If_Property_Alias_Have_More_Than_One_Argument() // Then Assert.IsType(result); - Assert.Equal("The property alias 'PropertyAliasWithMoreThanOneMethod' has an invalid signature.", result.Message); + Assert.Equal("The property alias 'PropertyAliasWithMoreThanOneMethod' has an invalid signature.", result?.Message); } [Fact] @@ -83,7 +88,7 @@ public void Should_Throw_If_Property_Alias_Do_Not_Have_A_Cake_Context_As_First_P // Then Assert.IsType(result); - Assert.Equal("The property alias 'PropertyAliasWithoutContext' has an invalid signature.", result.Message); + Assert.Equal("The property alias 'PropertyAliasWithoutContext' has an invalid signature.", result?.Message); } [Fact] @@ -94,7 +99,7 @@ public void Should_Throw_If_Method_Is_Generic() // Then Assert.IsType(result); - Assert.Equal("The property alias 'GenericScriptMethod' cannot be generic.", result.Message); + Assert.Equal("The property alias 'GenericScriptMethod' cannot be generic.", result?.Message); } [Fact] @@ -105,36 +110,35 @@ public void Should_Throw_If_Property_Alias_Returns_Void() // Then Assert.IsType(result); - Assert.Equal("The property alias 'PropertyAliasReturningVoid' cannot return void.", result.Message); + Assert.Equal("The property alias 'PropertyAliasReturningVoid' cannot return void.", result?.Message); } [Theory] [InlineData("NonCached_Value_Type")] - public void Should_Return_Correct_Generated_Code_For_Non_Cached_Properties(string name) + [InlineData("NonCached_Dynamic_Type")] + public Task Should_Return_Correct_Generated_Code_For_Non_Cached_Properties(string name) { - // Given - var expected = _fixture.GetExpectedData(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } [Theory] [InlineData("Cached_Reference_Type")] [InlineData("Cached_Value_Type")] - public void Should_Return_Correct_Generated_Code_For_Cached_Properties(string name) + [InlineData("Cached_Dynamic_Type")] + [InlineData("Cached_Nullable_Type")] + public Task Should_Return_Correct_Generated_Code_For_Cached_Properties(string name) { - // Given - var expected = _fixture.GetExpectedData(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } [Theory] @@ -142,16 +146,14 @@ public void Should_Return_Correct_Generated_Code_For_Cached_Properties(string na [InlineData("NonCached_Obsolete_ImplicitWarning_WithMessage")] [InlineData("NonCached_Obsolete_ExplicitWarning_WithMessage")] [InlineData("NonCached_Obsolete_ExplicitError_WithMessage")] - public void Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties(string name) + public Task Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Properties(string name) { - // Given - var expected = _fixture.GetExpectedData(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } [Theory] @@ -159,17 +161,15 @@ public void Should_Return_Correct_Generated_Code_For_Non_Cached_Obsolete_Propert [InlineData("Cached_Obsolete_ImplicitWarning_WithMessage")] [InlineData("Cached_Obsolete_ExplicitWarning_WithMessage")] [InlineData("Cached_Obsolete_ExplicitError_WithMessage")] - public void Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties(string name) + public Task Should_Return_Correct_Generated_Code_For_Cached_Obsolete_Properties(string name) { - // Given - var expected = _fixture.GetExpectedData(name); - - // When + // Given / When var result = _fixture.Generate(name); // Then - Assert.Equal(expected, result); + return VerifyCake(result) + .UseParameters(name); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs new file mode 100644 index 0000000000..6ec85b8662 --- /dev/null +++ b/src/Cake.Core.Tests/Unit/Scripting/Processors/LoadDirectiveProcessorTests.cs @@ -0,0 +1,296 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Cake.Core.Tests.Fixtures; +using Cake.Testing.Xunit; +using Xunit; + +namespace Cake.Core.Tests.Unit.Scripting.Processors +{ + public sealed class LoadDirectiveProcessorTests + { + [Theory] + [InlineData("#l \"utils.cake\"")] + [InlineData("#load \"utils.cake\"")] + public void Should_Process_Single_Script_Reference_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/Working/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("#l \"test/my utils.cake\"")] + [InlineData("#load \"test/my utils.cake\"")] + public void Should_Process_Single_Script_Reference_With_Spaces_In_File_Name_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/test/my utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/Working/test/my utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("utils.cs")] + [InlineData("utils.foo")] + public void Should_Process_Single_Script_Reference_With_Any_Extension_Found_In_Source(string filename) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", $"#l \"{filename}\""); + fixture.GivenScriptExist($"/Working/{filename}", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal($"/Working/{filename}", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("#l \"utils.cake\"\n#l \"other.cake\"")] + [InlineData("#load \"utils.cake\"\n#load \"other.cake\"")] + public void Should_Process_Multiple_Script_References_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/utils.cake", "Console.WriteLine();"); + fixture.GivenScriptExist("/Working/other.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Equal(2, result.Script.Includes.Count); + Assert.Equal("/Working/utils.cake", result.Script.Includes[0].Path.FullPath); + Assert.Equal("/Working/other.cake", result.Script.Includes[1].Path.FullPath); + } + + [Theory] + [InlineData("#l \"%CAKE_TEST_SCRIPT_PATH%/utils.cake\"")] + public void Should_Process_Environment_Variable_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.Environment.SetEnvironmentVariable("CAKE_TEST_SCRIPT_PATH", "test"); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/test/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("#l \"%CAKE_TEST_SCRIPT_BASE_PATH%/%CAKE_TEST_SCRIPT_PATH%/utils.cake\"")] + public void Should_Process_Multiple_Environment_Variable_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.Environment.SetEnvironmentVariable("CAKE_TEST_SCRIPT_BASE_PATH", "test"); + fixture.Environment.SetEnvironmentVariable("CAKE_TEST_SCRIPT_PATH", "scripts"); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/test/scripts/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/Working/test/scripts/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("#load \"scripts/*\"")] + [InlineData("#load \"scripts/*.*\"")] + [InlineData("#load \"scripts/**/*\"")] + [InlineData("#load \"scripts/**/*.*\"")] + [InlineData("#load \"scripts/{utils,other}.{cs,kake}\"")] + [InlineData("#load \"/Working/scripts/*\"")] + [InlineData("#load \"/Working/scripts/*.*\"")] + [InlineData("#load \"/Working/scripts/**/*\"")] + [InlineData("#load \"/Working/scripts/**/*.*\"")] + [InlineData("#load \"/Working/scripts/{utils,other}.{cs,kake}\"")] + public void Should_Ignore_Globber_Matches_With_Invalid_Extensions(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/bootstrap.cake", source); + fixture.GivenScriptExist("/Working/scripts/utils.cs", "Console.WriteLine();"); + fixture.GivenScriptExist("/Working/scripts/other.kake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/bootstrap.cake"); + + // Then + Assert.Empty(result.Script.Includes); + } + + [Theory] + [InlineData("#load \"scripts/*\"")] + [InlineData("#load \"scripts/*.*\"")] + [InlineData("#load \"scripts/*.cake\"")] + [InlineData("#load \"scripts/**/*\"")] + [InlineData("#load \"scripts/**/*.*\"")] + [InlineData("#load \"scripts/**/*.cake\"")] + [InlineData("#load \"scripts/{utils,other}.cake\"")] + [InlineData("#load \"/Working/scripts/*\"")] + [InlineData("#load \"/Working/scripts/*.*\"")] + [InlineData("#load \"/Working/scripts/*.cake\"")] + [InlineData("#load \"/Working/scripts/**/*\"")] + [InlineData("#load \"/Working/scripts/**/*.*\"")] + [InlineData("#load \"/Working/scripts/**/*.cake\"")] + [InlineData("#load \"/Working/scripts/{utils,other}.cake\"")] + public void Should_Process_Globber_Matches_With_Valid_Extension(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/bootstrap.cake", source); + fixture.GivenScriptExist("/Working/scripts/utils.cake", "Console.WriteLine();"); + fixture.GivenScriptExist("/Working/scripts/other.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/bootstrap.cake"); + + // Then + Assert.Equal(2, result.Script.Includes.Count); + Assert.Equal("/Working/scripts/utils.cake", result.Script.Includes[0].Path.FullPath); + Assert.Equal("/Working/scripts/other.cake", result.Script.Includes[1].Path.FullPath); + } + + [Fact] + public void Should_Insert_Line_Directives_When_Processing_Load_Directives() + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/a.cake", "int x=0;\n#l b.cake\nint y=2;"); + fixture.GivenScriptExist("/Working/b.cake", "int z=1;\n#l c.cake\nint p=4;"); + fixture.GivenScriptExist("/Working/c.cake", "int o=3;\n#r d.dll"); + + // When + var result = fixture.Analyze("/Working/a.cake"); + + // Then + Assert.Equal(13, result.Lines.Count); + Assert.Equal(result.Lines[0], "#line 1 \"/Working/a.cake\""); + Assert.Equal(result.Lines[1], "int x=0;"); + Assert.Equal(result.Lines[2], "#line 1 \"/Working/b.cake\""); + Assert.Equal(result.Lines[3], "int z=1;"); + Assert.Equal(result.Lines[4], "#line 1 \"/Working/c.cake\""); + Assert.Equal(result.Lines[5], "int o=3;"); + Assert.Equal(result.Lines[6], "// #r d.dll"); + Assert.Equal(result.Lines[7], "#line 2 \"/Working/b.cake\""); + Assert.Equal(result.Lines[8], "// #l c.cake"); + Assert.Equal(result.Lines[9], "int p=4;"); + Assert.Equal(result.Lines[10], "#line 2 \"/Working/a.cake\""); + Assert.Equal(result.Lines[11], "// #l b.cake"); + Assert.Equal(result.Lines[12], "int y=2;"); + } + + [Theory] + [InlineData("#load \"/utils.cake\"")] + [InlineData("#load \"local:?path=/utils.cake\"")] + public void Should_Process_AbsolutePath_Script_Reference_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [Theory] + [InlineData("#load \"test/utils.cake\"")] + [InlineData("#load \"local:?path=test/utils.cake\"")] + public void Should_Process_RelativePath_Script_Reference_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("/Working/script.cake", source); + fixture.GivenScriptExist("/Working/test/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [WindowsTheory] + [InlineData("#load \"C:/utils.cake\"")] + [InlineData("#load \"local:?path=C:/utils.cake\"")] + public void Should_Process_WindowsAbsolutePath_Script_Reference_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(windows: true); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("C:/Working/script.cake", source); + fixture.GivenScriptExist("C:/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("C:/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("C:/utils.cake", result.Script.Includes[0].Path.FullPath); + } + + [WindowsTheory] + [InlineData("#load \"test/utils.cake\"")] + [InlineData("#load \"local:?path=test/utils.cake\"")] + public void Should_Process_WindowsRelativePath_Script_Reference_Found_In_Source(string source) + { + // Given + var fixture = new ScriptAnalyzerFixture(windows: true); + fixture.AddFileLoadDirectiveProvider(); + fixture.GivenScriptExist("C:/Working/script.cake", source); + fixture.GivenScriptExist("C:/Working/test/utils.cake", "Console.WriteLine();"); + + // When + var result = fixture.Analyze("C:/Working/script.cake"); + + // Then + Assert.Single(result.Script.Includes); + Assert.Equal("C:/Working/test/utils.cake", result.Script.Includes[0].Path.FullPath); + } + } +} diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs index 453ec12737..c531b3b238 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptAliasTests.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System.Collections.Generic; using System.Linq; +using System.Reflection; using Cake.Core.Scripting; using Xunit; @@ -19,21 +21,21 @@ public void Should_Throw_If_Method_Is_Null() var result = Record.Exception(() => new ScriptAlias(null, ScriptAliasType.Method, new HashSet())); // Then - Assert.IsArgumentNullException(result, "method"); + AssertEx.IsArgumentNullException(result, "method"); } [Fact] public void Should_Not_Throw_If_Method_Is_Null() { // Given - var method = typeof(TheConstructor).GetMethods().First(); + var method = typeof(TheConstructor).GetTypeInfo().GetMethods().First(); // When var result = new ScriptAlias(method, ScriptAliasType.Method, null); // Then - Assert.Equal(0, result.Namespaces.Count); + Assert.Empty(result.Namespaces); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptHostTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptHostTests.cs index 2668d0fcca..4c84116acc 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptHostTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptHostTests.cs @@ -22,7 +22,7 @@ public void Should_Throw_If_Engine_Is_Null() var result = Record.Exception(() => new ScriptHostFixture.TestingScriptHost(null, context)); // Then - Assert.IsArgumentNullException(result, "engine"); + AssertEx.IsArgumentNullException(result, "engine"); } } @@ -68,7 +68,7 @@ public void Should_Proxy_Call_To_Engine() // Given var fixture = new ScriptHostFixture(); var host = fixture.CreateHost(); - Action action = (context, setupContext) => { }; + Action action = context => { }; // When host.TaskSetup(action); @@ -86,7 +86,7 @@ public void Should_Proxy_Call_To_Engine() // Given var fixture = new ScriptHostFixture(); var host = fixture.CreateHost(); - Action action = (context, setupContext) => { }; + Action action = context => { }; // When host.TaskTeardown(action); diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptProcessorTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptProcessorTests.cs index f04ba91cec..b668de82fd 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptProcessorTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptProcessorTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using Cake.Core.IO; using Cake.Core.Packaging; using Cake.Core.Tests.Fixtures; @@ -24,7 +25,7 @@ public void Should_Throw_If_File_System_Is_Null() var result = Record.Exception(() => fixture.CreateProcessor()); // Then - Assert.IsArgumentNullException(result, "fileSystem"); + AssertEx.IsArgumentNullException(result, "fileSystem"); } [Fact] @@ -38,7 +39,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.CreateProcessor()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -52,24 +53,24 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => fixture.CreateProcessor()); // Then - Assert.IsArgumentNullException(result, "log"); + AssertEx.IsArgumentNullException(result, "log"); } } public sealed class TheInstallAddinsMethod { [Fact] - public void Should_Throw_If_Analyzer_Result_Is_Null() + public void Should_Throw_If_Addins_Is_Null() { // Given var fixture = new ScriptProcessorFixture(); - fixture.Result = null; + fixture.Addins = null; // When var result = Record.Exception(() => fixture.InstallAddins()); // Then - Assert.IsArgumentNullException(result, "analyzerResult"); + AssertEx.IsArgumentNullException(result, "addins"); } [Fact] @@ -83,7 +84,7 @@ public void Should_Throw_If_Install_Path_Is_Null() var result = Record.Exception(() => fixture.InstallAddins()); // Then - Assert.IsArgumentNullException(result, "installPath"); + AssertEx.IsArgumentNullException(result, "installPath"); } [Fact] @@ -96,7 +97,7 @@ public void Should_Throw_If_Addins_Could_Not_Be_Found() var result = Record.Exception(() => fixture.InstallAddins()); // Then - Assert.IsCakeException(result, "Failed to install addin 'addin'."); + AssertEx.IsCakeException(result, "Failed to install addin 'addin'."); } [Fact] @@ -110,7 +111,7 @@ public void Should_Throw_If_Installer_Could_Not_Be_Resolved() var result = Record.Exception(() => fixture.InstallAddins()); // Then - Assert.IsCakeException(result, "Could not find an installer for the 'custom' scheme."); + AssertEx.IsCakeException(result, "Could not find an installer for the 'custom' scheme."); } [Fact] @@ -134,17 +135,17 @@ public void Should_Install_Addins_Referenced_By_Scripts() public sealed class TheInstallToolsMethod { [Fact] - public void Should_Throw_If_Analyzer_Result_Is_Null() + public void Should_Throw_If_Tools_Is_Null() { // Given var fixture = new ScriptProcessorFixture(); - fixture.Result = null; + fixture.Tools = null; // When var result = Record.Exception(() => fixture.InstallTools()); // Then - Assert.IsArgumentNullException(result, "analyzerResult"); + AssertEx.IsArgumentNullException(result, "tools"); } [Fact] @@ -158,7 +159,7 @@ public void Should_Throw_If_Install_Path_Is_Null() var result = Record.Exception(() => fixture.InstallTools()); // Then - Assert.IsArgumentNullException(result, "installPath"); + AssertEx.IsArgumentNullException(result, "installPath"); } [Fact] @@ -171,7 +172,7 @@ public void Should_Throw_If_Tools_Could_Not_Be_Found() var result = Record.Exception(() => fixture.InstallTools()); // Then - Assert.IsCakeException(result, "Failed to install tool 'tool'."); + AssertEx.IsCakeException(result, "Failed to install tool 'tool'."); } [Fact] @@ -185,7 +186,7 @@ public void Should_Throw_If_Installer_Could_Not_Be_Resolved() var result = Record.Exception(() => fixture.InstallTools()); // Then - Assert.IsCakeException(result, "Could not find an installer for the 'custom' scheme."); + AssertEx.IsCakeException(result, "Could not find an installer for the 'custom' scheme."); } [Fact] @@ -216,9 +217,9 @@ public void Should_Register_Installed_Tools_With_The_Tool_Service() fixture.InstallTools(); // Then - fixture.Tools.Received(1).RegisterFile( + fixture.ToolLocator.Received(1).RegisterFile( Arg.Is(path => path.FullPath == "/Working/Bin/Temp.dll")); } } } -} +} \ No newline at end of file diff --git a/src/Cake.Core.Tests/Unit/Scripting/ScriptRunnerTests.cs b/src/Cake.Core.Tests/Unit/Scripting/ScriptRunnerTests.cs index 76c85c2b00..6918745332 100644 --- a/src/Cake.Core.Tests/Unit/Scripting/ScriptRunnerTests.cs +++ b/src/Cake.Core.Tests/Unit/Scripting/ScriptRunnerTests.cs @@ -1,15 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Cake.Core.IO; +using Cake.Core.Packaging; using Cake.Core.Scripting; using Cake.Core.Scripting.Analysis; using Cake.Core.Tests.Fixtures; +using Cake.Testing; using NSubstitute; using Xunit; +using LogLevel = Cake.Core.Diagnostics.LogLevel; namespace Cake.Core.Tests.Unit.Scripting { @@ -28,7 +33,7 @@ public void Should_Throw_If_Environment_Is_Null() var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "environment"); + AssertEx.IsArgumentNullException(result, "environment"); } [Fact] @@ -42,7 +47,7 @@ public void Should_Throw_If_Script_Engine_Is_Null() var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "engine"); + AssertEx.IsArgumentNullException(result, "engine"); } [Fact] @@ -56,7 +61,7 @@ public void Should_Throw_If_Script_Alias_Finder_Is_Null() var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "aliasFinder"); + AssertEx.IsArgumentNullException(result, "aliasFinder"); } [Fact] @@ -70,7 +75,7 @@ public void Should_Throw_If_Script_Analyzer_Is_Null() var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "analyzer"); + AssertEx.IsArgumentNullException(result, "analyzer"); } [Fact] @@ -84,52 +89,52 @@ public void Should_Throw_If_Script_Conventions_Is_Null() var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "conventions"); + AssertEx.IsArgumentNullException(result, "conventions"); } - } - public sealed class TheRunMethod - { [Fact] - public void Should_Throw_If_Script_Host_Is_Null() + public void Should_Throw_If_AssemblyLoader_Is_Null() { // Given var fixture = new ScriptRunnerFixture(); - var runner = fixture.CreateScriptRunner(); + fixture.AssemblyLoader = null; // When - var result = Record.Exception(() => runner.Run(null, fixture.Script, fixture.ArgumentDictionary)); + var result = Record.Exception(() => fixture.CreateScriptRunner()); // Then - Assert.IsArgumentNullException(result, "host"); + AssertEx.IsArgumentNullException(result, "assemblyLoader"); } + } + public sealed class TheRunMethod + { [Fact] - public void Should_Throw_If_Script_Is_Null() + public void Should_Throw_If_Script_Host_Is_Null() { // Given var fixture = new ScriptRunnerFixture(); var runner = fixture.CreateScriptRunner(); // When - var result = Record.Exception(() => runner.Run(fixture.Host, null, fixture.ArgumentDictionary)); + var result = Record.Exception(() => runner.Run(null, fixture.Script)); // Then - Assert.IsArgumentNullException(result, "scriptPath"); + AssertEx.IsArgumentNullException(result, "host"); } [Fact] - public void Should_Throw_If_Arguments_Are_Null() + public void Should_Throw_If_Script_Is_Null() { // Given var fixture = new ScriptRunnerFixture(); var runner = fixture.CreateScriptRunner(); // When - var result = Record.Exception(() => runner.Run(fixture.Host, fixture.Script, null)); + var result = Record.Exception(() => runner.Run(fixture.Host, null)); // Then - Assert.IsArgumentNullException(result, "arguments"); + AssertEx.IsArgumentNullException(result, "scriptPath"); } [Fact] @@ -140,11 +145,10 @@ public void Should_Create_Session_Via_Session_Factory() var runner = fixture.CreateScriptRunner(); // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); + runner.Run(fixture.Host, fixture.Script); // Then - fixture.Engine.Received(1) - .CreateSession(fixture.Host, fixture.ArgumentDictionary); + fixture.Engine.Received(1).CreateSession(fixture.Host); } [Fact] @@ -155,33 +159,12 @@ public void Should_Set_Working_Directory_To_Script_Directory() var runner = fixture.CreateScriptRunner(); // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); + runner.Run(fixture.Host, fixture.Script); // Then Assert.Equal("/build", fixture.Environment.WorkingDirectory.FullPath); } - [Theory] - [InlineData("mscorlib")] - [InlineData("System")] - [InlineData("System.Core")] - [InlineData("System.Data")] - [InlineData("System.Xml")] - [InlineData("System.Xml.Linq")] - public void Should_Add_References_To_Session(string @assemblyName) - { - // Given - var fixture = new ScriptRunnerFixture(); - var runner = fixture.CreateScriptRunner(); - - // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); - - // Then - fixture.Session.Received(1).AddReference( - Arg.Is(a => a.FullName.StartsWith(assemblyName + ", ", StringComparison.OrdinalIgnoreCase))); - } - [Theory] [InlineData("System")] [InlineData("System.Collections.Generic")] @@ -200,7 +183,7 @@ public void Should_Add_Namespaces_To_Session(string @namespace) var runner = fixture.CreateScriptRunner(); // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); + runner.Run(fixture.Host, fixture.Script); // Then fixture.Session.Received(1).ImportNamespace(@namespace); @@ -214,7 +197,7 @@ public void Should_Generate_Script_Aliases() var runner = fixture.CreateScriptRunner(); // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); + runner.Run(fixture.Host, fixture.Script); // Then fixture.AliasFinder.Received(1).FindAliases( @@ -229,7 +212,7 @@ public void Should_Execute_Script_Code() var runner = fixture.CreateScriptRunner(); // When - runner.Run(fixture.Host, fixture.Script, fixture.ArgumentDictionary); + runner.Run(fixture.Host, fixture.Script); // Then fixture.Session.Received(1).Execute(Arg.Any