diff --git a/.azuredevops/dependabot.yml b/.azuredevops/dependabot.yml deleted file mode 100644 index f18e60565a4..00000000000 --- a/.azuredevops/dependabot.yml +++ /dev/null @@ -1,5 +0,0 @@ -version: 2 - -# Disabling dependabot on Azure DevOps as this is a mirrored repo. Updates should go through github. -enable-campaigned-updates: false -enable-security-updates: false diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index f132985f48d..235449d7f60 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "microsoft.dnceng.secretmanager": { - "version": "1.1.0-beta.26176.1", + "version": "1.1.0-beta.25421.1", "commands": [ "secret-manager" ] diff --git a/.gitattributes b/.gitattributes index bd40fc2cc51..7045cae3510 100644 --- a/.gitattributes +++ b/.gitattributes @@ -69,9 +69,3 @@ compat text eol=lf # not treat as generated ############################################################################### **/build/** linguist-generated=false - -############################################################################### -# Set file behavior to: -# treat as JSONC (JSON with Comments) for GitHub syntax highlighting -############################################################################### -*.json linguist-language=JSONC diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 524c5c84490..72b06568374 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -104,7 +104,7 @@ timeout 6000 ./build.sh --restore --build --configuration Release --test 4. Validate packaging via: `./build.sh --pack --configuration Release` ### Helix Test Development -1. Create test projects targeting `$(BundledNETCoreAppTargetFramework)` framework +1. Create test projects targeting `$(NetToolCurrent)` framework 2. Configure in `tests/UnitTests.proj` for Helix execution 3. Use XUnit with `Microsoft.DotNet.XUnitExtensions` for enhanced capabilities 4. Test timeout default: 300 seconds (override with XUnitWorkitemTimeout) diff --git a/.github/workflows/backport-base.yml b/.github/workflows/backport-base.yml index 4a18fd0dfdf..22b1aaa1609 100644 --- a/.github/workflows/backport-base.yml +++ b/.github/workflows/backport-base.yml @@ -14,29 +14,11 @@ on: Backport of #%source_pr_number% to %target_branch% /cc %cc_users% - pr_labels: - description: 'A comma-separated list of labels to add when the backport pull request is created.' - required: false - type: string - default: '' repository_owners: description: 'A comma-separated list of repository owners where the workflow will run. Defaults to "dotnet,microsoft".' required: false type: string default: 'dotnet,microsoft' - additional_git_am_switches: - description: 'Additional switches to pass to git am command (e.g., "--exclude=docs/release-notes/* --whitespace=fix"). Useful for excluding files that differ between branches or fixing whitespace conflicts.' - required: false - type: string - default: '' - conflict_resolution_command: - description: >- - Optional shell command to attempt automatic conflict resolution after a failed git am. - If the command eliminates merge conflicts, the backport proceeds automatically. - Do not run git commands from this parameter as they might have unintended side effects. - DO NOT PASS UNTRUSTED INPUT TO THIS PARAMETER. - required: false - type: string jobs: cleanup: @@ -82,8 +64,7 @@ jobs: with: script: | const target_branch = '${{ steps.target-branch-extractor.outputs.result }}'; - const workflow_run_url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - const backport_start_body = `Started backporting to \`${target_branch}\` ([link to workflow run](${workflow_run_url}))`; + const backport_start_body = `Started backporting to _${target_branch}_: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, @@ -97,12 +78,8 @@ jobs: - name: Run backport uses: actions/github-script@v7 env: - GH_TOKEN: ${{ github.token }} BACKPORT_PR_TITLE_TEMPLATE: ${{ inputs.pr_title_template }} BACKPORT_PR_DESCRIPTION_TEMPLATE: ${{ inputs.pr_description_template }} - BACKPORT_PR_LABELS: ${{ inputs.pr_labels }} - ADDITIONAL_GIT_AM_SWITCHES: ${{ inputs.additional_git_am_switches }} - CONFLICT_RESOLUTION_COMMAND: ${{ inputs.conflict_resolution_command }} with: script: | const target_branch = '${{ steps.target-branch-extractor.outputs.result }}'; @@ -110,40 +87,19 @@ jobs: const repo_name = context.payload.repository.name; const pr_number = context.payload.issue.number; const comment_user = context.payload.comment.user.login; - const workflow_run_url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - - const wrap_in_code_block = (language, content) => `\`\`\`${language}\n${content}\n\`\`\``; - const wrap_in_details_block = (summary, content) => `
\n${summary}\n\n${content}\n
`; - - // Post a comment on the PR and return the comment URL - async function postComment(body) { - const { data: comment } = await github.rest.issues.createComment({ - owner: repo_owner, - repo: repo_name, - issue_number: pr_number, - body - }); - return comment.html_url; - } try { - // verify the comment user has write access to the repo + // verify the comment user is a repo collaborator try { - const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ + await github.rest.repos.checkCollaborator({ owner: repo_owner, repo: repo_name, username: comment_user }); - - const writePermissions = ['admin', 'write']; - if (!writePermissions.includes(permission.permission)) { - throw new Error(`Insufficient permissions: ${permission.permission}`); - } - - console.log(`Verified ${comment_user} has ${permission.permission} access to the repo.`); + console.log(`Verified ${comment_user} is a repo collaborator.`); } catch (error) { console.log(error); - throw new Error(`Error: @${comment_user} does not have write access to this repo, backporting is not allowed. Required permissions: write or admin.`); + throw new Error(`Error: @${comment_user} is not a repo collaborator, backporting is not allowed. If you're a collaborator please make sure your ${repo_owner} team membership visibility is set to Public on https://github.com/orgs/${repo_owner}/people?query=${comment_user}`); } try { await exec.exec(`git ls-remote --exit-code --heads origin ${target_branch}`) } catch { throw new Error(`Error: The specified backport target branch "${target_branch}" wasn't found in the repo.`); } @@ -171,14 +127,9 @@ jobs: } catch { } // download and apply patch - const patch_file = 'changes.patch'; - await exec.exec(`bash -c "gh pr diff --patch ${pr_number} > ${patch_file}"`); + await exec.exec(`curl -sSL "${context.payload.issue.pull_request.patch_url}" --output changes.patch`); - const additional_switches = process.env.ADDITIONAL_GIT_AM_SWITCHES?.trim() || ''; - const base_switches = '--3way --empty=keep --ignore-whitespace --keep-non-patch'; - const git_am_command = additional_switches - ? `git am ${base_switches} ${additional_switches} ${patch_file}` - : `git am ${base_switches} ${patch_file}`; + const git_am_command = "git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch"; let git_am_output = `$ ${git_am_command}\n\n`; let git_am_failed = false; try { @@ -194,51 +145,20 @@ jobs: } if (git_am_failed) { - const resolution_command = process.env.CONFLICT_RESOLUTION_COMMAND || ''; - - // If no resolution command supplied, fail immediately - if (resolution_command.trim().length === 0) { - const details = `${wrap_in_code_block('console', git_am_output)}\n[Link to workflow output](${workflow_run_url})`; - const git_am_failed_body = `@${comment_user} backporting to \`${target_branch}\` failed, the patch most likely resulted in conflicts. Please backport manually!\n${wrap_in_details_block('git am output', details)}`; - postComment(git_am_failed_body); - core.setFailed("git am failed, most likely due to a merge conflict."); - return; - } - - console.log(`git am failed; attempting in-session conflict resolution via provided command: ${resolution_command}`); - - // Run user-provided resolution command - // Ignore return code to capture stdout/stderr - const resolution_result = await exec.getExecOutput(`bash -c "${resolution_command}"`, [], { ignoreReturnCode: true }); - if (resolution_result.exitCode !== 0) { - const details = `\`${resolution_command}\` stderr:\n${wrap_in_code_block('console', resolution_result.stderr)}\n[Link to workflow output](${workflow_run_url})`; - const resolution_failed_body = `@${comment_user} backporting to \`${target_branch}\` failed during automated conflict resolution. Please backport manually!\n${wrap_in_details_block('Error details', details)}`; - postComment(resolution_failed_body); - core.setFailed(`Automated conflict resolution command exited with code ${resolution_result.exitCode}`); - return; - } - - // Stage changes (excluding patch file) - await exec.exec(`git add -A`); - await exec.exec(`git reset HEAD ${patch_file}`); - - // Check for remaining conflicts - const diff_command = 'git diff --name-only --diff-filter=U'; - const diff_result = await exec.getExecOutput(diff_command); - if (diff_result.stdout.trim().length !== 0) { - const details = `${wrap_in_code_block('console', diff_result.stdout)}\n[Link to workflow output](${workflow_run_url})`; - const conflicts_body = `@${comment_user} backporting to \`${target_branch}\` failed. Automated conflict resolution did not resolve all conflicts. Please backport manually!\n${wrap_in_details_block(`${diff_command} output`, details)}`; - postComment(conflicts_body); - core.setFailed(`Automated conflict resolution did not resolve all conflicts.`); - return; - } - - console.log('Automated conflict resolution resolved all merge conflicts. Continuing.'); - await exec.exec('git am --continue'); + const git_am_failed_body = `@${context.payload.comment.user.login} backporting to "${target_branch}" failed, the patch most likely resulted in conflicts:\n\n\`\`\`shell\n${git_am_output}\n\`\`\`\n\nPlease backport manually!`; + await github.rest.issues.createComment({ + owner: repo_owner, + repo: repo_name, + issue_number: pr_number, + body: git_am_failed_body + }); + core.setFailed("Error: git am failed, most likely due to a merge conflict."); + return; + } + else { + // push the temp branch to the repository + await exec.exec(`git push --force --set-upstream origin HEAD:${temp_branch}`); } - - // push the temp branch to the repository - await exec.exec(`git push --force --set-upstream origin HEAD:${temp_branch}`); if (!should_open_pull_request) { console.log("Backport temp branch already exists, skipping opening a PR."); @@ -268,13 +188,8 @@ jobs: .replace(/%source_pr_author%/g, context.payload.issue.user.login) .replace(/%cc_users%/g, cc_users); - const backport_pr_labels = (process.env.BACKPORT_PR_LABELS || '') - .split(',') - .map(label => label.trim()) - .filter(label => label.length > 0); - // open the GitHub PR - const { data: backportPullRequest } = await github.rest.pulls.create({ + await github.rest.pulls.create({ owner: repo_owner, repo: repo_name, title: backport_pr_title, @@ -283,23 +198,21 @@ jobs: base: target_branch }); - if (backport_pr_labels.length > 0) { - await github.rest.issues.addLabels({ - owner: repo_owner, - repo: repo_name, - issue_number: backportPullRequest.number, - labels: backport_pr_labels - }); - } - console.log("Successfully opened the GitHub PR."); } catch (error) { - const body = `@${comment_user} an error occurred while backporting to \`${target_branch}\`. See the [workflow output](${workflow_run_url}) for details.`; - const comment_url = await postComment(body); - console.log(`Posted comment: ${comment_url}`); + core.setFailed(error); - } + // post failure to GitHub comment + const unknown_error_body = `@${comment_user} an error occurred while backporting to "${target_branch}", please check the run log for details!\n\n${error.message}`; + await github.rest.issues.createComment({ + owner: repo_owner, + repo: repo_name, + issue_number: pr_number, + body: unknown_error_body + }); + } + - name: Re-lock PR comments uses: actions/github-script@v7 if: ${{ github.event.issue.locked == true && (success() || failure()) }} diff --git a/.github/workflows/inter-branch-merge-base.yml b/.github/workflows/inter-branch-merge-base.yml index dee843a51b8..19ada5b4888 100644 --- a/.github/workflows/inter-branch-merge-base.yml +++ b/.github/workflows/inter-branch-merge-base.yml @@ -71,7 +71,7 @@ jobs: if: steps.extract-configuration-values.outputs.configurationFound != 'true' name: Read configuration status - - run: ${{ github.workspace }}\arcade-repository\.github\workflows\scripts\inter-branch-merge.ps1 -RepoName ${{ steps.fetch-repo-name.outputs.repository_name }} -RepoOwner ${{ github.repository_owner }} -MergeFromBranch $env:GITHUB_REF_NAME -MergeToBranch ${{ steps.extract-configuration-values.outputs.mergeToBranch }} -ResetToTargetPaths "${{ steps.extract-configuration-values.outputs.resetToTargetPaths }}" ${{ steps.extract-configuration-values.outputs.mergeSwitchArguments }} + - run: ${{ github.workspace }}\arcade-repository\.github\workflows\scripts\inter-branch-merge.ps1 -RepoName ${{ steps.fetch-repo-name.outputs.repository_name }} -RepoOwner ${{ github.repository_owner }} -MergeFromBranch $env:GITHUB_REF_NAME -MergeToBranch ${{ steps.extract-configuration-values.outputs.mergeToBranch }} ${{ steps.extract-configuration-values.outputs.mergeSwitchArguments }} if: steps.extract-configuration-values.outputs.configurationFound == 'true' name: Merge branches working-directory: ${{ github.workspace }}/repository diff --git a/.github/workflows/scripts/inter-branch-merge.ps1 b/.github/workflows/scripts/inter-branch-merge.ps1 index c05c2c87895..d1e251e09d6 100644 --- a/.github/workflows/scripts/inter-branch-merge.ps1 +++ b/.github/workflows/scripts/inter-branch-merge.ps1 @@ -15,10 +15,6 @@ The current branch Create a PR even if the only commits are from dotnet-maestro[bot] .PARAMETER QuietComments Do not tag commiters, do not comment on PR updates. Reduces GitHub notifications -.PARAMETER ResetToTargetPaths -Semicolon-separated list of glob patterns for files to reset to the target branch version. -After the merge branch is created, files matching these patterns will be checked out from -the target branch and committed, resolving potential merge conflicts for these files. #> [CmdletBinding(SupportsShouldProcess = $true)] param( @@ -40,10 +36,7 @@ param( [switch]$AllowAutomatedCommits, - [switch]$QuietComments, - - [Alias('r')] - [string]$ResetToTargetPaths = "" + [switch]$QuietComments ) $ErrorActionPreference = 'stop' @@ -112,70 +105,6 @@ function GetCommitterGitHubName($sha) { return $null } -function ResetFilesToTargetBranch($patterns, $targetBranch) { - if (-not $patterns -or $patterns.Count -eq 0) { - return - } - - Write-Host "Resetting files to $targetBranch for patterns: $($patterns -join ', ')" - - # Verify the target branch exists - $branchExists = & git rev-parse --verify "origin/$targetBranch" 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Warning "Target branch 'origin/$targetBranch' does not exist. Skipping file reset." - return - } - - # Configure git user for the commit - # Use GitHub Actions bot identity - Invoke-Block { & git config user.name "github-actions[bot]" } - Invoke-Block { & git config user.email "41898282+github-actions[bot]@users.noreply.github.com" } - - # Track which patterns had changes - $processedPatterns = @() - - foreach ($pattern in $patterns) { - $pattern = $pattern.Trim() - if (-not $pattern) { - continue - } - - Write-Host "Processing pattern: $pattern" - - # Use git checkout to reset files matching the pattern to the target branch - # The -- is needed to separate the revision from the pathspec - # Just attempt to checkout the pattern directly - git will handle whether files exist - try { - & git checkout "origin/$targetBranch" -- $pattern 2>&1 | Write-Host - if ($LASTEXITCODE -eq 0) { - Write-Host -f Green "Checked out pattern '$pattern' from $targetBranch" - $processedPatterns += $pattern - } else { - Write-Host -f Yellow "Pattern '$pattern' did not match any files in $targetBranch" - } - } - catch { - Write-Warning "Failed to checkout pattern '$pattern' from $targetBranch. Error: $_" - } - } - - # Check if there are any changes to commit after processing all patterns - $status = & git status --porcelain - if ($status -and $processedPatterns.Count -gt 0) { - # Add all changes (the checkout already modified the specific files) - Invoke-Block { & git add -A } - - # Create a commit message listing all patterns that were reset - $patternsList = $processedPatterns -join "`n- " - $commitMessage = "Reset files to $targetBranch`n`nReset patterns:`n- $patternsList" - - Invoke-Block { & git commit -m $commitMessage } - Write-Host -f Green "Successfully reset files to $targetBranch for patterns: $patternsList" - } else { - Write-Host "No changes to commit after processing all patterns" - } -} - # see https://git-scm.com/docs/pretty-formats $formatString = '%h %cn <%ce>: %s (%cr)' @@ -222,12 +151,6 @@ try { $mergeBranchName = "merge/$MergeFromBranch-to-$MergeToBranch" Invoke-Block { & git checkout -B $mergeBranchName } - # Reset specified files to target branch if ResetToTargetPaths is configured - if ($ResetToTargetPaths) { - $patterns = $ResetToTargetPaths -split ";" - ResetFilesToTargetBranch $patterns $MergeToBranch - } - $remoteName = 'origin' $prOwnerName = $RepoOwner $prRepoName = $RepoName diff --git a/.github/workflows/scripts/read-configuration.ps1 b/.github/workflows/scripts/read-configuration.ps1 index 73e72d7c37b..8fd99968fda 100644 --- a/.github/workflows/scripts/read-configuration.ps1 +++ b/.github/workflows/scripts/read-configuration.ps1 @@ -98,15 +98,8 @@ if ($configuration -ne $null) { $ExtraSwitches = $configuration['ExtraSwitches'] } - $ResetToTargetPaths = ""; - if($configuration.ContainsKey('ResetToTargetPaths')){ - # Convert array to semicolon-separated string for output - $ResetToTargetPaths = $configuration['ResetToTargetPaths'] -join ";" - } - "mergeSwitchArguments=$ExtraSwitches" | Out-File -FilePath $env:GITHUB_OUTPUT -Append "mergeToBranch=$MergeToBranch" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - "resetToTargetPaths=$ResetToTargetPaths" | Out-File -FilePath $env:GITHUB_OUTPUT -Append "configurationFound=$true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append } diff --git a/.vault-config/dnceng-partners-kv.yaml b/.vault-config/dnceng-partners-kv.yaml index a89cd41c4a5..43c6e9d730d 100644 --- a/.vault-config/dnceng-partners-kv.yaml +++ b/.vault-config/dnceng-partners-kv.yaml @@ -71,15 +71,6 @@ secrets: location: EngKeyVault gitHubBotAccountName: dotnet-bot - # repo, workflow scopes (classic token) - BotAccount-dotnet-renovate-bot-PAT: - type: github-access-token - parameters: - gitHubBotAccountSecret: - name: BotAccount-dotnet-renovate-bot - location: EngKeyVault - gitHubBotAccountName: dotnet-renovate-bot - roslyn-dn-bot-devdiv-build-r-release-r-code-r: type: azure-devops-access-token parameters: diff --git a/.vault-config/product-builds-engkeyvault.yaml b/.vault-config/product-builds-engkeyvault.yaml index 1f1ae7ce455..def0f0ff91b 100644 --- a/.vault-config/product-builds-engkeyvault.yaml +++ b/.vault-config/product-builds-engkeyvault.yaml @@ -22,11 +22,6 @@ secrets: parameters: Name: dotnet-bot - BotAccount-dotnet-renovate-bot: - type: github-account - parameters: - Name: dotnet-renovate-bot - #Publish-Build-Assets BotAccount-dotnet-maestro-bot-PAT: type: github-access-token @@ -56,6 +51,17 @@ secrets: parameters: description: Client id for akams app + dn-bot-dotnet-build-rw-code-rw: + type: azure-devops-access-token + parameters: + domainAccountName: dn-bot + domainAccountSecret: + location: helixkv + name: dn-bot-account-redmond + name: dn-bot-dotnet-build + organizations: dnceng + scopes: build_execute code_write + dn-bot-all-orgs-build-rw-code-rw: type: azure-devops-access-token parameters: diff --git a/Arcade.slnx b/Arcade.slnx index 1e9aa9aae67..337ee9c2279 100644 --- a/Arcade.slnx +++ b/Arcade.slnx @@ -35,7 +35,6 @@ - @@ -61,6 +60,7 @@ + @@ -72,6 +72,7 @@ + diff --git a/Directory.Build.targets b/Directory.Build.targets index 99449926093..1b8d3519ede 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -4,4 +4,9 @@ + + + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props index dc3da890ce4..9e2d6effc94 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,16 +10,15 @@ 4.0.0-rc3-24214-00 5.8.4 - 3.14.1-11027.2914512 + 3.14.1-9323.2545153 5.0.2-dotnet.2811440 - 2.9.3 - 3.2.2 + 3.0.0 1.22.0 $(XUnitVersion) - 3.1.5 - 1.9.1 + 3.1.3 @@ -28,10 +27,16 @@ + + + + + + @@ -50,6 +55,7 @@ + @@ -57,39 +63,44 @@ + + + + + - - + + - - + + - + - + - + - - + + diff --git a/Documentation/ArcadeSdk.md b/Documentation/ArcadeSdk.md index b926bbed1fb..a5df38e66b2 100644 --- a/Documentation/ArcadeSdk.md +++ b/Documentation/ArcadeSdk.md @@ -382,6 +382,35 @@ Optionally, a list of Visual Studio [workload component ids](https://docs.micros } ``` +If the build runs on a Windows machine that does not have the required Visual Studio version installed the `build.ps1` script attempts to use xcopy-deployable MSBuild package [`RoslynTools.MSBuild`](https://dotnet.myget.org/feed/roslyn-tools/package/nuget/RoslynTools.MSBuild). This package will allow the build to run on desktop msbuild but it may not provide all tools that the repository needs to build all projects and/or run all tests. + +The version of `RoslynTools.MSBuild` package can be specified in `global.json` file under `tools` like so: + +```json +{ + "tools": { + "vs": { + "version": "16.0" + }, + "xcopy-msbuild": "16.0.0-rc1-alpha" + } +} +``` + +If it is not specified the build script attempts to find `RoslynTools.MSBuild` version `{VSMajor}.{VSMinor}.0-alpha` where `VSMajor.VSMinor` is the value of `tools.vs.version`. + +If the fallback behavior to use xcopy-deployable MSBuild package is not desirable, then a version of `none` should be indicated in `global.json`, like this: + +```json +{ + "tools": { + "vs": { + "version": "16.4" + }, + "xcopy-msbuild": "none" + } +} +``` #### Example: Restoring multiple .NET Core Runtimes for running tests @@ -567,7 +596,6 @@ Set `VSSDKTargetPlatformRegRootSuffix` property to specify the root suffix of th If `source.extension.vsixmanifest` is present next to a project file the project is by default considered to be a VSIX producing project (`IsVsixProject` property is set to true). A package reference to `Microsoft.VSSDK.BuildTools` is automatically added to such project. -Set `IncludeMicrosoftVSSDKBuildToolsPackageReference` to `false` to opt out of the automatic package reference (e.g. if you need to manage the version yourself). Arcade SDK include build target for generating VS Template VSIXes. Adding `VSTemplate` items to project will trigger the target. @@ -1069,6 +1097,9 @@ If set to `true` calls to GetResourceString receive a default resource string va #### `GenerateResxSourceOmitGetResourceString` (bool) If set to `true` the GetResourceString method is not included in the generated class and must be specified in a separate source file. +#### `FlagNetStandard1XDependencies` (bool) +If set to `true` the `FlagNetStandard1xDependencies` target validates that the dependency graph doesn't contain any netstandard1.x packages. + Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CArcadeSdk.md)](https://helix.dot.net/f/p/5?p=Documentation%5CArcadeSdk.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CArcadeSdk.md) diff --git a/Documentation/AzureDevOps/SendingJobsToHelix.md b/Documentation/AzureDevOps/SendingJobsToHelix.md index 1a91b3ae0c4..6b93e46321a 100644 --- a/Documentation/AzureDevOps/SendingJobsToHelix.md +++ b/Documentation/AzureDevOps/SendingJobsToHelix.md @@ -80,8 +80,6 @@ steps: The simplest Helix use-case is zipping up a single folder containing your project's tests and a batch file which runs those tests. To accomplish this, reference Arcade's `send-to-helix` template in `eng/common/templates/steps/send-to-helix.yml` from your `azure-pipelines.yml` file. -### XUnit v2 - Simply specify the xUnit project(s) you wish to run (semicolon delimited) with the `XUnitProjects` parameter. Then, specify: * the `XUnitPublishTargetFramework` – this is the framework your **test projects are targeting**, e.g. `netcoreapp3.1`. * the `XUnitRuntimeTargetFramework` – this is the framework version of xUnit you want to use from the xUnit NuGet package, e.g. `netcoreapp2.0`. Notably, the xUnit console runner only supports up to netcoreapp2.0 as of 14 March 2018, so this is the target that should be specified for running against any higher version test projects. @@ -119,10 +117,6 @@ The list of available Helix queues can be found on the [Helix homepage](https:// # condition: succeeded() - defaults to succeeded() ``` -### XUnit v3 - -XUnit v3 test projects are self-hosting executables and do not need an external console runner. Instead of `XUnitProjects`, use `XUnitV3Project` items directly in your Helix MSBuild project file (see [the SDK's readme](/src/Microsoft.DotNet.Helix/Sdk/Readme.md) for details). The `XUnitPublishTargetFramework`, `XUnitRuntimeTargetFramework`, and `XUnitRunnerVersion` parameters are not needed for v3 projects. - ## The More Complex Case For anything more complex than the above example, you'll want to create your own MSBuild proj file to specify the work items and correlation payloads you want to send up to Helix. Full documentation on how to do this can be found [in the SDK's readme](https://github.com/dotnet/arcade/blob/master/src/Microsoft.DotNet.Helix/Sdk/Readme.md). diff --git a/Documentation/CodeQLGuidance.md b/Documentation/CodeQLGuidance.md index fa5a7aa5503..d9e7967f9d8 100644 --- a/Documentation/CodeQLGuidance.md +++ b/Documentation/CodeQLGuidance.md @@ -4,7 +4,7 @@ CodeQL is a code analysis platform owned by Semmle, now a subsidary of GitHub. It provides value by using extractors to construct a database representing the codebase, then providing a query language to perform sematic analysis. CodeQL instruments the build of compiled languages, and directly analyzes source code for interpreted languages. -CodeQL is required as part of Microsoft's Security Development Lifecycle (SDL) requirements. .NET Engineering Services supports CodeQL via CodeQL 3000, with scan results published to Trust Services Automation (TSA). SDL tooling is now handled entirely by 1ES Pipeline Templates. +CodeQL is required as part of Microsoft's Security Developent Lifecycle (SDL) requirements. .NET Engineering Services supports CodeQL via the Guardian toolset with scan results published to Trust Services Automation (TSA). CodeQL adds a significant time to builds. We therefore recommend creating a new, seperate pipeline instead of incorporating CodeQL scans into existing PR or testing pipelines. @@ -47,6 +47,8 @@ variables: ## Use with Arcade +**NOTE**: Arcade previously provided a Guardian-based job and step template that executed CodeQL using the Guardian toolset. This does not meet the requirements for CodeQL reporting and should not be used, as while it may produce scans it does not satisfy organizational requirements for CodeQL execution. + Much of https://github.dev/dotnet/arcade/blob/main/azure-pipelines-codeql.yml can be copied and used as-is for most repositories. With CodeQL3000, as long as the build completes and exercises the codebase to be scanned, all languages should be reported by the same tasks at the same time. diff --git a/Documentation/CorePackages/Publishing.md b/Documentation/CorePackages/Publishing.md index b9db6778a3c..67da815b6f5 100644 --- a/Documentation/CorePackages/Publishing.md +++ b/Documentation/CorePackages/Publishing.md @@ -117,6 +117,7 @@ These steps are needed for Arcade versions before `10.0.0`. After that, V3 is th | artifactsPublishingAdditionalParameters | string | Additional arguments for the PublishArtifactsInManifest sdk task. | '' | | signingValidationAdditionalParameters | string | Additional arguments for the SigningValidation sdk task. | '' | | publishInstallersAndChecksums | bool | Publish installers packages and checksums from the build artifacts to the dotnetcli storage account. Documentation for opting in to automatic checksum generation can be found in the [Checksum section](https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md#checksum-generation) of this document. | true | + | SDLValidationParameters | object | Parameters for the SDL job template, as documented in the [SDL template documentation](https://github.com/dotnet/arcade/blob/66175ebd3756697a3ca515e16cd5ffddc30582cd/Documentation/HowToAddSDLRunToPipeline.md) | -- | | validateDependsOn | [array] | Which stage(s) should the validation stage depend on. | build | | publishDependsOn | [array] | Which stage(s) should the publishing stage(s) depend on. | Validate | @@ -287,7 +288,7 @@ Each stable build (i.e., [Release Official Builds](https://github.com/dotnet/arc ### What benefits do I get from the new infrastructure? -There are a few benefits, but the bottom line is: you can rely on Arcade SDK and Maestro++ to determine the correct place to publish the build assets. This is specially important for servicing and/or private builds where assets must not go to public locations before further validations. The new infrastructure also performs Signing validation, SDL validation (via 1ES Pipeline Templates) and NuGet packages metadata validation. +There are a few benefits, but the bottom line is: you can rely on Arcade SDK and Maestro++ to determine the correct place to publish the build assets. This is specially important for servicing and/or private builds where assets must not go to public locations before further validations. The new infrastructure also performs Signing validation, SDL validation and NuGet packages metadata validation. ### What's this "Setup Maestro Vars" job? diff --git a/Documentation/HowToAddSDLRunToPipeline.md b/Documentation/HowToAddSDLRunToPipeline.md new file mode 100644 index 00000000000..3b9f8c1a5c1 --- /dev/null +++ b/Documentation/HowToAddSDLRunToPipeline.md @@ -0,0 +1,83 @@ +# Automate SDL tools run in AzDO Pipelines + +## SDL Scripts + +SDL scripts are a set of powershell scripts that provide the ability to run SDL tools. The scripts are located [here](../eng/common/sdl/) + +## How to add the SDL scripts to pipeline + +The pre-reqs needed to run the SDL scripts are defined with default values in a template - [execute-sdl.yml](../eng/common/templates/job/execute-sdl.yml). The template is added part of [post-build.yml](../eng/common/templates/post-build/post-build.yml) and is turned-off by default and can be enabled by setting the parameter [SDLValidationParameters.enable](../eng/common/templates/post-build/post-build.yml#L6). + +## Arguments + +All arguments that are not repo specific have default values specified to them, so only provide values for them if there is a need to override. + +| Name | Type | Description | +| ----------------------- | -------- | ------------------------------------------------------------ | +| GuardianPackageName | string | the name of guardian CLI package. Default is provided in post-build.yml. | +| NugetPackageDirectory | Dir Path | directory where NuGet packages are installed . The default is provided in post-build.yml. | +| Repository | string | the name of the repository (e.g. dotnet/arcade). The default is populated from the current build. | +| BranchName | string | name of branch or version of gdn settings; The default is populated from the current build. | +| SourceDirectory | Dir Path | the directory where source files are located. The default is populated from the current build.| +| ArtifactsDirectory | Dir Path | the directory where build artifacts are located. The default is populated from the current build. | +| AzureDevOpsAccessToken | string | access token to access internal AzDO repo which maintains the baseline data | +| **SourceToolsList** | Array | list of SDL tools to run on source code | +| **ArtifactToolsList** | Array | list of SDL tools to run on build artifacts | +| TsaPublish | bool | true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs.| +| TsaBranchName | string | TSA Parameter; The default is populated from the current build | +| TsaRepositoryName | string | TSA Parameter; The default is populated from the current build | +| BuildNumber | string | TSA Parameter; The default is populated from the current build | +| UpdateBaseline | bool | default value is false; if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed | +| TsaOnboard | bool | TSA Parameter; default value is false; if true, will onboard the repository to TSA; should only be run once | +| **TsaInstanceUrl** | string | TSA Parameter; the instance-url registered with TSA; | +| **TsaCodebaseName** | string | TSA Parameter; the name of the codebase registered with TSA; | +| **TsaProjectName** | string | TSA Parameter; the name of the project registered with TSA; | +| **TsaNotificationEmail**| string | TSA Parameter; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); | +| **TsaCodebaseAdmin** | string | TSA Parameter; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); | +| **TsaIterationPath** | string | TSA Parameter; the area path where TSA will file bugs in AzDO; | +| GuardianLoggerLevel | string | TSA Parameter; the iteration path where TSA will file bugs in AzDO; | + +**Note:** + +- Items in bold are repo specific and do not carry a default. +- All TSA parameters are needed only if `TsaPublish` and / or `TsaOnBoard` is set to true. + +## Usage Examples + +### Arcade +Arcade has enabled SDL runs in official-ci builds. +- [azure-pipeline.yml](https://github.com/dotnet/arcade/blob/master/azure-pipelines.yml#L192) +- [Build](https://dev.azure.com/dnceng/internal/_build/results?buildId=236348&view=logs&s=3df7d716-4c9c-5c26-9f45-11f62216640d&j=7d9eef18-6720-5c1f-4d30-89d7b76728e9) + +```yml + SDLValidationParameters: + enable: true + params: ' -SourceToolsList @("xyz","abc") + -ArtifactToolsList @("def") + -TsaInstanceURL "https://devdiv.visualstudio.com/" + -TsaProjectName "DEVDIV" + -TsaNotificationEmail "xxx@microsoft.com" + -TsaCodebaseAdmin "aa\bb" + -TsaBugAreaPath "DevDiv\NET Core " + -TsaIterationPath "DevDiv" + -TsaRepositoryName "Arcade" + -TsaCodebaseName "Arcade" + -TsaPublish $True' +``` + +## SDL run failures filed as bugs + +If `TsaPublish` is set to true, the output of the SDL tool runs for every build will be published to the account specified under `TsaInstanceURL` and `TsaProjectName`. + +If `TsaNotificationEmail` is set, a notification email will be sent out with a link to the bugs filed for each tool run. + +[Here](https://devdiv.visualstudio.com/DevDiv/_queries/query/?wiql=%20%20%20%20SELECT%20ID%2CSeverity%2CState%2C%5BAssigned%20To%5D%2CTitle%20FROM%20WorkItem%20WHERE%20Tags%20Contains%27TSA%23178337-Arcade-PoliCheck-12345.6%27%20%20%20%20) is the link to bugs filed after a test run for Arcade. + +## See Also +- [SDL Control Flow Document](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/442/ArcadeSecurityControlFlowDocumentation) +- [Introduction to Guardian and TSA](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/443/IntroToGuardianAndTSA) + + + +Was this helpful? [![Yes](https://helix.dot.net/f/ip/5?p=Documentation%5CHowToAddSDLRunToPipeline.md)](https://helix.dot.net/f/p/5?p=Documentation%5CHowToAddSDLRunToPipeline.md) [![No](https://helix.dot.net/f/in)](https://helix.dot.net/f/n/5?p=Documentation%5CHowToAddSDLRunToPipeline.md) + diff --git a/Documentation/HowToCreatePackages.md b/Documentation/HowToCreatePackages.md index 4b80cc52122..3f9d96abce0 100644 --- a/Documentation/HowToCreatePackages.md +++ b/Documentation/HowToCreatePackages.md @@ -32,9 +32,9 @@ One of the goals of Arcade is to be a vehicle to provide code sharing. One of th - If your package produces binaries that need to be signed, make sure to mark it as true or check the SignTool [documentation](../src/Microsoft.DotNet.SignTool/README.md) to see how to sign specific files. -- If the package needs to target a version of .NET Framework we recommend you to use the Arcade defined version, which is stored in the $(NetFrameworkMinimum), $(NetFrameworkCurrent) or $(NetFrameworkToolCurrent) properties. For instance: +- If the package needs to target a version of .NET Framework we recommend you to use the Arcade defined version, which is stored in the $(NetFxTfm) property. For instance: - `$(NetFrameworkMinimum);netcoreapp2.0` + `$(NetFxTfm);netcoreapp2.0` - There is no requirement to create a separate `.nuspec` file for the package. The package information will be automatically extracted from the `.csproj` file. diff --git a/Documentation/Policy/ArcadeContributorGuidance.md b/Documentation/Policy/ArcadeContributorGuidance.md index 59736585b7d..f48357ac251 100644 --- a/Documentation/Policy/ArcadeContributorGuidance.md +++ b/Documentation/Policy/ArcadeContributorGuidance.md @@ -21,6 +21,7 @@ For the most part, contributions to Arcade are straightforward and relatively sm ### Ownership The current owner for dotnet/arcade is Mark Wilkie . The current point persons are: - Michael Stuckey @garath / +- Ricardo Arenas @riarenas / ## Getting Things Fixed ### Submit a PR diff --git a/Documentation/Projects/Build Analysis/KnownIssues.md b/Documentation/Projects/Build Analysis/KnownIssues.md index 2a757d16a00..861e6f9b2c5 100644 --- a/Documentation/Projects/Build Analysis/KnownIssues.md +++ b/Documentation/Projects/Build Analysis/KnownIssues.md @@ -149,7 +149,6 @@ The matching process and its limitations depend on the type of error: ## How to fill out a Known Issue error section For the error matching to work, you need to provide an error message or an error pattern. -Both `ErrorMessage` and `ErrorPattern` accept either a single string or an [array of strings](#fill-out-known-issues-with-a-list-of-errors) for multi-line matching. ```json { @@ -164,7 +163,6 @@ The `ErrorMessage` value needs to be updated with an error message that meets th 1. Match at least one of the error messages of the reported build. For more details see: [How matching process works](#how-the-matching-process-works-between-an-issue-and-a-build-or-test-error) 1. It shouldn't include any unique identifier of the message e.g., machine, path, file name, etc. -1. For more specific matching, use an [array of strings](#fill-out-known-issues-with-a-list-of-errors) to match multiple lines in order. For example, for the following error: @@ -194,18 +192,10 @@ In the following example, the regular expression `The command .+ failed` would m } ``` -Like `ErrorMessage`, `ErrorPattern` also accepts an [array of strings](#fill-out-known-issues-with-a-list-of-errors) for matching multiple lines in order. For example: - -```json -{ - "ErrorPattern": ["\\[FAIL\\] System\\.Reflection\\.Context\\.Tests\\..+", "Assert\\.NotNull\\(\\) Failure"] -} -``` - We recommend you test your regular expression, to do it you can use [regex101 tester](https://regex101.com/) (choose `.NET (C#)` flavor) with the following regex options: - Single line -- Insensitive +- Insentitive - No backtracking ## What happens after creating or updating a Known Issue diff --git a/Documentation/Renovate.md b/Documentation/Renovate.md deleted file mode 100644 index 35a3dfff5b1..00000000000 --- a/Documentation/Renovate.md +++ /dev/null @@ -1,451 +0,0 @@ -# Renovate Dependency Update Tool - -## Table of Contents - -- [Introduction](#introduction) -- [Design](#design) -- [Security](#security) -- [Setting Up Renovate](#setting-up-renovate-for-your-repository) -- [Renovate Configuration Patterns](#renovate-configuration-patterns) -- [Troubleshooting](#troubleshooting) - -## Introduction - -This document outlines the integration of [Renovate](https://github.com/renovatebot/renovate) into Arcade to automate dependency updates. -Renovate is an automated dependency update tool that generates PRs for updating dependencies from a wide variety of sources. - -Renovate is similar to Dependabot in its purpose. -Dependabot should be used when possible. -However, Renovate supports a much broader range of [dependency types](https://docs.renovatebot.com/modules/datasource/), most notably Docker and GitHub releases. -For example, Renovate can automatically generate a PR to update a referenced Docker image when a newer version is available. - -### Example Scenarios - -Here are some scenarios demonstrating the usefulness of Renovate automatically making dependency updates: - -#### Container Tags - -Repos that reference container tags from the [dotnet/dotnet-buildtools-prereqs-docker](https://github.com/dotnet/dotnet-buildtools-prereqs-docker) repo need to maintain those tags to ensure they are supported. - -This can be as simple as automatically updating to a new major version of Linux distro: - -```diff --mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64 -+mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-amd64 -``` - -Or automatically pinning to the latest version of a tag by its digest value: - -```diff --mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-amd64@sha256:b99da50c4cb425e72ee69c2b8c1fdf99e0f71059aee19798e2f9310141ea48fb -+mcr.microsoft.com/dotnet-buildtools/prereqs:debian-12-helix-amd64@sha256:6bb6fef390e6f09a018f385e346b0fe5999d7662acd84ca2655e9a3c3e622b71 -``` - -Renovate can detect when these new container images are available and submit PRs to update sources accordingly. - -Related issue: [Automatically update image references in consuming repos (#1321)](https://github.com/dotnet/dotnet-buildtools-prereqs-docker/issues/1321) - -#### GitHub Release - -There are many cases where a version of OSS published via GitHub releases is being referenced by a .NET repository. -Those versions can be kept updated automatically as new releases occur. - -For example, there are Dockerfiles in the [dotnet/dotnet-buildtools-prereqs-docker](https://github.com/dotnet/dotnet-buildtools-prereqs-docker) repo which reference the LLVM version that can be maintained by having Renovate automatically check for new [LLVM releases](https://github.com/llvm/llvm-project/releases). - -```diff --LLVM_VERSION=19.1.7 -+LLVM_VERSION=20.1.0 -``` - -#### Others - -- GitHub Actions: Workflow files that reference GitHub Actions can be kept up to date as new action versions are released. -- Custom Version References: Any file containing version strings can be tracked, allowing Renovate to update versions in scripts, config files, or other custom formats. - -## Design - -### Fork Mode - -Protecting GitHub repositories in the dotnet organization from direct access by the Renovate tool is crucial. -Renovate will be used in fork mode, limiting its permissions to forked repositories. -This avoids giving write permissions to Renovate on any dotnet repository. -This means that Renovate acts as though it's any other outside contributor, not requiring any special permissions in the dotnet organization or repositories. - -A GitHub bot account, `dotnet-renovate-bot`, is used to manage the Renovate operations. -This account has the ability to create forks from dotnet repositories, which will be the source of the head branch for PRs that are created. -The PRs generated by Renovate will be done using this bot account. -Renovate also handles synchronization from the upstream branch automatically. -GitHub scopes required by this account: `repo`, `workflow`. - -```mermaid -sequenceDiagram - box AzDO - participant Pipeline as Renovate Pipeline - end - box GitHub - participant Fork as dotnet-renovate-bot fork - participant Upstream as dotnet repo - end - - Pipeline->>Fork: 1. Create/update fork - Pipeline->>Fork: 2. Push branch with dependency updates - Fork->>Upstream: 3. Create PR from fork - Note over Upstream: PR reviewed and merged by maintainers -``` - -### Repo Usage - -Arcade provides an [Azure DevOps pipeline YAML job template](../eng/common/core-templates/job/renovate.yml) that repositories should utilize when making use of Renovate. -This template handles the execution of Renovate, ensuring a standardized approach across all repositories. -Repositories wishing to make use of Renovate can define a pipeline by following the [onboarding instructions](#setting-up-renovate-for-your-repository). -Consuming repositories are responsible for providing their own [Renovate configuration file](https://docs.renovatebot.com/configuration-options/) that describes which dependencies should be updated. - -## Security - -### Bot Account Permissions - -The `dotnet-renovate-bot` account has minimal permissions by design: - -| Permission | Scope | Purpose | -|------------|-------|---------| -| `repo` | Bot's own forks only | Create branches and commits in forked repos | -| `workflow` | Bot's own forks only | Update workflow files if needed | - -**The bot has NO write access to any dotnet organization repositories.** All changes come through PRs from the fork. - -### What Renovate Can and Cannot Do - -**✅ Can Do:** -- Create PRs from its fork -- Update dependency versions -- Run on a schedule you control - -**❌ Cannot Do:** -- Push directly to your repo -- Access secrets or credentials -- Merge its own PRs -- Access private repos - -### PR Review Best Practices - -Since Renovate PRs come from an external fork, treat them like any external contribution: - -1. **Review the diff carefully** - Verify the version change is expected -2. **Check CI results** - Ensure all tests pass before merging -3. **Verify the upstream source** - Confirm the new version exists in the expected registry/repository - -## Setting Up Renovate for Your Repository - -This section provides step-by-step instructions for repo owners who want to enable automated dependency updates using Renovate. - -### Prerequisites - -Before setting up Renovate, verify the following requirements: - -- Your repository has Arcade's `eng/common` folder synchronized (via darc) - -### Create the Renovate Configuration File - -Create a Renovate configuration file at `eng/renovate.json` in your repository. This file defines which dependencies Renovate should monitor and update. - -Start with this base configuration: - -```json -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "enabledManagers": [ - "custom.regex" - ], - "vulnerabilityAlerts": { - "enabled": false - } -} -``` - -This configuration enables only the [custom regex manager](https://docs.renovatebot.com/modules/manager/regex/), disabling all built-in [managers](https://docs.renovatebot.com/modules/manager/) (e.g., dockerfile, nuget, github-actions) to prevent unwanted PRs. Without making any other changes to this configuration, it will result in no dependency updates. Add [presets](https://docs.renovatebot.com/config-presets/) to enable specific dependency types (see [Renovate Configuration Patterns](#renovate-configuration-patterns)). - -For more configuration options, see the [Renovate configuration documentation](https://docs.renovatebot.com/configuration-options/). - -### Create the Pipeline YAML - -Create a new pipeline YAML file in your repository (e.g., `eng/pipelines/renovate.yml`) that references the [Arcade Renovate job template](../eng/common/core-templates/job/renovate.yml). Set the `gitHubRepo` variable according to your repo name. - -```yaml -# Renovate Dependency Update Pipeline -# This pipeline runs Renovate to automatically create PRs for dependency updates. - -trigger: none # Manual trigger only; use schedules for automation - -schedules: -- cron: '0 8 * * 1' # Runs every Monday at 8:00 AM UTC - displayName: Weekly Renovate Run - branches: - include: - - main - always: true # Run even if there are no code changes - -parameters: -- name: dryRun - displayName: Dry Run (preview without creating PRs) - type: boolean - default: false -- name: forceRecreatePR - displayName: Force Recreate PR (recreate even if closed) - type: boolean - default: false - -variables: -- name: gitHubRepo - # TODO: Replace with your GitHub repo - value: 'dotnet/your-repo-name' - -extends: - template: /eng/common/core-templates/stages/renovate.yml@self - parameters: - dryRun: ${{ parameters.dryRun }} - forceRecreatePR: ${{ parameters.forceRecreatePR }} - gitHubRepo: ${{ variables.gitHubRepo }} -``` - -> **Note:** After creating the YAML file, you'll need to create the pipeline in Azure DevOps. Create the pipeline in **dnceng/internal** (not public) since the Renovate job requires access to internal resources and the `dotnet-renovate-bot` GitHub token stored in the `dotnet-renovate-bot-pat` variable group. - -### Test with Dry Run - -Before enabling automatic PR creation, test your configuration using [dry run mode](https://docs.renovatebot.com/self-hosted-configuration/#dryrun). -Any existing dependencies that Renovate detects that should be updated will appear in the `Run Renovate` -step's log as `Would create PR...` or `Would commit files to branch...`. -Examine the log to determine whether it meets your expectation. -See [Troubleshooting](#troubleshooting) for more information on investigating the Renovate log. -Examine any warnings that are output as well. -One example of a warning is a "Config migration necessary" message which means that Renovate automatically migrated your Renovate configuration file to the latest format. -In its output it includes the migrated version of your config file; update your config file to use that format instead. - -## Renovate Configuration Patterns - -This section provides common configuration patterns for typical dependency update scenarios. - -### Dockerfile Version Variables - -Use the [`customManagers:dockerfileVersions`](https://docs.renovatebot.com/presets-customManagers/#custommanagersdockerfileversions) preset to automatically detect and update version variables used within Dockerfiles. -This preset matches common patterns like `ARG VERSION=x.y.z` or `ENV VERSION=x.y.z` and can update them from various [datasources](https://docs.renovatebot.com/modules/datasource/) like GitHub releases. - -```json -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "customManagers:dockerfileVersions" - ] -} -``` - -This preset automatically matches variables in Dockerfiles that follow the pattern: - -```dockerfile -# renovate: datasource=github-releases depName=owner/repo -ARG SOME_VERSION=1.2.3 -``` - -The comment annotation tells Renovate where to look for updates. For example, to track LLVM releases: - -```dockerfile -# renovate: datasource=github-releases depName=llvm/llvm-project -ARG LLVM_VERSION=19.1.7 -``` - -Renovate will automatically create PRs when new versions are released on GitHub. - -#### Supported Annotations - -The [`dockerfileVersions` preset](https://docs.renovatebot.com/presets-customManagers/#custommanagersdockerfileversions) recognizes these annotations in the comment as defined by the `matchStrings` field of the preset: - -| Annotation | Description | Example | -|------------|-------------|---------| -| `datasource` | Where to look for updates | `github-releases`, `docker`, `npm` | -| `depName` | The dependency name/path | `llvm/llvm-project`, `node` | -| `versioning` | Version scheme to use | `semver`, `loose`, `regex:...` | -| `extractVersion` | Regex to extract version from release tag | `^llvmorg-(?.+)$` | - -#### Example: LLVM with Custom Version Extraction - -LLVM uses release tags like `llvmorg-19.1.7`, so you need to extract the version: - -```dockerfile -# renovate: datasource=github-releases depName=llvm/llvm-project extractVersion=^llvmorg-(?.+)$ -ARG LLVM_VERSION=19.1.7 -``` - -#### Example: CMake from GitHub Releases - -```dockerfile -# renovate: datasource=github-releases depName=Kitware/CMake -ARG CMAKE_VERSION=3.28.1 -``` - -For more information, see the [Renovate dockerfileVersions preset documentation](https://docs.renovatebot.com/presets-customManagers/#custommanagersdockerfileversions). - -### Common packageRules Examples - -The [`packageRules`](https://docs.renovatebot.com/configuration-options/#packagerules) configuration allows fine-grained control over how Renovate handles specific dependencies. - -#### Group Related Updates Together - -Combine related dependency updates into a single PR: - -```json -{ - "packageRules": [ - { - "groupName": "LLVM toolchain", - "matchDepNames": ["llvm/llvm-project", "llvm/clang"] - } - ] -} -``` - -#### Pin to Major Version - -Prevent major version updates for stability: - -```json -{ - "packageRules": [ - { - "matchDepNames": ["node"], - "allowedVersions": ">=18.0.0 <19.0.0" - } - ] -} -``` - -#### Exclude Specific Dependencies - -Skip updates for dependencies you want to manage manually: - -```json -{ - "packageRules": [ - { - "matchDepNames": ["some-internal-tool"], - "enabled": false - } - ] -} -``` - -### Multiple Schedules or Configurations - -If you need different update schedules for different dependencies: - -1. Create separate Renovate config files (e.g., `eng/renovate-docker.json`, `eng/renovate-tests.json`) -2. Create separate pipeline YAML files, each referencing a different config via `renovateConfigPath` - - ```yaml - extends: - template: /eng/common/core-templates/stages/renovate.yml@self - parameters: - dryRun: ${{ parameters.dryRun }} - forceRecreatePR: ${{ parameters.forceRecreatePR }} - gitHubRepo: ${{ variables.gitHubRepo }} - renovateConfigPath: 'eng/renovate-docker.json' - ``` - -3. Configure different schedules for each pipeline - -## Troubleshooting - -This section helps diagnose issues when Renovate isn't behaving as expected. - -### Finding the Log File - -The Renovate pipeline publishes detailed debug logs as pipeline artifacts after each run. To access them: - -Find the two log files in the pipeline artifacts: -- **`renovate.json`** - The main Renovate execution log with debug-level details -- **`renovate-config-validator.json`** - Log from the Renovate configuration file validation step - -The log files are in JSON Lines format (one JSON object per line), which can be viewed in any text editor or processed with tools like `jq`. - -### Understanding the Log File - -The `renovate.json` file contains detailed information about what Renovate did during execution. Each line is a JSON object with a `msg` field describing the action and additional context fields. - -#### Finding Which Files Are Being Scanned - -To see which files Renovate is checking for dependencies, search for entries related to file discovery and package extraction. - -Using `jq` to filter file matching information: - -```bash -# Find all files being matched by managers -cat renovate.json | jq -s 'map(select(.packageFile)) | .[] | {packageFile,msg}' -``` - -#### Finding Dependencies That Need Updating - -To identify which dependencies Renovate found that need updates, search for: - -```bash -# Find all dependency updates detected -cat renovate.json | jq -s 'map(select(.msg == "packageFiles with updates")) | .[0].config' - -# Find PRs that would be created (dry run mode) -cat renovate.json | jq -s 'map(select(.msg | contains("DRY-RUN"))) | .[].msg' - -# Find PRs that were actually created -cat renovate.json | jq -s 'map(select(.msg == "PR created")) | .[] | {pr, prTitle}' -``` - -#### Finding Errors in the Log - -Renovate logs errors as JSON objects with an `err` field containing structured error details. These entries indicate failures during execution, such as post-upgrade command failures or network errors. - -To extract all error entries from the log: - -```bash -# Find all log entries that contain errors -cat renovate.json | jq -s 'map(select(.err)) | .[] | {msg, repository, branch, err: {cmd: .err.cmd, stderr: .err.stderr, exitCode: .err.exitCode, message: .err.message}}' -``` - -### Common Issues - -#### No Files Being Matched - -If Renovate isn't finding your dependency files: - -1. Check your [`managerFilePatterns`](https://docs.renovatebot.com/configuration-options/#managerfilepatterns) patterns in your Renovate configuration file (e.g. `eng/renovate.json`) -2. Verify the file paths are relative to the repository root -3. Use dry run mode and check the logs for `"packageFile"` entries -4. Ensure regex patterns use proper escaping (double-escape in JSON: `\\.` for a literal dot) - -#### Dependencies Found But No PRs Created - -If Renovate finds dependencies but doesn't create PRs: - -1. Check if PRs already exist for those updates -2. Look for `"msg": "Skipping"` entries in the log explaining why -3. Verify the update isn't being filtered by your configuration (e.g., [`ignoreDeps`](https://docs.renovatebot.com/configuration-options/#ignoredeps), [`packageRules`](https://docs.renovatebot.com/configuration-options/#packagerules)) -4. Check if the dependency version is already at the latest - -#### Regex Manager Not Matching - -For custom regex patterns: - -1. Test your regex at [regex101.com](https://regex101.com/) (use ECMAScript/JavaScript flavor) -2. Ensure named capture groups are correct: `(?...)`, `(?...)`, etc. -3. Check the log for `"manager": "regex"` entries to see what's being matched -4. Verify `matchStrings` patterns don't have JSON escaping issues - -#### Post-Upgrade Task Failures - -If you have configured [post-upgrade tasks](https://docs.renovatebot.com/configuration-options/#postupgradetasks) (e.g., running a script after a dependency is updated) and the results are not what you expect, check the log for errors. Renovate treats post-upgrade task failures as non-fatal, meaning the pipeline will still complete successfully even if a post-upgrade command fails. This can make failures easy to miss. - -Look for errors in the log using the steps in [Finding Errors in the Log](#finding-errors-in-the-log). - -#### Changes Not Taking Effect - -Renovate operates on the contents of the target GitHub branch, not the AzDO pipeline branch. If you've made changes in your AzDO dev branch but Renovate doesn't seem to pick them up, it's likely because those changes don't exist in the GitHub branch that Renovate is scanning. This applies to: - -- Files containing versions you expect Renovate to update -- Scripts or files referenced by [post-upgrade tasks](https://docs.renovatebot.com/configuration-options/#postupgradetasks) - -To test without pushing to a production branch, push a dev branch to the target GitHub repo and pass the `baseBranches` parameter in your Renovate pipeline run to target it. diff --git a/Documentation/Validation.md b/Documentation/Validation.md index 9120fb111c5..ac739ac0ada 100644 --- a/Documentation/Validation.md +++ b/Documentation/Validation.md @@ -41,8 +41,9 @@ As a part of .NET 5, we had a goal of two hour build turn-around. In order to cl [Validate-DotNet](https://dev.azure.com/dnceng/internal/_build?definitionId=827) is a pipeline that automatically runs nightly validation for all repositories that have been onboarded. Onboarding to Validate-DotNet is quite simple: 1. Update the [list of repositories](https://dev.azure.com/dnceng/internal/_git/dotnet-release?path=%2Feng%2Fpipeline%2Ftools%2Frepos-to-validate.txt) in [dotnet-release](https://dev.azure.com/dnceng/internal/_git/dotnet-release) with your repository's URL. Please reach out to dnceng for PR approval. +2. To enable nightly SDL runs, add the [sdl-tsa-vars.config](https://github.com/dotnet/runtime/blob/main/eng/sdl-tsa-vars.config) file to your repository. This file should include all of the necessary information specific to your repository for creating SDL issues. -Once this step is complete, your repository will start validating on a nightly basis. SDL validation is now handled automatically by 1ES Pipeline Templates. +Once the first step is complete, your repository will start validating on a nightly basis. Once the second step is complete, you will also get SDL validation. ## How do I know if there are failures in my validation runs? @@ -116,7 +117,7 @@ Errors we commonly see in validation jobs include: * Signing Validation * Files that are not intended to be signed are not listed in the `eng/SignCheckExclusionsFile.txt` for that repository, so validation complains that the files are not signed. Mitigation: add that file type to the `eng/SignCheckExclusionsFile.txt` in your repository. -* SDL Validation (now handled by 1ES Pipeline Templates, which will open TSA issues for any failures found) +* SDL Validation (which will open TSA issues for any failures found) * Any pipeline failures in these legs should be reported to [DNCEng First Responders](https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/107/How-to-get-a-hold-of-Engineering-Servicing), as it suggests an infrastructure failure. SDL failures should automatically create TSA issues, which you should address as appropriate. * Localization Validation * Localization is done closer to release time. Localization failures suggest that either the localization team hasn't finished translations, or the translation PR hasn't been checked into your repository and should be. The closer to release we get, the more important fixing these failures becomes. diff --git a/Documentation/update-xunit.md b/Documentation/update-xunit.md index 270eaa1f6ed..9547416c55c 100644 --- a/Documentation/update-xunit.md +++ b/Documentation/update-xunit.md @@ -1,7 +1,7 @@ This document aims to establish the necessary actions to update the xunit version in Arcade which then gets propagated into consuming repositories. 1. For security reasons, nuget packages need to be manually mirrored from nuget.org to the dotnet-public AzDO feed. [See the instructions](/Documentation/MirroringPackages.md). Mirror the following xunit packages: `xunit,xunit.console,xunit.runner.reporters,xunit.runner.utility,xunit.runner.console,xunit.runner.visualstudio` with version `latest`. -2. Update `XUnitVersion`, `XUnitAnalyzersVersion`, `XUnitRunnerVisualStudioVersion`, `XUnitV3Version` and `MicrosoftTestingPlatformVersion` properties in [Arcade SDK's DefaultVersions.props](/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props) to the desired values. Make sure to use a coherent version of `xunit.analyzers`. Note that `MicrosoftTestingPlatformVersion` must be compatible with the `XUnitV3Version` (xunit.v3.mtp-v1 depends on a specific minimum version of Microsoft.Testing.Platform). +2. Update `XUnitVersion`, `XUnitAnalyzersVersion` and `XUnitRunnerVisualStudioVersion` properties in [Arcade SDK's DefaultVersions.props](/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props) to the desired values. Make sure to use a coherent version of `xunit.analyzers`. 3. Update other hardcoded values of `XUnitVersion` inside the Arcade repository (i.e. in [SendingJobsToHelix.md](/Documentation/AzureDevOps/SendingJobsToHelix.md), [Directory.Packages.props](/Directory.Packages.props) and others). 4. Update Microsoft.DotNet.XUnitAssert which is an AOT compatible fork of the xunit.assert library by following [the instructions](/src/Microsoft.DotNet.XUnitAssert/README.md). It's likely that new XUnit versions introduce AOT incompatibilities which will cause the compiler (AOT analyzer) to fail. If you aren't sure how to resolve the errors, consult with @agocke's team who owns this library. 5. Submit a Pull request with these changes to [dotnet/arcade](https://github.com/dotnet/arcade) and tag @ViktorHofer as a reviewer. diff --git a/NuGet.config b/NuGet.config index f0da823560c..20c5dc3fe74 100644 --- a/NuGet.config +++ b/NuGet.config @@ -14,8 +14,6 @@ - - @@ -52,13 +50,6 @@ - - - - - - - diff --git a/README.md b/README.md index 5f2ceb1b07b..ec0e5de86b3 100644 --- a/README.md +++ b/README.md @@ -4,35 +4,59 @@ Arcade is intended to provide well-understood and consistent mechanisms for consuming, updating, and sharing infrastructure across the .NET Core team. For more details about Arcade, please see the [Overview](./Documentation/Overview.md) documentation. +## Build & Test Status + +Status of Arcade public CI builds: [![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/arcade/arcade-ci?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=13&branchName=main) + ## Validation & Dependency Flow Status **[Arcade validation policy and process](Documentation/Validation/Overview.md)** -|Flow|BarViz Link| -|---|---| -|Current Version of Arcade in `.NET Eng - Latest`|[Link](https://maestro.dot.net/2/https:%2F%2Fdev.azure.com%2Fdnceng%2Finternal%2F_git%2Fdotnet-arcade/latest/graph)| -|Latest Version of Arcade Being Validated|[Link](https://maestro.dot.net/9/https:%2F%2Fdev.azure.com%2Fdnceng%2Finternal%2F_git%2Fdotnet-arcade/latest/graph)| +### Current Version of Arcade in `.NET Eng - Latest` + +[Link](https://maestro.dot.net/2/https:%2F%2Fdev.azure.com%2Fdnceng%2Finternal%2F_git%2Fdotnet-arcade/latest/graph) to BARViz -## Build Statuses +### Latest Version of Arcade Being Validated + +[Link](https://maestro.dot.net/9/https:%2F%2Fdev.azure.com%2Fdnceng%2Finternal%2F_git%2Fdotnet-arcade/latest/graph) to BARViz. + +### Build Statuses |Repo Name|Current Build Status| |---|---| -|Arcade Public CI|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Farcade%2Farcade-pr?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=283&branchName=main)| |Arcade Official Build|[![Build Status](https://dnceng.visualstudio.com/internal/_apis/build/status/dotnet/arcade/arcade-official-ci?branchName=main)](https://dnceng.visualstudio.com/internal/_build/latest?definitionId=6&branchName=main)| |Arcade Validation|[![Build Status](https://dnceng.visualstudio.com/internal/_apis/build/status/dotnet/arcade-validation/dotnet-arcade-validation-official?branchName=main)](https://dnceng.visualstudio.com/internal/_build/latest?definitionId=282&branchName=main)| +### Status of Latest Version of Arcade Being Validated + +- Please see the [Arcade channel on Teams](https://teams.microsoft.com/l/channel/19%3a1dad2081c8634f34915d88dce6220265%40thread.skype/Arcade?groupId=4d73664c-9f2f-450d-82a5-c2f02756606d&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) for the latest status regarding Arcade promotions. + ## Getting Started -Documentation, tutorials, and guides how to use Arcade may be found in the [Start Here](Documentation/StartHere.md) index. +Packages are published daily to our tools feed: -Packages are published daily to our tools feed: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json +> `https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json` -This feed is browsable from here: https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-eng +This feed is browsable from here: -## How to contribute +> https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-eng + +### Source Code + +`git clone https://github.com/dotnet/arcade.git` + +### How to use Arcade + +Documentation, tutorials, and guides may be found in the [Start Here](Documentation/StartHere.md) index. + +### How to contribute - [How to contribute to Arcade guide](Documentation/Policy/ArcadeContributorGuidance.md) -## License +- [Pull requests](https://github.com/dotnet/arcade/pulls): [Open](https://github.com/dotnet/arcade/pulls?q=is%3Aopen+is%3Apr)/[Closed](https://github.com/dotnet/arcade/pulls?q=is%3Apr+is%3Aclosed) + +- [Issues](https://github.com/dotnet/arcade/issues) + +### License -.NET (including the Arcade repo) is licensed under the [MIT license](LICENSE.TXT). +.NET Core (including the Arcade repo) is licensed under the [MIT license](LICENSE.TXT). diff --git a/azure-pipelines-daily.yaml b/azure-pipelines-daily.yaml index cbbb1713e0e..9bb7e6fb9b1 100644 --- a/azure-pipelines-daily.yaml +++ b/azure-pipelines-daily.yaml @@ -29,7 +29,7 @@ stages: version: 8.x installationPath: $(System.DefaultWorkingDirectory)/.dotnet - - script: dotnet tool restore + - script: $(System.DefaultWorkingDirectory)/.dotnet/dotnet.exe tool restore displayName: Restore dotnet tools - task: AzureCLI@2 diff --git a/azure-pipelines-pr.yml b/azure-pipelines-pr.yml index 7ca86bef5c4..6c5a6c28ec5 100644 --- a/azure-pipelines-pr.yml +++ b/azure-pipelines-pr.yml @@ -18,7 +18,7 @@ pr: branches: include: - main - - release/*.0 + - release/*.0 - templates paths: include: @@ -135,6 +135,7 @@ stages: publish: logs: name: Logs_Test_$(Agent.OS)_$(_BuildConfig)_$(_Testing) + download: true workspace: clean: all jobs: @@ -153,13 +154,6 @@ stages: - checkout: self clean: true steps: - - script: eng\common\cibuild.cmd - -configuration $(_BuildConfig) - -prepareMachine - $(_InternalBuildArgs) - /p:Test=false - displayName: Windows Build / Publish - - task: PowerShell@2 displayName: sdk-task verification inputs: @@ -172,7 +166,6 @@ stages: /p:SymbolPublishingExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SymbolPublishingExclusionsFile.txt' /p:Configuration=Release /p:PublishToMSDL=false - - powershell: eng\common\build.ps1 -configuration $(_BuildConfig) -prepareMachine @@ -204,12 +197,6 @@ stages: - checkout: self clean: true steps: - - script: eng/common/cibuild.sh - --configuration $(_BuildConfig) - --prepareMachine - /p:Test=false - displayName: Unix Build / Publish - - script: eng/common/build.sh --configuration $(_BuildConfig) --prepareMachine diff --git a/azure-pipelines-renovate.yml b/azure-pipelines-renovate.yml deleted file mode 100644 index 9a38c212cf8..00000000000 --- a/azure-pipelines-renovate.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Renovate Dependency Update Pipeline -# This pipeline runs Renovate to automatically create PRs for dependency updates. - -trigger: none - -schedules: -- cron: '0 8 * * 1' - displayName: Weekly Renovate Run - branches: - include: - - main - always: true - -parameters: -- name: dryRun - displayName: Dry Run (preview without creating PRs) - type: boolean - default: false -- name: forceRecreatePR - displayName: Force Recreate PR (recreate even if closed) - type: boolean - default: false - -extends: - template: /eng/common/core-templates/stages/renovate.yml@self - parameters: - gitHubRepo: 'dotnet/arcade' - dryRun: ${{ parameters.dryRun }} - forceRecreatePR: ${{ parameters.forceRecreatePR }} diff --git a/eng/BuildTask.Packages.props b/eng/BuildTask.Packages.props new file mode 100644 index 00000000000..fd82c1e0941 --- /dev/null +++ b/eng/BuildTask.Packages.props @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/BuildTask.targets b/eng/BuildTask.targets index c3fc4636f2b..68346ee899f 100644 --- a/eng/BuildTask.targets +++ b/eng/BuildTask.targets @@ -1,17 +1,17 @@ + + false true true - + true tools - net - $(TargetsForTfmSpecificContentInPackage);_AddBuildOutputToPackage - - $(BeforePack);Publish + true + $(TargetsForTfmSpecificContentInPackage);_AddBuildOutputToPackageCore;_AddBuildOutputToPackageDesktop + - - - - + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + true + runtime + + + + + + + + + + net + + + + + netframework + + + + + + diff --git a/eng/Microsoft.DotNet.SwaggerGenerator.MSBuild.InTree.targets b/eng/Microsoft.DotNet.SwaggerGenerator.MSBuild.InTree.targets index 815402b0374..7772d6a8c88 100644 --- a/eng/Microsoft.DotNet.SwaggerGenerator.MSBuild.InTree.targets +++ b/eng/Microsoft.DotNet.SwaggerGenerator.MSBuild.InTree.targets @@ -2,7 +2,12 @@ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'Microsoft.DotNet.SwaggerGenerator', 'Microsoft.DotNet.SwaggerGenerator.MSBuild')) - $(ArtifactsBinDir)Microsoft.DotNet.SwaggerGenerator.MSBuild\$(Configuration)\ + $(ArtifactsBinDir)Microsoft.DotNet.SwaggerGenerator.MSBuild\$(Configuration)\ + + $(MicrosoftDotNetSwaggerGeneratorMSBuildBaseOutputDirectory)$(NetToolCurrent)\ + $(MicrosoftDotNetSwaggerGeneratorMSBuildBaseOutputDirectory)$(NetFrameworkToolCurrent)\ + + TaskHostFactory ResolveProjectReferences @@ -19,7 +24,11 @@ + Private="false"> + + TargetFramework=$(NetToolCurrent) + TargetFramework=$(NetFrameworkToolCurrent) + diff --git a/eng/Signing.props b/eng/Signing.props index afd81da3439..78a3204d090 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -4,28 +4,22 @@ are already signed. However, they must be signed with a 3rd party certificate. --> - - - - + + + - - - - - + - - - - + + + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 145460f34b6..dc2a4e1e273 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,19 +6,19 @@ This file should be imported by eng/Versions.props - 11.0.0-beta.26203.4 - 11.0.0-beta.26203.4 + 10.0.0-beta.26177.7 + 10.0.0-beta.26177.7 - 1.1.0-beta.26176.1 - 1.1.0-beta.26176.1 + 1.1.0-beta.25424.1 + 1.1.0-beta.25424.1 - 2.0.3 + 2.0.0-beta5.25210.1 2.0.0-preview.1.24305.1 8.0.0-preview.24461.2 - 1.1.0-beta.26176.1 + 1.1.0-beta.25421.1 17.12.50 17.12.50 @@ -28,28 +28,38 @@ This file should be imported by eng/Versions.props 4.8.0 4.8.0 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 - 10.0.3 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24473.5 9.0.100-preview.6.24328.19 1.1.0-beta2-19575-01 1.1.0-beta2-19575-01 + 2.0.0-preview.1.23470.14 2.0.0-preview.1.23470.14 10.0.100-preview.4.25220.1 - 11.0.0-prerelease.26181.1 + 11.0.0-prerelease.26169.1 + + 13.0.3 + + 2.23.0 @@ -76,6 +86,7 @@ This file should be imported by eng/Versions.props $(MicrosoftCodeAnalysisCSharpPackageVersion) $(MicrosoftNetCompilersToolsetPackageVersion) + $(MicrosoftBclAsyncInterfacesPackageVersion) $(MicrosoftExtensionsDependencyInjectionPackageVersion) $(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion) $(MicrosoftExtensionsDependencyModelPackageVersion) @@ -83,20 +94,29 @@ This file should be imported by eng/Versions.props $(MicrosoftExtensionsFileSystemGlobbingPackageVersion) $(MicrosoftExtensionsHttpPackageVersion) $(MicrosoftExtensionsLoggingConsolePackageVersion) - $(SystemCompositionPackageVersion) + $(SystemCollectionsImmutablePackageVersion) + $(SystemFormatsAsn1PackageVersion) $(SystemIOPackagingPackageVersion) + $(SystemReflectionMetadataPackageVersion) $(SystemSecurityCryptographyPkcsPackageVersion) $(SystemSecurityCryptographyXmlPackageVersion) + $(SystemTextEncodingsWebPackageVersion) + $(SystemTextJsonPackageVersion) $(MicrosoftNETSdkWorkloadManifestReaderPackageVersion) $(MicrosoftDiaSymReaderConverterPackageVersion) $(MicrosoftDiaSymReaderPdb2PdbPackageVersion) + $(MicrosoftSymbolUploaderPackageVersion) $(MicrosoftSymbolUploaderBuildTaskPackageVersion) $(MicrosoftTemplateEngineAuthoringTasksPackageVersion) $(MicrosoftDotNetXHarnessCLIPackageVersion) + + $(NewtonsoftJsonPackageVersion) + + $(MicrosoftApplicationInsightsPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ff41ad6b686..319ef2ef662 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,9 +1,16 @@ - + + + + https://github.com/microsoft/ApplicationInsights-dotnet + 2faa7e8b157a431daa2e71785d68abd5fa817b53 + https://github.com/dotnet/roslyn e091728607ca0fc9efca55ccfb3e59259c6b5a0a @@ -12,29 +19,33 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-symuploader d617bc8ed2787c235a57cf0dcdfd087b86ff9521 + + https://dev.azure.com/dnceng/internal/_git/dotnet-symuploader + d617bc8ed2787c235a57cf0dcdfd087b86ff9521 + https://github.com/dotnet/templating 43b5827697e501c442eb75ffff832cd4df2514fe - + https://github.com/dotnet/arcade - a27cb13c8355fd3711a66e8c0d4f71e76dafaa18 + 62dc2defffeadabf6761a9ed7e142692107330c0 - + https://github.com/dotnet/arcade - a27cb13c8355fd3711a66e8c0d4f71e76dafaa18 + 62dc2defffeadabf6761a9ed7e142692107330c0 - + https://github.com/dotnet/arcade-services - a9d498ae6ab98685d6d8376c6f47604d4638651e + e6a28a549cc932140719fd63ba0387254db0ac51 - + https://github.com/dotnet/arcade-services - a9d498ae6ab98685d6d8376c6f47604d4638651e + e6a28a549cc932140719fd63ba0387254db0ac51 - + https://github.com/dotnet/xharness - 3d43498414571f4f63c4dd14241c46404e8c60f9 + b0c8bf6dba87c70e284cff06819f0cd714c8f2e4 https://github.com/dotnet/roslyn @@ -54,47 +65,66 @@ - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a - - + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + + + https://github.com/JamesNK/Newtonsoft.Json + 0a2e291c0d9c0c7675d445703e51750363a549ef + + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + https://github.com/dotnet/deployment-tools d882ae4af9fb09a89e36487a9c8cb7dfde713927 @@ -103,26 +133,34 @@ ef4c24166691977558e5312758df4313ab310dc0 - + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + https://github.com/dotnet/command-line-api - c7b5e07cfed85e88c162dc1c916efaff03742e6e + e9b0511d7f1128e2bc3be7a658a2a4ea977e602d - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 + + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - - https://github.com/dotnet/runtime - dc5fd7a8dce8309e4add8fd4bd5d8718f221b15a + + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime + 990ebf52fc408ca45929fd176d2740675a67fab8 - + https://github.com/dotnet/dnceng - ccb98f08f700e29ad1dca55697c1e1472cb6cb17 + 949759ff23ff663a2c3e105cf504b281fe667d01 diff --git a/eng/Versions.props b/eng/Versions.props index 00e71372322..50fe1932967 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,26 +1,40 @@ - - - + - 11.0.0 + + 10.0.0 beta - - + false + + true + + + + 6.0.0 + 4.6.3 + 6.1.3 + + + 1.1.1 + 4.5.5 + 6.0.1 - + + 9.0.0-beta.24223.1 4.3.0 + + 2.0.3 - 7.0.1 - 7.0.1 - 7.0.1 - 7.0.1 - 7.0.1 + 6.13.2 + 6.13.2 + 6.13.2 + 6.13.2 + 6.13.2 5.0.0 6.0.4 @@ -35,6 +49,8 @@ 6.0.22 15.2.302-preview.14.122 16.0.527 + 9.0.0-preview.6.24327.7 + + 17.5.0 - diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index fc8d618014e..65ed3a8adef 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -1,6 +1,7 @@ # This script adds internal feeds required to build commits that depend on internal package sources. For instance, -# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables -# disabled internal Maestro (darc-int*) feeds. +# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly, +# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present. +# In addition, this script also enables disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # @@ -173,4 +174,16 @@ foreach ($dotnetVersion in $dotnetVersions) { } } +# Check for dotnet-eng and add dotnet-eng-internal if present +$dotnetEngSource = $sources.SelectSingleNode("add[@key='dotnet-eng']") +if ($dotnetEngSource -ne $null) { + AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-eng-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password +} + +# Check for dotnet-tools and add dotnet-tools-internal if present +$dotnetToolsSource = $sources.SelectSingleNode("add[@key='dotnet-tools']") +if ($dotnetToolsSource -ne $null) { + AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-tools-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password +} + $doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index b97cc536379..b2163abbe71 100755 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # This script adds internal feeds required to build commits that depend on internal package sources. For instance, -# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables -# disabled internal Maestro (darc-int*) feeds. +# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly, +# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present. +# In addition, this script also enables disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # @@ -173,6 +174,18 @@ for DotNetVersion in ${DotNetVersions[@]} ; do fi done +# Check for dotnet-eng and add dotnet-eng-internal if present +grep -i " /dev/null +if [ "$?" == "0" ]; then + AddOrEnablePackageSource "dotnet-eng-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$FeedSuffix" +fi + +# Check for dotnet-tools and add dotnet-tools-internal if present +grep -i " /dev/null +if [ "$?" == "0" ]; then + AddOrEnablePackageSource "dotnet-tools-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$FeedSuffix" +fi + # I want things split line by line PrevIFS=$IFS IFS=$'\n' diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 18397a60eb8..8cfee107e7a 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -6,7 +6,6 @@ Param( [string][Alias('v')]$verbosity = "minimal", [string] $msbuildEngine = $null, [bool] $warnAsError = $true, - [string] $warnNotAsError = '', [bool] $nodeReuse = $true, [switch] $buildCheck = $false, [switch][Alias('r')]$restore, @@ -71,7 +70,6 @@ function Print-Usage() { Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" - Write-Host " -warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" diff --git a/eng/common/build.sh b/eng/common/build.sh index 5883e53bcfb..9767bb411a4 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -42,7 +42,6 @@ usage() echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" - echo " --warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" echo " --buildCheck Sets /check msbuild parameter" echo " --fromVMR Set when building from within the VMR" echo "" @@ -79,7 +78,6 @@ ci=false clean=false warn_as_error=true -warn_not_as_error='' node_reuse=true build_check=false binary_log=false @@ -94,7 +92,7 @@ runtime_source_feed='' runtime_source_feed_key='' properties=() -while [[ $# -gt 0 ]]; do +while [[ $# > 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) @@ -178,10 +176,6 @@ while [[ $# -gt 0 ]]; do warn_as_error=$2 shift ;; - -warnnotaserror) - warn_not_as_error=$2 - shift - ;; -nodereuse) node_reuse=$2 shift diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index 66c7988f222..eaed6d87e65 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -19,8 +19,6 @@ parameters: # publishing defaults artifacts: '' enableMicrobuild: false - enablePreviewMicrobuild: false - microbuildPluginVersion: 'latest' enableMicrobuildForMacAndLinux: false microbuildUseESRP: true enablePublishBuildArtifacts: false @@ -73,8 +71,6 @@ jobs: templateContext: ${{ parameters.templateContext }} variables: - - name: AllowPtrToDetectTestRunRetryFiles - value: true - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' @@ -132,8 +128,6 @@ jobs: - template: /eng/common/core-templates/steps/install-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} - enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} - microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} microbuildUseESRP: ${{ parameters.microbuildUseESRP }} continueOnError: ${{ parameters.continueOnError }} @@ -156,8 +150,6 @@ jobs: - template: /eng/common/core-templates/steps/cleanup-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} - enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} - microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index 700f7711465..06f2eed0323 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -91,8 +91,8 @@ jobs: fetchDepth: 3 clean: true - - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: - - ${{ if eq(parameters.publishingVersion, 3) }}: + - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: + - ${{ if eq(parameters.publishingVersion, 3) }}: - task: DownloadPipelineArtifact@2 displayName: Download Asset Manifests inputs: @@ -117,7 +117,7 @@ jobs: flattenFolders: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: NuGetAuthenticate@1 # Populate internal runtime variables. @@ -125,7 +125,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: parameters: legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - + - template: /eng/common/templates/steps/enable-internal-runtimes.yml - task: AzureCLI@2 @@ -145,7 +145,7 @@ jobs: condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: @@ -191,7 +191,7 @@ jobs: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - + # Darc is targeting 8.0, so make sure it's installed - task: UseDotNet@2 inputs: diff --git a/eng/common/core-templates/job/renovate.yml b/eng/common/core-templates/job/renovate.yml deleted file mode 100644 index ff86c80b468..00000000000 --- a/eng/common/core-templates/job/renovate.yml +++ /dev/null @@ -1,196 +0,0 @@ -# -------------------------------------------------------------------------------------- -# Renovate Bot Job Template -# -------------------------------------------------------------------------------------- -# This Azure DevOps pipeline job template runs Renovate (https://docs.renovatebot.com/) -# to automatically update dependencies in a GitHub repository. -# -# Renovate scans the repository for dependency files and creates pull requests to update -# outdated dependencies based on the configuration specified in the renovateConfigPath -# parameter. -# -# Usage: -# For each product repo wanting to make use of Renovate, this template is called from -# an internal Azure DevOps pipeline, typically with a schedule trigger, to check for -# and propose dependency updates. -# -# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md -# -------------------------------------------------------------------------------------- - -parameters: - -# Path to the Renovate configuration file within the repository. -- name: renovateConfigPath - type: string - default: 'eng/renovate.json' - -# GitHub repository to run Renovate against, in the format 'owner/repo'. -# This could technically be any repo but convention is to target the same -# repo that contains the calling pipeline. The Renovate config file would -# be co-located with the pipeline's repo and, in most cases, the config -# file is specific to the repo being targeted. -- name: gitHubRepo - type: string - -# List of base branches to target for Renovate PRs. -# NOTE: The Renovate configuration file is always read from the branch where the -# pipeline is run, NOT from the target branches specified here. If you need different -# configurations for different branches, run the pipeline from each branch separately. -- name: baseBranches - type: object - default: - - main - -# When true, Renovate will run in dry run mode, which previews changes without creating PRs. -# See the 'Run Renovate' step log output for details of what would have been changed. -- name: dryRun - type: boolean - default: false - -# By default, Renovate will not recreate a PR for a given dependency/version pair that was -# previously closed. This allows opting in to always recreating PRs even if they were -# previously closed. -- name: forceRecreatePR - type: boolean - default: false - -# Name of the arcade repository resource in the pipeline. -# This allows repos which haven't been onboarded to Arcade to still use this -# template by checking out the repo as a resource with a custom name and pointing -# this parameter to it. -- name: arcadeRepoResource - type: string - default: self - -# Directory name for the self repo under $(Build.SourcesDirectory) in multi-checkout. -# In multi-checkout (when arcadeRepoResource != 'self'), Azure DevOps checks out the -# self repo to $(Build.SourcesDirectory)/. Set this to match the auto-generated -# directory name. Using the auto-generated name is necessary rather than explicitly -# defining a checkout path because container jobs expect repos to live under the agent's -# workspace ($(Pipeline.Workspace)). On some self-hosted setups the host path -# (e.g., /mnt/vss/_work) differs from the container path (e.g., /__w), and a custom checkout -# path can fail validation. Using the default checkout location keeps the paths consistent -# and avoids this issue. -- name: selfRepoName - type: string - default: '' -- name: arcadeRepoName - type: string - default: '' - -# Pool configuration for the job. -- name: pool - type: object - default: - name: NetCore1ESPool-Internal - image: build.azurelinux.3.amd64 - os: linux - -jobs: -- job: Renovate - displayName: Run Renovate - container: RenovateContainer - variables: - - group: dotnet-renovate-bot - # The Renovate version is automatically updated by https://github.com/dotnet/arcade/blob/main/azure-pipelines-renovate.yml. - # Changing the variable name here would require updating the name in https://github.com/dotnet/arcade/blob/main/eng/renovate.json as well. - - name: renovateVersion - value: '42' - readonly: true - - name: renovateLogFilePath - value: '$(Build.ArtifactStagingDirectory)/renovate.json' - readonly: true - - name: dryRunArg - readonly: true - ${{ if eq(parameters.dryRun, true) }}: - value: 'full' - ${{ else }}: - value: '' - - name: recreateWhenArg - readonly: true - ${{ if eq(parameters.forceRecreatePR, true) }}: - value: 'always' - ${{ else }}: - value: '' - # In multi-checkout (without custom paths), Azure DevOps places each repo under - # $(Build.SourcesDirectory)/. selfRepoName must be provided in that case. - - name: selfRepoPath - readonly: true - ${{ if eq(parameters.arcadeRepoResource, 'self') }}: - value: '$(Build.SourcesDirectory)' - ${{ else }}: - value: '$(Build.SourcesDirectory)/${{ parameters.selfRepoName }}' - - name: arcadeRepoPath - readonly: true - ${{ if eq(parameters.arcadeRepoResource, 'self') }}: - value: '$(Build.SourcesDirectory)' - ${{ else }}: - value: '$(Build.SourcesDirectory)/${{ parameters.arcadeRepoName }}' - pool: ${{ parameters.pool }} - - templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory) - outputs: - - output: pipelineArtifact - displayName: Publish Renovate Log - condition: succeededOrFailed() - targetPath: $(Build.ArtifactStagingDirectory) - artifactName: $(Agent.JobName)_Logs_Attempt$(System.JobAttempt) - isProduction: false # logs are non-production artifacts - - steps: - - checkout: self - fetchDepth: 1 - - - ${{ if ne(parameters.arcadeRepoResource, 'self') }}: - - checkout: ${{ parameters.arcadeRepoResource }} - fetchDepth: 1 - - - script: | - renovate-config-validator $(selfRepoPath)/${{parameters.renovateConfigPath}} 2>&1 | tee /tmp/renovate-config-validator.out - validatorExit=${PIPESTATUS[0]} - if grep -q '^ WARN:' /tmp/renovate-config-validator.out; then - echo "##vso[task.logissue type=warning]Renovate config validator produced warnings." - echo "##vso[task.complete result=SucceededWithIssues]" - fi - exit $validatorExit - displayName: Validate Renovate config - env: - LOG_LEVEL: info - LOG_FILE_LEVEL: debug - LOG_FILE: $(Build.ArtifactStagingDirectory)/renovate-config-validator.json - - - script: | - . $(arcadeRepoPath)/eng/common/renovate.env - renovate 2>&1 | tee /tmp/renovate.out - renovateExit=${PIPESTATUS[0]} - if grep -q '^ WARN:' /tmp/renovate.out; then - echo "##vso[task.logissue type=warning]Renovate produced warnings." - echo "##vso[task.complete result=SucceededWithIssues]" - fi - exit $renovateExit - displayName: Run Renovate - env: - RENOVATE_FORK_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) - RENOVATE_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) - RENOVATE_REPOSITORIES: ${{parameters.gitHubRepo}} - RENOVATE_BASE_BRANCHES: ${{ convertToJson(parameters.baseBranches) }} - RENOVATE_DRY_RUN: $(dryRunArg) - RENOVATE_RECREATE_WHEN: $(recreateWhenArg) - LOG_LEVEL: info - LOG_FILE_LEVEL: debug - LOG_FILE: $(renovateLogFilePath) - RENOVATE_CONFIG_FILE: $(selfRepoPath)/${{parameters.renovateConfigPath}} - - - script: | - echo "PRs created by Renovate:" - if [ -s "$(renovateLogFilePath)" ]; then - if ! jq -r 'select(.msg == "PR created" and .pr != null) | "https://github.com/\(.repository)/pull/\(.pr)"' "$(renovateLogFilePath)" | sort -u; then - echo "##vso[task.logissue type=warning]Failed to parse Renovate log file with jq." - echo "##vso[task.complete result=SucceededWithIssues]" - fi - else - echo "##vso[task.logissue type=warning]No Renovate log file found or file is empty." - echo "##vso[task.complete result=SucceededWithIssues]" - fi - displayName: List created PRs - condition: and(succeededOrFailed(), eq('${{ parameters.dryRun }}', false)) diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml index bac6ac5faac..76baf5c2725 100644 --- a/eng/common/core-templates/job/source-index-stage1.yml +++ b/eng/common/core-templates/job/source-index-stage1.yml @@ -15,8 +15,6 @@ jobs: variables: - name: BinlogPath value: ${{ parameters.binlogPath }} - - name: skipComponentGovernanceDetection - value: true - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} @@ -27,10 +25,10 @@ jobs: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $(DncEngPublicBuildPool) - image: windows.vs2026.amd64.open + image: windows.vs2026preview.scout.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: windows.vs2026.amd64 + image: windows.vs2026preview.scout.amd64 steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: diff --git a/eng/common/core-templates/jobs/codeql-build.yml b/eng/common/core-templates/jobs/codeql-build.yml new file mode 100644 index 00000000000..dbc14ac580a --- /dev/null +++ b/eng/common/core-templates/jobs/codeql-build.yml @@ -0,0 +1,32 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + is1ESPipeline: '' + +jobs: +- template: /eng/common/core-templates/jobs/jobs.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml index fcf40d1d2e6..b54db518826 100644 --- a/eng/common/core-templates/post-build/post-build.yml +++ b/eng/common/core-templates/post-build/post-build.yml @@ -1,108 +1,118 @@ parameters: -# Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. -# Publishing V1 is no longer supported -# Publishing V2 is no longer supported -# Publishing V3 is the default -- name: publishingInfraVersion - displayName: Which version of publishing should be used to promote the build definition? - type: number - default: 3 - values: - - 3 - - 4 - -- name: BARBuildId - displayName: BAR Build Id - type: number - default: 0 - -- name: PromoteToChannelIds - displayName: Channel to promote BARBuildId to - type: string - default: '' - -- name: enableSourceLinkValidation - displayName: Enable SourceLink validation - type: boolean - default: false - -- name: enableSigningValidation - displayName: Enable signing validation - type: boolean - default: true - -- name: enableSymbolValidation - displayName: Enable symbol validation - type: boolean - default: false - -- name: enableNugetValidation - displayName: Enable NuGet validation - type: boolean - default: true - -- name: publishInstallersAndChecksums - displayName: Publish installers and checksums - type: boolean - default: true - -- name: requireDefaultChannels - displayName: Fail the build if there are no default channel(s) registrations for the current build - type: boolean - default: false - -- name: isAssetlessBuild - type: boolean - displayName: Is Assetless Build - default: false - -# These parameters let the user customize the call to sdk-task.ps1 for publishing -# symbols & general artifacts as well as for signing validation -- name: symbolPublishingAdditionalParameters - displayName: Symbol publishing additional parameters - type: string - default: '' - -- name: artifactsPublishingAdditionalParameters - displayName: Artifact publishing additional parameters - type: string - default: '' - -- name: signingValidationAdditionalParameters - displayName: Signing validation additional parameters - type: string - default: '' - -# Which stages should finish execution before post-build stages start -- name: validateDependsOn - type: object - default: - - build - -- name: publishDependsOn - type: object - default: - - Validate - -# Optional: Call asset publishing rather than running in a separate stage -- name: publishAssetsImmediately - type: boolean - default: false - -- name: is1ESPipeline - type: boolean - default: false + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + - 4 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: requireDefaultChannels + displayName: Fail the build if there are no default channel(s) registrations for the current build + type: boolean + default: false + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + - name: isAssetlessBuild + type: boolean + displayName: Is Assetless Build + default: false + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + + - name: is1ESPipeline + type: boolean + default: false stages: -- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: - stage: Validate dependsOn: ${{ parameters.validateDependsOn }} displayName: Validate Build Assets variables: - - template: /eng/common/core-templates/post-build/common-variables.yml - - template: /eng/common/core-templates/variables/pool-providers.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} + - template: /eng/common/core-templates/post-build/common-variables.yml + - template: /eng/common/core-templates/variables/pool-providers.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: NuGet Validation @@ -118,49 +128,49 @@ stages: ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) - image: windows.vs2026.amd64 + image: windows.vs2026preview.scout.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2026.amd64 + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} + + - ${{ if ne(parameters.publishingInfraVersion, 4) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.publishingInfraVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts (V4) + inputs: + itemPattern: '*/packages/**/*.nupkg' + targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + - task: CopyFiles@2 + displayName: Flatten packages to PackageArtifacts + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + Contents: '**/*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' + flattenFolders: true + + - task: PowerShell@2 + displayName: Validate inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true - - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Artifacts (V4) - inputs: - itemPattern: '*/packages/**/*.nupkg' - targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - - task: CopyFiles@2 - displayName: Flatten packages to PackageArtifacts - inputs: - SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - Contents: '**/*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' - flattenFolders: true - - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - job: displayName: Signing Validation @@ -174,68 +184,68 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - ${{ if eq(parameters.is1ESPipeline, true) }}: + ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2026.amd64 + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true - - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Artifacts (V4) + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} + + - ${{ if ne(parameters.publishingInfraVersion, 4) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.publishingInfraVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts (V4) + inputs: + itemPattern: '*/packages/**/*.nupkg' + targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + - task: CopyFiles@2 + displayName: Flatten packages to PackageArtifacts + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + Contents: '**/*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' + flattenFolders: true + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate inputs: - itemPattern: '*/packages/**/*.nupkg' - targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - - task: CopyFiles@2 - displayName: Flatten packages to PackageArtifacts - inputs: - SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - Contents: '**/*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' - flattenFolders: true - - # This is necessary whenever we want to publish/restore to an AzDO private feed - # Since sdk-task.ps1 tries to restore packages we need to do this authentication here - # otherwise it'll complain about accessing a private feed. - - task: NuGetAuthenticate@1 - displayName: 'Authenticate to AzDO Feeds' - - # Signing validation will optionally work with the buildmanifest file which is downloaded from - # Azure DevOps above. - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: eng\common\sdk-task.ps1 - arguments: -task SigningValidation -restore - /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' - /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' - ${{ parameters.signingValidationAdditionalParameters }} - - - template: /eng/common/core-templates/steps/publish-logs.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} - StageLabel: 'Validation' - JobLabel: 'Signing' - BinlogToolVersion: $(BinlogToolVersion) + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: /eng/common/core-templates/steps/publish-logs.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} + StageLabel: 'Validation' + JobLabel: 'Signing' + BinlogToolVersion: $(BinlogToolVersion) # SourceLink validation has been removed — the underlying CLI tool # (targeting netcoreapp2.1) has not functioned for years. @@ -250,20 +260,20 @@ stages: - task: Delay@1 displayName: 'Warning: SourceLink validation removed (see https://github.com/dotnet/arcade/issues/16647)' inputs: - delayForMinutes: '0' + delayForMinutes: '0' - ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: - stage: publish_using_darc - ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: dependsOn: ${{ parameters.publishDependsOn }} ${{ else }}: dependsOn: ${{ parameters.validateDependsOn }} displayName: Publish using Darc variables: - - template: /eng/common/core-templates/post-build/common-variables.yml - - template: /eng/common/core-templates/variables/pool-providers.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} + - template: /eng/common/core-templates/post-build/common-variables.yml + - template: /eng/common/core-templates/variables/pool-providers.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: Publish Using Darc @@ -277,7 +287,7 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - ${{ if eq(parameters.is1ESPipeline, true) }}: + ${{ if eq(parameters.is1ESPipeline, true) }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2026.amd64 os: windows @@ -285,33 +295,34 @@ stages: name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2026.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} - - task: NuGetAuthenticate@1 + - task: NuGetAuthenticate@1 - # Populate internal runtime variables. - - template: /eng/common/templates/steps/enable-internal-sources.yml - parameters: - legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) + # Populate internal runtime variables. + - template: /eng/common/templates/steps/enable-internal-sources.yml + parameters: + legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - - template: /eng/common/templates/steps/enable-internal-runtimes.yml + - template: /eng/common/templates/steps/enable-internal-runtimes.yml - - task: UseDotNet@2 - inputs: - version: 8.0.x + # Darc is targeting 8.0, so make sure it's installed + - task: UseDotNet@2 + inputs: + version: 8.0.x - - task: AzureCLI@2 - displayName: Publish Using Darc - inputs: - azureSubscription: "Darc: Maestro Production" - scriptType: ps - scriptLocation: scriptPath - scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: > + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' diff --git a/eng/common/core-templates/stages/renovate.yml b/eng/common/core-templates/stages/renovate.yml deleted file mode 100644 index edab2818258..00000000000 --- a/eng/common/core-templates/stages/renovate.yml +++ /dev/null @@ -1,111 +0,0 @@ -# -------------------------------------------------------------------------------------- -# Renovate Pipeline Template -# -------------------------------------------------------------------------------------- -# This template provides a complete reusable pipeline definition for running Renovate -# in a 1ES Official pipeline. Pipelines can extend from this template and only need -# to pass the Renovate job parameters. -# -# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md -# -------------------------------------------------------------------------------------- - -parameters: - -# Path to the Renovate configuration file within the repository. -- name: renovateConfigPath - type: string - default: 'eng/renovate.json' - -# GitHub repository to run Renovate against, in the format 'owner/repo'. -- name: gitHubRepo - type: string - -# List of base branches to target for Renovate PRs. -- name: baseBranches - type: object - default: - - main - -# When true, Renovate will run in dry run mode. -- name: dryRun - type: boolean - default: false - -# When true, Renovate will recreate PRs even if they were previously closed. -- name: forceRecreatePR - type: boolean - default: false - -# Name of the arcade repository resource in the pipeline. -# This allows repos which haven't been onboarded to Arcade to still use this -# template by checking out the repo as a resource with a custom name and pointing -# this parameter to it. -- name: arcadeRepoResource - type: string - default: 'self' - -- name: selfRepoName - type: string - default: '' -- name: arcadeRepoName - type: string - default: '' - -# Pool configuration for the pipeline. -- name: pool - type: object - default: - name: NetCore1ESPool-Internal - image: build.azurelinux.3.amd64 - os: linux - -# Renovate version used in the container image tag. -- name: renovateVersion - default: 43 - type: number - -# Pool configuration for SDL analysis. -- name: sdlPool - type: object - default: - name: NetCore1ESPool-Internal - image: windows.vs2026.amd64 - os: windows - -resources: - repositories: - - repository: 1ESPipelineTemplates - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release - -extends: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates - parameters: - pool: ${{ parameters.pool }} - sdl: - sourceAnalysisPool: ${{ parameters.sdlPool }} - # When repos that aren't onboarded to Arcade use this template, they set the - # arcadeRepoResource parameter to point to their Arcade repo resource. In that case, - # Aracde will be excluded from SDL analysis. - ${{ if ne(parameters.arcadeRepoResource, 'self') }}: - sourceRepositoriesToScan: - exclude: - - repository: ${{ parameters.arcadeRepoResource }} - containers: - RenovateContainer: - image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-renovate-${{ parameters.renovateVersion }}-amd64 - stages: - - stage: Renovate - displayName: Run Renovate - jobs: - - template: /eng/common/core-templates/job/renovate.yml@${{ parameters.arcadeRepoResource }} - parameters: - renovateConfigPath: ${{ parameters.renovateConfigPath }} - gitHubRepo: ${{ parameters.gitHubRepo }} - baseBranches: ${{ parameters.baseBranches }} - dryRun: ${{ parameters.dryRun }} - forceRecreatePR: ${{ parameters.forceRecreatePR }} - pool: ${{ parameters.pool }} - arcadeRepoResource: ${{ parameters.arcadeRepoResource }} - selfRepoName: ${{ parameters.selfRepoName }} - arcadeRepoName: ${{ parameters.arcadeRepoName }} diff --git a/eng/common/core-templates/steps/install-microbuild-impl.yml b/eng/common/core-templates/steps/install-microbuild-impl.yml deleted file mode 100644 index da22beb3f60..00000000000 --- a/eng/common/core-templates/steps/install-microbuild-impl.yml +++ /dev/null @@ -1,34 +0,0 @@ -parameters: - - name: microbuildTaskInputs - type: object - default: {} - - - name: microbuildEnv - type: object - default: {} - - - name: enablePreviewMicrobuild - type: boolean - default: false - - - name: condition - type: string - - - name: continueOnError - type: boolean - -steps: -- ${{ if eq(parameters.enablePreviewMicrobuild, true) }}: - - task: MicroBuildSigningPluginPreview@4 - displayName: Install Preview MicroBuild plugin - inputs: ${{ parameters.microbuildTaskInputs }} - env: ${{ parameters.microbuildEnv }} - continueOnError: ${{ parameters.continueOnError }} - condition: ${{ parameters.condition }} -- ${{ else }}: - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin - inputs: ${{ parameters.microbuildTaskInputs }} - env: ${{ parameters.microbuildEnv }} - continueOnError: ${{ parameters.continueOnError }} - condition: ${{ parameters.condition }} diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index 76a54e157fd..553fce66b94 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -4,8 +4,6 @@ parameters: # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false - # Enable preview version of MB signing plugin - enablePreviewMicrobuild: false # Determines whether the ESRP service connection information should be passed to the signing plugin. # This overlaps with _SignType to some degree. We only need the service connection for real signing. # It's important that the service connection not be passed to the MicroBuildSigningPlugin task in this place. @@ -15,8 +13,6 @@ parameters: microbuildUseESRP: true # Microbuild installation directory microBuildOutputFolder: $(Agent.TempDirectory)/MicroBuild - # Microbuild version - microbuildPluginVersion: 'latest' continueOnError: false @@ -73,46 +69,42 @@ steps: # YAML expansion, and Windows vs. Linux/Mac uses different service connections. However, # we can avoid including the MB install step if not enabled at all. This avoids a bunch of # extra pipeline authorizations, since most pipelines do not sign on non-Windows. - - template: /eng/common/core-templates/steps/install-microbuild-impl.yml - parameters: - enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} - microbuildTaskInputs: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin (Windows) + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + ${{ if eq(parameters.microbuildUseESRP, true) }}: + ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea + ${{ else }}: + ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca + env: + TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) + + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin (non-Windows) + inputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - version: ${{ parameters.microbuildPluginVersion }} + workingDirectory: ${{ parameters.microBuildOutputFolder }} ${{ if eq(parameters.microbuildUseESRP, true) }}: ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea + ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 ${{ else }}: - ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca - microbuildEnv: + ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc + env: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) - - - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: - - template: /eng/common/core-templates/steps/install-microbuild-impl.yml - parameters: - enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} - microbuildTaskInputs: - signType: $(_SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - version: ${{ parameters.microbuildPluginVersion }} - workingDirectory: ${{ parameters.microBuildOutputFolder }} - ${{ if eq(parameters.microbuildUseESRP, true) }}: - ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 - ${{ else }}: - ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc - microbuildEnv: - TeamName: $(_TeamName) - MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml index b75f59c428d..09ae5cd73ae 100644 --- a/eng/common/core-templates/steps/source-build.yml +++ b/eng/common/core-templates/steps/source-build.yml @@ -24,7 +24,7 @@ steps: # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey '$(dotnetbuilds-internal-container-read-token-base64)'' fi buildConfig=Release diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml index 3ad83b8c307..e9a694afa58 100644 --- a/eng/common/core-templates/steps/source-index-stage1-publish.yml +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -1,6 +1,6 @@ parameters: - sourceIndexUploadPackageVersion: 2.0.0-20250906.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20250906.1 + sourceIndexUploadPackageVersion: 2.0.0-20250818.1 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250818.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json binlogPath: artifacts/log/Debug/Build.binlog @@ -14,8 +14,8 @@ steps: workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 314c93c5759..8abfb71f727 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -9,7 +9,6 @@ usage() echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" - echo " for OpenBSD can be: openbsd" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" @@ -28,8 +27,6 @@ __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 -__OpenBSDArch=arm -__OpenBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm @@ -75,7 +72,7 @@ __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" -__FreeBSDBase="13.5-RELEASE" +__FreeBSDBase="13.4-RELEASE" __FreeBSDPkg="1.21.3" __FreeBSDABI="13" __FreeBSDPackages="libunwind" @@ -85,12 +82,6 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" -__OpenBSDVersion="7.8" -__OpenBSDPackages="heimdal-libs" -__OpenBSDPackages+=" icu4c" -__OpenBSDPackages+=" inotify-tools" -__OpenBSDPackages+=" openssl" - __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" @@ -169,8 +160,6 @@ while :; do __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 - __OpenBSDArch=arm64 - __OpenBSDMachineArch=aarch64 ;; armel) __BuildArch=armel @@ -246,8 +235,6 @@ while :; do __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 - __OpenBSDArch=amd64 - __OpenBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" @@ -308,7 +295,9 @@ while :; do ;; noble) # Ubuntu 24.04 __CodeName=noble - __LLDB_Package="liblldb-19-dev" + if [[ -z "$__LLDB_Package" ]]; then + __LLDB_Package="liblldb-19-dev" + fi ;; stretch) # Debian 9 __CodeName=stretch @@ -394,14 +383,10 @@ while :; do ;; freebsd14) __CodeName=freebsd - __FreeBSDBase="14.3-RELEASE" + __FreeBSDBase="14.2-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; - openbsd) - __CodeName=openbsd - __SkipUnmount=1 - ;; illumos) __CodeName=illumos __SkipUnmount=1 @@ -610,62 +595,6 @@ elif [[ "$__CodeName" == "freebsd" ]]; then INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages -elif [[ "$__CodeName" == "openbsd" ]]; then - # determine mirrors - OPENBSD_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/$__OpenBSDVersion/$__OpenBSDMachineArch" - - # download base system sets - ensureDownloadTool - - BASE_SETS=(base comp) - for set in "${BASE_SETS[@]}"; do - FILE="${set}${__OpenBSDVersion//./}.tgz" - echo "Downloading $FILE..." - if [[ "$__hasWget" == 1 ]]; then - wget -O- "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - - else - curl -SL "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - - fi - done - - PKG_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/${__OpenBSDVersion}/packages/${__OpenBSDMachineArch}" - - echo "Installing packages into sysroot..." - - # Fetch package index once - if [[ "$__hasWget" == 1 ]]; then - PKG_INDEX=$(wget -qO- "$PKG_MIRROR/") - else - PKG_INDEX=$(curl -s "$PKG_MIRROR/") - fi - - for pkg in $__OpenBSDPackages; do - PKG_FILE=$(echo "$PKG_INDEX" | grep -Po ">\K${pkg}-[0-9][^\" ]*\.tgz" \ - | sort -V | tail -n1) - - echo "Resolved package filename for $pkg: $PKG_FILE" - - [[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; } - - if [[ "$__hasWget" == 1 ]]; then - wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - - else - curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - - fi - done - - echo "Creating versionless symlinks for shared libraries..." - # Find all versioned .so files and create the base .so symlink - for lib in "$__RootfsDir/usr/lib/libc++.so."* "$__RootfsDir/usr/lib/libc++abi.so."* "$__RootfsDir/usr/lib/libpthread.so."*; do - if [ -f "$lib" ]; then - # Extract the filename (e.g., libc++.so.12.0) - VERSIONED_NAME=$(basename "$lib") - # Remove the trailing version numbers (e.g., libc++.so) - BASE_NAME=${VERSIONED_NAME%.so.*}.so - # Create the symlink in the same directory - ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME" - fi - done elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index ff2dfdb4a5b..0ff85cf0367 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -3,22 +3,15 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) -unset(OPENBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) - -file(GLOB OPENBSD_PROBE "${CROSS_ROOTFS}/etc/signify/openbsd-*.pub") - if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) -elseif(OPENBSD_PROBE) - set(CMAKE_SYSTEM_NAME OpenBSD) - set(OPENBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) @@ -60,8 +53,6 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") - elseif(OPENBSD) - set(triple "aarch64-unknown-openbsd") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) @@ -118,8 +109,6 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") - elseif(OPENBSD) - set(triple "x86_64-unknown-openbsd") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) @@ -204,7 +193,7 @@ if(ANDROID) # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) -elseif(FREEBSD OR OPENBSD) +elseif(FREEBSD) # we cross-compile by instructing clang set(CMAKE_C_COMPILER_TARGET ${triple}) set(CMAKE_CXX_COMPILER_TARGET ${triple}) @@ -302,7 +291,7 @@ endif() # Specify compile options -if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD AND NOT OPENBSD) OR ILLUMOS OR HAIKU) +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index a5be41db690..e3374310563 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -29,11 +29,11 @@ function InstallDarcCli ($darcVersion, $toolpath) { Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity -g" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g }else { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index b56d40e5706..e889f439b8d 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -5,7 +5,7 @@ darcVersion='' versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20' verbosity='minimal' -while [[ $# -gt 0 ]]; do +while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) @@ -73,9 +73,9 @@ function InstallDarcCli { echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g) + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) else - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index 61f302bb677..7b9d97e3bd4 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -18,7 +18,7 @@ architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' -while [[ $# -gt 0 ]]; do +while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh index f6d24871c1d..2ef68235675 100755 --- a/eng/common/dotnet.sh +++ b/eng/common/dotnet.sh @@ -19,7 +19,7 @@ source $scriptroot/tools.sh InitializeDotNetCli true # install # Invoke acquired SDK with args if they are provided -if [[ $# -gt 0 ]]; then +if [[ $# > 0 ]]; then __dotnetDir=${_InitializeDotNetCli} dotnetPath=${__dotnetDir}/dotnet ${dotnetPath} "$@" diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh index 6299e7effd4..9378223ba09 100755 --- a/eng/common/internal-feed-operations.sh +++ b/eng/common/internal-feed-operations.sh @@ -100,7 +100,7 @@ operation='' authToken='' repoName='' -while [[ $# -gt 0 ]]; do +while [[ $# > 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) diff --git a/eng/common/native/init-distro-rid.sh b/eng/common/native/init-distro-rid.sh index 8fc6d2fec78..83ea7aab0e0 100644 --- a/eng/common/native/init-distro-rid.sh +++ b/eng/common/native/init-distro-rid.sh @@ -39,8 +39,6 @@ getNonPortableDistroRid() # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" - elif [ "$targetOs" = "openbsd" ]; then - nonPortableRid="openbsd.$(uname -r)-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 4742177a768..477a44f335b 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -24,16 +24,14 @@ case "$os" in apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ - libssl-dev libkrb5-dev pigz cpio ninja-build + libssl-dev libkrb5-dev pigz cpio localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" - $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build - elif [ "$ID" = "amzn" ]; then - dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio elif [ "$ID" = "alpine" ]; then - apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja + apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio else echo "Unsupported distro. distro: $ID" exit 1 @@ -54,7 +52,6 @@ brew "openssl@3" brew "pkgconf" brew "python3" brew "pigz" -brew "ninja" EOF ;; diff --git a/eng/common/post-build/redact-logs.ps1 b/eng/common/post-build/redact-logs.ps1 index 672f4e2652e..472d5bb562c 100644 --- a/eng/common/post-build/redact-logs.ps1 +++ b/eng/common/post-build/redact-logs.ps1 @@ -9,8 +9,7 @@ param( [Parameter(Mandatory=$false)][string] $TokensFilePath, [Parameter(ValueFromRemainingArguments=$true)][String[]]$TokensToRedact, [Parameter(Mandatory=$false)][string] $runtimeSourceFeed, - [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey -) + [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey) try { $ErrorActionPreference = 'Stop' @@ -49,8 +48,8 @@ try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest - Write-Host "'$dotnet' tool install $packageName --local --source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" - & "$dotnet" tool install $packageName --local --source "$PackageFeed" -v $verbosity --version $BinlogToolVersion + Write-Host "'$dotnet' tool install $packageName --local --add-source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" + & "$dotnet" tool install $packageName --local --add-source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath diff --git a/eng/common/renovate.env b/eng/common/renovate.env deleted file mode 100644 index 17ecc05d9b1..00000000000 --- a/eng/common/renovate.env +++ /dev/null @@ -1,42 +0,0 @@ -# Renovate Global Configuration -# https://docs.renovatebot.com/self-hosted-configuration/ -# -# NOTE: This file uses bash/shell format and is sourced via `. renovate.env`. -# Values containing spaces or special characters must be quoted. - -# Author to use for git commits made by Renovate -# https://docs.renovatebot.com/configuration-options/#gitauthor -export RENOVATE_GIT_AUTHOR='.NET Renovate ' - -# Disable rate limiting for PR creation (0 = unlimited) -# https://docs.renovatebot.com/presets-default/#prhourlylimitnone -# https://docs.renovatebot.com/presets-default/#prconcurrentlimitnone -export RENOVATE_PR_HOURLY_LIMIT=0 -export RENOVATE_PR_CONCURRENT_LIMIT=0 - -# Skip the onboarding PR that Renovate normally creates for new repos -# https://docs.renovatebot.com/config-overview/#onboarding -export RENOVATE_ONBOARDING=false - -# Any Renovate config file in the cloned repository is ignored. Only -# the Renovate config file from the repo where the pipeline is running -# is used (yes, those are the same repo but the sources may be different). -# https://docs.renovatebot.com/self-hosted-configuration/#requireconfig -export RENOVATE_REQUIRE_CONFIG=ignored - -# Customize the PR body content. This removes some of the default -# sections that aren't relevant in a self-hosted config. -# https://docs.renovatebot.com/configuration-options/#prheader -# https://docs.renovatebot.com/configuration-options/#prbodynotes -# https://docs.renovatebot.com/configuration-options/#prbodytemplate -export RENOVATE_PR_HEADER='## Automated Dependency Update' -export RENOVATE_PR_BODY_NOTES='["This PR has been created automatically by the [.NET Renovate Bot](https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md) to update one or more dependencies in your repo. Please review the changes and merge the PR if everything looks good."]' -export RENOVATE_PR_BODY_TEMPLATE='{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}' - -# Extend the global config with additional presets -# https://docs.renovatebot.com/self-hosted-configuration/#globalextends -# Disable the Dependency Dashboard issue that tracks all updates -export RENOVATE_GLOBAL_EXTENDS='[":disableDependencyDashboard"]' - -# Allow all commands for post-upgrade commands. -export RENOVATE_ALLOWED_COMMANDS='[".*"]' diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 64fd2f8abec..b64b66a6275 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -66,7 +66,20 @@ try { if( $msbuildEngine -eq "vs") { # Ensure desktop MSBuild is available for sdk tasks. - $global:_MSBuildExe = InitializeVisualStudioMSBuild + if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { + $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty + } + if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "18.0.0" -MemberType NoteProperty + } + if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { + $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true + } + if ($xcopyMSBuildToolsFolder -eq $null) { + throw 'Unable to get xcopy downloadable version of msbuild' + } + + $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" } $taskProject = GetSdkTaskProject $task diff --git a/eng/common/sdl/NuGet.config b/eng/common/sdl/NuGet.config new file mode 100644 index 00000000000..3849bdb3cf5 --- /dev/null +++ b/eng/common/sdl/NuGet.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/eng/common/sdl/configure-sdl-tool.ps1 b/eng/common/sdl/configure-sdl-tool.ps1 new file mode 100644 index 00000000000..27f5a4115fc --- /dev/null +++ b/eng/common/sdl/configure-sdl-tool.ps1 @@ -0,0 +1,130 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $TargetDirectory, + [string] $GdnFolder, + # The list of Guardian tools to configure. For each object in the array: + # - If the item is a [hashtable], it must contain these entries: + # - Name = The tool name as Guardian knows it. + # - Scenario = (Optional) Scenario-specific name for this configuration entry. It must be unique + # among all tool entries with the same Name. + # - Args = (Optional) Array of Guardian tool configuration args, like '@("Target > C:\temp")' + # - If the item is a [string] $v, it is treated as '@{ Name="$v" }' + [object[]] $ToolsList, + [string] $GuardianLoggerLevel='Standard', + # Optional: Additional params to add to any tool using CredScan. + [string[]] $CrScanAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using PoliCheck. + [string[]] $PoliCheckAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using CodeQL/Semmle. + [string[]] $CodeQLAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using Binskim. + [string[]] $BinskimAdditionalRunConfigParams +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # Normalize tools list: all in [hashtable] form with defined values for each key. + $ToolsList = $ToolsList | + ForEach-Object { + if ($_ -is [string]) { + $_ = @{ Name = $_ } + } + + if (-not ($_['Scenario'])) { $_.Scenario = "" } + if (-not ($_['Args'])) { $_.Args = @() } + $_ + } + + Write-Host "List of tools to configure:" + $ToolsList | ForEach-Object { $_ | Out-String | Write-Host } + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + foreach ($tool in $ToolsList) { + # Put together the name and scenario to make a unique key. + $toolConfigName = $tool.Name + if ($tool.Scenario) { + $toolConfigName += "_" + $tool.Scenario + } + + Write-Host "=== Configuring $toolConfigName..." + + $gdnConfigFile = Join-Path $gdnConfigPath "$toolConfigName-configure.gdnconfig" + + # For some tools, add default and automatic args. + switch -Exact ($tool.Name) { + 'credscan' { + if ($targetDirectory) { + $tool.Args += "`"TargetDirectory < $TargetDirectory`"" + } + $tool.Args += "`"OutputType < pre`"" + $tool.Args += $CrScanAdditionalRunConfigParams + } + 'policheck' { + if ($targetDirectory) { + $tool.Args += "`"Target < $TargetDirectory`"" + } + $tool.Args += $PoliCheckAdditionalRunConfigParams + } + {$_ -in 'semmle', 'codeql'} { + if ($targetDirectory) { + $tool.Args += "`"SourceCodeDirectory < $TargetDirectory`"" + } + $tool.Args += $CodeQLAdditionalRunConfigParams + } + 'binskim' { + if ($targetDirectory) { + # Binskim crashes due to specific PDBs. GitHub issue: https://github.com/microsoft/binskim/issues/924. + # We are excluding all `_.pdb` files from the scan. + $tool.Args += "`"Target < $TargetDirectory\**;-:file|$TargetDirectory\**\_.pdb`"" + } + $tool.Args += $BinskimAdditionalRunConfigParams + } + } + + # Create variable pointing to the args array directly so we can use splat syntax later. + $toolArgs = $tool.Args + + # Configure the tool. If args array is provided or the current tool has some default arguments + # defined, add "--args" and splat each element on the end. Arg format is "{Arg id} < {Value}", + # one per parameter. Doc page for "guardian configure": + # https://dev.azure.com/securitytools/SecurityIntegration/_wiki/wikis/Guardian/1395/configure + Exec-BlockVerbosely { + & $GuardianCliLocation configure ` + --working-directory $WorkingDirectory ` + --tool $tool.Name ` + --output-path $gdnConfigFile ` + --logger-level $GuardianLoggerLevel ` + --noninteractive ` + --force ` + $(if ($toolArgs) { "--args" }) @toolArgs + Exit-IfNZEC "Sdl" + } + + Write-Host "Created '$toolConfigName' configuration file: $gdnConfigFile" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 new file mode 100644 index 00000000000..4715d75e974 --- /dev/null +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -0,0 +1,167 @@ +Param( + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + + # Optional: list of SDL tools to run on source code. See 'configure-sdl-tool.ps1' for tools list + # format. + [object[]] $SourceToolsList, + # Optional: list of SDL tools to run on built artifacts. See 'configure-sdl-tool.ps1' for tools + # list format. + [object[]] $ArtifactToolsList, + # Optional: list of SDL tools to run without automatically specifying a target directory. See + # 'configure-sdl-tool.ps1' for tools list format. + [object[]] $CustomToolsList, + + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional Params to custom build a Semmle/CodeQL run config in the format @("xyz < abc","sdf < 1") + [string[]] $BinskimAdditionalRunConfigParams, # Optional: Additional Params to custom build a Binskim run config in the format @("xyz < abc","sdf < 1") + [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run +) + +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } + + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } + + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel + } + $gdnFolder = Join-Path $workingDirectory '.gdn' + + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Exec-BlockVerbosely { + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 + } + } + + # Configure a list of tools with a default target directory. Populates the ".gdn/r" directory. + function Configure-ToolsList([object[]] $tools, [string] $targetDirectory) { + if ($tools -and $tools.Count -gt 0) { + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'configure-sdl-tool.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $workingDirectory ` + -TargetDirectory $targetDirectory ` + -GdnFolder $gdnFolder ` + -ToolsList $tools ` + -AzureDevOpsAccessToken $AzureDevOpsAccessToken ` + -GuardianLoggerLevel $GuardianLoggerLevel ` + -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams ` + -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams ` + -CodeQLAdditionalRunConfigParams $CodeQLAdditionalRunConfigParams ` + -BinskimAdditionalRunConfigParams $BinskimAdditionalRunConfigParams + if ($BreakOnFailure) { + Exit-IfNZEC "Sdl" + } + } + } + } + + # Configure Artifact and Source tools with default Target directories. + Configure-ToolsList $ArtifactToolsList $ArtifactsDirectory + Configure-ToolsList $SourceToolsList $SourceDirectory + # Configure custom tools with no default Target directory. + Configure-ToolsList $CustomToolsList $null + + # At this point, all tools are configured in the ".gdn" directory. Run them all in a single call. + # (If we used "run" multiple times, each run would overwrite data from earlier runs.) + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $SourceDirectory ` + -UpdateBaseline $UpdateBaseline ` + -GdnFolder $gdnFolder + } + + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Exec-BlockVerbosely { + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 + } + } + + if ($BreakOnFailure) { + Write-Host "Failing the build in case of breaking results..." + Exec-BlockVerbosely { + & $guardianCliLocation break --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + } else { + Write-Host "Letting the build pass even if there were breaking results..." + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 +} diff --git a/eng/common/sdl/extract-artifact-archives.ps1 b/eng/common/sdl/extract-artifact-archives.ps1 new file mode 100644 index 00000000000..68da4fbf257 --- /dev/null +++ b/eng/common/sdl/extract-artifact-archives.ps1 @@ -0,0 +1,63 @@ +# This script looks for each archive file in a directory and extracts it into the target directory. +# For example, the file "$InputPath/bin.tar.gz" extracts to "$ExtractPath/bin.tar.gz.extracted/**". +# Uses the "tar" utility added to Windows 10 / Windows 2019 that supports tar.gz and zip. +param( + # Full path to directory where archives are stored. + [Parameter(Mandatory=$true)][string] $InputPath, + # Full path to directory to extract archives into. May be the same as $InputPath. + [Parameter(Mandatory=$true)][string] $ExtractPath +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + Measure-Command { + $jobs = @() + + # Find archive files for non-Windows and Windows builds. + $archiveFiles = @( + Get-ChildItem (Join-Path $InputPath "*.tar.gz") + Get-ChildItem (Join-Path $InputPath "*.zip") + ) + + foreach ($targzFile in $archiveFiles) { + $jobs += Start-Job -ScriptBlock { + $file = $using:targzFile + $fileName = [System.IO.Path]::GetFileName($file) + $extractDir = Join-Path $using:ExtractPath "$fileName.extracted" + + New-Item $extractDir -ItemType Directory -Force | Out-Null + + Write-Host "Extracting '$file' to '$extractDir'..." + + # Pipe errors to stdout to prevent PowerShell detecting them and quitting the job early. + # This type of quit skips the catch, so we wouldn't be able to tell which file triggered the + # error. Save output so it can be stored in the exception string along with context. + $output = tar -xf $file -C $extractDir 2>&1 + # Handle NZEC manually rather than using Exit-IfNZEC: we are in a background job, so we + # don't have access to the outer scope. + if ($LASTEXITCODE -ne 0) { + throw "Error extracting '$file': non-zero exit code ($LASTEXITCODE). Output: '$output'" + } + + Write-Host "Extracted to $extractDir" + } + } + + Receive-Job $jobs -Wait + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 new file mode 100644 index 00000000000..f031ed5b25e --- /dev/null +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -0,0 +1,82 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored + [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +function ExtractArtifacts { + if (!(Test-Path $InputPath)) { + Write-Host "Input Path does not exist: $InputPath" + ExitWithExitCode 0 + } + $Jobs = @() + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetPath = Join-Path -Path $ExtractPath -ChildPath (Split-Path -Path $_.FullName) + [System.IO.Directory]::CreateDirectory($TargetPath); + + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.FullName + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile) + } + } + catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } + Measure-Command { ExtractArtifacts } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 new file mode 100644 index 00000000000..3ac1d92b370 --- /dev/null +++ b/eng/common/sdl/init-sdl.ps1 @@ -0,0 +1,55 @@ +Param( + [string] $GuardianCliLocation, + [string] $Repository, + [string] $BranchName='master', + [string] $WorkingDirectory, + [string] $AzureDevOpsAccessToken, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +# Don't display the console progress UI - it's a huge perf hit +$ProgressPreference = 'SilentlyContinue' + +# Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file +$encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) +$escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" +$zipFile = "$WorkingDirectory/gdn.zip" + +Add-Type -AssemblyName System.IO.Compression.FileSystem +$gdnFolder = (Join-Path $WorkingDirectory '.gdn') + +try { + # if the folder does not exist, we'll do a guardian init and push it to the remote repository + Write-Host 'Initializing Guardian...' + Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" + & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + # We create the mainbaseline so it can be edited later + Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" + & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + ExitWithExitCode 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config new file mode 100644 index 00000000000..e5f543ea68c --- /dev/null +++ b/eng/common/sdl/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 new file mode 100644 index 00000000000..2eac8c78f10 --- /dev/null +++ b/eng/common/sdl/run-sdl.ps1 @@ -0,0 +1,49 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $GdnFolder, + [string] $UpdateBaseline, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + $gdnConfigFiles = Get-ChildItem $gdnConfigPath -Recurse -Include '*.gdnconfig' + Write-Host "Discovered Guardian config files:" + $gdnConfigFiles | Out-String | Write-Host + + Exec-BlockVerbosely { + & $GuardianCliLocation run ` + --working-directory $WorkingDirectory ` + --baseline mainbaseline ` + --update-baseline $UpdateBaseline ` + --logger-level $GuardianLoggerLevel ` + --config @gdnConfigFiles + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/sdl.ps1 b/eng/common/sdl/sdl.ps1 new file mode 100644 index 00000000000..648c5068d7d --- /dev/null +++ b/eng/common/sdl/sdl.ps1 @@ -0,0 +1,38 @@ + +function Install-Gdn { + param( + [Parameter(Mandatory=$true)] + [string]$Path, + + # If omitted, install the latest version of Guardian, otherwise install that specific version. + [string]$Version + ) + + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") + + if ($Version) { + $argumentList += "-Version $Version" + } + + Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait + + $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path + + if (!$gdnCliPath) + { + Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' + } + + return $gdnCliPath.FullName +} \ No newline at end of file diff --git a/eng/common/sdl/trim-assets-version.ps1 b/eng/common/sdl/trim-assets-version.ps1 new file mode 100644 index 00000000000..0daa2a9e946 --- /dev/null +++ b/eng/common/sdl/trim-assets-version.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Install and run the 'Microsoft.DotNet.VersionTools.Cli' tool with the 'trim-artifacts-version' command to trim the version from the NuGet assets file name. + +.PARAMETER InputPath +Full path to directory where artifact packages are stored + +.PARAMETER Recursive +Search for NuGet packages recursively + +#> + +Param( + [string] $InputPath, + [bool] $Recursive = $true +) + +$CliToolName = "Microsoft.DotNet.VersionTools.Cli" + +function Install-VersionTools-Cli { + param( + [Parameter(Mandatory=$true)][string]$Version + ) + + Write-Host "Installing the package '$CliToolName' with a version of '$version' ..." + $feed = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + + $argumentList = @("tool", "install", "--local", "$CliToolName", "--add-source $feed", "--no-cache", "--version $Version", "--create-manifest-if-needed") + Start-Process "$dotnet" -Verbose -ArgumentList $argumentList -NoNewWindow -Wait +} + +# ------------------------------------------------------------------- + +if (!(Test-Path $InputPath)) { + Write-Host "Input Path '$InputPath' does not exist" + ExitWithExitCode 1 +} + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +try { + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + + $toolsetVersion = Read-ArcadeSdkVersion + Install-VersionTools-Cli -Version $toolsetVersion + + $cliToolFound = (& "$dotnet" tool list --local | Where-Object {$_.Split(' ')[0] -eq $CliToolName}) + if ($null -eq $cliToolFound) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "The '$CliToolName' tool is not installed." + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & "$dotnet" $CliToolName trim-assets-version ` + --assets-path $InputPath ` + --recursive $Recursive + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/template-guidance.md b/eng/common/template-guidance.md index f772aa3d78f..e2b07a865f1 100644 --- a/eng/common/template-guidance.md +++ b/eng/common/template-guidance.md @@ -71,6 +71,7 @@ eng\common\ source-build.yml (shim) source-index-stage1.yml (shim) jobs\ + codeql-build.yml (shim) jobs.yml (shim) source-build.yml (shim) post-build\ @@ -87,6 +88,7 @@ eng\common\ source-build.yml (shim) variables\ pool-providers.yml (logic + redirect) # templates/variables/pool-providers.yml will redirect to templates-official/variables/pool-providers.yml if you are running in the internal project + sdl-variables.yml (logic) core-templates\ job\ job.yml (logic) @@ -95,6 +97,7 @@ eng\common\ source-build.yml (logic) source-index-stage1.yml (logic) jobs\ + codeql-build.yml (logic) jobs.yml (logic) source-build.yml (logic) post-build\ diff --git a/eng/common/templates-official/jobs/codeql-build.yml b/eng/common/templates-official/jobs/codeql-build.yml new file mode 100644 index 00000000000..a726322ecfe --- /dev/null +++ b/eng/common/templates-official/jobs/codeql-build.yml @@ -0,0 +1,7 @@ +jobs: +- template: /eng/common/core-templates/jobs/codeql-build.yml + parameters: + is1ESPipeline: true + + ${{ each parameter in parameters }}: + ${{ parameter.key }}: ${{ parameter.value }} diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml new file mode 100644 index 00000000000..f1311bbb1b3 --- /dev/null +++ b/eng/common/templates-official/variables/sdl-variables.yml @@ -0,0 +1,7 @@ +variables: +# The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in +# sync with the packages.config file. +- name: DefaultGuardianVersion + value: 0.109.0 +- name: GuardianPackagesConfigFile + value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/eng/common/templates/jobs/codeql-build.yml b/eng/common/templates/jobs/codeql-build.yml new file mode 100644 index 00000000000..517f24d6a52 --- /dev/null +++ b/eng/common/templates/jobs/codeql-build.yml @@ -0,0 +1,7 @@ +jobs: +- template: /eng/common/core-templates/jobs/codeql-build.yml + parameters: + is1ESPipeline: false + + ${{ each parameter in parameters }}: + ${{ parameter.key }}: ${{ parameter.value }} diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index b6787991769..977a2d4b103 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -34,9 +34,6 @@ # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } -# Specifies semi-colon delimited list of warning codes that should not be treated as errors. -[string]$warnNotAsError = if (Test-Path variable:warnNotAsError) { $warnNotAsError } else { '' } - # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } @@ -160,6 +157,9 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { return $global:_DotNetInstallDir } + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + $env:DOTNET_MULTILEVEL_LOOKUP=0 + # Disable first run since we do not need all ASP.NET packages restored. $env:DOTNET_NOLOGO=1 @@ -225,6 +225,7 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot @@ -298,8 +299,6 @@ function InstallDotNet([string] $dotnetRoot, $dotnetVersionLabel = "'sdk v$version'" - # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs - # if you are making changes here, consider if you need to make changes there as well. if ($runtime -ne '' -and $runtime -ne 'sdk') { $runtimePath = $dotnetRoot $runtimePath = $runtimePath + "\shared" @@ -375,11 +374,12 @@ function InstallDotNet([string] $dotnetRoot, # # 1. MSBuild from an active VS command prompt # 2. MSBuild from a compatible VS installation +# 3. MSBuild from the xcopy tool package # # Returns full path to msbuild.exe. # Throws on failure. # -function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { +function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { if (-not (IsWindowsPlatform)) { throw "Cannot initialize Visual Studio on non-Windows" } @@ -389,7 +389,13 @@ function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { } # Minimum VS version to require. - $vsMinVersionReqdStr = '18.0' + $vsMinVersionReqdStr = '17.7' + $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) + + # If the version of msbuild is going to be xcopied, + # use this version. Version matches a package here: + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/18.0.0 + $defaultXCopyMSBuildVersion = '18.0.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { @@ -419,16 +425,46 @@ function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { } } - # Locate Visual Studio installation. + # Locate Visual Studio installation or download x-copy msbuild. $vsInfo = LocateVisualStudio $vsRequirements - if ($vsInfo -ne $null) { + if ($vsInfo -ne $null -and $env:ForceUseXCopyMSBuild -eq $null) { # Ensure vsInstallDir has a trailing slash $vsInstallDir = Join-Path $vsInfo.installationPath "\" $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { - throw 'Unable to find Visual Studio that has required version and components installed' + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { + $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } else { + #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download + if($vsMinVersion -lt $vsMinVersionReqd){ + Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" + $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } + else{ + # If the VS version IS compatible, look for an xcopy msbuild package + # with a version matching VS. + # Note: If this version does not exist, then an explicit version of xcopy msbuild + # can be specified in global.json. This will be required for pre-release versions of msbuild. + $vsMajorVersion = $vsMinVersion.Major + $vsMinorVersion = $vsMinVersion.Minor + $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" + } + } + + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + if ($vsInstallDir -eq $null) { + throw "Could not xcopy msbuild. Please check that package 'Microsoft.DotNet.Arcade.MSBuild.Xcopy @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." + } + } + if ($vsInstallDir -eq $null) { + throw 'Unable to find Visual Studio that has required version and components installed' + } } $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } @@ -455,6 +491,38 @@ function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [str } } +function InstallXCopyMSBuild([string]$packageVersion) { + return InitializeXCopyMSBuild $packageVersion -install $true +} + +function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { + $packageName = 'Microsoft.DotNet.Arcade.MSBuild.Xcopy' + $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" + $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" + + if (!(Test-Path $packageDir)) { + if (!$install) { + return $null + } + + Create-Directory $packageDir + + Write-Host "Downloading $packageName $packageVersion" + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + Retry({ + Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -UseBasicParsing -OutFile $packagePath + }) + + if (!(Test-Path $packagePath)) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "See https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/1074/Updating-Microsoft.DotNet.Arcade.MSBuild.Xcopy-WAS-RoslynTools.MSBuild-(xcopy-msbuild)-generation?anchor=troubleshooting for help troubleshooting issues with XCopy MSBuild" + throw + } + Unzip $packagePath $packageDir + } + + return Join-Path $packageDir 'tools' +} + # # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # @@ -524,11 +592,6 @@ function LocateVisualStudio([object]$vsRequirements = $null){ return $null } - if ($null -eq $vsInfo -or $vsInfo.Count -eq 0) { - throw "No instance of Visual Studio meeting the requirements specified was found. Requirements: $($args -join ' ')" - return $null - } - # use first matching instance return $vsInfo[0] } @@ -564,7 +627,7 @@ function InitializeBuildTool() { $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } } elseif ($msbuildEngine -eq "vs") { try { - $msbuildPath = InitializeVisualStudioMSBuild + $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 @@ -773,10 +836,6 @@ function MSBuild-Core() { $cmdArgs += ' /p:TreatWarningsAsErrors=false' } - if ($warnNotAsError) { - $cmdArgs += " /warnnotaserror:$warnNotAsError /p:AdditionalWarningsNotAsErrors=$warnNotAsError" - } - foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { diff --git a/eng/common/tools.sh b/eng/common/tools.sh index a6e0ed594fd..1b296f646c2 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -52,9 +52,6 @@ fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} -# Specifies semi-colon delimited list of warning codes that should not be treated as errors. -warn_not_as_error=${warn_not_as_error:-''} - # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} @@ -118,6 +115,9 @@ function InitializeDotNetCli { local install=$1 + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + export DOTNET_MULTILEVEL_LOOKUP=0 + # Disable first run since we want to control all package sources export DOTNET_NOLOGO=1 @@ -166,6 +166,7 @@ function InitializeDotNetCli { # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" + Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_NOLOGO" -value "1" # return value @@ -187,8 +188,6 @@ function InstallDotNet { local version=$2 local runtime=$4 - # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs - # if you are making changes here, consider if you need to make changes there as well. local dotnetVersionLabel="'$runtime v$version'" if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then runtimePath="$root" @@ -533,12 +532,7 @@ function MSBuild-Core { mt_switch="-mt" fi - local warnnotaserror_switch="" - if [[ -n "$warn_not_as_error" ]]; then - warnnotaserror_switch="/warnnotaserror:$warn_not_as_error /p:AdditionalWarningsNotAsErrors=$warn_not_as_error" - fi - - RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch $warnnotaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } function GetDarc { diff --git a/eng/renovate.json b/eng/renovate.json deleted file mode 100644 index a1129866d81..00000000000 --- a/eng/renovate.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "enabledManagers": [ - "custom.regex" - ], - "vulnerabilityAlerts": { - "enabled": false - }, - "customManagers": [ - { - "description": "Update the renovateVersion parameter in the Renovate stages template when a new Renovate Docker version is released.", - "customType": "regex", - "managerFilePatterns": [ - "/eng/common/core-templates/stages/renovate\\.yml$/" - ], - "matchStrings": [ - "renovateVersion\\n\\s+default:\\s*(?\\d+)" - ], - "depNameTemplate": "mcr.microsoft.com/dotnet-buildtools/prereqs", - "datasourceTemplate": "docker", - "extractVersionTemplate": "^azurelinux-3\\.0-renovate-(?\\d+)-amd64$" - } - ] -} diff --git a/eng/xcopy-msbuild/azure-pipelines-xcopy-msbuild.yml b/eng/xcopy-msbuild/azure-pipelines-xcopy-msbuild.yml new file mode 100644 index 00000000000..55b41095290 --- /dev/null +++ b/eng/xcopy-msbuild/azure-pipelines-xcopy-msbuild.yml @@ -0,0 +1,49 @@ +parameters: +- name: Channel + displayName: Visual Studio Build Tools Channel (ie, pre == Preview, rel == Release, intpreview == Dogfood, etc...) + type: string + default: rel + values: + - pre + - rel + - intpreview + +- name: Release + displayName: Visual Studio Build Tools Release (ie, 16, 17, etc...) + type: number + +trigger: none +pr: none + +name: BuildTools_${{ parameters.Release }}_${{ parameters.Channel }}-$(Rev:r) + +jobs: +- job: Build + displayName: Build xcopy-msbuild package + pool: + name: NetCore1ESPool-Internal + demands: ImageOverride -equals windows.vs2026.amd64 + steps: + - task: PowerShell@2 + displayName: Download Visual Studio Build Tools + inputs: + filePath: 'eng\xcopy-msbuild\install-visualstudiobuildtools.ps1' + arguments: -channel "${{ parameters.channel }}" -release "${{ parameters.release }}" -outputDirectory "$(Build.ArtifactStagingDirectory)\install" + + - task: PowerShell@2 + displayName: Build Xcopy-MSBuild package + inputs: + filePath: 'eng\xcopy-msbuild\build-msbuild-package.ps1' + arguments: -buildToolsDir "$(Build.ArtifactStagingDirectory)\install" -outputDirectory "$(Build.ArtifactStagingDirectory)\package" + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)\package' + Contents: '*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)\publish' + OverWrite: true + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)\publish' + artifactName: 'package' \ No newline at end of file diff --git a/eng/xcopy-msbuild/build-msbuild-package.ps1 b/eng/xcopy-msbuild/build-msbuild-package.ps1 new file mode 100644 index 00000000000..dfbd75a3ebe --- /dev/null +++ b/eng/xcopy-msbuild/build-msbuild-package.ps1 @@ -0,0 +1,126 @@ +[CmdletBinding(PositionalBinding=$false)] +param ( + [Parameter(Mandatory=$true)][string]$buildToolsDir, + [Parameter(Mandatory=$true)][string]$outputDirectory, + [string]$packageName = "RoslynTools.MSBuild" +) + +Set-StrictMode -version 2.0 +$ErrorActionPreference="Stop" + +function Print-Usage() { + Write-Host "build-msbuild.ps1" + Write-Host "`t-buildToolsDir path Path to Build Tools Installation" + Write-Host "`t-packageName Name of the nuget package (RoslynTools.MSBuild)" +} + +function Get-MSBuildFileInfo() { + $fileInfo = New-Object IO.FileInfo $msbuildExe + return $fileInfo +} +function Get-Description() { + $fileInfo = Get-MSBuildFileInfo + $sha = & git show-ref HEAD -s + $text = +" +This is an xcopy version of MSBuild with the following version: + +- Product Version: $($fileInfo.VersionInfo.ProductVersion) +- File Version: $($fileInfo.VersionInfo.FileVersion) + +This is built using the following tool: + +- Repo: https://github.com/dotnet/arcade/eng/xcopy-msbuild +- Source: https://github.com/dotnet/arcade/eng/xcopy-msbuild/commit/$($sha) +" + return $text +} + +function Create-ReadMe() { + Write-Host "Creating README.md" + $text = Get-Description + $text | Out-File (Join-Path $outputDirectory "README.md") +} + +function Create-Packages() { + + $text = Get-Description + $nuget = Ensure-NuGet + $fileInfo = Get-MSBuildFileInfo + $packageVersion = $fileInfo.VersionInfo.FileVersion + # Split the file version by '.' and take the first 3 components + $buildNumber = $Env:BUILD_BUILDNUMBER + if($buildNumber -ne $null) + { + $revision = $buildNumber.Substring($buildNumber.LastIndexOf('-') + 1) + } + else { + $revision = '1' + } + $packageVersion = $packageVersion.Split('.')[0..2] -join '.' + $packageVersion = "$packageVersion-$revision" + Write-Host "Packing $packageName, version $packageVersion" + & $nuget pack msbuild.nuspec -ExcludeEmptyDirectories -OutputDirectory $outputDirectory -Properties name=$packageName`;version=$packageVersion`;filePath=$outputBuildToolsDirectory`;description=$text +} + +function Create-Directory([string]$dir) { + New-Item $dir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null +} + +function Ensure-NuGet() { + $nuget = Join-Path $outputDirectory "nuget.exe" + if (-not (Test-Path $nuget)) { + Create-Directory (Split-Path -parent $nuget) + $version = "6.7.0" + Write-Host "Downloading NuGet.exe $version" + $webClient = New-Object -TypeName "System.Net.WebClient" + $webClient.DownloadFile("https://dist.nuget.org/win-x86-commandline/v$version/NuGet.exe", $nuget) + } + + return $nuget +} + +function Get-PackagesDir() { + $d = $null + if ($env:NUGET_PACKAGES -ne $null) { + $d = $env:NUGET_PACKAGES + } + else { + $d = Join-Path $env:UserProfile ".nuget\packages\" + } + + Create-Directory $d + return $d +} + +Push-Location $PSScriptRoot +try { + $repoDir = $PSScriptRoot + + $msbuildDir = Join-Path $buildToolsDir "MSBuild\Current\Bin" + $msbuildExe = Join-Path $msbuildDir "msbuild.exe" + + if (-not (Test-Path $msbuildExe)) { + Write-Host "Did not find msbuild at $msbuildExe" + exit 1 + } + + $outputBuildToolsDirectory = Join-Path $outputDirectory "BuildTools" + Create-Directory $outputBuildToolsDirectory -ErrorAction SilentlyContinue | Out-Null + Remove-Item -re -fo "$outputBuildToolsDirectory\*" + Write-Host "Copying Build Tools" + Copy-Item -re "$buildToolsDir\*" $outputBuildToolsDirectory + Create-ReadMe + Create-Packages + + exit 0 +} +catch [exception] { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 +} +finally { + Pop-Location +} diff --git a/eng/xcopy-msbuild/install-visualstudiobuildtools.ps1 b/eng/xcopy-msbuild/install-visualstudiobuildtools.ps1 new file mode 100644 index 00000000000..766b683ed42 --- /dev/null +++ b/eng/xcopy-msbuild/install-visualstudiobuildtools.ps1 @@ -0,0 +1,43 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [Parameter(Mandatory=$true)][String][Alias('c')]$channel, + [Parameter(Mandatory=$true)][String][Alias('r')]$release, + [Parameter(Mandatory=$true)][String][Alias('o')]$outputDirectory +) +# sku: BuildTools, Enterprise, Professional, Community +# Setting sku explicitly to BuildTools for xcopy MSBuild generation +$sku = "BuildTools" +# channel: pre, etc.. +# release: 16, 17, etc.. +$downloadUrl = "https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=$sku&ch=$channel&rel=$release" + +$folderName = "$sku-$channel-$release" +$destinationDir = [System.IO.Path]::Combine("$PSScriptRoot", '.download', "$folderName") +$installerFilename = "vs_installer.exe" +$installerPath = "$destinationDir\$InstallerFilename" + +if(-Not (Test-Path $destinationDir)) +{ + New-Item -ItemType 'Directory' -Path "$destinationDir" -Force | Out-Null +} +# Query the page to get the download link +$response = Invoke-WebRequest $downloadUrl -UseBasicParsing + +$regex = "downloadUrl: '(?[^']+)'" +$response.Content -Match $regex | Out-Null +$downloadLink = $Matches['downloadUrl'] + +Write-Host "download link: $downloadLink" +$response = Invoke-WebRequest $downloadLink -UseBasicParsing -OutFile "$installerPath" + +if(-Not (Test-Path $outputDirectory)) +{ + New-Item -ItemType 'Directory' -Path "$outputDirectory" -Force | Out-Null +} + +# Install +Write-Host "Installing..." +Write-Host "Start-Process -FilePath $installerPath -ArgumentList install, --installPath, $outputDirectory, --quiet, --norestart, --force, --add, 'Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools;includeRecommended' -Verb RunAs -Wait" +Start-Process -FilePath $installerPath -ArgumentList install, --installPath, $outputDirectory, --quiet, --norestart, --force, --add, 'Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools;includeRecommended' -Verb RunAs -Wait + +Write-Host "Installation complete." \ No newline at end of file diff --git a/eng/xcopy-msbuild/msbuild.nuspec b/eng/xcopy-msbuild/msbuild.nuspec new file mode 100644 index 00000000000..68944347e21 --- /dev/null +++ b/eng/xcopy-msbuild/msbuild.nuspec @@ -0,0 +1,21 @@ + + + + $name$ + $version$ + $name$ + Microsoft + http://go.microsoft.com/fwlink/?LinkID=614949 + http://aka.ms/vsextensibility + http://aka.ms/vsextensibilityicon + true + $description$ + Roslyn xcopy msbuild + https://www.visualstudio.com/news/vs2015-vs + Copyright © Microsoft + + + + + + diff --git a/es-metadata.yml b/es-metadata.yml index 76c257c77ef..f28b35357a5 100644 --- a/es-metadata.yml +++ b/es-metadata.yml @@ -4,5 +4,5 @@ accountableOwners: service: b3bbd815-183a-4142-8056-3a676d687f71 routing: defaultAreaPath: - org: dnceng - path: internal\Dotnet-Core-Engineering + org: devdiv + path: DevDiv\NET Fundamentals\Infrastructure\Arcade\SDL diff --git a/global.json b/global.json index 3f50003b140..4e7b5e61606 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "11.0.100-preview.3.26170.106", + "version": "10.0.105", "rollForward": "latestFeature", "paths": [ ".dotnet", @@ -9,11 +9,11 @@ "errorMessage": "The required .NET SDK wasn't found. Please run ./eng/common/dotnet.cmd/sh to install it." }, "tools": { - "dotnet": "11.0.100-preview.3.26170.106" + "dotnet": "10.0.105" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26203.4", - "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26203.4", + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26177.7", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26177.7", "Microsoft.Build.NoTargets": "3.7.0" } } diff --git a/scripts/create-preview-flow.ps1 b/scripts/create-preview-flow.ps1 index cb122c6635a..02db735cdf0 100644 --- a/scripts/create-preview-flow.ps1 +++ b/scripts/create-preview-flow.ps1 @@ -162,11 +162,12 @@ if ($AddInternalFlow) { Write-Host "Making default channels for SDK repos" MakeDefaultChannel https://github.com/dotnet/sdk $SdkBranch $SdkChannel +MakeDefaultChannel https://github.com/dotnet/roslyn-analyzers $SdkBranch $SdkChannel MakeDefaultChannel https://github.com/dotnet/templating $SdkBranch $SdkChannel if ($AddInternalFlow) { # Because of where internal fixes tend to be, we eliminate some leaves in the sdk graph - # and flow them through the normal public channels: templating + # and flow them through the normal public channels: templating, roslyn-analyzers Write-Host "Making default channels for SDK repos" MakeDefaultChannel https://dev.azure.com/dnceng/internal/_git/dotnet-sdk $InternalSdkBranch $InternalSdkChannel MakeDefaultChannel https://dev.azure.com/dnceng/internal/_git/dotnet-templating $InternalSdkBranch $InternalSdkChannel @@ -189,6 +190,7 @@ if ($AddInternalFlow) { Write-Host "Adding arcade flow to repos not building in the VMR" AddArcadeFlow https://dev.azure.com/dnceng/internal/_git/dotnet-wpf-int $RuntimeBranch +AddArcadeFlow https://github.com/dotnet/icu "dotnet/$RuntimeBranch" Write-Host "Adding non-VMR runtime flow" AddPackageOnlyFlow https://dev.azure.com/dnceng/internal/_git/dotnet-wpf-int $RuntimeChannel https://github.com/dotnet/wpf $RuntimeBranch EveryBuild @@ -221,12 +223,14 @@ if ($AddInternalFlow) { } Write-Host "Add VMR sdk repo forward flow" +AddForwardFlow https://github.com/dotnet/roslyn-analyzers $SdkChannel $publicVMR roslyn-analyzers $SdkBranch EveryBuild AddForwardFlow https://github.com/dotnet/templating $SdkChannel $publicVMR templating $SdkBranch EveryBuild # SDK is batched so that it batches alongside NuGet for a cohesive set of changes. AddBatchedForwardFlow https://github.com/dotnet/sdk $SdkChannel $publicVMR sdk $SdkBranch EveryBuild if ($AddInternalFlow) { Write-Host "Adding internal VMR sdk repo forward flow" + AddForwardFlow https://dev.azure.com/dnceng/internal/_git/dotnet-roslyn-analyzers $InternalSdkChannel $internalVMR roslyn-analyzers $InternalSdkBranch EveryBuild AddForwardFlow https://dev.azure.com/dnceng/internal/_git/dotnet-sdk $InternalSdkChannel $internalVMR sdk $InternalSdkBranch EveryBuild AddForwardFlow https://dev.azure.com/dnceng/internal/_git/dotnet-templating $InternalSdkChannel $internalVMR templating $InternalSdkBranch EveryBuild @@ -235,7 +239,10 @@ if ($AddInternalFlow) { } Write-Host "Adding tooling repo VMR forward flow" -AddForwardFlow https://github.com/nuget/nuget.client $VSChannel $publicVMR nuget-client $SdkBranch EveryBuild +# NuGet is special in that it flows into the SDK and then batched source into the VMR +# Change to traditional flow when https://github.com/dotnet/arcade-services/issues/4665 is resolved +AddPackageOnlyFlow https://github.com/nuget/nuget.client $VSChannel https://github.com/dotnet/sdk $SdkBranch EveryBuild +AddBatchedForwardFlow https://github.com/nuget/nuget.client $VSChannel $publicVMR nuget-client $SdkBranch EveryBuild AddForwardFlow https://github.com/dotnet/roslyn $VSChannel $publicVMR roslyn $SdkBranch EveryBuild AddForwardFlow https://github.com/dotnet/fsharp $VSChannel $publicVMR fsharp $SdkBranch EveryBuild AddForwardFlow https://github.com/dotnet/msbuild $VSChannel $publicVMR msbuild $SdkBranch EveryBuild @@ -248,9 +255,6 @@ if ($AddInternalFlow) { throw "NYI" } -Write-Host "Adding VMR->repo package flow" -AddPackageOnlyFlow https://github.com/dotnet/dotnet $SdkBranch https://github.com/dotnet/icu "dotnet/$RuntimeBranch" None - Write-Host "Adding VMR->repo backflow." # Only repos that branch for a release get backflow AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/runtime runtime $RuntimeBranch EveryBuild @@ -260,6 +264,7 @@ AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/emsdk emsdk $R AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/windowsdesktop windowsdesktop $RuntimeBranch EveryBuild AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/winforms winforms $RuntimeBranch EveryBuild AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/wpf wpf $RuntimeBranch EveryBuild +AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/roslyn-analyzers templating $SdkBranch EveryBuild AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/sdk sdk $SdkBranch EveryBuild AddBackwardsFlow $publicVMR $SdkChannel https://github.com/dotnet/templating templating $SdkBranch EveryBuild diff --git a/scripts/m2m-dotnet.ps1 b/scripts/m2m-dotnet.ps1 new file mode 100644 index 00000000000..b8f8bb6562e --- /dev/null +++ b/scripts/m2m-dotnet.ps1 @@ -0,0 +1,404 @@ +<# +.SYNOPSIS +Prepares data for migration, disables subscriptions, migrates and verifies +DARC subcriptions and default channels. + +.DESCRIPTION +This script runs in 3 modes: + 1. GenerateDataFile - creates json file which describes DARC migration. + 2. DisableSubscriptions - disables targeting subscriptions for your repository and old branch. + 3. Migration - using json file generated during Initializatin, this script removes default channels + and targeting subscriptions for your repository and branch. Then it recreates them under + a new branch. + 4. Verification - Compares default channels and targeting subscriptions from json file against current + state in DARC. + +.PARAMETER Repository +Mandatory short name of GitHub repository (e.g. dotnet/runtime or dotnet/wpf). This short name is transformed to +public and internal repository (e.g. for dotnet/runtime https://github.com/dotnet/runtime and +https://dev.azure.com/dnceng/internal/_git/dotnet-runtime). + +.PARAMETER NewBranch +Optional new name of branch, defaults to 'main'. + +.PARAMETER OldBranch +Optional old name of branch, defaults to 'master'. + +.PARAMETER GenerateDataFile +Switch to run in generate data file mode. Repository parameter is required and NewBranch, OldBranch are optional. + +.PARAMETER DisableSubscriptions +Switch to run in disable subscriptions on old branch mode. DataFile parameter is required. + +.PARAMETER Migrate +Switch to run in migration mode. DataFile parameter is required. + +.PARAMETER Verify +Switch to run in verification mode. DataFile parameter is required. + +.PARAMETER DataFile +json file path used for DARC validation. + +.PARAMETER DryRun +When specified then no DARC updates are executed, but only logged. + +.EXAMPLE +1. For initilization execute: +./m2m-dotnet.ps1 -GenerateDataFile -Repository dotnet/m2m-renaming-test-1 +or you can additionaly specify branch names: +./m2m-dotnet.ps1 -GenerateDataFile -Repository dotnet/m2m-renaming-test-1 -OldBranch master -NewBranch main + +This generates data file m2m-dotnet_[timestamp].json and disables all targeting subscriptions. + +2. To disable targeting subscriptions for your repository and old branch execute: +.\m2m-dotnet.ps1 -DisableSubscriptions -DataFile m2m-dotnet_[timestamp].json + +3. For migration execute: +.\m2m-dotnet.ps1 -Migrate -DataFile m2m-dotnet_[timestamp].json + +4. For verification execute: +.\m2m-dotnet.ps1 -Verify -DataFile m2m-dotnet_[timestamp].json + +#> + +[CmdletBinding()] +param ( + + [Parameter(ParameterSetName = 'GenerateDataFile', Mandatory = $true)] + [switch]$GenerateDataFile, + + [Parameter(ParameterSetName = 'DisableSubscriptions', Mandatory = $true)] + [switch]$DisableSubscriptions, + + [Parameter(ParameterSetName = 'Migrate', Mandatory = $true)] + [switch]$Migrate, + + [Parameter(ParameterSetName = 'Verify', Mandatory = $true)] + [switch]$Verify, + + [Parameter(ParameterSetName = 'GenerateDataFile', Mandatory = $true)] + [string]$Repository, + [Parameter(ParameterSetName = 'GenerateDataFile')] + [string]$NewBranch = "main", + [Parameter(ParameterSetName = 'GenerateDataFile')] + [string]$OldBranch = "master", + + [Parameter(ParameterSetName = 'Verify', Mandatory = $true)] + [Parameter(ParameterSetName = 'Migrate', Mandatory = $true)] + [Parameter(ParameterSetName = 'DisableSubscriptions', Mandatory = $true)] + [string]$DataFile, + + [switch]$DryRun = $false +) + + +Class DarcExecutor { + [bool]$DryRun = $false + + [string[]] ParseIgnoreChecks([string] $line) { + $ignoreChecks = @() + # Matches fragment like : ignoreChecks = [ "WIP", "license/cla" ] + if ($line -match "ignoreChecks\s*=\s*\[\s*([^\]]+)\s*\]") { + $ignoreChecksValuesMatches = [regex]::matches($matches[1], "`"([^`"]+)`"") + ForEach ($check in $ignoreChecksValuesMatches) { + $ignoreChecks += $check.Groups[1].Value + } + } + + return $ignoreChecks + } + + [object[]] ParseMergePolicies([string] $line) { + $line = $line -replace "ignoreChecks\s*=\s*\[\s*[^\]]*\s*\]", "" + $policies = $line -split "\s+" | Where-Object { $_ } + return $policies + } + + [object[]] ParseSubscriptions([string] $output) { + $darcOutputLines = $output.Split([Environment]::NewLine) + $list = @() + $processingMergePolicies = $false + $batchable = $fromRepo = $fromChannel = $updateFrequency = $enabled = $mergePolicies = $null + For ($i = 0; $i -le $darcOutputLines.Length; $i++) { + $line = $darcOutputLines[$i] + # Matches header like: https://github.com/dotnet/arcade (.NET Eng - Latest) ==> 'https://github.com/dotnet/m2m-renaming-test-1' ('main') + if ($line -match "([^\s]+)\s+\(([^\)]+)\)\s+==>\s+'([^']+)'\s+\('([^\)]+)'\)") { + if ($i -ne 0) { + $list += @{fromRepo = $fromRepo; fromChannel = $fromChannel; updateFrequency = $updateFrequency; enabled = $enabled; batchable = $batchable; ignoreChecks = @($this.ParseIgnoreChecks($mergePolicies)); mergePolicies = @($this.ParseMergePolicies($mergePolicies)) }; + } + + $updateFrequency = $enabled = $batchable = $mergePolicies = "" + + $fromRepo = $matches[1] + $fromChannel = $matches[2] + continue + } + # Matches field like: - Update Frequency: EveryWeek + if ($line -match "^\s+\-\s+([^:]+):\s*(.*)") { + $processingMergePolicies = $false + if ($matches[1] -eq "Update Frequency") { + $updateFrequency = $matches[2] + continue + } + if ($matches[1] -eq "Enabled") { + $enabled = $matches[2] + continue + } + if ($matches[1] -eq "Batchable") { + $batchable = $matches[2] + continue + } + if ($matches[1] -eq "Merge Policies") { + $mergePolicies = $matches[2] + $processingMergePolicies = $true + continue + } + } + if ($processingMergePolicies) { + $mergePolicies += $line + continue + } + } + + if ($null -ne $fromRepo) { + $list += @{fromRepo = $fromRepo; fromChannel = $fromChannel; updateFrequency = $updateFrequency; enabled = $enabled; batchable = $batchable; ignoreChecks = @($this.ParseIgnoreChecks($mergePolicies)); mergePolicies = @($this.ParseMergePolicies($mergePolicies)) }; + } + + return $list + } + + [object[]] GetSubscriptions([string]$repo, [string]$branch) { + $arguments = @("get-subscriptions", "--exact", "--target-repo", $repo, "--target-branch", $branch) + $output = $this.Execute($arguments, $false) + $subscriptions = @($this.ParseSubscriptions($output)) + return $subscriptions + } + + + [void]AddSubscription($repo, $branch, $item) { + $arguments = @("add-subscription", "--channel", $item.fromChannel, "--source-repo", $item.fromRepo, "--target-repo", $repo, "--update-frequency", $item.updateFrequency, "--target-branch", $branch, "--no-trigger", "-q") + $policiesArguments = @("set-repository-policies", "--repo", $repo, "--branch", $branch, "-q") + $targetArgumentsRef = [ref]$arguments + if ($item.batchable -eq "True") { + $arguments += "--batchable" + $targetArgumentsRef = [ref]$policiesArguments + } + if ($item.mergePolicies -contains "Standard") { + $targetArgumentsRef.value += "--standard-automerge" + } + if ($item.mergePolicies -like "NoRequestedChanges") { + $targetArgumentsRef.value += "--no-requested-changes" + } + if ($item.mergePolicies -like "NoExtraCommits") { + $targetArgumentsRef.value += "--no-extra-commits" + } + if ($item.mergePolicies -like "AllChecksSuccessful") { + $targetArgumentsRef.value += "--all-checks-passed" + } + if ($item.ignoreChecks.length -gt 0) { + $targetArgumentsRef.value += "--ignore-checks" + $targetArgumentsRef.value += $item.ignoreChecks -join "," + } + + if ($item.batchable -eq "True") { + $this.Execute($policiesArguments, $true) + } + + $output = $this.Execute($arguments, $true) + + if ($output -match "Successfully created new subscription with id '([^']+)'.") { + $id = $matches[1] + if ($item.enabled -eq [bool]::FalseString) { + $this.DisableSubscription($id) + } + } + else { + Write-Error(" WARNING: {0}" -f $output) + } + } + + [void]DeleteSubscriptions($repo, $branch) { + $arguments = @("get-subscriptions", "--exact", "--target-repo", $repo, "--target-branch", $branch) + $output = $this.Execute($arguments, $false) + if (-not ($output -match "^No subscriptions found matching the specified criteria.")) { + Write-Host ("Deleting subscriptions for {0} {1}" -f $repo, $branch) + $arguments = @("delete-subscriptions", "--exact", "--target-repo", $repo, "--target-branch", $branch, "-q") + $this.Execute($arguments, $true) + } + } + + [void]CreateDefaultChannel($repo, $branch, $channel) { + Write-Host ("Creating default channel {2} for branch {0} {1}" -f $repo, $branch, $channel) + $arguments = @("add-default-channel", "--repo", $repo, "--branch", $branch, "--channel", $channel, "-q") + $this.Execute($arguments, $true) + } + + [void]DisableSubscription ([string] $id) { + Write-Host ("Disabling subscription {0}" -f $id) + $arguments = @("subscription-status", "--id", $id, "-d", "-q") + $this.Execute($arguments, $true) + } + + [string[]]GetTargetSubscriptionIds ([string] $repo, [string] $branch) { + $arguments = @("get-subscriptions", "--exact", "--target-repo", $repo, "--target-branch", $branch) + $ids = $this.Execute($arguments, $false) | Select-String -AllMatches -Pattern "\s+-\s+Id:\s+([^\s]+)" | ForEach-Object { $_.Matches } | Foreach-Object { $_.Groups[1].Value } + return $ids + } + + [void]DisableTargetSubscriptions ([string] $repo, [string] $branch) { + Write-Host "Disabling targeting subscriptions for $repo ($branch)" + + $ids = $this.GetTargetSubscriptionIds($repo, $branch) + ForEach ($id in $ids) { + $this.DisableSubscription($id) + } + } + + [Hashtable[]]GetDefaultChannels ([string] $repo, [string] $branch) { + $arguments = @("get-default-channels", "--source-repo", $repo, "--branch", $branch) + $output = $this.Execute($arguments, $true) + $records = @($output | Select-String -AllMatches -Pattern "\((\d+)\)\s+$repo\s+@\s+$branch\s+->\s+(.*)\b" | ForEach-Object { $_.Matches } | ForEach-Object { @{id = $_.Groups[1].Value; channel = $_.Groups[2].Value } }) + return $records + } + + [void]DeleteDefaultChannel([string] $id) { + Write-Host ("Deleting default channel {0}" -f $id) + $arguments = @("delete-default-channel", "--id", $id) + $this.Execute($arguments, $true) + } + + [void]DeleteDefaultChannels([string] $repo, [string] $branch) { + $channels = @($this.GetDefaultChannels($repo, $branch)) + ForEach ($item in $channels) { + $this.DeleteDefaultChannel($item.id) + } + } + + [Hashtable]GetRepoConfig([string] $repo, [string] $newBranch, [string] $oldBranch) { + $defaultChannels = @($this.GetDefaultChannels($repo, $oldBranch) | ForEach-Object { $_.channel }) + $subscriptions = @($this.GetSubscriptions($repo, $oldBranch)) + $config = @{repo = $repo; newBranch = $newBranch; oldBranch = $oldBranch; defaultChannels = $defaultChannels; subscriptions = $subscriptions; } + return $config + } + + [void]MigrateRepo([PSCustomObject]$config) { + Write-Host (">>>Migrating repository {0} {1} ==> {2}..." -f $config.repo, $config.oldBranch, $config.newBranch) + + $this.DeleteDefaultChannels($config.repo, $config.oldBranch) + ForEach ($channel in $config.defaultChannels) { + $this.CreateDefaultChannel($config.repo, $config.newBranch, $channel) + } + + $this.DeleteSubscriptions($config.repo, $config.oldBranch) + $this.DeleteSubscriptions($config.repo, $config.newBranch) + + Write-Host ("Adding subscriptions") + ForEach ($item in $config.subscriptions) { + $this.AddSubscription($config.repo, $config.newBranch, $item) + } + } + + [void]VerifyRepo([PSCustomObject]$config) { + Write-Host (">>>Verifying repository {0} {1} ==> {2}..." -f $config.repo, $config.oldBranch, $config.newBranch) + if ($this.GetDefaultChannels($config.repo, $config.oldBranch).length -ne 0) { + throw("Default channels for old branch haven't been removed.") + } + if ($this.GetTargetSubscriptionIds($config.repo, $config.oldBranch).length -ne 0) { + throw("Subscriptions for old branch haven't been removed.") + } + + $actualConfig = $this.GetRepoConfig($config.repo, $config.oldBranch, $config.newBranch) + if ($actualConfig.defaultChannels.length -ne $config.defaultChannels.length) { + throw("Subscriptions for old branch haven't been removed.") + } + + $expectedDefaultChannels = ConvertTo-Json($actualConfig.defaultChannels | Sort-Object) + $actualDefaultChannels = ConvertTo-Json($config.defaultChannels | Sort-Object) + if ($expectedDefaultChannels -ne $actualDefaultChannels) { + throw("Expected default channels {0} don't match actual {1}." -f $actualDefaultChannels, $actualDefaultChannels) + } + + $actualSubscriptions = ConvertTo-Json($actualConfig.subscriptions | Sort-Object { $_.fromRepo }) + $expectedSubscriptions = ConvertTo-Json($config.subscriptions | Sort-Object { $_.fromRepo }) + + if ($expectedSubscriptions -ne $actualSubscriptions) { + throw("Expected subscriptions {0} don't match actual {1}." -f $expectedSubscriptions, $actualSubscriptions) + } + + Write-Host ("Validation of {0} passed" -f $config.repo) + } + + [string]Execute ([string[]] $arguments, [bool]$exitCodeCheck) { + if ($this.DryRun -and ($arguments[0] -ne "get-default-channels") -and ($arguments[0] -ne "get-subscriptions")) { + Write-Host (">>> darc {0}" -f ($arguments -join " ")) + return "Successfully created new subscription with id 'TEST_ID'." + } + else { + $output = (&"darc" $arguments | Out-String) + if ($exitCodeCheck -and $LASTEXITCODE -ne 0) { + throw (" Error executing command ""darc {0}"" with status code {1}: {2}" -f ($arguments -join " "), $LASTEXITCODE, $output) + } + return $output + } + } +} +function InitializeDarc { + param ( + [DarcExecutor] $darc + ) + $configFile = "m2m-dotnet_{0:yyyyMMdd_HHmmss}.json" -f (get-date) + $internalRepo = "https://dev.azure.com/dnceng/internal/_git/{0}" -f ($Repository -replace "/", "-") + $publicRepo = "https://github.com/{0}" -f $Repository + + Write-Host ("Creating configuration for repository {0} {1} ==> {2}..." -f $publicRepo, $OldBranch, $NewBranch) + $configPublic = $darc.GetRepoConfig($publicRepo, $NewBranch, $OldBranch) + + Write-Host ("Creating configuration for repository {0} {1} ==> {2}..." -f $internalRepo, $OldBranch, $NewBranch) + $configInternal = $darc.GetRepoConfig($internalRepo, $NewBranch, $OldBranch) + + $configs = @($configPublic, $configInternal) + ConvertTo-Json $configs -Depth 4 | Out-File -FilePath $configFile + Write-Host ("Configuration has been saved as {0}" -f $configFile) +} + +function DisableDarcSubscriptions { + param ( + [DarcExecutor] $darc + ) + + $configs = Get-Content -Raw -Path $DataFile | ConvertFrom-Json + ForEach ($config in $configs) { + $darc.DisableTargetSubscriptions($config.repo, $config.oldBranch) + } +} +function MigrateDarc { + param ( + [DarcExecutor]$darc + ) + + $configs = Get-Content -Raw -Path $DataFile | ConvertFrom-Json + ForEach ($config in $configs) { + $darc.MigrateRepo($config) + } +} +function VerifyDarc { + param ( + [DarcExecutor]$darc + ) + + $configs = Get-Content -Raw -Path $DataFile | ConvertFrom-Json + ForEach ($config in $configs) { + $darc.VerifyRepo($config) + } +} + +$ErrorActionPreference = 'Stop' +$darc = [DarcExecutor]::new() +$darc.DryRun = $DryRun + +switch ($PSCmdlet.ParameterSetName) { + "GenerateDataFile" { InitializeDarc -darc $darc } + "DisableSubscriptions" { DisableDarcSubscriptions -darc $darc } + "Migrate" { MigrateDarc -darc $darc } + "Verify" { VerifyDarc -darc $darc } +} diff --git a/src/Common/Internal/AssemblyResolution.cs b/src/Common/Internal/AssemblyResolution.cs new file mode 100644 index 00000000000..caea03260c1 --- /dev/null +++ b/src/Common/Internal/AssemblyResolution.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. + +#if NET472 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.DotNet +{ + internal static class AssemblyResolution + { + internal static TaskLoggingHelper Log; + + public static void Initialize() + { + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + } + + private static Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + var name = new AssemblyName(args.Name); + + if (!name.Name.Equals("System.Collections.Immutable", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Collections.Immutable.dll"); + + Assembly sci; + try + { + sci = Assembly.LoadFile(fullPath); + } + catch (Exception e) + { + Log?.LogWarning($"AssemblyResolve: exception while loading '{fullPath}': {e.Message}"); + return null; + } + + if (name.Version <= sci.GetName().Version) + { + Log?.LogMessage(MessageImportance.Low, $"AssemblyResolve: loaded '{fullPath}' to {AppDomain.CurrentDomain.FriendlyName}"); + return sci; + } + + return null; + } + } +} + +#endif diff --git a/src/Common/Internal/AssemblyResolver.cs b/src/Common/Internal/AssemblyResolver.cs new file mode 100644 index 00000000000..d398408cc29 --- /dev/null +++ b/src/Common/Internal/AssemblyResolver.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. + +#nullable disable + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace Microsoft.Arcade.Common.Desktop +{ + /// + /// Used to enable app-local assembly unification. + /// + internal static class AssemblyResolver + { + static AssemblyResolver() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + /// + /// Call to enable the assembly resolver for the current AppDomain. + /// + public static void Enable() + { + // intentionally empty. This is just meant to ensure the static constructor + // has run. + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // apply any existing policy + AssemblyName referenceName = new AssemblyName(AppDomain.CurrentDomain.ApplyPolicy(args.Name)); + + string fileName = referenceName.Name + ".dll"; + string assemblyPath = null; + string probingPath = null; + Assembly assm = null; + + // look next to requesting assembly + assemblyPath = args.RequestingAssembly?.Location; + if (!String.IsNullOrEmpty(assemblyPath)) + { + probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); + Debug.WriteLine($"Considering {probingPath} based on RequestingAssembly"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + } + + // look next to the executing assembly + assemblyPath = Assembly.GetExecutingAssembly().Location; + if (!String.IsNullOrEmpty(assemblyPath)) + { + probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); + + Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + } + + // look in AppDomain base directory + probingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + Debug.WriteLine($"Considering {probingPath} based on BaseDirectory"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + + // look in current directory + Debug.WriteLine($"Considering {fileName}"); + if (Probe(fileName, referenceName.Version, out assm)) + { + return assm; + } + + return null; + } + + /// + /// Considers a path to load for satisfying an assembly ref and loads it + /// if the file exists and version is sufficient. + /// + /// Path to consider for load + /// Minimum version to consider + /// loaded assembly + /// true if assembly was loaded + private static bool Probe(string filePath, Version minimumVersion, out Assembly assembly) + { + if (File.Exists(filePath)) + { + AssemblyName name = AssemblyName.GetAssemblyName(filePath); + + if (name.Version >= minimumVersion) + { + assembly = Assembly.Load(name); + return true; + } + } + + assembly = null; + return false; + } + } +} diff --git a/src/Common/Internal/BuildTask.Desktop.cs b/src/Common/Internal/BuildTask.Desktop.cs new file mode 100644 index 00000000000..f71609229a9 --- /dev/null +++ b/src/Common/Internal/BuildTask.Desktop.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. + +namespace Microsoft.Arcade.Common.Desktop +{ + public partial class BuildTask + { + static BuildTask() + { + AssemblyResolver.Enable(); + } + } +} diff --git a/src/Common/Microsoft.Arcade.Common.Tests/ArgumentEscaperTests.cs b/src/Common/Microsoft.Arcade.Common.Tests/ArgumentEscaperTests.cs index 7033e48ab8d..5dd54761600 100644 --- a/src/Common/Microsoft.Arcade.Common.Tests/ArgumentEscaperTests.cs +++ b/src/Common/Microsoft.Arcade.Common.Tests/ArgumentEscaperTests.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. -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Xunit; diff --git a/src/Common/Microsoft.Arcade.Common.Tests/Microsoft.Arcade.Common.Tests.csproj b/src/Common/Microsoft.Arcade.Common.Tests/Microsoft.Arcade.Common.Tests.csproj index 708b2001440..11cd8e38804 100644 --- a/src/Common/Microsoft.Arcade.Common.Tests/Microsoft.Arcade.Common.Tests.csproj +++ b/src/Common/Microsoft.Arcade.Common.Tests/Microsoft.Arcade.Common.Tests.csproj @@ -1,12 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) enable - + diff --git a/src/Common/Microsoft.Arcade.Common/Command.cs b/src/Common/Microsoft.Arcade.Common/Command.cs index c7c1ef638dc..0ab31c5ad61 100644 --- a/src/Common/Microsoft.Arcade.Common/Command.cs +++ b/src/Common/Microsoft.Arcade.Common/Command.cs @@ -118,7 +118,11 @@ public ICommand WorkingDirectory(string projectDirectory) public ICommand EnvironmentVariable(string name, string value) { +#if NET45 + _process.StartInfo.EnvironmentVariables[name] = value; +#else _process.StartInfo.Environment[name] = value; +#endif _process.StartInfo.UseShellExecute = false; return this; } diff --git a/src/Common/Microsoft.Arcade.Common/MSBuildTaskBase.Desktop.cs b/src/Common/Microsoft.Arcade.Common/MSBuildTaskBase.Desktop.cs new file mode 100644 index 00000000000..39d999e8f92 --- /dev/null +++ b/src/Common/Microsoft.Arcade.Common/MSBuildTaskBase.Desktop.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Arcade.Common.Desktop; + +namespace Microsoft.Arcade.Common +{ + public partial class MSBuildTaskBase + { + static MSBuildTaskBase() + { + AssemblyResolver.Enable(); + } + } +} diff --git a/src/Common/Microsoft.Arcade.Common/Microsoft.Arcade.Common.csproj b/src/Common/Microsoft.Arcade.Common/Microsoft.Arcade.Common.csproj index fbc3b77904b..bf6d4c11032 100644 --- a/src/Common/Microsoft.Arcade.Common/Microsoft.Arcade.Common.csproj +++ b/src/Common/Microsoft.Arcade.Common/Microsoft.Arcade.Common.csproj @@ -2,8 +2,9 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);netstandard2.0;$(NetFrameworkToolCurrent) true + **/*.Desktop.* @@ -12,4 +13,14 @@ + + + + + + + + + + diff --git a/src/Common/Microsoft.Arcade.Test.Common/Microsoft.Arcade.Test.Common.csproj b/src/Common/Microsoft.Arcade.Test.Common/Microsoft.Arcade.Test.Common.csproj index 53ed94d1db7..4867a58638f 100644 --- a/src/Common/Microsoft.Arcade.Test.Common/Microsoft.Arcade.Test.Common.csproj +++ b/src/Common/Microsoft.Arcade.Test.Common/Microsoft.Arcade.Test.Common.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true @@ -10,6 +10,10 @@ + + + + diff --git a/src/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj b/src/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj index 3e834bcfe86..4a6d36711e0 100644 --- a/src/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj +++ b/src/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj @@ -1,7 +1,8 @@ - $(BundledNETCoreAppTargetFramework) + + $(NetToolCurrent);netstandard2.0;$(NetFrameworkToolCurrent) true true true @@ -19,4 +20,16 @@ + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/CentralPackageManagementTests.cs b/src/Microsoft.DotNet.Arcade.Sdk.Tests/CentralPackageManagementTests.cs deleted file mode 100644 index 8e06b67f0c8..00000000000 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/CentralPackageManagementTests.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Xunit; - -namespace Microsoft.DotNet.Arcade.Sdk.Tests -{ - /// - /// Validates that implicitly defined PackageReferences in .targets and .props files - /// do not conflict with PackageVersion entries in Directory.Packages.props. - /// NuGet CPM rule: implicit PackageReferences cannot have a corresponding - /// PackageVersion entry — they must set Version directly on the PackageReference. - /// Violation produces NU1009 at restore time. - /// - public class CentralPackageManagementTests - { - private static readonly string? s_repoRoot = TryGetRepoRoot(); - - [Fact] - public void ImplicitPackageReferences_ShouldNotConflictWithPackageVersionEntries() - { - if (s_repoRoot == null) - { - // Running on Helix or outside the repo — skip - return; - } - - var directoryPackagesPropsPath = Path.Combine(s_repoRoot, "Directory.Packages.props"); - Assert.True(File.Exists(directoryPackagesPropsPath), $"Directory.Packages.props not found at {directoryPackagesPropsPath}"); - - // Collect all PackageVersion entries from Directory.Packages.props - var packageVersionIds = GetPackageIds(directoryPackagesPropsPath, "PackageVersion"); - - // Find all IsImplicitlyDefined="true" PackageReferences in .targets and .props files - var implicitReferences = new List<(string file, string packageId)>(); - var sdkToolsDir = Path.Combine(s_repoRoot, "src", "Microsoft.DotNet.Arcade.Sdk", "tools"); - - foreach (var file in Directory.EnumerateFiles(sdkToolsDir, "*.targets", SearchOption.AllDirectories) - .Concat(Directory.EnumerateFiles(sdkToolsDir, "*.props", SearchOption.AllDirectories))) - { - foreach (var id in GetImplicitPackageReferenceIds(file)) - { - implicitReferences.Add((file, id)); - } - } - - // Assert: no implicit PackageReference should have a matching PackageVersion - var conflicts = implicitReferences - .Where(r => packageVersionIds.Contains(r.packageId)) - .ToList(); - - Assert.True(conflicts.Count == 0, - $"NU1009 conflict: the following implicitly defined PackageReferences have " + - $"corresponding PackageVersion entries in Directory.Packages.props. " + - $"Remove the PackageVersion entries or remove IsImplicitlyDefined from the PackageReference.\n" + - string.Join("\n", conflicts.Select(c => - $" - '{c.packageId}' (implicit in {Path.GetRelativePath(s_repoRoot, c.file)})"))); - } - - /// - /// Collects PackageVersion Include values from an XML file, - /// following simple Import directives (relative paths without MSBuild properties). - /// - private static HashSet GetPackageIds(string propsFile, string elementName) - { - var result = new HashSet(StringComparer.OrdinalIgnoreCase); - CollectElementIds(propsFile, elementName, result); - return result; - } - - private static void CollectElementIds(string xmlFile, string elementName, HashSet result) - { - XDocument doc; - try - { - doc = XDocument.Load(xmlFile); - } - catch - { - return; - } - - string? directory = Path.GetDirectoryName(xmlFile); - - foreach (var element in doc.Descendants()) - { - if (element.Name.LocalName == elementName) - { - string? id = element.Attribute("Include")?.Value; - if (id != null) - { - result.Add(id); - } - } - else if (element.Name.LocalName == "Import") - { - string? project = element.Attribute("Project")?.Value; - if (project != null && !project.Contains("$(") && directory != null) - { - string importPath = Path.GetFullPath(Path.Combine(directory, project)); - if (File.Exists(importPath)) - { - CollectElementIds(importPath, elementName, result); - } - } - } - } - } - - private static IEnumerable GetImplicitPackageReferenceIds(string targetsFile) - { - XDocument doc; - try - { - doc = XDocument.Load(targetsFile); - } - catch (System.Xml.XmlException) - { - // Non-XML files with .targets/.props extension — skip - yield break; - } - - foreach (var element in doc.Descendants().Where(e => e.Name.LocalName == "PackageReference")) - { - var isImplicit = element.Attribute("IsImplicitlyDefined")?.Value; - if (string.Equals(isImplicit, "true", StringComparison.OrdinalIgnoreCase)) - { - var id = element.Attribute("Include")?.Value; - if (id != null) - { - yield return id; - } - } - } - } - - private static string? TryGetRepoRoot() - { - var dir = AppContext.BaseDirectory; - while (dir != null) - { - if (File.Exists(Path.Combine(dir, "Directory.Packages.props")) && - Directory.Exists(Path.Combine(dir, "src", "Microsoft.DotNet.Arcade.Sdk"))) - { - return dir; - } - dir = Path.GetDirectoryName(dir); - } - - dir = Directory.GetCurrentDirectory(); - while (dir != null) - { - if (File.Exists(Path.Combine(dir, "Directory.Packages.props")) && - Directory.Exists(Path.Combine(dir, "src", "Microsoft.DotNet.Arcade.Sdk"))) - { - return dir; - } - dir = Path.GetDirectoryName(dir); - } - - return null; - } - } -} diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj index d8dd48a2ef3..01234e4c065 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) $(DefaultItemExcludes);testassets\**\* diff --git a/src/Microsoft.DotNet.Arcade.Sdk/Microsoft.DotNet.Arcade.Sdk.csproj b/src/Microsoft.DotNet.Arcade.Sdk/Microsoft.DotNet.Arcade.Sdk.csproj index 861028474b9..baebf769c53 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/Microsoft.DotNet.Arcade.Sdk.csproj +++ b/src/Microsoft.DotNet.Arcade.Sdk/Microsoft.DotNet.Arcade.Sdk.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true @@ -26,6 +26,11 @@ + + + + + @@ -42,15 +47,13 @@ PackagePath="tools\DefaultVersions.Generated.props" /> + + + + - + @@ -81,14 +84,4 @@ - - - - - diff --git a/src/Microsoft.DotNet.Arcade.Sdk/src/DownloadFile.cs b/src/Microsoft.DotNet.Arcade.Sdk/src/DownloadFile.cs index d76eb4aa53a..cd1a18098d2 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/src/DownloadFile.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk/src/DownloadFile.cs @@ -128,6 +128,7 @@ private async Tasks.Task DownloadFromUriAsync(string uri) { // on Mac if the endpoint is not available. This is only available on .NET Core, but has only been // observed on Mac anyway. +#if NET using SocketsHttpHandler handler = new SocketsHttpHandler(); handler.SslOptions.CertificateChainPolicy = new X509ChainPolicy { @@ -150,6 +151,9 @@ private async Tasks.Task DownloadFromUriAsync(string uri) { }; using (var httpClient = new HttpClient(handler)) +#else + using (var httpClient = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true })) +#endif { httpClient.Timeout = TimeSpan.FromSeconds(TimeoutInSeconds); try @@ -211,11 +215,11 @@ private async Tasks.Task DownloadWithRetriesAsync(HttpClient httpClient, s if (attempt > Retries) { Log.LogMessage($"Failed to download '{uri}' to '{DestinationPath}': {e.Message}"); - Log.LogErrorFromException(e, true, true, null); return false; } Log.LogMessage($"Retrying download of '{uri}' to '{DestinationPath}' due to failure: '{e.Message}' ({attempt}/{Retries})"); + Log.LogErrorFromException(e, true, true, null); await Tasks.Task.Delay(RetryDelayMilliseconds).ConfigureAwait(false); continue; diff --git a/src/Microsoft.DotNet.Arcade.Sdk/src/GetLicenseFilePath.cs b/src/Microsoft.DotNet.Arcade.Sdk/src/GetLicenseFilePath.cs index a0abef70c0f..d195c79398e 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/src/GetLicenseFilePath.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk/src/GetLicenseFilePath.cs @@ -37,6 +37,15 @@ private void ExecuteImpl() { const string fileName = "license"; +#if NET472 + IEnumerable enumerateFiles(string extension) + { + var fileNameWithExtension = fileName + extension; + return System.IO.Directory.EnumerateFiles(Directory, "*", SearchOption.TopDirectoryOnly) + .Where(path => string.Equals(fileNameWithExtension, System.IO.Path.GetFileName(path), System.StringComparison.OrdinalIgnoreCase)); + } + +#else var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, @@ -48,7 +57,7 @@ private void ExecuteImpl() IEnumerable enumerateFiles(string extension) => System.IO.Directory.EnumerateFileSystemEntries(Directory, fileName + extension, options); - +#endif var matches = (from extension in new[] { ".txt", ".md", "" } from path in enumerateFiles(extension) diff --git a/src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs b/src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs index 8e72a3686db..d3ff77edbb5 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs @@ -16,9 +16,16 @@ namespace Microsoft.DotNet.Arcade.Sdk { +#if NET472 + [LoadInSeparateAppDomain] + public class InstallDotNetCore : AppDomainIsolatedTask + { + static InstallDotNetCore() => AssemblyResolution.Initialize(); +#else public class InstallDotNetCore : Microsoft.Build.Utilities.Task { - private static readonly char[] s_keyTrimChars = ['$', '(', ')']; +#endif + private static readonly char[] s_keyTrimChars = [ '$', '(', ')' ]; public string VersionsPropsPath { get; set; } @@ -27,12 +34,10 @@ public class InstallDotNetCore : Microsoft.Build.Utilities.Task [Required] public string GlobalJsonPath { get; set; } [Required] - public string DotNetPath { get; set; } - [Required] public string Platform { get; set; } public string RuntimeSourceFeed { get; set; } - + public string RuntimeSourceFeedKey { get; set; } public override bool Execute() @@ -120,9 +125,7 @@ public override bool Execute() if (version != null) { - string normalizedVersion = version.ToNormalizedString(); - string runtime = runtimeItem.Key; - string arguments = $"-runtime \"{runtime}\" -version \"{normalizedVersion}\""; + string arguments = $"-runtime \"{runtimeItem.Key}\" -version \"{version.ToNormalizedString()}\""; if (!string.IsNullOrEmpty(architecture)) { arguments += $" -architecture {architecture}"; @@ -139,20 +142,6 @@ public override bool Execute() arguments += $" -runtimeSourceFeedKey {RuntimeSourceFeedKey}"; } - // Null architecture means that the script should infer it, we don't want to re-implement too much logic here, - // so we skip the quick check. - if (architecture != null) - { - // Quickly check if the runtime is already installed, skipping double process hop, - // load of powershell, and load of tools.sh, or similar overhead for shell script. - // Saving about 1 second per runtime. - if (CheckRuntimeDotnetInstalled(DotNetPath, normalizedVersion, architecture, runtime)) - - { - continue; - } - } - Log.LogMessage(MessageImportance.Low, $"Executing: {DotNetInstallScript} {arguments}"); var process = Process.Start(new ProcessStartInfo() { @@ -239,42 +228,5 @@ private IEnumerable> GetItemsFromJsonElementArray(J } return items.ToArray(); } - - private static bool CheckRuntimeDotnetInstalled( - string dotnetRoot, - string version, - string architecture, - string runtime) - { - // For performance this check is duplicated from InstallDotnet in tools.sh and tools.ps1 - // if you are making changes here, consider if you need to make changes there as well. - if (string.IsNullOrEmpty(runtime) && runtime == "sdk") - { - throw new ArgumentException($"{nameof(InstallDotNetCore)} cannot be used for .NET SDK installation."); - } - - if (!string.Equals(architecture, RuntimeInformation.OSArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)) - { - // This istallation is not native to this OS, it will be installed into a subfolder with the architecture name. - // See eng/common/dotnet-install.sh and eng/common/dotnet-install.ps1 - dotnetRoot = Path.Combine(dotnetRoot, architecture.ToLowerInvariant()); - } - - string runtimePath = runtime switch - { - "dotnet" => Path.Combine(dotnetRoot, "shared", "Microsoft.NETCore.App", version), - "aspnetcore" => Path.Combine(dotnetRoot, "shared", "Microsoft.AspNetCore.App", version), - "windowsdesktop" => Path.Combine(dotnetRoot, "shared", "Microsoft.WindowsDesktop.App", version), - _ => Path.Combine(dotnetRoot, "shared", version) - }; - - if (Directory.Exists(runtimePath)) - { - Console.WriteLine($" Runtime toolset '{runtime}/{architecture} v{version}' already installed in directory '{runtimePath}'."); - return true; - } - - return false; - } } } diff --git a/src/Microsoft.DotNet.Arcade.Sdk/src/SetCorFlags.cs b/src/Microsoft.DotNet.Arcade.Sdk/src/SetCorFlags.cs index 98525024117..593ba369bc9 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/src/SetCorFlags.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk/src/SetCorFlags.cs @@ -12,8 +12,15 @@ namespace Microsoft.DotNet.Arcade.Sdk { +#if NET472 + [LoadInSeparateAppDomain] + public class SetCorFlags : AppDomainIsolatedTask + { + static SetCorFlags() => AssemblyResolution.Initialize(); +#else public class SetCorFlags : Microsoft.Build.Utilities.Task { +#endif [Required] public string FilePath { get; set; } @@ -28,6 +35,9 @@ public class SetCorFlags : Microsoft.Build.Utilities.Task public override bool Execute() { +#if NET472 + AssemblyResolution.Log = Log; +#endif try { ExecuteImpl(); @@ -35,6 +45,9 @@ public override bool Execute() } finally { +#if NET472 + AssemblyResolution.Log = null; +#endif } } diff --git a/src/Microsoft.DotNet.Arcade.Sdk/src/Unsign.cs b/src/Microsoft.DotNet.Arcade.Sdk/src/Unsign.cs index f9c8bf00b2b..934939e265d 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/src/Unsign.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk/src/Unsign.cs @@ -12,13 +12,23 @@ namespace Microsoft.DotNet.Arcade.Sdk { +#if NET472 + [LoadInSeparateAppDomain] + public sealed class Unsign : AppDomainIsolatedTask + { + static Unsign() => AssemblyResolution.Initialize(); +#else public class Unsign : Microsoft.Build.Utilities.Task { +#endif [Required] public string FilePath { get; set; } public override bool Execute() { +#if NET472 + AssemblyResolution.Log = Log; +#endif try { ExecuteImpl(); @@ -26,6 +36,9 @@ public override bool Execute() } finally { +#if NET472 + AssemblyResolution.Log = null; +#endif } } diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/BeforeCommonTargets.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/BeforeCommonTargets.targets index 76e89170b45..d5d1ae7e4b7 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/BeforeCommonTargets.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/BeforeCommonTargets.targets @@ -9,7 +9,6 @@ - - - - - - $(BundledRuntimeIdentifierGraphFile) - - - $(MSBuildThisFileDirectory)net\Microsoft.DotNet.Arcade.Sdk.dll + $(MSBuildThisFileDirectory)netframework\Microsoft.DotNet.Arcade.Sdk.dll + $(MSBuildThisFileDirectory)net\Microsoft.DotNet.Arcade.Sdk.dll diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props index 8a17e952a46..bb618cf9850 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/DefaultVersions.props @@ -67,12 +67,7 @@ 1.0.422 5.1.0-beta.21356.1 18.0.1 - - 17.14.2120 + 16.9.1050 $(ArcadeSdkVersion) $(ArcadeSdkVersion) $(ArcadeSdkVersion) @@ -83,12 +78,12 @@ 2.9.3 1.22.0 $(XUnitVersion) - 3.1.5 + 3.1.3 - 3.2.2 - 1.9.1 + 3.0.0 + 1.7.3 - 4.1.0 + 3.9.3 $(MSTestVersion) $(MSTestVersion) $(ArcadeSdkVersion) @@ -103,7 +98,7 @@ 1.0.0 2.1.3 1.1.286 - 3.14.1-11027.2914512 + 3.14.1-9323.2545153 5.0.2-dotnet.2811440 diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/GenerateChecksums.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/GenerateChecksums.targets index b625b3e70e9..b272e667b44 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/GenerateChecksums.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/GenerateChecksums.targets @@ -1,7 +1,7 @@ - + - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Imports.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/Imports.targets index a50d2207589..2bbcc145865 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Imports.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Imports.targets @@ -9,8 +9,8 @@ - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/InstallDotNetCore.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/InstallDotNetCore.targets index aba176e26b2..c59c07cb980 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/InstallDotNetCore.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/InstallDotNetCore.targets @@ -1,7 +1,7 @@ - + @@ -16,7 +16,6 @@ - - - + + + $(IntermediateOutputPath)$(TargetFileName).pcbm diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.props index b8cb8efe741..e443c179b2a 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.props +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.props @@ -20,9 +20,6 @@ Icon.png $(MSBuildThisFileDirectory)Assets\DotNetPackageIcon.png - - $(WarningsNotAsErrors);$(AdditionalWarningsNotAsErrors) - true diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.targets index 1dff8890440..ff765c31141 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.targets @@ -1,6 +1,11 @@ + + + false + + $(__DeployProjectOutput) diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj index 44834f60188..f56db0fa71d 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj @@ -34,7 +34,7 @@ - + - + $(BeforePack);_AddSourcePackageSourceLinkFile diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/RepositoryValidation.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/RepositoryValidation.proj index 5efc4b438dd..ba32af54e3f 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/RepositoryValidation.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/RepositoryValidation.proj @@ -8,8 +8,8 @@ - - + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/RuntimeIdentifierInference.BeforeNETSdkTargets.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/RuntimeIdentifierInference.BeforeNETSdkTargets.targets index e0d310a92a4..8ec183cd272 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/RuntimeIdentifierInference.BeforeNETSdkTargets.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/RuntimeIdentifierInference.BeforeNETSdkTargets.targets @@ -1,32 +1,5 @@ - - - - <_IsExecutable Condition="'$(OutputType)' == 'Exe' or '$(OutputType)'=='WinExe'">true - - - - true - - - $(NETCoreSdkPortableRuntimeIdentifier) - - <_EnableArcadeRuntimeIdentifierInference Condition="'$(_EnableArcadeRuntimeIdentifierInference)' == ''">$(EnableArcadeRuntimeIdentifierInference) @@ -37,30 +10,41 @@ If the SDK will infer this project as "RID agnostic", don't infer RIDs. This should generally match the logic for setting IsRidAgnostic in the SDK. --> - <_RidAgnosticProject Condition="('$(OutputType)' == 'Library' or '$(IsTestProject)' == 'true') and ('$(RuntimeIdentifiers)' == '' and '$(PublishRuntimeIdentifier)' == '')">true + <_RidAgnosticProject Condition="('$(OutputType)' == 'Library' or '$(IsTestProject)' == 'true') and '$(RuntimeIdentifiers)' == ''">true <_EnableArcadeRuntimeIdentifierInference Condition="'$(_EnableArcadeRuntimeIdentifierInference)' == '' and ('$(IsRidAgnostic)' == 'true' or '$(_RidAgnosticProject)' == 'true')">false - - <_BuildFlavorRequiredRid Condition="'$(SelfContained)' == 'true' or '$(PublishRuntimeIdentifier)' != ''">true + + <_BuildFlavorRequiredRid + Condition=" + '$(SelfContained)' == 'true' or + ('$(_IsPublishing)' == 'true' and + ( + '$(PublishReadyToRun)' == 'true' or + '$(PublishSingleFile)' == 'true' or + '$(PublishAot)' == 'true' + ) + )">true <_EnableArcadeRuntimeIdentifierInference Condition="'$(_EnableArcadeRuntimeIdentifierInference)' == '' and '$(_BuildFlavorRequiredRid)' != 'true'">false - <_EnableArcadeRuntimeIdentifierInference Condition="'$(_EnableArcadeRuntimeIdentifierInference)' == '' and '$(DotNetBuildTargetRidOnly)' == 'true' and $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '$(NetCurrent)'))">true + <_EnableArcadeRuntimeIdentifierInference Condition="'$(_EnableArcadeRuntimeIdentifierInference)' == '' and '$(DotNetBuildTargetRidOnly)' == 'true'">true <_EnableArcadeRuntimeIdentifierFilters Condition="'$(EnableArcadeRuntimeIdentifierFilters)' != ''">$(EnableArcadeRuntimeIdentifierFilters) - <_EnableArcadeRuntimeIdentifierFilters Condition="'$(_EnableArcadeRuntimeIdentifierFilters)' == '' and '$(_EnableArcadeRuntimeIdentifierInference)' == 'true'">true + <_EnableArcadeRuntimeIdentifierFilters Condition="'$(_EnableArcadeRuntimeIdentifierFilters)' == '' and '$(_EnableArcadeRuntimeIdentifierInference)' == 'true'">$(_EnableArcadeRuntimeIdentifierFilters) @@ -72,9 +56,9 @@ true - + - <_ExplicitlySpecifiedRuntimeIdentifiers>;$(RuntimeIdentifiers);$(PublishRuntimeIdentifier); + <_ExplicitlySpecifiedRuntimeIdentifiers>;$(RuntimeIdentifiers); - <_SuppressAllTargets Condition="'$(DisableArcadeExcludeFromBuildSupport)' != 'true' and $(_ExplicitlySpecifiedRuntimeIdentifiers.Contains(';$(_FilterRuntimeIdentifier);')) == 'false'">true + <_SuppressAllTargets Condition="'$(DisableArcadeExcludeFromBuildSupport)' != 'true' and $(_ExplicitlySpecifiedRuntimeIdentifiers).Contains(';$(_FilterRuntimeIdentifier);')) == 'false'">true $(RuntimeIdentifier) - - - $(RuntimeIdentifier) - false - diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/CreateBaselineUpdatePR.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/CreateBaselineUpdatePR.proj index c3c1920baaa..2687f94083a 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/CreateBaselineUpdatePR.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/CreateBaselineUpdatePR.proj @@ -2,7 +2,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetFrameworkToolCurrent) Publish diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj index 1c09e11e205..d0c77effc66 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj @@ -75,7 +75,7 @@ --> - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Publish false diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishBuildAssets.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishBuildAssets.proj index 851fbaaae10..6fbe0aa5c73 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishBuildAssets.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishBuildAssets.proj @@ -19,7 +19,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetFrameworkToolCurrent) Publish diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj index 64464f4785c..83a3e1f12eb 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj @@ -3,7 +3,7 @@ Publish - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Publish diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj index e1d00e97224..c9b5e09585f 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SigningValidation.proj @@ -19,7 +19,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Build diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/VisualStudio.BuildIbcTrainingSettings.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/VisualStudio.BuildIbcTrainingSettings.proj index cec7c2c0a5c..3b423874bdb 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/VisualStudio.BuildIbcTrainingSettings.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/VisualStudio.BuildIbcTrainingSettings.proj @@ -20,10 +20,10 @@ - <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\net\Microsoft.DotNet.Build.Tasks.VisualStudio.dll + <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\netframework\Microsoft.DotNet.Build.Tasks.VisualStudio.dll - + <_OutputFilePath>$(VisualStudioSetupInsertionPath)OptProf\Training.runsettings diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj index 8049677420e..cf2a163077a 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj @@ -60,12 +60,10 @@ $(NuGetPackageRoot)sn\$(SNVersion)\sn.exe - - - + - - + $(NuGetPackageRoot)microsoft.dotnet.macospkg.cli\$(MicrosoftDotNetMacOsPkgVersion)\tools\$(NetToolCurrent)\any\Microsoft.Dotnet.MacOsPkg.Cli.dll + @@ -74,6 +72,7 @@ <_DotNetCorePath>$(DotNetTool) + - - false + false diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SymStore.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/SymStore.targets index fa8bd14863b..a32bf6dec13 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SymStore.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SymStore.targets @@ -4,7 +4,7 @@ - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkDefaults.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkDefaults.props index 1fd3a08a451..33c2fea4ed1 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkDefaults.props +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkDefaults.props @@ -10,28 +10,34 @@ --> - - net11.0 + + net10.0 - - $(NetCurrent) + Undefined when NetMinimum and NetPrevious are identical. --> + net9.0 - - net10.0 - $(NetCurrent) + + net8.0 net481 net462 + + + + + net10.0 + $(NetCurrent) + + + net8.0 + $(NetToolCurrent) net472 diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkFilters.BeforeCommonTargets.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkFilters.BeforeCommonTargets.targets index 311e242010f..d78e613edd0 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkFilters.BeforeCommonTargets.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetFrameworkFilters.BeforeCommonTargets.targets @@ -4,7 +4,7 @@ - netstandard2.0%3bnetstandard2.1%3bnetcoreapp2.1%3bnetcoreapp3.1%3bnet5.0%3bnet6.0%3bnet7.0%3bnet8.0%3bnet9.0%3bnet10.0%3bnet11.0%3b$(NetCurrent) + netstandard2.0%3bnetstandard2.1%3bnetcoreapp2.1%3bnetcoreapp3.1%3bnet5.0%3bnet6.0%3bnet7.0%3bnet8.0%3bnet9.0%3bnet10.0 <_EnableTargetFrameworkFiltering>false <_EnableTargetFrameworkFiltering Condition="'$(NoTargetFrameworkFiltering)' != 'true' and '$(DotNetTargetFrameworkFilter)' != ''">true @@ -20,5 +20,71 @@ true + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetingPacks.BeforeCommonTargets.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetingPacks.BeforeCommonTargets.targets deleted file mode 100644 index 90a5318d581..00000000000 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/TargetingPacks.BeforeCommonTargets.targets +++ /dev/null @@ -1,158 +0,0 @@ - - - - - true - - - true - - - - - - $([System.Text.RegularExpressions.Regex]::Match('%(TargetFramework)', '\d+').Value).0.0 - - - 6.0.2 - - - - - - - - - - - - - <_BundledAnalyzer Include="$(NetCoreTargetingPackRoot)\Microsoft.NETCore.App.Ref\$(BundledNETCoreAppPackageVersion)\analyzers\dotnet\cs\*.dll" /> - - - <_BundledAnalyzerByName Include="@(_BundledAnalyzer->Metadata('FileName'))" OriginalIdentity="%(Identity)" /> - <_AnalyzerByName Include="@(Analyzer->Metadata('FileName'))" /> - <_BundledAnalyzerByName Remove="@(_AnalyzerByName)" /> - - - - - - - - true - true - - $(NetCurrent) - $(NetPrevious) - $(NetMinimum) - - - - - - - - - - - - - - - - - - - - - - - %(RuntimePackRuntimeIdentifiers);$(NETCoreSdkRuntimeIdentifier) - - - %(Crossgen2RuntimeIdentifiers);$(NETCoreSdkRuntimeIdentifier) - - - - - - - $(KnownFrameworkReferenceMicrosoftNETCoreAppCurrentDefaultRuntimeFrameworkVersion) - $(KnownFrameworkReferenceMicrosoftNETCoreAppCurrentLatestRuntimeFrameworkVersion) - $(KnownFrameworkReferenceMicrosoftNETCoreAppCurrentTargetingPackVersion) - - - - $(KnownRuntimePackMicrosoftNETCoreAppCurrentMonoLatestRuntimeFrameworkVersion) - - - - $(KnownRuntimePackMicrosoftNETCoreAppCurrentNativeAOTLatestRuntimeFrameworkVersion) - - - - $(KnownILCompilerPackCurrentVersion) - - - - $(KnownCrossgen2PackCurrentVersion) - - - - $(KnownAppHostPackCurrentVersion) - - - - $(KnownILLinkPackCurrentVersion) - - - - diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tools.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tools.proj index c5ba4421054..bb32919efd8 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tools.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tools.proj @@ -3,7 +3,7 @@ - + net472 @@ -12,9 +12,6 @@ .NETFramework,Version=v4.7.2 $(BaseIntermediateOutputPath) - PackageReference - true - true @@ -37,7 +34,7 @@ - + @@ -48,23 +45,24 @@ - - - - - - - - - - + + + + + + + + + + + - + @@ -99,10 +97,10 @@ - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Version.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/Version.targets index fab5620c279..7dbd2411e9c 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Version.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Version.targets @@ -8,7 +8,7 @@ SemanticVersioningV1 "true" if the Version needs to respect SemVer 1.0. Default is false, which means format following SemVer 2.0. --> - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.AcquireOptimizationData.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.AcquireOptimizationData.targets index 33704816096..9979a784c8c 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.AcquireOptimizationData.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.AcquireOptimizationData.targets @@ -11,10 +11,10 @@ --> - <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\net\Microsoft.DotNet.Build.Tasks.VisualStudio.dll + <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\netframework\Microsoft.DotNet.Build.Tasks.VisualStudio.dll - + true diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.BuildIbcTrainingInputs.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.BuildIbcTrainingInputs.targets index 39949cff687..d9d6a384058 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.BuildIbcTrainingInputs.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.BuildIbcTrainingInputs.targets @@ -8,11 +8,11 @@ --> - <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\net\Microsoft.DotNet.Build.Tasks.VisualStudio.dll + <_VisualStudioBuildTasksAssembly>$(NuGetPackageRoot)microsoft.dotnet.build.tasks.visualstudio\$(MicrosoftDotNetBuildTasksVisualStudioVersion)\tools\netframework\Microsoft.DotNet.Build.Tasks.VisualStudio.dll - - + + - + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.targets index ab840aae8ae..c408c823b3c 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/VisualStudio.targets @@ -24,8 +24,8 @@ - - + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets index 9fc8dc9e7a4..e8e529cec4a 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/XUnitV3/XUnitV3.targets @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/src/Microsoft.DotNet.ArcadeAzureIntegration/AzureCliCredentialWithAzNoUpdateWrapper.cs b/src/Microsoft.DotNet.ArcadeAzureIntegration/AzureCliCredentialWithAzNoUpdateWrapper.cs index 67a03e88b10..1aab8408c4d 100644 --- a/src/Microsoft.DotNet.ArcadeAzureIntegration/AzureCliCredentialWithAzNoUpdateWrapper.cs +++ b/src/Microsoft.DotNet.ArcadeAzureIntegration/AzureCliCredentialWithAzNoUpdateWrapper.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. +#if !NET472_OR_GREATER + #nullable enable using System; @@ -133,3 +135,5 @@ public override async ValueTask GetTokenAsync(TokenRequestContext r return await _azureCliCredential.GetTokenAsync(requestContext, cancellationToken); } } + +#endif diff --git a/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredential.cs b/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredential.cs index 0447dbc83b7..4d609d3e254 100644 --- a/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredential.cs +++ b/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredential.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. +#if !NET472_OR_GREATER + #nullable enable using System; @@ -172,3 +174,5 @@ private static TokenCredential CreateAvailableTokenCredential(DefaultIdentityTok return null; } } + +#endif diff --git a/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredentialOptions.cs b/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredentialOptions.cs index c854367692a..eb91074ff15 100644 --- a/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredentialOptions.cs +++ b/src/Microsoft.DotNet.ArcadeAzureIntegration/DefaultIdentityTokenCredentialOptions.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. +#if !NET472_OR_GREATER + #nullable enable namespace Microsoft.DotNet.ArcadeAzureIntegration; @@ -13,3 +15,5 @@ public class DefaultIdentityTokenCredentialOptions public bool ExcludeAzureCliCredential { get; set; } public bool DisableShortCache { get; set; } } + +#endif diff --git a/src/Microsoft.DotNet.ArcadeAzureIntegration/Microsoft.DotNet.ArcadeAzureIntegration.csproj b/src/Microsoft.DotNet.ArcadeAzureIntegration/Microsoft.DotNet.ArcadeAzureIntegration.csproj index 7f4307d5dfe..514a08a6c5a 100644 --- a/src/Microsoft.DotNet.ArcadeAzureIntegration/Microsoft.DotNet.ArcadeAzureIntegration.csproj +++ b/src/Microsoft.DotNet.ArcadeAzureIntegration/Microsoft.DotNet.ArcadeAzureIntegration.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) diff --git a/src/Microsoft.DotNet.ArcadeAzureIntegration/TokenCredentialShortCache.cs b/src/Microsoft.DotNet.ArcadeAzureIntegration/TokenCredentialShortCache.cs index f84ca91e78e..2f263ece4d6 100644 --- a/src/Microsoft.DotNet.ArcadeAzureIntegration/TokenCredentialShortCache.cs +++ b/src/Microsoft.DotNet.ArcadeAzureIntegration/TokenCredentialShortCache.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. +#if !NET472_OR_GREATER + #nullable enable using System; @@ -87,3 +89,5 @@ public async ValueTask GetToken(TokenRequestContext requestContext, } } } + +#endif diff --git a/src/Microsoft.DotNet.ArcadeLogging/Microsoft.DotNet.ArcadeLogging.csproj b/src/Microsoft.DotNet.ArcadeLogging/Microsoft.DotNet.ArcadeLogging.csproj index 8ba87b42b34..66710e1db8f 100644 --- a/src/Microsoft.DotNet.ArcadeLogging/Microsoft.DotNet.ArcadeLogging.csproj +++ b/src/Microsoft.DotNet.ArcadeLogging/Microsoft.DotNet.ArcadeLogging.csproj @@ -1,8 +1,7 @@ - - $(BundledNETCoreAppTargetFramework);$(NetFrameworkToolCurrent) + $(NetToolCurrent);$(NetFrameworkToolCurrent) diff --git a/src/Microsoft.DotNet.Baselines.Tasks/Microsoft.DotNet.Baselines.Tasks.csproj b/src/Microsoft.DotNet.Baselines.Tasks/Microsoft.DotNet.Baselines.Tasks.csproj index 87b04d4e9e8..424d6c6c739 100644 --- a/src/Microsoft.DotNet.Baselines.Tasks/Microsoft.DotNet.Baselines.Tasks.csproj +++ b/src/Microsoft.DotNet.Baselines.Tasks/Microsoft.DotNet.Baselines.Tasks.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true true true diff --git a/src/Microsoft.DotNet.Baselines.Tasks/build/Microsoft.DotNet.Baselines.Tasks.targets b/src/Microsoft.DotNet.Baselines.Tasks/build/Microsoft.DotNet.Baselines.Tasks.targets index 2158a0e36bd..192e58623d0 100644 --- a/src/Microsoft.DotNet.Baselines.Tasks/build/Microsoft.DotNet.Baselines.Tasks.targets +++ b/src/Microsoft.DotNet.Baselines.Tasks/build/Microsoft.DotNet.Baselines.Tasks.targets @@ -1,10 +1,10 @@ - - <_MicrosoftDotNetBaselinesTasksDir>$(MSBuildThisFileDirectory)../tools/net/ + <_MicrosoftDotNetBaselinesTasksDir>$(MSBuildThisFileDirectory)../tools/netframework/ + <_MicrosoftDotNetBaselinesTasksDir Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)../tools/net/ - + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj b/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj index ec8a65ac470..03301ff8e12 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj @@ -1,9 +1,10 @@ - + - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true true + false Targets for producing an archive of file outputs. MSBuildSdk diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/README.md b/src/Microsoft.DotNet.Build.Tasks.Archives/README.md index 164873c8b11..6764d5669e1 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Archives/README.md +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/README.md @@ -6,25 +6,12 @@ This package generates an archive that can be extracted on top of an existing .N ## Build Skip Support for Unsupported Platforms and Servicing -This SDK also supports automatically skipping builds on unsupported platforms or in servicing releases. If a project with a list of provided RIDs in `RuntimeIdentifiers` is built with the `RuntimeIdentifier` property set to a RID that is not in the `RuntimeIdentifiers` list, the build will be skipped. This enables cleanly skipping optional packs, installers, or bundles that only exist on specific platforms. +This SDK also supports automatically skipping builds on unsupported platforms or in servicing releases. If a project with a list of provided RIDs in `RuntimeIdentifiers` is built with the `RuntimeIdentifier` property set to a RID that is not in the `RuntimeIdentifiers` list, the build will be skipped. This enables cleanly skipping optional packs, installers, or bundles that only exist on specific platforms. Additionally, if a `ProjectServicingConfiguration` item is provided with the identity of the project name and the `PatchVersion` metadata on the item is not equal to the current `PatchVersion`, the build will be skipped. This support enables a repository to disable building targeting packs in servicing releases if that is desired. -## Archive Formats +# Creating tar.gz archives on Windows -The `ArchiveFormat` ItemGroup specifies which archive formats to generate. By default: - -- **Windows**: Both `zip` and `tar.gz` archives are generated -- **Linux/macOS**: Only `tar.gz` archives are generated - -The `PublishToDisk` target runs once, then each archive format is created from the same staged output directory. - -### Customizing Archive Formats - -To customize which formats are generated, define the `ArchiveFormat` ItemGroup in your project: - -```xml - - - -``` +There is an override that you can use to opt into generating tar.gz archives instead of zip archives on Windows to get an consistent experience as with linux and macos. +That opt-in is setting ``ArchiveFormat`` to ``tar.gz`` on a project that uses this package when building for Windows. +This can also be used on Linux and MacOS to force creating ``zip`` archives as well. diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.props b/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.props index 6e5961c01dd..f6a41b80468 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.props +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.props @@ -1,9 +1,9 @@ - - - - + + zip + tar.gz + diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets b/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets index 606568efd1b..bc5c2486639 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets @@ -5,8 +5,8 @@ $(BuildDependsOn); _GetSkipArchivesBuildProps; - _CreateArchives; - _CreateSymbolsArchives + _CreateArchive; + _CreateSymbolsArchive @@ -87,102 +87,72 @@ - - <_OutputPathRoot>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'output')) + <_ArchiveFileName>$(ArchiveName)-$(Version) + <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(ArchiveName)-$(Version)-$(RuntimeIdentifier) + <_DestinationFileName>$([MSBuild]::NormalizePath($(PackageOutputPath), '$(_ArchiveFileName).$(ArchiveFormat)')) - - <_ArchiveBuildProject Include="$(MSBuildProjectFile)"> - - _CurrentArchiveFormat=%(ArchiveFormat.Identity); - _OutputPathRoot=$(_OutputPathRoot) - - - - - - - - - - - <_ArchiveFileName>$(ArchiveName)-$(Version) - <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(ArchiveName)-$(Version)-$(RuntimeIdentifier) - <_DestinationFileName>$([MSBuild]::NormalizePath($(PackageOutputPath), '$(_ArchiveFileName).$(_CurrentArchiveFormat)')) - - + Condition="'$(ArchiveFormat)' == 'zip'"/> - + Condition="'$(ArchiveFormat)' == 'tar.gz' and '$(_PigzFoundExitCode)' == '0'"/> - + Condition="'$(ArchiveFormat)' == 'tar.gz' and '$(_PigzFoundExitCode)' != '0'"/> - - + <_SymbolsOutputPathRoot>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'symbols')) + <_ArchiveFileName>$(SymbolsArchiveName)-$(Version) + <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(SymbolsArchiveName)-$(RuntimeIdentifier)-$(Version) + <_DestinationFileName>$([MSBuild]::NormalizePath($(PackageOutputPath), '$(_ArchiveFileName).$(ArchiveFormat)')) - - <_SymbolsArchiveBuildProject Include="$(MSBuildProjectFile)"> - - _CurrentArchiveFormat=%(ArchiveFormat.Identity); - _SymbolsOutputPathRoot=$(_SymbolsOutputPathRoot) - - - - - - - - - - - <_ArchiveFileName>$(SymbolsArchiveName)-$(Version) - <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(SymbolsArchiveName)-$(RuntimeIdentifier)-$(Version) - <_DestinationFileName>$([MSBuild]::NormalizePath($(PackageOutputPath), '$(_ArchiveFileName).$(_CurrentArchiveFormat)')) - - + Condition="'$(ArchiveFormat)' == 'zip'"/> - + Condition="'$(ArchiveFormat)' == 'tar.gz' and '$(_PigzFoundExitCode)' == '0'"/> - + Condition="'$(ArchiveFormat)' == 'tar.gz' and '$(_PigzFoundExitCode)' != '0'"/> diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ArtifactUrlHelperTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ArtifactUrlHelperTests.cs index 33e7ea23b37..096a648dd34 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ArtifactUrlHelperTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ArtifactUrlHelperTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using AwesomeAssertions; +using FluentAssertions; using Xunit; using Microsoft.DotNet.Build.Tasks.Feed; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/DownloadFileTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/DownloadFileTests.cs index 75120e217f5..c1a97909043 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/DownloadFileTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/DownloadFileTests.cs @@ -8,7 +8,7 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.DotNet.Build.Manifest.Tests; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/GeneralTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/GeneralTests.cs index 8eb36cf1183..afa9d0642c1 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/GeneralTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/GeneralTests.cs @@ -7,7 +7,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.DotNet.Build.Tasks.Feed.Model; using Microsoft.DotNet.Build.Tasks.Feed.Tests.TestDoubles; @@ -37,23 +37,6 @@ public void ChannelConfigsHaveAllConfigs() } } - [Theory] - [InlineData(8103)] - [InlineData(8104)] - [InlineData(8105)] - public void AspireChannelsAllowOnlyGetAspireCliInstallScripts(int channelId) - { - var channelConfig = PublishingConstants.ChannelInfos.Single(c => c.Id == channelId); - - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/foo.zip")).Should().BeTrue(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/get-aspire-cli.ps1")).Should().BeTrue(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/get-aspire-cli.ps1.sha512")).Should().BeTrue(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/get-aspire-cli.sh")).Should().BeTrue(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/get-aspire-cli.sh.sha512")).Should().BeTrue(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/install-other-tool.ps1")).Should().BeFalse(); - channelConfig.AkaMSCreateLinkPatterns.Any(pattern => pattern.IsMatch("assets/installers/install-other-tool.sh")).Should().BeFalse(); - } - [Theory] [InlineData("foo/bar/baz/bop.symbols.nupkg", true)] [InlineData("foo/bar/baz/bop.symbols.nupkg.sha512", false)] @@ -281,28 +264,5 @@ public void TargetChannelConfig_TargetFeeds_UnequalTest() actualResult.Should().BeFalse(); } - - [Theory] - [InlineData("https://dotnetcli.blob.core.windows.net/test", "https://builds.dotnet.microsoft.com/test")] - [InlineData("https://dotnetbuilds.blob.core.windows.net/internal", "https://ci.dot.net/internal")] - [InlineData("https://dotnetbuilds.blob.core.windows.net/public?sv=token", "https://ci.dot.net/public")] - [InlineData("https://unknown.blob.core.windows.net/test", "https://unknown.blob.core.windows.net/test")] - [InlineData("https://pkgs.dev.azure.com/dnceng/public/_packaging/feed/nuget/v3/index.json", "https://pkgs.dev.azure.com/dnceng/public/_packaging/feed/nuget/v3/index.json")] - public void TargetFeedConfig_SafeTargetURL_AppliesCdnSubstitution(string targetUrl, string expectedSafeUrl) - { - // Arrange - var feedConfig = new TargetFeedConfig( - contentType: TargetFeedContentType.Installer, - targetURL: targetUrl, - type: FeedType.AzureStorageContainer, - token: "dummyToken" - ); - - // Act - var actualSafeUrl = feedConfig.SafeTargetURL; - - // Assert - actualSafeUrl.Should().Be(expectedSafeUrl); - } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/LatestLinksManagerTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/LatestLinksManagerTests.cs index bf782ca0891..5c0ffcd7f54 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/LatestLinksManagerTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/LatestLinksManagerTests.cs @@ -6,7 +6,7 @@ using System.Collections.Immutable; using System.Linq; using System.Text.RegularExpressions; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Utilities; using Microsoft.DotNet.Build.Tasks.Feed.Model; @@ -221,47 +221,5 @@ public void GetLatestLinksToCreate_NonFlattenedShouldNotFlatten() new AkaMSLink("prefix2/assets/plop/Microsoft.stuff.json.zip", "https://example.com/feed/assets/plop/Microsoft.stuff.json.zip") }); } - - [Fact] - public void GetLatestLinksToCreate_RestrictedInstallScriptPatternShouldOnlyIncludeGetAspireCli() - { - var taskLoggingHelper = new TaskLoggingHelper(new StubTask()); - var assetsToPublish = new HashSet - { - "assets/installers/get-aspire-cli.ps1", - "assets/installers/get-aspire-cli.ps1.sha512", - "assets/installers/get-aspire-cli.sh", - "assets/installers/get-aspire-cli.sh.sha512", - "assets/installers/install-other-tool.ps1", - "assets/installers/install-other-tool.sh", - }; - var feedConfig = new TargetFeedConfig( - contentType: TargetFeedContentType.Other, - targetURL: "https://example.com/feed", - type: FeedType.AzureStorageContainer, - token: "", - latestLinkShortUrlPrefixes: ImmutableList.Create("dotnet/9/aspire/rc/daily"), - akaMSCreateLinkPatterns: PublishingConstants.AspireAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: [], - assetSelection: AssetSelection.All, - isolated: false, - @internal: false, - allowOverwrite: false, - symbolPublishVisibility: SymbolPublishVisibility.None, - flatten: true - ); - - var manager = new LatestLinksManager("clientId", null, "tenant", "groupOwner", "createdBy", "owners", taskLoggingHelper); - - var links = manager.GetLatestLinksToCreate(assetsToPublish, feedConfig, "https://example.com/feed/"); - - links.Should().BeEquivalentTo(new List - { - new AkaMSLink("dotnet/9/aspire/rc/daily/get-aspire-cli.ps1", "https://example.com/feed/assets/installers/get-aspire-cli.ps1"), - new AkaMSLink("dotnet/9/aspire/rc/daily/get-aspire-cli.ps1.sha512", "https://example.com/feed/assets/installers/get-aspire-cli.ps1.sha512"), - new AkaMSLink("dotnet/9/aspire/rc/daily/get-aspire-cli.sh", "https://example.com/feed/assets/installers/get-aspire-cli.sh"), - new AkaMSLink("dotnet/9/aspire/rc/daily/get-aspire-cli.sh.sha512", "https://example.com/feed/assets/installers/get-aspire-cli.sh.sha512"), - }); - } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/Microsoft.DotNet.Build.Tasks.Feed.Tests.csproj b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/Microsoft.DotNet.Build.Tasks.Feed.Tests.csproj index 37b966d0379..7b091823dbe 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/Microsoft.DotNet.Build.Tasks.Feed.Tests.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/Microsoft.DotNet.Build.Tasks.Feed.Tests.csproj @@ -1,12 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) false - + @@ -21,6 +21,11 @@ + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ProductionChannelValidatorTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ProductionChannelValidatorTests.cs index bdfd15efd93..ece57d4bd3d 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ProductionChannelValidatorTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/ProductionChannelValidatorTests.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.DotNet.Build.Tasks.Feed.Model; using Microsoft.DotNet.Build.Manifest; using Microsoft.Extensions.Logging; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishArtifactsInManifestTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishArtifactsInManifestTests.cs index 1c59370ecd5..4b150806a7d 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishArtifactsInManifestTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishArtifactsInManifestTests.cs @@ -9,7 +9,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.DotNet.Build.Manifest; @@ -333,7 +333,7 @@ await task.PushNugetPackageAsync( if (!expectedFailure && localPackageMatchesFeed) { // Successful retry scenario; make sure we ran the # of retries we thought. - timesCalled.Should().BeLessThanOrEqualTo(task.MaxRetryCount); + timesCalled.Should().BeLessOrEqualTo(task.MaxRetryCount); } expectedFailure.Should().Be(task.Log.HasLoggedErrors); } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishBuildToMaestroTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishBuildToMaestroTests.cs index 59636478869..64623a4efc4 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishBuildToMaestroTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PublishBuildToMaestroTests.cs @@ -3,7 +3,7 @@ using System; using System.Net; -using AwesomeAssertions; +using FluentAssertions; using Xunit; namespace Microsoft.DotNet.Build.Tasks.Feed.Tests diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PushToBuildStorageTests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PushToBuildStorageTests.cs index cb8c9fe78d8..d6b9788f4f4 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PushToBuildStorageTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/PushToBuildStorageTests.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Text; using System.Xml.Linq; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Utilities; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs index 415220de981..461ca229999 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV4Tests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV4Tests.cs index 7470b2ac354..e43bdc96cf3 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV4Tests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV4Tests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.DotNet.Build.Tasks.Feed.Model; using System.Collections.Generic; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj b/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj index adb882fef1f..f3cb15a12eb 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true false This package provides support for publishing assets to appropriate channels. @@ -23,7 +23,7 @@ - + @@ -34,11 +34,19 @@ the symbol publishing doesn't run on framework anyway (this project has a throwing stub for .NET framework in that path due to the reliance in maestro). Until we really need to multitarget, this is the better path. --> + + + + + + + @@ -47,8 +55,8 @@ - - + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/build/Microsoft.DotNet.Build.Tasks.Feed.targets b/src/Microsoft.DotNet.Build.Tasks.Feed/build/Microsoft.DotNet.Build.Tasks.Feed.targets index 05579d54b39..9b7f8eb8a71 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/build/Microsoft.DotNet.Build.Tasks.Feed.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/build/Microsoft.DotNet.Build.Tasks.Feed.targets @@ -43,14 +43,15 @@ --> - <_MicrosoftDotNetBuildTasksFeedTaskDir>$(MSBuildThisFileDirectory)../tools/net/ + <_MicrosoftDotNetBuildTasksFeedTaskDir>$(MSBuildThisFileDirectory)../tools/netframework/ + <_MicrosoftDotNetBuildTasksFeedTaskDir Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)../tools/net/ - - - - - - + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishArtifactsInManifest.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishArtifactsInManifest.cs index b3429746a2e..480d5985395 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishArtifactsInManifest.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishArtifactsInManifest.cs @@ -389,7 +389,11 @@ internal PublishArtifactsInManifestBase ConstructPublishingV3Task(BuildModel bui SkipSafetyChecks = this.SkipSafetyChecks, AkaMSClientId = this.AkaMSClientId, AkaMSClientCertificate = !string.IsNullOrEmpty(AkaMSClientCertificate) ? +#if NET9_0_OR_GREATER X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(File.ReadAllText(AkaMSClientCertificate)), password: null) : null, +#else + new X509Certificate2(Convert.FromBase64String(File.ReadAllText(AkaMSClientCertificate))) : null, +#endif AkaMSCreatedBy = this.AkaMSCreatedBy, AkaMSGroupOwner = this.AkaMSGroupOwner, AkaMsOwners = this.AkaMsOwners, @@ -435,7 +439,11 @@ internal PublishArtifactsInManifestBase ConstructPublishingV4Task(BuildModel bui SkipSafetyChecks = this.SkipSafetyChecks, AkaMSClientId = this.AkaMSClientId, AkaMSClientCertificate = !string.IsNullOrEmpty(AkaMSClientCertificate) ? +#if NET9_0_OR_GREATER X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(File.ReadAllText(AkaMSClientCertificate)), password: null) : null, +#else + new X509Certificate2(Convert.FromBase64String(File.ReadAllText(AkaMSClientCertificate))) : null, +#endif AkaMSCreatedBy = this.AkaMSCreatedBy, AkaMSGroupOwner = this.AkaMSGroupOwner, AkaMsOwners = this.AkaMsOwners, diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishBuildToMaestro.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishBuildToMaestro.cs index e3249051803..80e05cb284a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishBuildToMaestro.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PublishBuildToMaestro.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using System.Xml.Linq; using System.Xml.Serialization; -using Maestro.Common; using Microsoft.Arcade.Common; using Microsoft.Build.Framework; using Microsoft.DotNet.DarcLib; diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/LatestLinksManager.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/LatestLinksManager.cs index cf5a509ac58..d2e63487485 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/LatestLinksManager.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/LatestLinksManager.cs @@ -22,6 +22,12 @@ public class LatestLinksManager private string _akaMSCreatedBy { get; } private string _akaMSGroupOwner { get; } + private static Dictionary AccountsWithCdns { get; } = new() + { + {"dotnetcli.blob.core.windows.net", "builds.dotnet.microsoft.com" }, + {"dotnetbuilds.blob.core.windows.net", "ci.dot.net" } + }; + public LatestLinksManager( string akaMSClientId, X509Certificate2 certificate, @@ -100,6 +106,14 @@ public static string ComputeLatestLinkBase(TargetFeedConfig feedConfig) { feedBaseUrl += "/"; } + var authority = new Uri(feedBaseUrl).Authority; + if (AccountsWithCdns.TryGetValue(authority, out var replacementAuthority)) + { + // The storage accounts are in a single datacenter in the US and thus download + // times can be painful elsewhere. The CDN helps with this therefore we point the target + // of the aka.ms links to the CDN. + feedBaseUrl = feedBaseUrl.Replace(authority, replacementAuthority); + } return feedBaseUrl; } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs index daaa643f1b5..7eaa76db72a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs @@ -3,18 +3,30 @@ using System; using System.Runtime.InteropServices; +#if NET using System.Runtime.InteropServices.Marshalling; +#endif namespace Microsoft.DotNet.Build.Tasks.Feed { internal partial class NativeMethods { +#if NET [LibraryImport("kernel32.dll", EntryPoint = "CreateHardLinkW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool CreateHardLink(string newFileName, string exitingFileName, IntPtr securityAttributes); +#else + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern bool CreateHardLink(string newFileName, string exitingFileName, IntPtr securityAttributes); +#endif +#if NET [LibraryImport("libc", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] internal static partial int link(string oldpath, string newpath); +#else + [DllImport("libc", SetLastError = true)] + internal static extern int link(string oldpath, string newpath); +#endif internal static bool MakeHardLink(string newFileName, string exitingFileName, ref string errorMessage) { diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs index cdb6fe4b89e..87aa7803983 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs @@ -13,16 +13,6 @@ public class PublishingConstants { public static readonly string ExpectedFeedUrlSuffix = "index.json"; - /// - /// Mapping of Azure storage accounts to their corresponding CDN URLs. - /// Used to replace blob storage URLs with CDN URLs for both aka.ms links and asset locations. - /// - public static readonly Dictionary AccountsWithCdns = new() - { - {"dotnetcli.blob.core.windows.net", "builds.dotnet.microsoft.com" }, - {"dotnetbuilds.blob.core.windows.net", "ci.dot.net" } - }; - // Matches package feeds like // https://pkgs.dev.azure.com/dnceng/public/_packaging/public-feed-name/nuget/v3/index.json // or https://pkgs.dev.azure.com/dnceng/_packaging/internal-feed-name/nuget/v3/index.json @@ -104,8 +94,6 @@ public enum BuildQuality public const string FeedDotNetEng = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json"; - private const string FeedDotNetEngInternal = "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/v3/index.json"; - private const string FeedDotNetTools = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json"; private const string FeedDotNetToolsInternal = "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json"; @@ -237,16 +225,8 @@ public enum BuildQuality private static TargetFeedSpecification[] DotNet10InternalFeeds = { - (TargetFeedContentType.Package, FeedDotNet10InternalShipping, AssetSelection.ShippingOnly), - (TargetFeedContentType.Package, FeedDotNet10InternalTransport, AssetSelection.NonShippingOnly), - (TargetFeedContentType.InfrastructurePackage, FeedDotNetEngInternal, AssetSelection.ShippingOnly), - (TargetFeedContentType.InfrastructurePackage, FeedDotNetEngInternal, AssetSelection.NonShippingOnly), - (TargetFeedContentType.CorePackage, FeedDotNet10InternalShipping, AssetSelection.ShippingOnly), - (TargetFeedContentType.CorePackage, FeedDotNet10InternalTransport, AssetSelection.NonShippingOnly), - (TargetFeedContentType.LibraryPackage, FeedDotNetLibrariesInternalShipping, AssetSelection.ShippingOnly), - (TargetFeedContentType.LibraryPackage, FeedDotNetLibrariesInternalTransport, AssetSelection.NonShippingOnly), - (TargetFeedContentType.ToolingPackage, FeedDotNetToolsInternal, AssetSelection.ShippingOnly), - (TargetFeedContentType.ToolingPackage, FeedDotNetToolsInternal, AssetSelection.NonShippingOnly), + (Packages, FeedDotNet10InternalShipping, AssetSelection.ShippingOnly), + (Packages, FeedDotNet10InternalTransport, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedStagingInternalForInstallers), (TargetFeedContentType.Checksum, FeedStagingInternalForChecksums), }; @@ -374,12 +354,6 @@ public enum BuildQuality new Regex(@"productversion", RegexOptions.IgnoreCase) ]; - public static readonly ImmutableList AspireAkaMSCreateLinkPatterns = - [ - ..DefaultAkaMSCreateLinkPatterns, - new Regex(@"(^|[\\/])get-aspire-cli\.(ps1|sh)(\.sha512)?$", RegexOptions.IgnoreCase) - ]; - public static readonly ImmutableList DefaultAkaMSDoNotCreateLinkPatterns = [ new Regex(@"wixpack", RegexOptions.IgnoreCase), @@ -640,17 +614,6 @@ public enum BuildQuality targetFeeds: DotNet8InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8 HotFix Internal, - new TargetChannelConfig( - id: 8624, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/8.0-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet8InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.1xx SDK, new TargetChannelConfig( id: 3074, @@ -673,17 +636,6 @@ public enum BuildQuality targetFeeds: DotNet8InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.1xx SDK HotFix Internal, - new TargetChannelConfig( - id: 8625, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/8.0.1xx-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet8InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.2xx SDK, new TargetChannelConfig( id: 4036, @@ -728,17 +680,6 @@ public enum BuildQuality targetFeeds: DotNet8InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.3xx SDK HotFix Internal, - new TargetChannelConfig( - id: 8627, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/8.0.3xx-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet8InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.4xx SDK, new TargetChannelConfig( id: 4586, @@ -761,17 +702,6 @@ public enum BuildQuality targetFeeds: DotNet8InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 8.0.4xx SDK HotFix Internal, - new TargetChannelConfig( - id: 8628, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/8.0.4xx-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet8InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - #endregion #region .NET 9 Channels @@ -832,17 +762,6 @@ public enum BuildQuality targetFeeds: DotNet9InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 9 HotFix Internal, - new TargetChannelConfig( - id: 8629, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/9.0-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet9InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 9 Workload Release, new TargetChannelConfig( id: 4611, @@ -876,17 +795,6 @@ public enum BuildQuality targetFeeds: DotNet9InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 9.0.1xx SDK HotFix Internal, - new TargetChannelConfig( - id: 8630, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/9.0.1xx-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet9InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 9.0.2xx SDK, new TargetChannelConfig( id: 5286, @@ -931,17 +839,6 @@ public enum BuildQuality targetFeeds: DotNet9InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 9.0.3xx SDK HotFix Internal, - new TargetChannelConfig( - id: 8632, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/9.0.3xx-hotfix"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet9InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - #endregion #region .NET 10 Channels @@ -957,28 +854,6 @@ public enum BuildQuality targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10 Internal, - new TargetChannelConfig( - id: 5177, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - - // .NET 10 Private, - new TargetChannelConfig( - id: 8710, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0-private"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 10 Eng, new TargetChannelConfig( id: 8394, @@ -1024,133 +899,111 @@ public enum BuildQuality targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.1xx SDK Release, + // .NET 10 UB, new TargetChannelConfig( - id: 8859, + id: 5708, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "10.0.1xx-release", "10.0-release" ], + akaMSChannelNames: ["10.0.1xx-ub", "10.0-ub"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.1xx SDK Internal, - new TargetChannelConfig( - id: 5178, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.1xx", "internal/10.0"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - - // .NET 10.0.1xx SDK Release Internal, + // .NET 10 Preview 1, new TargetChannelConfig( - id: 8858, - isInternal: true, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.1xx-release", "internal/10.0-release"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), - - // .NET 10.0.1xx SDK HotFix Internal, - new TargetChannelConfig( - id: 9399, - isInternal: true, + id: 6545, + isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.1xx-hotfix"], + akaMSChannelNames: ["10.0-preview1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), + targetFeeds: DotNet10Feeds, + symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.2xx SDK, + // .NET 10 Preview 2, new TargetChannelConfig( - id: 8856, + id: 6547, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "10.0.2xx", "10.0" ], + akaMSChannelNames: ["10.0-preview2"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.2xx SDK Release, + // .NET 10 Preview 3, new TargetChannelConfig( - id: 8860, + id: 6549, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "10.0.2xx-release" ], + akaMSChannelNames: ["10.0-preview3"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.2xx SDK Internal, + // .NET 10 Preview 4, new TargetChannelConfig( - id: 8857, - isInternal: true, + id: 6551, + isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.2xx"], + akaMSChannelNames: ["10.0-preview4"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), + targetFeeds: DotNet10Feeds, + symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.2xx SDK Internal, + // .NET 10 Preview 5, new TargetChannelConfig( - id: 8861, - isInternal: true, + id: 6553, + isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.2xx-release"], + akaMSChannelNames: ["10.0-preview5"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), + targetFeeds: DotNet10Feeds, + symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.2xx SDK HotFix Internal, + // .NET 10 Preview 6, new TargetChannelConfig( - id: 9400, - isInternal: true, + id: 6555, + isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.2xx-hotfix"], + akaMSChannelNames: ["10.0-preview6"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), + akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, + targetFeeds: DotNet10Feeds, + symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.3xx SDK, + // .NET 10 Preview 7, new TargetChannelConfig( - id: 9626, + id: 6557, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "10.0.3xx", "10.0" ], + akaMSChannelNames: ["10.0-preview7"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.3xx SDK Internal, + // .NET 10 RC 1, new TargetChannelConfig( - id: 9627, - isInternal: true, + id: 6494, + isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.3xx", "internal/10.0"], + akaMSChannelNames: ["10.0-rc1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet10InternalFeeds, - symbolTargetType: SymbolPublishVisibility.Internal), + targetFeeds: DotNet10Feeds, + symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.3xx SDK HotFix Internal, + // .NET 10 RC 1 Internal, new TargetChannelConfig( - id: 9401, + id: 6496, isInternal: true, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.3xx-hotfix"], + akaMSChannelNames: ["internal/10.0-rc1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10InternalFeeds, @@ -1178,217 +1031,162 @@ public enum BuildQuality targetFeeds: DotNet10InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 10.0.1xx RC 2, + // .NET 10.0.1xx RC 1, new TargetChannelConfig( - id: 6577, + id: 6573, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["10.0.1xx-rc2"], + akaMSChannelNames: ["10.0.1xx-rc1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 10.0.1xx RC 2 Internal, + // .NET 10.0.1xx RC 1 Internal, new TargetChannelConfig( - id: 6579, + id: 6575, isInternal: true, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["internal/10.0.1xx-rc2"], + akaMSChannelNames: ["internal/10.0.1xx-rc1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet10InternalFeeds, symbolTargetType: SymbolPublishVisibility.Internal), - #endregion - - #region .NET 11 Channels - - // .NET 11, - new TargetChannelConfig( - id: 8297, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, - symbolTargetType: SymbolPublishVisibility.Public), - - // .NET 11 Workload Release, - new TargetChannelConfig( - id: 8299, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "11.0-workloads" ], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11WorkloadFeeds, - symbolTargetType: SymbolPublishVisibility.Public), - - // .NET 11.0.1xx SDK, - new TargetChannelConfig( - id: 8298, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [ "11.0.1xx", "11.0" ], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, - symbolTargetType: SymbolPublishVisibility.Public), - - // .NET 11 Preview 1, + // .NET 10.0.1xx RC 2, new TargetChannelConfig( - id: 9581, + id: 6577, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview1"], + akaMSChannelNames: ["10.0.1xx-rc2"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 1, + // .NET 10.0.1xx RC 2 Internal, new TargetChannelConfig( - id: 9582, - isInternal: false, + id: 6579, + isInternal: true, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview1"], + akaMSChannelNames: ["internal/10.0.1xx-rc2"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, - symbolTargetType: SymbolPublishVisibility.Public), + targetFeeds: DotNet10InternalFeeds, + symbolTargetType: SymbolPublishVisibility.Internal), - // .NET 11 Preview 2, + // .NET 10.0.1xx SDK Preview 1, new TargetChannelConfig( - id: 9583, + id: 6476, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview2"], + akaMSChannelNames: ["10.0.1xx-preview1"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 2, + // .NET 10.0.1xx SDK Preview 2, new TargetChannelConfig( - id: 9584, + id: 6478, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview2"], + akaMSChannelNames: ["10.0.1xx-preview2"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11 Preview 3, + // .NET 10.0.1xx SDK Preview 3, new TargetChannelConfig( - id: 9585, + id: 6484, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview3"], + akaMSChannelNames: ["10.0.1xx-preview3"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 3, + // .NET 10.0.1xx SDK Preview 4, new TargetChannelConfig( - id: 9586, + id: 6486, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview3"], + akaMSChannelNames: ["10.0.1xx-preview4"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11 Preview 4, + // .NET 10.0.1xx SDK Preview 5, new TargetChannelConfig( - id: 9587, + id: 6488, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview4"], + akaMSChannelNames: ["10.0.1xx-preview5"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 4, + // .NET 10.0.1xx SDK Preview 6, new TargetChannelConfig( - id: 9588, + id: 6490, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview4"], + akaMSChannelNames: ["10.0.1xx-preview6"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11 Preview 5, + // .NET 10.0.1xx SDK Preview 7, new TargetChannelConfig( - id: 9589, + id: 6492, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview5"], + akaMSChannelNames: ["10.0.1xx-preview7"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 5, - new TargetChannelConfig( - id: 9590, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview5"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, - symbolTargetType: SymbolPublishVisibility.Public), + #endregion - // .NET 11 Preview 6, - new TargetChannelConfig( - id: 9591, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview6"], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, - symbolTargetType: SymbolPublishVisibility.Public), + #region .NET 11 Channels - // .NET 11.0.1xx SDK Preview 6, + // .NET 11, new TargetChannelConfig( - id: 9592, + id: 8297, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview6"], + akaMSChannelNames: ["11.0"], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet11Feeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11 Preview 7, + // .NET 11 Workload Release, new TargetChannelConfig( - id: 9593, + id: 8299, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0-preview7"], + akaMSChannelNames: [ "11.0-workloads" ], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet11WorkloadFeeds, symbolTargetType: SymbolPublishVisibility.Public), - // .NET 11.0.1xx SDK Preview 7, + // .NET 11.0.1xx SDK, new TargetChannelConfig( - id: 9594, + id: 8298, isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: ["11.0.1xx-preview7"], + akaMSChannelNames: [ "11.0.1xx", "11.0" ], akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: UnifiedBuildAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNet11Feeds, + targetFeeds: DotNet10Feeds, symbolTargetType: SymbolPublishVisibility.Public), #endregion @@ -1569,7 +1367,7 @@ public enum BuildQuality isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, akaMSChannelNames: ["9/aspire"], - akaMSCreateLinkPatterns: AspireAkaMSCreateLinkPatterns, + akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet9Feeds, symbolTargetType: SymbolPublishVisibility.Public), @@ -1580,7 +1378,7 @@ public enum BuildQuality isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, akaMSChannelNames: ["9/aspire/rc"], - akaMSCreateLinkPatterns: AspireAkaMSCreateLinkPatterns, + akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet9Feeds, symbolTargetType: SymbolPublishVisibility.Public), @@ -1591,7 +1389,7 @@ public enum BuildQuality isInternal: false, publishingInfraVersion: PublishingInfraVersion.Latest, akaMSChannelNames: ["9/aspire/ga"], - akaMSCreateLinkPatterns: AspireAkaMSCreateLinkPatterns, + akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, targetFeeds: DotNet9Feeds, symbolTargetType: SymbolPublishVisibility.Public), @@ -1898,126 +1696,7 @@ public enum BuildQuality targetFeeds: DotNetToolsFeeds, symbolTargetType: SymbolPublishVisibility.Public, flatten: false), - - // 18.1 - new TargetChannelConfig( - id: 8703, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.2 - new TargetChannelConfig( - id: 8704, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.3 - new TargetChannelConfig( - id: 8705, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.4 - new TargetChannelConfig( - id: 8706, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.5 - new TargetChannelConfig( - id: 8707, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.6 - new TargetChannelConfig( - id: 8708, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.7 - new TargetChannelConfig( - id: 10189, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.8 - new TargetChannelConfig( - id: 10188, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.9 - new TargetChannelConfig( - id: 10190, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), - - // 18.10 - new TargetChannelConfig( - id: 10191, - isInternal: false, - publishingInfraVersion: PublishingInfraVersion.Latest, - akaMSChannelNames: [], - akaMSCreateLinkPatterns: DefaultAkaMSCreateLinkPatterns, - akaMSDoNotCreateLinkPatterns: DefaultAkaMSDoNotCreateLinkPatterns, - targetFeeds: DotNetToolsFeeds, - symbolTargetType: SymbolPublishVisibility.Public, - flatten: false), + #endregion ]; } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetFeedConfig.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetFeedConfig.cs index fe3220c3739..b2f25c8fb4f 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetFeedConfig.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetFeedConfig.cs @@ -16,25 +16,9 @@ namespace Microsoft.DotNet.Build.Tasks.Feed.Model public class TargetFeedConfig { /// - /// Returns the TargetURL stripped of SAS token and with CDN substitution applied - /// for known blob storage accounts. This is used for both logging purposes and - /// for storing asset locations in BAR that point to publicly accessible CDN URLs. + /// Returns the TargetURL stripped of SAS token so it can be used for logging purposes. /// - public string SafeTargetURL - { - get - { - var uriBuilder = new UriBuilder(TargetURL) { Query = "", Fragment = "" }; - - // Apply CDN substitution for known storage accounts - if (PublishingConstants.AccountsWithCdns.TryGetValue(uriBuilder.Host, out var replacementHost)) - { - uriBuilder.Host = replacementHost; - } - - return uriBuilder.Uri.AbsoluteUri; - } - } + public string SafeTargetURL => new UriBuilder(TargetURL) {Query = "", Fragment = ""}.Uri.AbsoluteUri; public TargetFeedContentType ContentType { get; } diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj index dd3b461547d..b802bc9a24a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true true @@ -39,6 +39,12 @@ Pack="true" /> + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props index 29db77597b8..f5996ed1b57 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props @@ -2,7 +2,8 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Installers.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Installers.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.Build.Tasks.Installers.dll $(MSBuildThisFileDirectory) diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj new file mode 100644 index 00000000000..c283a08255e --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj @@ -0,0 +1,28 @@ + + + + + + + + https://dist.nuget.org/win-x86-commandline/v3.5.0/nuget.exe + $(BaseIntermediateOutputPath)nuget\ + $(NuGetExeToolDir)NuGet.exe + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj index 830cf565e72..5b3076d67cf 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj @@ -1,7 +1,7 @@ - + - + - - diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props index beefa1d5462..1ec247f1718 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props @@ -6,17 +6,22 @@ true - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets index 08f5414e896..b5006eb8130 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets @@ -452,10 +452,24 @@ ComponentMsiFile=$(CrossArchMsiFile)" /> + + + + + + + @@ -466,12 +480,25 @@ $(MSBuildThisFileDirectory)vs\VS.Redist.Common.Component.nuspec.txt $(IntermediateOutputPath)vs\VS.Redist.Common.Component.nuspec - COMPONENT_MSI=$(ComponentMsiFile);ARCH=$(MsiArch);COMPONENT_NAME=$(VSInsertionComponentName);FRIENDLY_NAME=$(ProductBrandName);PROJECT_URL=$(RepositoryUrl) + + $(PackProperties)COMPONENT_MSI=$(ComponentMsiFile); + $(PackProperties)ARCH=$(MsiArch); + $(PackProperties)COMPONENT_NAME=$(VSInsertionComponentName); + $(PackProperties)FRIENDLY_NAME=$(ProductBrandName); + $(PackProperties)PROJECT_URL=$(RepositoryUrl); + + + $(PackArgs) $(VsInsertionNuspecFile) + $(PackArgs) -Version $(Version) + $(PackArgs) -OutputDirectory $(ArtifactsNonShippingPackagesDir) + $(PackArgs) -NoDefaultExcludes + $(PackArgs) -NoPackageAnalysis + $(PackArgs) -Properties "$(PackProperties)" - + - + + + + + + + @@ -482,12 +496,25 @@ $(MSBuildThisFileDirectory)vs\VS.Redist.Common.Component.nuspec.txt $(IntermediateOutputPath)vs\VS.Redist.Common.Component.nuspec - COMPONENT_MSI=$(ComponentMsiFile);ARCH=$(MsiArch);COMPONENT_NAME=$(VSInsertionComponentName);FRIENDLY_NAME=$(ProductBrandName);PROJECT_URL=$(RepositoryUrl) + + $(PackProperties)COMPONENT_MSI=$(ComponentMsiFile); + $(PackProperties)ARCH=$(MsiArch); + $(PackProperties)COMPONENT_NAME=$(VSInsertionComponentName); + $(PackProperties)FRIENDLY_NAME=$(ProductBrandName); + $(PackProperties)PROJECT_URL=$(RepositoryUrl); + + + $(PackArgs) $(VsInsertionNuspecFile) + $(PackArgs) -Version $(Version) + $(PackArgs) -OutputDirectory $(ArtifactsNonShippingPackagesDir) + $(PackArgs) -NoDefaultExcludes + $(PackArgs) -NoPackageAnalysis + $(PackArgs) -Properties "$(PackProperties)" - + 0) + { + int read = stream.Read(buffer, offset, count); + if (read == 0) + { + throw new InvalidOperationException("Unexpected end of stream"); + } + offset += read; + count -= read; + } + } +#else private void ReadExactly(byte[] buffer, int offset, int count) { stream.ReadExactly(buffer.AsSpan(offset, count)); } +#endif } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/ArWriter.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/ArWriter.cs index 3e44acbef07..cbfbf4e66e1 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/ArWriter.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/ArWriter.cs @@ -19,7 +19,12 @@ public ArWriter(Stream output, bool leaveOpen) { _stream = output; _leaveOpen = leaveOpen; +#if NET _stream.Write("!\n"u8); +#else + byte[] magic = Encoding.ASCII.GetBytes("!\n"); + _stream.Write(magic, 0, magic.Length); +#endif } public void AddEntry(ArEntry entry) @@ -45,7 +50,11 @@ public void AddEntry(ArEntry entry) private void Write(byte[] data) { +#if NET _stream.Write(data); +#else + _stream.Write(data, 0, data.Length); +#endif } public void Dispose() diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateMD5SumsFile.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateMD5SumsFile.cs index 2f1bad1a3a1..69ab5340c52 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateMD5SumsFile.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateMD5SumsFile.cs @@ -40,7 +40,11 @@ public override bool Execute() byte[] hash = md5.ComputeHash(fileStream); string relativePath = file.ItemSpec.Substring(RootDirectory.Length).TrimStart(Path.DirectorySeparatorChar).Replace('\\', '/'); // Always use Linux line-endings +#if NET writer.Write($"{Convert.ToHexStringLower(hash)} {relativePath}\n"); +#else + writer.Write($"{BitConverter.ToString(hash).Replace("-", "").ToLower()} {relativePath}\n"); +#endif } InstalledSize = (installedSize / 1024).ToString(); diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateRpmPackage.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateRpmPackage.cs index 102d3e93eac..41bd5450031 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateRpmPackage.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/CreateRpmPackage.cs @@ -72,11 +72,13 @@ public override bool Execute() "x64" => Architecture.X64, "arm" => Architecture.Arm, "arm64" => Architecture.Arm64, +#if NET "armv6" => Architecture.Armv6, "s390x" => Architecture.S390x, "ppc64le" => Architecture.Ppc64le, "riscv64" => Architecture.RiscV64, "loongarch64" => Architecture.LoongArch64, +#endif _ => throw new ArgumentException($"Unknown architecture: {PackageArchitecture}") }; RpmBuilder builder = new(PackageName, PackageVersion, PackageRelease, arch, OSPlatform.Create(PackageOS)) diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/HexConverter.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/HexConverter.cs index e1aec007337..0e6db40a605 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/HexConverter.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/HexConverter.cs @@ -1,4 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -global using HexConverter = System.Convert; \ No newline at end of file +#if NET +global using HexConverter = System.Convert; +#else +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + internal static class HexConverter + { + public static string ToHexStringLower(byte[] byteArray) + { + StringBuilder hexString = new(byteArray.Length * 2); + foreach (byte b in byteArray) + { + hexString.AppendFormat("{0:x2}", b); + } + return hexString.ToString(); + } + } +} + +#endif diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs index 3ae52d789d7..68b5f81f4f9 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/RpmBuilder.cs @@ -64,11 +64,13 @@ private static short GetRpmLeadArchitecture(Architecture architecture) Architecture.X64 => 1, Architecture.Arm => 12, Architecture.Arm64 => 19, +#if NET Architecture.Armv6 => 12, Architecture.S390x => 15, Architecture.Ppc64le => 16, Architecture.RiscV64 => 22, Architecture.LoongArch64 => 23, +#endif _ => throw new ArgumentException("Unsupported architecture", nameof(architecture)) }; } @@ -82,11 +84,13 @@ public static string GetRpmHeaderArchitecture(Architecture architecture) Architecture.X64 => "x86_64", Architecture.Arm => "armv7hl", Architecture.Arm64 => "aarch64", +#if NET Architecture.Armv6 => "armv6hl", Architecture.S390x => "s390x", Architecture.Ppc64le => "ppc64le", Architecture.RiscV64 => "riscv64", Architecture.LoongArch64 => "loongarch64", +#endif _ => throw new ArgumentException("Unsupported architecture", nameof(architecture)) }; } diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/StreamHelpers.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/StreamHelpers.cs index 0205a218743..b5ab9a9ce5a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/src/StreamHelpers.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/StreamHelpers.cs @@ -7,6 +7,27 @@ namespace Microsoft.DotNet.Build.Tasks.Installers { internal static class StreamHelpers { +#if !NET + public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + while (count > 0) + { + int read = stream.Read(buffer, offset, count); + if (read == 0) + { + throw new InvalidOperationException("Unexpected end of stream"); + } + offset += read; + count -= read; + } + } + + public static void Write(this Stream stream, byte[] buffer) + { + stream.Write(buffer, 0, buffer.Length); + } +#endif + public static Span ReadExactly(this Stream stream, int n) { byte[] buffer = new byte[n]; diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/Microsoft.DotNet.Build.Tasks.Packaging.csproj b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/Microsoft.DotNet.Build.Tasks.Packaging.csproj index 2e455a9faa6..3fd7becffb3 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/Microsoft.DotNet.Build.Tasks.Packaging.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/Microsoft.DotNet.Build.Tasks.Packaging.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true MSBuildSdk @@ -55,6 +55,12 @@ + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Microsoft.DotNet.Build.Tasks.Packaging.props b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Microsoft.DotNet.Build.Tasks.Packaging.props index 81116d55833..77c5c70435f 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Microsoft.DotNet.Build.Tasks.Packaging.props +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Microsoft.DotNet.Build.Tasks.Packaging.props @@ -1,16 +1,6 @@ - - - - - $(BundledRuntimeIdentifierGraphFile) - - true diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.common.targets b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.common.targets index 0705afd686a..cddcada8201 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.common.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.common.targets @@ -6,7 +6,8 @@ - $(MSBuildThisFileDirectory)../tools/net/ + $(MSBuildThisFileDirectory)../tools/net/ + $(MSBuildThisFileDirectory)../tools/netframework/ $(MSBuildThisFileDirectory)runtime.json $([MSBuild]::NormalizeDirectory('$(BaseOutputPath)', 'pkg')) @@ -18,32 +19,32 @@ $(PackageTargetRuntimeSuffix) - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.targets b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.targets index 52a20ab5b53..38ff3e34be0 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/src/build/Packaging.targets @@ -958,10 +958,6 @@ @(NETCoreApp100RIDs) - - - @(NETCoreAppCurrentRIDs) - diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ApplyBaseLineTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ApplyBaseLineTests.cs index 84b6a27b637..2dc393fb43a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ApplyBaseLineTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ApplyBaseLineTests.cs @@ -5,7 +5,7 @@ using Microsoft.Build.Utilities; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/CreateTrimDependencyGroupsTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/CreateTrimDependencyGroupsTests.cs index f9225cace72..7918aadc066 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/CreateTrimDependencyGroupsTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/CreateTrimDependencyGroupsTests.cs @@ -8,7 +8,7 @@ using System.Linq; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GenerateNuSpecAndPackTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GenerateNuSpecAndPackTests.cs index e442c18190c..47eefb4e9d9 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GenerateNuSpecAndPackTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GenerateNuSpecAndPackTests.cs @@ -7,7 +7,7 @@ using System.IO; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GetLastStablePackageTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GetLastStablePackageTests.cs index 5ccd9fb6058..c5ea06cb3d9 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GetLastStablePackageTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/GetLastStablePackageTests.cs @@ -5,7 +5,7 @@ using Microsoft.Build.Utilities; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/HarvestPackageTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/HarvestPackageTests.cs index 5fe2594cad0..919975aa504 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/HarvestPackageTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/HarvestPackageTests.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. -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.IO; diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Log.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Log.cs index ccb5cbf79f3..4805498dc36 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Log.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Log.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. -using AwesomeAssertions; +using FluentAssertions; using Xunit.Abstractions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Microsoft.DotNet.Build.Tasks.Packaging.Tests.csproj b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Microsoft.DotNet.Build.Tasks.Packaging.Tests.csproj index 6f48692c843..a8d9333437e 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Microsoft.DotNet.Build.Tasks.Packaging.Tests.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/Microsoft.DotNet.Build.Tasks.Packaging.Tests.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) false $(NoWarn);xUnit2013 @@ -16,7 +16,7 @@ - + diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NuGetAssetResolverTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NuGetAssetResolverTests.cs index 175bbf5ff4d..412faac4387 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NuGetAssetResolverTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NuGetAssetResolverTests.cs @@ -4,7 +4,7 @@ using NuGet.Frameworks; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NugetPropertyStringProviderTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NugetPropertyStringProviderTests.cs index 2435f749174..f08cbe55a68 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NugetPropertyStringProviderTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/NugetPropertyStringProviderTests.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. -using AwesomeAssertions; +using FluentAssertions; using System; using System.Collections.Generic; using System.IO; diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/PackageIndexTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/PackageIndexTests.cs index ddc595a795c..b4e26887681 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/PackageIndexTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/PackageIndexTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using Xunit; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/RuntimeGraphTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/RuntimeGraphTests.cs index b9b55b01d12..eef0d6735ee 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/RuntimeGraphTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/RuntimeGraphTests.cs @@ -6,7 +6,7 @@ using System.IO; using Xunit; using Xunit.Abstractions; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.Build.Tasks.Packaging.Tests { diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ValidateHarvestVersionIsLatestForReleaseTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ValidateHarvestVersionIsLatestForReleaseTests.cs index 60e2b4b1eaf..268f9c02a0a 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ValidateHarvestVersionIsLatestForReleaseTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/ValidateHarvestVersionIsLatestForReleaseTests.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. -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System; diff --git a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/VersionUtilityTests.cs b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/VersionUtilityTests.cs index da81df09b97..41d7546c6bb 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/VersionUtilityTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Packaging/tests/VersionUtilityTests.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. -using AwesomeAssertions; +using FluentAssertions; using System; using System.Collections.Generic; using Xunit; diff --git a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/ChooseBestP2PTargetFrameworkTask.cs b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/ChooseBestP2PTargetFrameworkTask.cs index 764fc9b52b8..d98db883c6b 100644 --- a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/ChooseBestP2PTargetFrameworkTask.cs +++ b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/ChooseBestP2PTargetFrameworkTask.cs @@ -111,9 +111,15 @@ public override bool Execute() private static bool TryParseFramework(string targetFrameworkMoniker, string? targetPlatformMoniker, string errorMessage, Log logger, out NuGetFramework nugetFramework) { // Check if we have a long name. +#if NETFRAMEWORK || NETSTANDARD + nugetFramework = targetFrameworkMoniker.Contains(",") + ? NuGetFramework.ParseComponents(targetFrameworkMoniker, targetPlatformMoniker) + : NuGetFramework.Parse(targetFrameworkMoniker); +#else nugetFramework = targetFrameworkMoniker.Contains(',', System.StringComparison.Ordinal) ? NuGetFramework.ParseComponents(targetFrameworkMoniker, targetPlatformMoniker) : NuGetFramework.Parse(targetFrameworkMoniker); +#endif // validate framework if (nugetFramework.IsUnsupported) diff --git a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj index b8d5a7ec9fb..c3ad17606c7 100644 --- a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/Microsoft.DotNet.Build.Tasks.TargetFramework.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true Configuration system for cross-targeting projects. @@ -13,6 +13,11 @@ + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/BinPlace.targets b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/BinPlace.targets index 68f71d4c843..e9be0f36a70 100644 --- a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/BinPlace.targets +++ b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/BinPlace.targets @@ -1,7 +1,7 @@ - + true diff --git a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.props b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.props index b548e38c216..d0180c04142 100644 --- a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.props +++ b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.props @@ -2,7 +2,8 @@ - ..\tools\net\Microsoft.DotNet.Build.Tasks.TargetFramework.dll + ..\tools\net\Microsoft.DotNet.Build.Tasks.TargetFramework.dll + ..\tools\netframework\Microsoft.DotNet.Build.Tasks.TargetFramework.dll diff --git a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.targets b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.targets index 19f7f200885..ccf6b879ff0 100644 --- a/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.targets +++ b/src/Microsoft.DotNet.Build.Tasks.TargetFramework/src/build/Microsoft.DotNet.Build.Tasks.TargetFramework.targets @@ -1,7 +1,7 @@ - + - + - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) Templating task package true true diff --git a/src/Microsoft.DotNet.Build.Tasks.Templating/src/build/Microsoft.DotNet.Build.Tasks.Templating.props b/src/Microsoft.DotNet.Build.Tasks.Templating/src/build/Microsoft.DotNet.Build.Tasks.Templating.props index c2835e13031..358cf132b71 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Templating/src/build/Microsoft.DotNet.Build.Tasks.Templating.props +++ b/src/Microsoft.DotNet.Build.Tasks.Templating/src/build/Microsoft.DotNet.Build.Tasks.Templating.props @@ -2,9 +2,10 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Templating.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Templating.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.Build.Tasks.Templating.dll - + diff --git a/src/Microsoft.DotNet.Build.Tasks.Templating/test/Microsoft.DotNet.Build.Tasks.Templating.Tests.csproj b/src/Microsoft.DotNet.Build.Tasks.Templating/test/Microsoft.DotNet.Build.Tasks.Templating.Tests.csproj index a1264fe5b35..03679598d86 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Templating/test/Microsoft.DotNet.Build.Tasks.Templating.Tests.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Templating/test/Microsoft.DotNet.Build.Tasks.Templating.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) diff --git a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests.csproj b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests.csproj index 7f4630f73fc..e276d85658e 100644 --- a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests.csproj @@ -1,13 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetFrameworkToolCurrent) - diff --git a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GenerateTrainingInputFilesTests.cs b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GenerateTrainingInputFilesTests.cs index 483994409c2..3b001e11895 100644 --- a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GenerateTrainingInputFilesTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GenerateTrainingInputFilesTests.cs @@ -130,7 +130,7 @@ private static void CreateVsix(string vsixPath, string manifestContent) } } - [WindowsOnlyFact] + [Fact] public void Execute() { var temp = Path.GetTempPath(); @@ -182,7 +182,7 @@ public void Execute() ""RelativeInstallationPath"": ""Common7\\IDE\\PrivateAssemblies\\System.Collections.Immutable.dll"", ""InstrumentationArguments"": ""/ExeConfig:\""%VisualStudio.InstallationUnderTest.Path%\\Common7\\IDE\\vsn.exe\"""" } -", json, ignoreLineEndingDifferences: true); +", json); JObject.Parse(json); @@ -193,7 +193,7 @@ public void Execute() ""RelativeInstallationPath"": ""MSBuild\\15.0\\Bin\\Roslyn\\System.Collections.Immutable.dll"", ""InstrumentationArguments"": ""/ExeConfig:\""%VisualStudio.InstallationUnderTest.Path%\\Common7\\IDE\\zzz.exe\"""" } -", json, ignoreLineEndingDifferences: true); +", json); JObject.Parse(json); @@ -204,7 +204,7 @@ public void Execute() ""RelativeInstallationPath"": ""Common7\\IDE\\CommonExtensions\\Microsoft\\ManagedLanguages\\VBCSharp\\LanguageServices\\x\\y\\z\\Microsoft.CodeAnalysis.CSharp.dll"", ""InstrumentationArguments"": ""/ExeConfig:\""%VisualStudio.InstallationUnderTest.Path%\\Common7\\IDE\\vsn.exe\"""" } -", json, ignoreLineEndingDifferences: true); +", json); JObject.Parse(json); json = File.ReadAllText(Path.Combine(outputDir, @"TeamEng\Configurations\TeamEng.OptProfTest.vs_debugger_start_no_build_cs_scribble\xyzMicrosoft.CodeAnalysis.VisualBasic.0.IBC.json")); @@ -214,7 +214,7 @@ public void Execute() ""RelativeInstallationPath"": ""Common7\\IDE\\CommonExtensions\\Microsoft\\ManagedLanguages\\VBCSharp\\LanguageServices\\x\\y\\z\\Microsoft.CodeAnalysis.VisualBasic.dll"", ""InstrumentationArguments"": ""/ExeConfig:\""%VisualStudio.InstallationUnderTest.Path%\\Common7\\IDE\\vsn.exe\"""" } -", json, ignoreLineEndingDifferences: true); +", json); JObject.Parse(json); Assert.True(result); diff --git a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GetRunSettingsSessionConfigurationTests.cs b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GetRunSettingsSessionConfigurationTests.cs index 6e28e615327..b042f2e5840 100644 --- a/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GetRunSettingsSessionConfigurationTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.VisualStudio.Tests/OptProf/GetRunSettingsSessionConfigurationTests.cs @@ -439,7 +439,7 @@ public void Execute() -FullyQualifiedName=DDRIT.RPS.CSharp.CSharpTest.EditingAndDesigner|FullyQualifiedName=VSPE.OptProfTests.vs_perf_designtime_ide_searchtest|FullyQualifiedName=VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs|FullyQualifiedName=VSPE.OptProfTests.vs_asl_cs_scenario|FullyQualifiedName=VSPE.OptProfTests.vs_ddbvtqa_vbwi|FullyQualifiedName=VSPE.OptProfTests.vs_asl_vb_scenario|FullyQualifiedName=VSPE.OptProfTests.vs_env_solution_createnewproject_vb_winformsapp|FullyQualifiedName=DDRIT.RPS.CSharp.CSharpTest.BuildAndDebugging", task.SessionConfiguration, ignoreLineEndingDifferences: true); +FullyQualifiedName=DDRIT.RPS.CSharp.CSharpTest.EditingAndDesigner|FullyQualifiedName=VSPE.OptProfTests.vs_perf_designtime_ide_searchtest|FullyQualifiedName=VSPE.OptProfTests.vs_perf_designtime_editor_intellisense_globalcompletionlist_cs|FullyQualifiedName=VSPE.OptProfTests.vs_asl_cs_scenario|FullyQualifiedName=VSPE.OptProfTests.vs_ddbvtqa_vbwi|FullyQualifiedName=VSPE.OptProfTests.vs_asl_vb_scenario|FullyQualifiedName=VSPE.OptProfTests.vs_env_solution_createnewproject_vb_winformsapp|FullyQualifiedName=DDRIT.RPS.CSharp.CSharpTest.BuildAndDebugging", task.SessionConfiguration); Assert.True(result); @@ -454,8 +454,8 @@ public void Execute() public void TestProductsOnly(string configJson, string expectedContainerString, string expectedTestCaseFilterString) { var (actualContainerString, actualTestCaseFilterString) = GetRunSettingsSessionConfiguration.GetTestContainersAndFilters(configJson, "config.json"); - Assert.Equal(expectedContainerString, actualContainerString, ignoreLineEndingDifferences: true); - Assert.Equal(expectedTestCaseFilterString, actualTestCaseFilterString, ignoreLineEndingDifferences: true); + Assert.Equal(expectedContainerString, actualContainerString); + Assert.Equal(expectedTestCaseFilterString, actualTestCaseFilterString); } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Microsoft.DotNet.Build.Tasks.VisualStudio.csproj b/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Microsoft.DotNet.Build.Tasks.VisualStudio.csproj index e7816e1e66b..410c5cee1c6 100644 --- a/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Microsoft.DotNet.Build.Tasks.VisualStudio.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Microsoft.DotNet.Build.Tasks.VisualStudio.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetFrameworkToolCurrent) true true Arcade SDK build tasks for Visual Studio profile guided optimization training @@ -12,7 +12,9 @@ - + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Vsix/FinalizeInsertionVsixFile.cs b/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Vsix/FinalizeInsertionVsixFile.cs index dc8f0bee9b1..ad8211eb5cd 100644 --- a/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Vsix/FinalizeInsertionVsixFile.cs +++ b/src/Microsoft.DotNet.Build.Tasks.VisualStudio/Vsix/FinalizeInsertionVsixFile.cs @@ -127,7 +127,7 @@ private static void UpdatePartHashInManifestJson(Package package, string partNam using (var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 2048, leaveOpen: false)) { - writer.Write(json.ToString(Formatting.None, Array.Empty())); + writer.Write(json.ToString(Formatting.None)); } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/CreateVisualStudioWorkloadSetTests.cs b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/CreateVisualStudioWorkloadSetTests.cs index ded16c69915..670aa8a5659 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/CreateVisualStudioWorkloadSetTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/CreateVisualStudioWorkloadSetTests.cs @@ -3,7 +3,7 @@ using System.IO; using System.Linq; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj index ddbfc1f02f7..cd322aac72d 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) @@ -13,7 +13,11 @@ - + + + + + @@ -32,6 +36,10 @@ + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/MsiTests.cs b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/MsiTests.cs index 6d8db4df90b..05a2099959d 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/MsiTests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads.Tests/MsiTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Linq; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -20,9 +20,8 @@ public class MsiTests : TestBase { private static ITaskItem BuildManifestMsi(string path, string msiVersion = "1.2.3", string platform = "x64", string msiOutputPath = null) { - string packageRootDirectory = Path.Combine(PackageRootDirectory, Guid.NewGuid().ToString("N")); TaskItem packageItem = new(path); - WorkloadManifestPackage pkg = new(packageItem, packageRootDirectory, new Version(msiVersion)); + WorkloadManifestPackage pkg = new(packageItem, PackageRootDirectory, new Version(msiVersion)); pkg.Extract(); WorkloadManifestMsi msi = new(pkg, platform, new MockBuildEngine(), WixToolsetPath, BaseIntermediateOutputPath, isSxS: true); @@ -44,6 +43,8 @@ public void WorkloadManifestsIncludeInstallationRecords() [WindowsOnlyFact] public void ItCanBuildSideBySideManifestMsis() { + string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg"); + // Build 6.0.200 manifest for version 6.0.3 ITaskItem msi603 = BuildManifestMsi(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.3.nupkg")); string msiPath603 = msi603.GetMetadata(Metadata.FullPath); @@ -79,9 +80,9 @@ public void ItCanBuildSideBySideManifestMsis() [WindowsOnlyFact] public void ItCanBuildAManifestMsi() { - string packageRootDirectory = Path.Combine(PackageRootDirectory, Guid.NewGuid().ToString("N")); + string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg"); TaskItem packageItem = new(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.3.nupkg")); - WorkloadManifestPackage pkg = new(packageItem, packageRootDirectory, new Version("1.2.3")); + WorkloadManifestPackage pkg = new(packageItem, PackageRootDirectory, new Version("1.2.3")); pkg.Extract(); WorkloadManifestMsi msi = new(pkg, "x64", new MockBuildEngine(), WixToolsetPath, BaseIntermediateOutputPath); @@ -109,11 +110,11 @@ public void ItCanBuildAManifestMsi() [WindowsOnlyFact] public void ItCanBuildATemplatePackMsi() { - string packageRootDirectory = Path.Combine(PackageRootDirectory, Guid.NewGuid().ToString("N")); + string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg"); string packagePath = Path.Combine(TestAssetsPath, "microsoft.ios.templates.15.2.302-preview.14.122.nupkg"); WorkloadPack p = new(new WorkloadPackId("Microsoft.iOS.Templates"), "15.2.302-preview.14.122", WorkloadPackKind.Template, null); - TemplatePackPackage pkg = new(p, packagePath, new[] { "x64" }, packageRootDirectory); + TemplatePackPackage pkg = new(p, packagePath, new[] { "x64" }, PackageRootDirectory); pkg.Extract(); var buildEngine = new MockBuildEngine(); WorkloadPackMsi msi = new(pkg, "x64", buildEngine, WixToolsetPath, BaseIntermediateOutputPath); diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj index 3abcaf2fe62..0f1d87796bc 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Microsoft.DotNet.Build.Tasks.Workloads.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true true @@ -30,6 +30,11 @@ + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Misc/msi.csproj b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Misc/msi.csproj index f50babc64d6..02fad4832cb 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Misc/msi.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/Misc/msi.csproj @@ -7,7 +7,6 @@ - $(BundledNETCoreAppTargetFramework) $(_Authors) $(_Copyright) __DESCRIPTION__ @@ -21,6 +20,7 @@ DotnetPlatform __PACKAGE_VERSION__ true + net10.0 diff --git a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/build/Microsoft.DotNet.Build.Tasks.Workloads.props b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/build/Microsoft.DotNet.Build.Tasks.Workloads.props index 3636efb67aa..6c44c02a663 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Workloads/src/build/Microsoft.DotNet.Build.Tasks.Workloads.props +++ b/src/Microsoft.DotNet.Build.Tasks.Workloads/src/build/Microsoft.DotNet.Build.Tasks.Workloads.props @@ -2,10 +2,11 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Workloads.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Build.Tasks.Workloads.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.Build.Tasks.Workloads.dll - - + + diff --git a/src/Microsoft.DotNet.CMake.Sdk/Microsoft.DotNet.CMake.Sdk.csproj b/src/Microsoft.DotNet.CMake.Sdk/Microsoft.DotNet.CMake.Sdk.csproj index f1f1af126e1..47b6a076af6 100644 --- a/src/Microsoft.DotNet.CMake.Sdk/Microsoft.DotNet.CMake.Sdk.csproj +++ b/src/Microsoft.DotNet.CMake.Sdk/Microsoft.DotNet.CMake.Sdk.csproj @@ -1,21 +1,19 @@ - + - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true - true Common toolset for calling into CMake from MSBuild and easily reference native assets from managed projects. MSBuildSdk - $(NoWarn);NU5128;CS0649 + $(NoWarn);NU5128 + true - - - - - - + + diff --git a/src/Microsoft.DotNet.CMake.Sdk/build/Microsoft.DotNet.CMake.Sdk.targets b/src/Microsoft.DotNet.CMake.Sdk/build/Microsoft.DotNet.CMake.Sdk.targets index 7680c206ab1..c34dc5d02ed 100644 --- a/src/Microsoft.DotNet.CMake.Sdk/build/Microsoft.DotNet.CMake.Sdk.targets +++ b/src/Microsoft.DotNet.CMake.Sdk/build/Microsoft.DotNet.CMake.Sdk.targets @@ -1,13 +1,6 @@ - - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.CMake.Sdk.dll - - - - - @@ -99,12 +92,6 @@ - - - - - - @@ -188,14 +175,8 @@ - - - - + DependsOnTargets="GetConfigScript"> diff --git a/src/Microsoft.DotNet.CMake.Sdk/sdk/ProjectReference.targets b/src/Microsoft.DotNet.CMake.Sdk/sdk/ProjectReference.targets index 5ba98a479a7..ad3c2720d8c 100644 --- a/src/Microsoft.DotNet.CMake.Sdk/sdk/ProjectReference.targets +++ b/src/Microsoft.DotNet.CMake.Sdk/sdk/ProjectReference.targets @@ -1,12 +1,6 @@ - - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.CMake.Sdk.dll - - - - @@ -17,6 +11,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -48,6 +116,12 @@ + + + + <_NativeProjectReferenceToBuild Include="%(NativeProjectReferenceNormalized.CMakeProject)" Condition="'%(NativeProjectReferenceNormalized.BuildNative)' == 'true'" @@ -61,62 +135,17 @@ + - - - - - - - - - - - - - <_SourceDirectory>$([System.IO.Path]::GetDirectoryName($(ReferencedCMakeLists))) - - - - - - - - - - <_ArtifactsToCopy Include="@(NativeProjectBinaries)" Condition="Exists('%(Identity)')" /> - - - - - - - - - - - diff --git a/src/Microsoft.DotNet.CMake.Sdk/src/CMakeFileApiModels.cs b/src/Microsoft.DotNet.CMake.Sdk/src/CMakeFileApiModels.cs deleted file mode 100644 index 96173cd1894..00000000000 --- a/src/Microsoft.DotNet.CMake.Sdk/src/CMakeFileApiModels.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. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Microsoft.DotNet.CMake.Sdk -{ - /// - /// C# object models for the CMake File API. - /// These types represent a subset of the CMake File API that we use for discovering build artifacts. - /// - /// For the full CMake File API specification, see: - /// https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html - /// - /// Specifically, we use: - /// - Index file: https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html#index-file - /// - Codemodel object (v2): https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html#codemodel-version-2 - /// - - internal sealed class CMakeFileApiIndex - { - public CMakeFileApiIndexReply Reply { get; set; } - } - - internal sealed class CMakeFileApiIndexReply - { - [JsonPropertyName("client-Microsoft.DotNet.CMake.Sdk")] - public CMakeFileApiClientReply ClientReply { get; set; } - } - - internal sealed class CMakeFileApiClientReply - { - [JsonPropertyName("codemodel-v2")] - public CMakeFileApiIndexCodeModel CodemodelV2 { get; set; } - } - - internal sealed class CMakeFileApiIndexCodeModel - { - public string JsonFile { get; set; } - } - - internal sealed class CMakeCodeModel - { - public CMakeCodeModelPaths Paths { get; set; } - public List Configurations { get; set; } - } - - internal sealed class CMakeCodeModelPaths - { - public string Source { get; set; } - public string Build { get; set; } - } - - internal sealed class CMakeConfiguration - { - public string Name { get; set; } - public List Directories { get; set; } - public List Targets { get; set; } - } - - internal sealed class CMakeDirectory - { - public string Source { get; set; } - public string Build { get; set; } - public List TargetIndexes { get; set; } - } - - internal sealed class CMakeTarget - { - public string Name { get; set; } - public string JsonFile { get; set; } - } - - internal sealed class CMakeTargetDetails - { - public List Artifacts { get; set; } - } - - internal sealed class CMakeArtifact - { - public string Path { get; set; } - } -} diff --git a/src/Microsoft.DotNet.CMake.Sdk/src/CreateCMakeFileApiQuery.cs b/src/Microsoft.DotNet.CMake.Sdk/src/CreateCMakeFileApiQuery.cs deleted file mode 100644 index 56e6fe8abbc..00000000000 --- a/src/Microsoft.DotNet.CMake.Sdk/src/CreateCMakeFileApiQuery.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Microsoft.DotNet.Build.Tasks; -using System; -using System.IO; - -namespace Microsoft.DotNet.CMake.Sdk -{ - /// - /// Creates a CMake File API query file to request codemodel information. - /// - public class CreateCMakeFileApiQuery : BuildTask - { - /// - /// The CMake build output directory where the query should be created. - /// - [Required] - public string CMakeOutputDir { get; set; } - - public override bool Execute() - { - try - { - // Create a client stateless query file with client name "Microsoft.DotNet.CMake.Sdk" - string queryDir = Path.Combine(CMakeOutputDir, ".cmake", "api", "v1", "query", "client-Microsoft.DotNet.CMake.Sdk"); - Directory.CreateDirectory(queryDir); - - string queryFile = Path.Combine(queryDir, "codemodel-v2"); - - // Create an empty file to request codemodel-v2 information - File.WriteAllText(queryFile, string.Empty); - - Log.LogMessage(LogImportance.Low, "Created CMake File API query at: {0}", queryFile); - - return true; - } - catch (Exception ex) - { - Log.LogErrorFromException(ex, showStackTrace: false); - return false; - } - } - } -} diff --git a/src/Microsoft.DotNet.CMake.Sdk/src/GetCMakeArtifactsFromFileApi.cs b/src/Microsoft.DotNet.CMake.Sdk/src/GetCMakeArtifactsFromFileApi.cs deleted file mode 100644 index 8baeea06470..00000000000 --- a/src/Microsoft.DotNet.CMake.Sdk/src/GetCMakeArtifactsFromFileApi.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Microsoft.DotNet.Build.Tasks; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json; - -namespace Microsoft.DotNet.CMake.Sdk -{ - /// - /// Reads CMake File API response to find artifacts for a specific source directory. - /// - public class GetCMakeArtifactsFromFileApi : BuildTask - { - /// - /// The CMake build output directory containing the File API response. - /// - [Required] - public string CMakeOutputDir { get; set; } - - /// - /// The source directory of the CMakeLists.txt to find artifacts for. - /// - [Required] - public string SourceDirectory { get; set; } - - /// - /// The configuration name (e.g., Debug, Release). - /// - [Required] - public string Configuration { get; set; } - - /// - /// Output: The list of artifact file paths. - /// - [Output] - public ITaskItem[] Artifacts { get; set; } - - public override bool Execute() - { - try - { - string replyDir = Path.Combine(CMakeOutputDir, ".cmake", "api", "v1", "reply"); - - if (!Directory.Exists(replyDir)) - { - Log.LogError("CMake File API reply directory does not exist: {0}", replyDir); - return false; - } - - // Find the latest index file - var indexFiles = Directory.GetFiles(replyDir, "index-*.json"); - if (indexFiles.Length == 0) - { - Log.LogError("No CMake File API index files found."); - return false; - } - - string indexFile = indexFiles.OrderByDescending(f => f).First(); - Log.LogMessage(LogImportance.Low, "Reading CMake File API index: {0}", indexFile); - - string indexJson = File.ReadAllText(indexFile); - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - var index = JsonSerializer.Deserialize(indexJson, options); - - if (index?.Reply?.ClientReply?.CodemodelV2?.JsonFile == null) - { - Log.LogError("No 'codemodel-v2' found in File API index reply."); - return false; - } - - string codeModelFile = Path.Combine(replyDir, index.Reply.ClientReply.CodemodelV2.JsonFile); - if (!File.Exists(codeModelFile)) - { - Log.LogError("Codemodel file not found: {0}", codeModelFile); - return false; - } - - Log.LogMessage(LogImportance.Low, "Reading codemodel: {0}", codeModelFile); - - string codeModelJson = File.ReadAllText(codeModelFile); - var codeModel = JsonSerializer.Deserialize(codeModelJson, options); - - if (codeModel == null) - { - Log.LogError("Failed to deserialize codemodel."); - return false; - } - - // Get the source root from the codemodel - string sourceRoot = codeModel.Paths?.Source?.Replace('\\', '/').TrimEnd('/') ?? ""; - - // Normalize source directory for comparison - string normalizedSourceDir = Path.GetFullPath(SourceDirectory).Replace('\\', '/').TrimEnd('/'); - - // Find the configuration using LINQ - var config = codeModel.Configurations?.FirstOrDefault(c => - string.Equals(c.Name, Configuration, StringComparison.OrdinalIgnoreCase)); - - if (config == null) - { - Log.LogError("Configuration '{0}' not found in CMake File API response.", Configuration); - return false; - } - - Log.LogMessage(LogImportance.Low, "Found configuration: {0}", Configuration); - - if (config.Directories == null || config.Targets == null) - { - Log.LogError("Configuration '{0}' has no directories or targets.", Configuration); - return false; - } - - // Find the matching directory using LINQ - var directory = config.Directories.FirstOrDefault(d => - { - string dirSource = d.Source?.Replace('\\', '/').TrimEnd('/') ?? ""; - - // Make the directory source path absolute - if (!Path.IsPathRooted(dirSource)) - { - dirSource = Path.Combine(sourceRoot, dirSource); - dirSource = Path.GetFullPath(dirSource).Replace('\\', '/').TrimEnd('/'); - } - - return string.Equals(dirSource, normalizedSourceDir, StringComparison.OrdinalIgnoreCase); - }); - - if (directory == null) - { - Log.LogError("Source directory '{0}' not found in CMake File API response.", SourceDirectory); - return false; - } - - Log.LogMessage(LogImportance.Low, "Found matching directory: {0}", SourceDirectory); - - // Get artifacts - var artifacts = new List(); - - if (directory.TargetIndexes != null) - { - foreach (int targetIndex in directory.TargetIndexes) - { - if (targetIndex < 0 || targetIndex >= config.Targets.Count) - { - continue; - } - - var target = config.Targets[targetIndex]; - if (string.IsNullOrEmpty(target.JsonFile)) - { - continue; - } - - string targetFile = Path.Combine(replyDir, target.JsonFile); - if (!File.Exists(targetFile)) - { - continue; - } - - Log.LogMessage(LogImportance.Low, "Reading target file: {0}", targetFile); - - // Read target details - string targetJson = File.ReadAllText(targetFile); - var targetDetails = JsonSerializer.Deserialize(targetJson, options); - - // Get artifacts - if (targetDetails?.Artifacts != null) - { - foreach (var artifact in targetDetails.Artifacts) - { - if (!string.IsNullOrEmpty(artifact.Path)) - { - string fullPath = Path.Combine(CMakeOutputDir, artifact.Path); - fullPath = Path.GetFullPath(fullPath); - - var item = new TaskItem(fullPath); - artifacts.Add(item); - - Log.LogMessage(LogImportance.Low, "Found artifact: {0}", fullPath); - } - } - } - } - } - - if (artifacts.Count == 0) - { - Log.LogWarning("No artifacts found for source directory '{0}' in configuration '{1}'.", SourceDirectory, Configuration); - } - - Artifacts = artifacts.ToArray(); - Log.LogMessage(LogImportance.Normal, "Found {0} artifact(s) for source directory '{1}' in configuration '{2}'", Artifacts.Length, SourceDirectory, Configuration); - - return true; - } - catch (Exception ex) - { - Log.LogErrorFromException(ex, showStackTrace: false); - return false; - } - } - } -} diff --git a/src/Microsoft.DotNet.Deployment.Tasks.Links/Microsoft.DotNet.Deployment.Tasks.Links.csproj b/src/Microsoft.DotNet.Deployment.Tasks.Links/Microsoft.DotNet.Deployment.Tasks.Links.csproj index 2c6e5be3b50..18ba7e8e323 100644 --- a/src/Microsoft.DotNet.Deployment.Tasks.Links/Microsoft.DotNet.Deployment.Tasks.Links.csproj +++ b/src/Microsoft.DotNet.Deployment.Tasks.Links/Microsoft.DotNet.Deployment.Tasks.Links.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true Aka.ms link manager @@ -20,4 +20,8 @@ + + + + diff --git a/src/Microsoft.DotNet.Deployment.Tasks.Links/build/Microsoft.DotNet.Deployment.Tasks.Links.props b/src/Microsoft.DotNet.Deployment.Tasks.Links/build/Microsoft.DotNet.Deployment.Tasks.Links.props index 5a0d42049fa..50e5224cdb5 100644 --- a/src/Microsoft.DotNet.Deployment.Tasks.Links/build/Microsoft.DotNet.Deployment.Tasks.Links.props +++ b/src/Microsoft.DotNet.Deployment.Tasks.Links/build/Microsoft.DotNet.Deployment.Tasks.Links.props @@ -2,9 +2,11 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Deployment.Tasks.Links.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.Deployment.Tasks.Links.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.Deployment.Tasks.Links.dll - - + + + diff --git a/src/Microsoft.DotNet.Deployment.Tasks.Links/src/AkaMSLinksBase.cs b/src/Microsoft.DotNet.Deployment.Tasks.Links/src/AkaMSLinksBase.cs index 704a968428d..77a64125333 100644 --- a/src/Microsoft.DotNet.Deployment.Tasks.Links/src/AkaMSLinksBase.cs +++ b/src/Microsoft.DotNet.Deployment.Tasks.Links/src/AkaMSLinksBase.cs @@ -24,7 +24,11 @@ protected AkaMSLinkManager CreateAkaMSLinksManager() AkaMSLinkManager manager; if (!string.IsNullOrEmpty(ClientCertificate)) { +#if NET9_0_OR_GREATER manager = new AkaMSLinkManager(ClientId, X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(File.ReadAllText(ClientCertificate)), password: null), Tenant, Log); +#else + manager = new AkaMSLinkManager(ClientId, new X509Certificate2(Convert.FromBase64String(File.ReadAllText(ClientCertificate))), Tenant, Log); +#endif } else if (!string.IsNullOrEmpty(ClientSecret)) { diff --git a/src/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj b/src/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj index 1fd26013eb6..841055a6823 100644 --- a/src/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj +++ b/src/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true MSBuildSdk true diff --git a/src/Microsoft.DotNet.GenAPI/build/Microsoft.DotNet.GenAPI.targets b/src/Microsoft.DotNet.GenAPI/build/Microsoft.DotNet.GenAPI.targets index dd8968a723a..38560453f49 100644 --- a/src/Microsoft.DotNet.GenAPI/build/Microsoft.DotNet.GenAPI.targets +++ b/src/Microsoft.DotNet.GenAPI/build/Microsoft.DotNet.GenAPI.targets @@ -2,7 +2,8 @@ - $(MSBuildThisFileDirectory)\..\tools\net\Microsoft.DotNet.GenAPI.dll + $(MSBuildThisFileDirectory)\..\tools\net\Microsoft.DotNet.GenAPI.dll + $(MSBuildThisFileDirectory)\..\tools\netframework\Microsoft.DotNet.GenAPI.dll @@ -24,7 +25,7 @@ - + diff --git a/src/Microsoft.DotNet.GenFacades/Microsoft.DotNet.GenFacades.csproj b/src/Microsoft.DotNet.GenFacades/Microsoft.DotNet.GenFacades.csproj index f4166824922..119fce5fc77 100644 --- a/src/Microsoft.DotNet.GenFacades/Microsoft.DotNet.GenFacades.csproj +++ b/src/Microsoft.DotNet.GenFacades/Microsoft.DotNet.GenFacades.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) MSBuildSdk true true @@ -16,4 +16,9 @@ + + + + + diff --git a/src/Microsoft.DotNet.GenFacades/RoslynBuildTask.cs b/src/Microsoft.DotNet.GenFacades/RoslynBuildTask.cs index c96e5c37b26..42b61be4397 100644 --- a/src/Microsoft.DotNet.GenFacades/RoslynBuildTask.cs +++ b/src/Microsoft.DotNet.GenFacades/RoslynBuildTask.cs @@ -6,7 +6,9 @@ using System; using System.IO; using System.Reflection; +#if NETCOREAPP using System.Runtime.Loader; +#endif namespace Microsoft.DotNet.Build.Tasks { @@ -17,24 +19,40 @@ public abstract partial class RoslynBuildTask : BuildTask public override bool Execute() { +#if NETCOREAPP AssemblyLoadContext currentContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!; currentContext.Resolving += ResolverForRoslyn; +#else + AppDomain.CurrentDomain.AssemblyResolve += ResolverForRoslyn; +#endif try { return ExecuteCore(); } finally { +#if NETCOREAPP currentContext.Resolving -= ResolverForRoslyn; +#else + AppDomain.CurrentDomain.AssemblyResolve -= ResolverForRoslyn; +#endif } } public abstract bool ExecuteCore(); +#if NETCOREAPP private Assembly ResolverForRoslyn(AssemblyLoadContext context, AssemblyName assemblyName) { return LoadRoslyn(assemblyName, path => context.LoadFromAssemblyPath(path)); } +#else + private Assembly ResolverForRoslyn(object sender, ResolveEventArgs args) + { + AssemblyName name = new(args.Name); + return LoadRoslyn(name, path => Assembly.LoadFrom(path)); + } +#endif private Assembly LoadRoslyn(AssemblyName name, Func loadFromPath) { diff --git a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.ClearVersion.targets b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.ClearVersion.targets index 22620a07d9f..34b4f7787b4 100644 --- a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.ClearVersion.targets +++ b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.ClearVersion.targets @@ -1,7 +1,7 @@ - + diff --git a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.NotSupported.targets b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.NotSupported.targets index 46a061c0f5a..86ead85a490 100644 --- a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.NotSupported.targets +++ b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.NotSupported.targets @@ -1,7 +1,7 @@ - + AddGenFacadeNotSupportedCompileItem;$(CoreCompileDependsOn) diff --git a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.PartialFacadeSource.targets b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.PartialFacadeSource.targets index df655759d85..5a1c6439a83 100644 --- a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.PartialFacadeSource.targets +++ b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.PartialFacadeSource.targets @@ -1,7 +1,7 @@ - + ReferencePath diff --git a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.targets b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.targets index 20656c4312b..0d0215d40ac 100644 --- a/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.targets +++ b/src/Microsoft.DotNet.GenFacades/build/Microsoft.DotNet.GenFacades.targets @@ -2,7 +2,8 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.GenFacades.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.GenFacades.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.GenFacades.dll diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Clients/AzureDevOpsClient.cs b/src/Microsoft.DotNet.Git.IssueManager/src/Clients/AzureDevOpsClient.cs new file mode 100644 index 00000000000..69261ed743b --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Clients/AzureDevOpsClient.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. + +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Git.IssueManager.Clients +{ + static class AzureDevOpsClient + { + private static readonly Regex RepositoryUriPattern = new Regex( + @"^https://dev\.azure\.com\/(?[a-zA-Z0-9]+)/(?[a-zA-Z0-9-]+)/_git/(?[a-zA-Z0-9-\\.]+)"); + + private static readonly Regex LegacyRepositoryUriPattern = new Regex( + @"^https://(?[a-zA-Z0-9]+)\.visualstudio\.com/(?[a-zA-Z0-9-]+)/_git/(?[a-zA-Z0-9-\.]+)"); + + private const string DefaultApiVersion = "5.0"; + + public static async Task GetCommitAuthorAsync( + string repositoryUrl, + string commit, + string personalAccessToken) + { + (string accountName, string projectName, string repoName) = ParseRepoUri(repositoryUrl); + + using (HttpClient httpClient = GetHttpClient(accountName, projectName, personalAccessToken)) + { + HttpRequestMessage getMessage = new HttpRequestMessage(HttpMethod.Get, $"_apis/git/repositories/{repoName}/commits?searchCriteria.ids={commit}"); + HttpResponseMessage response = await httpClient.SendAsync(getMessage); + + response.EnsureSuccessStatusCode(); + + AzureDevOpsCommit commitResponse = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + if (commitResponse == null) + { + throw new Exception($"No commit with id {commit} found in '{repositoryUrl}'"); + } + + return $"Azure DevOps user: {commitResponse.Value.First().Author.Name}"; + } + } + + private static HttpClient GetHttpClient(string accountName, string projectName, string personalAccessToken) + { + HttpClient client = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true }) + { + BaseAddress = new Uri($"https://dev.azure.com/{accountName}/{projectName}/") + }; + + client.DefaultRequestHeaders.Add( + "Accept", + $"application/json;api-version={DefaultApiVersion}"); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalAccessToken)))); + + return client; + } + + private static (string accountName, string projectName, string repoName) ParseRepoUri(string repositoryUri) + { + Match m = RepositoryUriPattern.Match(repositoryUri); + if (!m.Success) + { + m = LegacyRepositoryUriPattern.Match(repositoryUri); + if (!m.Success) + { + throw new ArgumentException( + "Repository URI should be in the form https://dev.azure.com/:account/:project/_git/:repo or " + + "https://:account.visualstudio.com/:project/_git/:repo"); + } + } + + return (m.Groups["account"].Value, + m.Groups["project"].Value, + m.Groups["repo"].Value); + } + } +} diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Clients/GitHubClient.cs b/src/Microsoft.DotNet.Git.IssueManager/src/Clients/GitHubClient.cs new file mode 100644 index 00000000000..438eb49763e --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Clients/GitHubClient.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. + +using Octokit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.DotNet.Git.IssueManager.Helpers; + +namespace Microsoft.DotNet.Git.IssueManager.Clients +{ + static class GitHubClient + { + public static async Task GetCommitAuthorAsync( + string repositoryUrl, + string commit, + string personalAccessToken) + { + (string owner, string repoName) = ParseRepoUri(repositoryUrl); + + Octokit.GitHubClient client = new Octokit.GitHubClient(new ProductHeaderValue("assets-publisher")); + Credentials tokenAuth = new Credentials(personalAccessToken); + client.Credentials = tokenAuth; + + GitHubCommit commitInfo = await client.Repository.Commit.Get(owner, repoName, commit); + + while (commitInfo.Author.Type == "Bot") + { + if (!commitInfo.Parents.Any()) break; + commit = commitInfo.Parents.First().Sha; + commitInfo = await client.Repository.Commit.Get(owner, repoName, commit); + } + + return $"@{commitInfo.Author.Login}"; + } + + public static async Task CreateNewIssueAsync( + string repositoryUrl, + string issueTitle, + string issueDescription, + string personalAccessToken, + int? milestone = null, + IEnumerable labels = null, + IEnumerable assignees = null) + { + (string owner, string repoName) = ParseRepoUri(repositoryUrl); + + Octokit.GitHubClient client = new Octokit.GitHubClient(new ProductHeaderValue("assets-publisher")); + Credentials tokenAuth = new Credentials(personalAccessToken); + client.Credentials = tokenAuth; + + NewIssue issueToBeCreated = new NewIssue(issueTitle) + { + Body = issueDescription, + Milestone = milestone + }; + + if (labels is not null) + { + issueToBeCreated.Labels.AddRange(labels); + } + + if (assignees is not null) + { + issueToBeCreated.Assignees.AddRange(assignees); + } + + Issue createdIssue = await client.Issue.Create(owner, repoName, issueToBeCreated); + + return createdIssue.Number; + } + + public static async Task CreateNewIssueCommentAsync( + string repositoryUrl, + int issueNumber, + string comment, + string personalAccessToken) + { + (string owner, string repoName) = ParseRepoUri(repositoryUrl); + + Octokit.GitHubClient client = new Octokit.GitHubClient(new ProductHeaderValue("assets-publisher")); + Credentials tokenAuth = new Credentials(personalAccessToken); + client.Credentials = tokenAuth; + + IssueComment createdComment = await client.Issue.Comment.Create(owner, repoName, issueNumber, comment); + + return createdComment.HtmlUrl; + } + + /// + /// Extracts the owner and repository name from . + /// + /// The repository URI. + /// The owner and the repository name + private static (string owner, string repositoryName) ParseRepoUri(string repositoryUri) + { + Regex repositoryUriPattern = new Regex(@"^/(?[^/]+)/(?[^/]+)/?$"); + Uri uri = new Uri(repositoryUri); + + Match match = repositoryUriPattern.Match(uri.AbsolutePath); + + if (!match.Success) + { + return default; + } + + return (match.Groups["owner"].Value, match.Groups["repo"].Value); + } + } +} diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/CollectionExtensions.cs b/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/CollectionExtensions.cs new file mode 100644 index 00000000000..dfe6aa27d6e --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/CollectionExtensions.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. + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Microsoft.DotNet.Git.IssueManager.Helpers +{ + internal static class CollectionExtensions + { + public static void AddRange(this Collection collection, IEnumerable items) + { + foreach (T item in items) + { + collection.Add(item); + } + } + } +} diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/RepositoryHelper.cs b/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/RepositoryHelper.cs new file mode 100644 index 00000000000..aeb18c58877 --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Helpers/RepositoryHelper.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. + +using Microsoft.DotNet.Git.IssueManager.Clients; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Git.IssueManager.Helpers +{ + static class RepositoryHelper + { + public static async Task GetCommitAuthorAsync( + string repositoryUrl, + string commit, + string gitHubPersonalAccessToken, + string azureDevOpsPersonalAccessToken) + { + if (Uri.TryCreate(repositoryUrl, UriKind.Absolute, out Uri parsedUri)) + { + if (parsedUri.Host == "github.com") + { + if (string.IsNullOrEmpty(gitHubPersonalAccessToken)) + { + throw new ArgumentException("A GitHub personal access token is needed for this operation."); + } + + return await GitHubClient.GetCommitAuthorAsync(repositoryUrl, commit, gitHubPersonalAccessToken); + } + + if (string.IsNullOrEmpty(azureDevOpsPersonalAccessToken)) + { + throw new ArgumentException("An Azure DevOps personal access token is needed for this operation."); + } + + return await AzureDevOpsClient.GetCommitAuthorAsync(repositoryUrl, commit, azureDevOpsPersonalAccessToken); + } + + throw new InvalidCastException($"'{parsedUri}' is not a valid URI"); + } + + public static async Task CreateNewIssueAsync( + string repositoryUrl, + string issueTitle, + string issueDescription, + string gitHubPersonalAccessToken, + int? milestone = null, + IEnumerable labels = null, + IEnumerable assignees = null) + { + if (Uri.TryCreate(repositoryUrl, UriKind.Absolute, out Uri parsedUri)) + { + if (parsedUri.Host == "github.com") + { + if (string.IsNullOrEmpty(gitHubPersonalAccessToken)) + { + throw new ArgumentException("A GitHub personal access token is needed for this operation."); + } + + return await GitHubClient.CreateNewIssueAsync( + repositoryUrl, + issueTitle, + issueDescription, + gitHubPersonalAccessToken, + milestone, + labels, + assignees); + } + + throw new NotImplementedException("Creating issues is not currently supported for an Azure DevOps repo."); + } + + throw new InvalidCastException($"'{parsedUri}' is not a valid URI"); + } + + public static async Task CreateNewIssueCommentAsync( + string repositoryUrl, + int issueNumber, + string comment, + string gitHubPersonalAccessToken) + { + if (Uri.TryCreate(repositoryUrl, UriKind.Absolute, out Uri parsedUri)) + { + if (parsedUri.Host == "github.com") + { + if (string.IsNullOrEmpty(gitHubPersonalAccessToken)) + { + throw new ArgumentException("A GitHub personal access token is needed for this operation."); + } + + return await GitHubClient.CreateNewIssueCommentAsync( + repositoryUrl, + issueNumber, + comment, + gitHubPersonalAccessToken); + } + + throw new NotImplementedException("Creating comments is not currently supported for an Azure DevOps repo."); + } + + throw new InvalidCastException($"'{parsedUri}' is not a valid URI"); + } + } +} diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/IssueManager.cs b/src/Microsoft.DotNet.Git.IssueManager/src/IssueManager.cs new file mode 100644 index 00000000000..f0bfce1ea7b --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/IssueManager.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. + +using Microsoft.DotNet.Git.IssueManager.Helpers; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Git.IssueManager +{ + public class IssueManager + { + public string GitHubPersonalAccessToken { get; set; } + + public string AzureDevOpsPersonalAccessToken { get; set; } + + public IssueManager( + string gitHubPersonalAccessToken = null, + string azureDevOpsPersonalAccessToken = null) + { + GitHubPersonalAccessToken = gitHubPersonalAccessToken; + AzureDevOpsPersonalAccessToken = azureDevOpsPersonalAccessToken; + } + + /// + /// Gets the author of a commit from a repo+commit + /// + /// The repository URL + /// The commit SHA. + /// In GitHub returns the handle, in AzDO returns the full name. + public async Task GetCommitAuthorAsync(string repositoryUrl, string commit) + { + if (string.IsNullOrEmpty(repositoryUrl)) + { + throw new ArgumentException(nameof(repositoryUrl)); + } + + if (string.IsNullOrEmpty(commit)) + { + throw new ArgumentException(nameof(commit)); + } + + return await RepositoryHelper.GetCommitAuthorAsync(repositoryUrl, commit, GitHubPersonalAccessToken, AzureDevOpsPersonalAccessToken); + + } + + /// + /// Creates a new GitHub issue. + /// + /// Repository URL where to create the issue. + /// Title of the issue. + /// Description of the issue. + /// + public async Task CreateNewIssueAsync( + string repositoryUrl, + string issueTitle, + string issueDescription, + int? milestone = null, + IEnumerable labels = null, + IEnumerable assignees = null) + { + return await RepositoryHelper.CreateNewIssueAsync( + repositoryUrl, + issueTitle, + issueDescription, + GitHubPersonalAccessToken, + milestone, + labels, + assignees); + } + + /// + /// Creates a new comment on a GitHub issue. + /// + /// Repository URL where to create the issue. + /// Title of the issue. + /// Description of the issue. + /// + public async Task CreateNewIssueCommentAsync( + string repositoryUrl, + int issueNumber, + string comment) + { + return await RepositoryHelper.CreateNewIssueCommentAsync( + repositoryUrl, + issueNumber, + comment, + GitHubPersonalAccessToken); + } + } +} diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Microsoft.DotNet.Git.IssueManager.csproj b/src/Microsoft.DotNet.Git.IssueManager/src/Microsoft.DotNet.Git.IssueManager.csproj new file mode 100644 index 00000000000..07472d5b491 --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Microsoft.DotNet.Git.IssueManager.csproj @@ -0,0 +1,19 @@ + + + + $(NetMinimum);$(NetFrameworkMinimum) + false + true + true + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Git.IssueManager/src/Models/AzureDevOpsCommit.cs b/src/Microsoft.DotNet.Git.IssueManager/src/Models/AzureDevOpsCommit.cs new file mode 100644 index 00000000000..5180373b656 --- /dev/null +++ b/src/Microsoft.DotNet.Git.IssueManager/src/Models/AzureDevOpsCommit.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. + +using System; +using System.Collections.Generic; + +namespace Microsoft.DotNet.Git.IssueManager +{ + public class AzureDevOpsCommit + { + public List Value { get; set; } + } + + public class Value + { + public Author Author { get; set; } + } + + public class Author + { + public string Name { get; set; } + + public string Email { get; set; } + + public DateTime Date { get; set; } + } +} diff --git a/src/Microsoft.DotNet.Helix/Client/CSharp/Microsoft.DotNet.Helix.Client.csproj b/src/Microsoft.DotNet.Helix/Client/CSharp/Microsoft.DotNet.Helix.Client.csproj index 061f83a5238..e0e56b3ae1c 100644 --- a/src/Microsoft.DotNet.Helix/Client/CSharp/Microsoft.DotNet.Helix.Client.csproj +++ b/src/Microsoft.DotNet.Helix/Client/CSharp/Microsoft.DotNet.Helix.Client.csproj @@ -2,7 +2,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);netstandard2.0;$(NetFrameworkToolCurrent) true This package provides access to the Helix Api located at https://helix.dot.net/ https://helix.dot.net/api/openapi.json @@ -17,8 +17,17 @@ + + + + + + + + + diff --git a/src/Microsoft.DotNet.Helix/Client/CSharp/generated-code/HelixApi.cs b/src/Microsoft.DotNet.Helix/Client/CSharp/generated-code/HelixApi.cs index e03da99984b..3739b4d5d67 100644 --- a/src/Microsoft.DotNet.Helix/Client/CSharp/generated-code/HelixApi.cs +++ b/src/Microsoft.DotNet.Helix/Client/CSharp/generated-code/HelixApi.cs @@ -333,7 +333,9 @@ public RestApiException(Request request, Response response, string responseConte Response = new ResponseWrapper(response, responseContent); } +#if NET [Obsolete] +#endif protected RestApiException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -343,7 +345,9 @@ protected RestApiException(SerializationInfo info, StreamingContext context) Response = JsonConvert.DeserializeObject(responseString, SerializerSettings); } +#if NET [Obsolete] +#endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) @@ -371,14 +375,18 @@ public RestApiException(Request request, Response response, string responseConte Body = body; } +#if NET [Obsolete] +#endif protected RestApiException(SerializationInfo info, StreamingContext context) : base(info, context) { Body = JsonConvert.DeserializeObject(info.GetString("Body")); } +#if NET [Obsolete] +#endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) diff --git a/src/Microsoft.DotNet.Helix/JobSender.Tests/Microsoft.DotNet.Helix.JobSender.Tests.csproj b/src/Microsoft.DotNet.Helix/JobSender.Tests/Microsoft.DotNet.Helix.JobSender.Tests.csproj index 9bfe75e833c..63f357bd05a 100644 --- a/src/Microsoft.DotNet.Helix/JobSender.Tests/Microsoft.DotNet.Helix.JobSender.Tests.csproj +++ b/src/Microsoft.DotNet.Helix/JobSender.Tests/Microsoft.DotNet.Helix.JobSender.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) diff --git a/src/Microsoft.DotNet.Helix/JobSender/Microsoft.DotNet.Helix.JobSender.csproj b/src/Microsoft.DotNet.Helix/JobSender/Microsoft.DotNet.Helix.JobSender.csproj index acb482b56fa..e6886316066 100644 --- a/src/Microsoft.DotNet.Helix/JobSender/Microsoft.DotNet.Helix.JobSender.csproj +++ b/src/Microsoft.DotNet.Helix/JobSender/Microsoft.DotNet.Helix.JobSender.csproj @@ -1,7 +1,8 @@ - $(BundledNETCoreAppTargetFramework) + + $(NetToolCurrent);netstandard2.0;$(NetFrameworkToolCurrent) true Microsoft.DotNet.Helix.Client This package provides a simple API for constructing and sending jobs to the Helix Api @@ -17,8 +18,15 @@ + + + + + + + diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAndroidWorkItemsTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAndroidWorkItemsTests.cs index 6880c3ee589..3a583af2cf4 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAndroidWorkItemsTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAndroidWorkItemsTests.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAppleWorkItemsTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAppleWorkItemsTests.cs index 7a4df6ffb1a..afc04b2181c 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAppleWorkItemsTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/CreateXHarnessAppleWorkItemsTests.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/FindDotNetCliPackageTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/FindDotNetCliPackageTests.cs index d07e4c67bb8..b5eca9d6bb1 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/FindDotNetCliPackageTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/FindDotNetCliPackageTests.cs @@ -5,7 +5,7 @@ using System.Net; using System.Net.Http; using System.Text.RegularExpressions; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/InstallDotNetToolTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/InstallDotNetToolTests.cs index 204c09e1d9d..66eb52cca63 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/InstallDotNetToolTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/InstallDotNetToolTests.cs @@ -7,7 +7,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Microsoft.Arcade.Test.Common; using Microsoft.DotNet.Internal.DependencyInjection.Testing; @@ -174,7 +174,7 @@ public void InstallsToolWithExtraParamsSuccessfully() "net6.0", "--arch", "arm64", - "--source", + "--add-source", "https://dev.azure.com/some/feed", ToolName, }; diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests.csproj b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests.csproj index 7fd8f7231cf..970d2627c6f 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests.csproj +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests.csproj @@ -1,11 +1,11 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) - + diff --git a/src/Microsoft.DotNet.Helix/Sdk/AzureDevOpsTask.cs b/src/Microsoft.DotNet.Helix/Sdk/AzureDevOpsTask.cs index e6e2b759a9c..a102acb64dc 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/AzureDevOpsTask.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/AzureDevOpsTask.cs @@ -62,6 +62,7 @@ private async Task ExecuteAsync() // observed on Mac anyway. // https://github.com/dotnet/dnceng/issues/6410 +#if NET using SocketsHttpHandler handler = new SocketsHttpHandler { AllowAutoRedirect = false, @@ -87,6 +88,13 @@ private async Task ExecuteAsync() }; using (var client = new HttpClient(handler) +#else + using (var client = new HttpClient(new HttpClientHandler + { + AllowAutoRedirect = false, + CheckCertificateRevocationList = true, + }) +#endif { DefaultRequestHeaders = { diff --git a/src/Microsoft.DotNet.Helix/Sdk/BaseTask.Desktop.cs b/src/Microsoft.DotNet.Helix/Sdk/BaseTask.Desktop.cs new file mode 100644 index 00000000000..9d6c80c8af3 --- /dev/null +++ b/src/Microsoft.DotNet.Helix/Sdk/BaseTask.Desktop.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. + +using Microsoft.Arcade.Common.Desktop; + +namespace Microsoft.DotNet.Helix +{ + public partial class BaseTask + { + static BaseTask() + { + AssemblyResolver.Enable(); + } + } +} diff --git a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAndroidWorkItems.cs b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAndroidWorkItems.cs index e8f00390894..fa0544c68a5 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAndroidWorkItems.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAndroidWorkItems.cs @@ -157,9 +157,7 @@ private string GetDefaultCommand(ITaskItem appPackage, int expectedExitCode) "--timeout \"$timeout\" " + "--package-name \"$package_name\" " + " -v " + - $"{ devOutArg } { instrumentationArg } { exitCodeArg } { extraArguments } " + - "--arg env:DOTNET_CI=true " + - $"{ passthroughArgs }"; + $"{ devOutArg } { instrumentationArg } { exitCodeArg } { extraArguments } { passthroughArgs }"; } private string GetHelixCommand( diff --git a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs index a1d7c943d8c..0eb88de121b 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/CreateXHarnessAppleWorkItems.cs @@ -213,7 +213,6 @@ private string GetDefaultCommand(bool includesTestRunner, bool resetSimulator) = "-v " + (!includesTestRunner ? "--expected-exit-code $expected_exit_code " : string.Empty) + (resetSimulator ? $"--reset-simulator " : string.Empty) + - "--set-env=DOTNET_CI=true " + (!string.IsNullOrEmpty(AppArguments) ? "-- " + AppArguments : string.Empty); private string GetHelixCommand( diff --git a/src/Microsoft.DotNet.Helix/Sdk/CreateXUnitV3WorkItems.cs b/src/Microsoft.DotNet.Helix/Sdk/CreateXUnitV3WorkItems.cs deleted file mode 100644 index 1cad75467b1..00000000000 --- a/src/Microsoft.DotNet.Helix/Sdk/CreateXUnitV3WorkItems.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Build.Framework; - -namespace Microsoft.DotNet.Helix.Sdk -{ - /// - /// MSBuild custom task to create HelixWorkItems for XUnit v3 test projects. - /// Unlike v2 tests which need an external console runner, v3 tests are - /// self-hosting executables that can be run directly with 'dotnet exec'. - /// - public class CreateXUnitV3WorkItems : BaseTask - { - /// - /// An array of XUnit v3 project workitems containing the following metadata: - /// - [Required] PublishDirectory: the publish output directory of the test project - /// - [Required] TargetPath: the output dll path - /// - [Optional] Arguments: a string of arguments to be passed to the test executable - /// The two required parameters will be automatically created if XUnitV3Project.Identity is set to the path of the XUnit v3 csproj file - /// - [Required] - public ITaskItem[] XUnitV3Projects { get; set; } - - /// - /// The path to the dotnet executable on the Helix agent. Defaults to "dotnet" - /// - public string PathToDotnet { get; set; } = "dotnet"; - - /// - /// Boolean true if this is a posix shell, false if not. - /// This does not need to be set by a user; it is automatically determined in Microsoft.DotNet.Helix.Sdk.MonoQueue.targets - /// - [Required] - public bool IsPosixShell { get; set; } - - /// - /// Optional timeout for all created workitems. - /// Defaults to 300s. - /// - public string XUnitWorkItemTimeout { get; set; } - - /// - /// Whether to use Microsoft Testing Platform (MTP) command-line arguments. - /// When true, uses --report-xunit/--auto-reporters off style arguments. - /// When false, uses legacy -xml/-noAutoReporters style arguments. - /// - public bool UseMicrosoftTestingPlatformRunner { get; set; } - - /// - /// An array of ITaskItems of type HelixWorkItem - /// - [Output] - public ITaskItem[] XUnitV3WorkItems { get; set; } - - /// - /// The main method of this MSBuild task which calls the asynchronous execution method and - /// collates logged errors in order to determine the success of HelixWorkItem creation per - /// provided XUnit v3 project data. - /// - /// A boolean value indicating the success of HelixWorkItem creation per provided XUnit v3 project data. - public override bool Execute() - { - ExecuteAsync().GetAwaiter().GetResult(); - return !Log.HasLoggedErrors; - } - - /// - /// The asynchronous execution method for this MSBuild task which verifies the integrity of required properties - /// and validates their formatting, specifically determining whether the provided XUnit v3 project data have a - /// one-to-one mapping. It then creates this mapping before asynchronously preparing the HelixWorkItem TaskItem - /// objects via the PrepareWorkItem method. - /// - private async Task ExecuteAsync() - { - XUnitV3WorkItems = (await Task.WhenAll(XUnitV3Projects.Select(PrepareWorkItem))).Where(wi => wi != null).ToArray(); - } - - /// - /// Prepares HelixWorkItem given XUnit v3 project information. - /// - /// An ITaskItem instance representing the prepared HelixWorkItem. - private async Task PrepareWorkItem(ITaskItem xunitV3Project) - { - // Forces this task to run asynchronously - await Task.Yield(); - - if (!xunitV3Project.GetRequiredMetadata(Log, "PublishDirectory", out string publishDirectory)) - { - return null; - } - if (!xunitV3Project.GetRequiredMetadata(Log, "TargetPath", out string targetPath)) - { - return null; - } - - xunitV3Project.TryGetMetadata("Arguments", out string arguments); - - string assemblyName = Path.GetFileName(targetPath); - string assemblyBaseName = assemblyName; - if (assemblyBaseName.EndsWith(".dll")) - { - assemblyBaseName = assemblyBaseName.Substring(0, assemblyBaseName.Length - 4); - } - - // XUnit v3 tests are self-hosting - run the assembly directly with dotnet exec - string resultArgs = UseMicrosoftTestingPlatformRunner - ? "--results-directory . --report-xunit --report-xunit-filename testResults.xml --auto-reporters off" - : "-xml testResults.xml -noAutoReporters"; - - string command = $"{PathToDotnet} exec --roll-forward Major " + - $"--runtimeconfig {assemblyBaseName}.runtimeconfig.json " + - $"--depsfile {assemblyBaseName}.deps.json " + - $"{assemblyName} {resultArgs}" + - (string.IsNullOrEmpty(arguments) ? "" : " " + arguments); - - Log.LogMessage($"Creating XUnit v3 work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); - - TimeSpan timeout = TimeSpan.FromMinutes(5); - if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) - { - if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) - { - Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); - } - } - - var result = new Microsoft.Build.Utilities.TaskItem(assemblyName, new Dictionary() - { - { "Identity", assemblyName }, - { "PayloadDirectory", publishDirectory }, - { "Command", command }, - { "Timeout", timeout.ToString() }, - }); - xunitV3Project.CopyMetadataTo(result); - return result; - } - } -} diff --git a/src/Microsoft.DotNet.Helix/Sdk/FindDotNetCliPackage.cs b/src/Microsoft.DotNet.Helix/Sdk/FindDotNetCliPackage.cs index 59723ce43c2..76a6faa1848 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/FindDotNetCliPackage.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/FindDotNetCliPackage.cs @@ -60,6 +60,7 @@ public class FindDotNetCliPackage : MSBuildTaskBase public override void ConfigureServices(IServiceCollection collection) { +#if NET var socketsHandler = new SocketsHttpHandler { AllowAutoRedirect = true, @@ -84,7 +85,9 @@ public override void ConfigureServices(IServiceCollection collection) VerificationTimeIgnored = true, }; _httpMessageHandler = socketsHandler; - +#else + _httpMessageHandler = new HttpClientHandler { CheckCertificateRevocationList = true }; +#endif collection.TryAddSingleton(_httpMessageHandler); collection.TryAddSingleton(Log); } diff --git a/src/Microsoft.DotNet.Helix/Sdk/InstallDotNetTool.cs b/src/Microsoft.DotNet.Helix/Sdk/InstallDotNetTool.cs index 5e636785c46..12dd11c595a 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/InstallDotNetTool.cs +++ b/src/Microsoft.DotNet.Helix/Sdk/InstallDotNetTool.cs @@ -145,7 +145,7 @@ private bool InstallTool(ICommandFactory commandFactory) if (!string.IsNullOrEmpty(Source)) { - args.Add("--source"); + args.Add("--add-source"); args.Add(Source); } diff --git a/src/Microsoft.DotNet.Helix/Sdk/Microsoft.DotNet.Helix.Sdk.csproj b/src/Microsoft.DotNet.Helix/Sdk/Microsoft.DotNet.Helix.Sdk.csproj index bb3d2d3ea8b..6e1b0a8cf9c 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/Microsoft.DotNet.Helix.Sdk.csproj +++ b/src/Microsoft.DotNet.Helix/Sdk/Microsoft.DotNet.Helix.Sdk.csproj @@ -1,11 +1,10 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) MSBuildSdk true - false - true + **/*.Desktop.* @@ -15,8 +14,13 @@ + - + + + @@ -25,6 +29,12 @@ + + + + + + - - - - - - - true - - - - - + + - - - $(BundledRuntimeIdentifierGraphFile) + + $(MSBuildThisFileDirectory)net/Microsoft.DotNet.Helix.Sdk.dll + $(MSBuildThisFileDirectory)netframework/Microsoft.DotNet.Helix.Sdk.dll - $(MSBuildThisFileDirectory)net/Microsoft.DotNet.Helix.Sdk.dll - AssemblyTaskFactory Helix - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.targets b/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.targets index 3e8fc6f81d7..f4e34394a37 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.targets +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/Microsoft.DotNet.Helix.Sdk.targets @@ -1,9 +1,64 @@ - - - + + + + + + + BuildOnlySettings; + PrepareForBuild; + PreBuildEvent; + ResolveReferences; + PostBuildEvent + + + + + + BeforeResolveReferences; + AssignProjectConfiguration; + ResolveProjectReferences; + FindInvalidProjectReferences; + AfterResolveReferences + + + + BeforeClean; + UnmanagedUnregistration; + CoreClean; + PrepareProjectReferences; + CleanPublishFolder; + AfterClean + + + + + + + + + + Build; + BeforeTest; + CoreTest; + AfterTest; + + + + + + + + + diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/NoBuild.targets b/src/Microsoft.DotNet.Helix/Sdk/tools/NoBuild.targets deleted file mode 100644 index d8385a64845..00000000000 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/NoBuild.targets +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - BuildOnlySettings; - PrepareForBuild; - PreBuildEvent; - ResolveReferences; - PostBuildEvent - - - - - - BeforeResolveReferences; - AssignProjectConfiguration; - ResolveProjectReferences; - FindInvalidProjectReferences; - AfterResolveReferences - - - - BeforeClean; - UnmanagedUnregistration; - CoreClean; - PrepareProjectReferences; - CleanPublishFolder; - AfterClean - - - - - - - - - - Build; - BeforeTest; - CoreTest; - AfterTest; - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/trx.py b/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/trx.py index 850da8658c6..cb2afbec916 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/trx.py +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/azure-pipelines/reporter/formats/trx.py @@ -45,13 +45,7 @@ def read_results(self, path): classname = test_classes[test_id] method = test_methods[test_id] - # xunit reports testName as the fully qualified name - # (ClassName.MethodName), while MSTest uses just the method name. - # Avoid duplicating the class prefix when it's already present. - if test_name.startswith(classname + '.'): - name = test_name - else: - name = classname + '.' + test_name + name = classname + '.' + test_name type_name = classname duration = 0.0 result = "Pass" diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-helix-job.android.sh b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-helix-job.android.sh index 68f943d56d3..c6a5c4023ea 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-helix-job.android.sh +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-helix-job.android.sh @@ -78,15 +78,6 @@ function report_infrastructure_failure() { } # Act out the actual commands (and time constrain them to create buffer for the end of this script) -# shellcheck disable=SC1091 -source command.sh & -COMMAND_PID=$! -sleep "$command_timeout" && kill -s 0 $COMMAND_PID > /dev/null 2>&1 && echo "ERROR: WORKLOAD TIMED OUT - Killing user command.." && kill $COMMAND_PID 2> /dev/null & -WATCHDOG_PID=$! -wait $COMMAND_PID -exit_code=$? -# Kill the watchdog process (and its sleeping child) now that the command has finished -kill $WATCHDOG_PID 2> /dev/null -wait $WATCHDOG_PID 2> /dev/null +source command.sh & PID=$! ; (sleep "$command_timeout" && kill -s 0 $PID > /dev/null 2>&1 && echo "ERROR: WORKLOAD TIMED OUT - Killing user command.." && kill $PID 2> /dev/null & ) ; wait $PID -exit $exit_code +exit $? diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-runner.apple.sh b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-runner.apple.sh index df05d3dab70..d9a19177447 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-runner.apple.sh +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/xharness-runner.apple.sh @@ -184,15 +184,8 @@ start_time="$(date '+%Y-%m-%d %H:%M:%S')" # Act out the actual commands (and time constrain them to create buffer for the end of this script) # shellcheck disable=SC1091 -source command.sh & -COMMAND_PID=$! -sleep "$command_timeout" && kill -s 0 $COMMAND_PID > /dev/null 2>&1 && echo "ERROR: WORKLOAD TIMED OUT - Killing user command.." && kill $COMMAND_PID 2> /dev/null & -WATCHDOG_PID=$! -wait $COMMAND_PID +source command.sh & PID=$! ; (sleep "$command_timeout" && kill -s 0 $PID > /dev/null 2>&1 && echo "ERROR: WORKLOAD TIMED OUT - Killing user command.." && kill $PID 2> /dev/null & ) ; wait $PID exit_code=$? -# Kill the watchdog process (and its sleeping child) now that the command has finished -kill $WATCHDOG_PID 2> /dev/null -wait $WATCHDOG_PID 2> /dev/null # In case of issues, include the syslog (last 2 MB from the time this work item has been running) if [ $exit_code -ne 0 ]; then diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xunit-runner/XUnitRunner.targets b/src/Microsoft.DotNet.Helix/Sdk/tools/xunit-runner/XUnitRunner.targets index 5c2f250360e..93d1cc833a2 100644 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xunit-runner/XUnitRunner.targets +++ b/src/Microsoft.DotNet.Helix/Sdk/tools/xunit-runner/XUnitRunner.targets @@ -2,7 +2,7 @@ - net11.0 + net10.0 netcoreapp2.0 2.9.3 diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.props b/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.props deleted file mode 100644 index 448853fb587..00000000000 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - <_HelixMonoQueueTargets>$(_HelixMonoQueueTargets);$(MSBuildThisFileDirectory)XUnitV3Runner.targets - - - diff --git a/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.targets b/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.targets deleted file mode 100644 index 95b55816a0f..00000000000 --- a/src/Microsoft.DotNet.Helix/Sdk/tools/xunitv3-runner/XUnitV3Runner.targets +++ /dev/null @@ -1,58 +0,0 @@ - - - - - <_XUnitV3PublishTargetsPath>$(MSBuildThisFileDirectory)../xunit-runner/XUnitPublish.targets - - - true - - - - - - - - - - <_CurrentXUnitV3Project>%(XUnitV3Project.Identity) - <_CurrentXUnitV3AdditionalProperties>%(XUnitV3Project.AdditionalProperties) - - - - - - - - - - - $(_XUnitV3PublishOutputDir) - $(_XUnitV3TargetPath) - - - - - - - - - - - - diff --git a/src/Microsoft.DotNet.Internal.DependencyInjection.Testing/Microsoft.DotNet.Internal.DependencyInjection.Testing.csproj b/src/Microsoft.DotNet.Internal.DependencyInjection.Testing/Microsoft.DotNet.Internal.DependencyInjection.Testing.csproj index 00ee17d4f1b..61cf5eb57df 100644 --- a/src/Microsoft.DotNet.Internal.DependencyInjection.Testing/Microsoft.DotNet.Internal.DependencyInjection.Testing.csproj +++ b/src/Microsoft.DotNet.Internal.DependencyInjection.Testing/Microsoft.DotNet.Internal.DependencyInjection.Testing.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) diff --git a/src/Microsoft.DotNet.Internal.SymbolHelper/Microsoft.DotNet.Internal.SymbolHelper.csproj b/src/Microsoft.DotNet.Internal.SymbolHelper/Microsoft.DotNet.Internal.SymbolHelper.csproj index 517a3da0038..9b2bc487369 100644 --- a/src/Microsoft.DotNet.Internal.SymbolHelper/Microsoft.DotNet.Internal.SymbolHelper.csproj +++ b/src/Microsoft.DotNet.Internal.SymbolHelper/Microsoft.DotNet.Internal.SymbolHelper.csproj @@ -1,12 +1,20 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true true + + + diff --git a/src/Microsoft.DotNet.MacOsPkg.Tests/Microsoft.DotNet.MacOsPkg.Tests.csproj b/src/Microsoft.DotNet.MacOsPkg.Tests/Microsoft.DotNet.MacOsPkg.Tests.csproj index c315fe4f2fd..bf8fa2ea936 100644 --- a/src/Microsoft.DotNet.MacOsPkg.Tests/Microsoft.DotNet.MacOsPkg.Tests.csproj +++ b/src/Microsoft.DotNet.MacOsPkg.Tests/Microsoft.DotNet.MacOsPkg.Tests.csproj @@ -1,12 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) enable - + @@ -20,15 +20,16 @@ - - + <_MacOSPkgToolPattern>@(_MacOsPkgToolPath->'%(RootDir)%(Directory)')**\*.* diff --git a/src/Microsoft.DotNet.MacOsPkg.Tests/UnpackPackTests.cs b/src/Microsoft.DotNet.MacOsPkg.Tests/UnpackPackTests.cs index 5a38af10d1a..743cb881934 100644 --- a/src/Microsoft.DotNet.MacOsPkg.Tests/UnpackPackTests.cs +++ b/src/Microsoft.DotNet.MacOsPkg.Tests/UnpackPackTests.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. -using AwesomeAssertions; +using FluentAssertions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,7 +24,7 @@ public class UnpackPackTests Path.GetDirectoryName(typeof(UnpackPackTests).Assembly.Location)!, "tools", "macospkg", - "Microsoft.DotNet.MacOsPkg.Cli.dll"); + "Microsoft.Dotnet.MacOsPkg.dll"); const UnixFileMode nonExecutableFileMode = UnixFileMode.OtherRead | UnixFileMode.GroupRead | @@ -216,7 +216,7 @@ private bool RunPkgProcess(string inputPath, string outputPath, string action) var process = Process.Start(new ProcessStartInfo() { FileName = "dotnet", - Arguments = $@"exec ""{pkgToolPath}"" {action} ""{inputPath}"" ""{outputPath}""", + Arguments = $@"exec ""{pkgToolPath}"" ""{inputPath}"" ""{outputPath}"" {action}", UseShellExecute = false, RedirectStandardError = true, }); diff --git a/src/Microsoft.DotNet.MacOsPkg/Cli/Microsoft.DotNet.MacOsPkg.Cli.csproj b/src/Microsoft.DotNet.MacOsPkg/Cli/Microsoft.DotNet.MacOsPkg.Cli.csproj index 4491fe5ef78..c5ac6d05aea 100644 --- a/src/Microsoft.DotNet.MacOsPkg/Cli/Microsoft.DotNet.MacOsPkg.Cli.csproj +++ b/src/Microsoft.DotNet.MacOsPkg/Cli/Microsoft.DotNet.MacOsPkg.Cli.csproj @@ -1,21 +1,25 @@ - $(BundledNETCoreAppTargetFramework) + + $(NetToolCurrent);$(NetFrameworkToolCurrent) Exe enable true The MacOsPkg CLI tool is used for unpacking, packing, and validating MacOS .pkg files and nested .app bundles. Arcade Build Tool MacOS Pkg false - false - true - dotnet-macos-pkg - - + + + + true + dotnet-macos-pkg + + diff --git a/src/Microsoft.DotNet.MacOsPkg/Core/Microsoft.DotNet.MacOsPkg.Core.csproj b/src/Microsoft.DotNet.MacOsPkg/Core/Microsoft.DotNet.MacOsPkg.Core.csproj index 6cbf9db68cd..dc9776f9fe8 100644 --- a/src/Microsoft.DotNet.MacOsPkg/Core/Microsoft.DotNet.MacOsPkg.Core.csproj +++ b/src/Microsoft.DotNet.MacOsPkg/Core/Microsoft.DotNet.MacOsPkg.Core.csproj @@ -1,7 +1,8 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) + Library enable true The MacOsPkg Library is used for unpacking, packing, and validating MacOS .pkg files and nested .app bundles. diff --git a/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj b/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj index 28543282f6c..64d4a8c22a0 100644 --- a/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj +++ b/src/Microsoft.DotNet.NuGetRepack/tasks/Microsoft.DotNet.NuGetRepack.Tasks.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true MSBuildSdk @@ -13,9 +13,17 @@ + + + + + + + + diff --git a/src/Microsoft.DotNet.NuGetRepack/tasks/src/AssemblyResolution.cs b/src/Microsoft.DotNet.NuGetRepack/tasks/src/AssemblyResolution.cs new file mode 100644 index 00000000000..07d6a5d2fa6 --- /dev/null +++ b/src/Microsoft.DotNet.NuGetRepack/tasks/src/AssemblyResolution.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. + +#if NET472 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.DotNet.Tools +{ + internal static class AssemblyResolution + { + internal static TaskLoggingHelper Log; + + public static void Initialize() + { + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + } + + private static Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + var name = new AssemblyName(args.Name); + + if (!name.Name.Equals("System.Collections.Immutable", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var fullPath = Path.Combine(Path.GetDirectoryName(typeof(AssemblyResolution).Assembly.Location), "System.Collections.Immutable.dll"); + + Assembly sci; + try + { + sci = Assembly.LoadFile(fullPath); + } + catch (Exception e) + { + Log?.LogWarning($"AssemblyResolve: exception while loading '{fullPath}': {e.Message}"); + return null; + } + + if (name.Version <= sci.GetName().Version) + { + Log?.LogMessage(MessageImportance.Low, $"AssemblyResolve: loaded '{fullPath}' to {AppDomain.CurrentDomain.FriendlyName}"); + return sci; + } + + return null; + } + } +} + +#endif diff --git a/src/Microsoft.DotNet.NuGetRepack/tasks/src/ReplacePackageParts.cs b/src/Microsoft.DotNet.NuGetRepack/tasks/src/ReplacePackageParts.cs index e715f80a5d4..0d9d22ec6a5 100644 --- a/src/Microsoft.DotNet.NuGetRepack/tasks/src/ReplacePackageParts.cs +++ b/src/Microsoft.DotNet.NuGetRepack/tasks/src/ReplacePackageParts.cs @@ -17,8 +17,15 @@ namespace Microsoft.DotNet.Tools /// /// Replaces content of files in specified package with new content and updates version of the package. /// +#if NET472 + [LoadInSeparateAppDomain] + public sealed class ReplacePackageParts : AppDomainIsolatedTask + { + static ReplacePackageParts() => AssemblyResolution.Initialize(); +#else public sealed class ReplacePackageParts : Microsoft.Build.Utilities.Task { +#endif /// /// Full path to the package to process. /// @@ -60,6 +67,9 @@ public sealed class ReplacePackageParts : Microsoft.Build.Utilities.Task public override bool Execute() { +#if NET472 + AssemblyResolution.Log = Log; +#endif try { ExecuteImpl(); @@ -67,6 +77,9 @@ public override bool Execute() } finally { +#if NET472 + AssemblyResolution.Log = null; +#endif } } diff --git a/src/Microsoft.DotNet.NuGetRepack/tasks/src/UpdatePackageVersionTask.cs b/src/Microsoft.DotNet.NuGetRepack/tasks/src/UpdatePackageVersionTask.cs index 751c56330df..8f922025d15 100644 --- a/src/Microsoft.DotNet.NuGetRepack/tasks/src/UpdatePackageVersionTask.cs +++ b/src/Microsoft.DotNet.NuGetRepack/tasks/src/UpdatePackageVersionTask.cs @@ -10,8 +10,15 @@ namespace Microsoft.DotNet.Tools { +#if NET472 + [LoadInSeparateAppDomain] + public sealed class UpdatePackageVersionTask : AppDomainIsolatedTask + { + static UpdatePackageVersionTask() => AssemblyResolution.Initialize(); +#else public class UpdatePackageVersionTask : Microsoft.Build.Utilities.Task { +#endif public string VersionKind { get; set; } [Required] @@ -26,6 +33,9 @@ public class UpdatePackageVersionTask : Microsoft.Build.Utilities.Task public override bool Execute() { +#if NET472 + AssemblyResolution.Log = Log; +#endif try { ExecuteImpl(); @@ -33,6 +43,9 @@ public override bool Execute() } finally { +#if NET472 + AssemblyResolution.Log = null; +#endif } } diff --git a/src/Microsoft.DotNet.NuGetRepack/tests/Microsoft.DotNet.NuGetRepack.Tests.csproj b/src/Microsoft.DotNet.NuGetRepack/tests/Microsoft.DotNet.NuGetRepack.Tests.csproj index 1fa17785aa7..c202f2e3d6a 100644 --- a/src/Microsoft.DotNet.NuGetRepack/tests/Microsoft.DotNet.NuGetRepack.Tests.csproj +++ b/src/Microsoft.DotNet.NuGetRepack/tests/Microsoft.DotNet.NuGetRepack.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) $(DefaultExcludesInProjectFolder);Resources\*.cs diff --git a/src/Microsoft.DotNet.PackageTesting.Tests/Microsoft.DotNet.PackageTesting.Tests.csproj b/src/Microsoft.DotNet.PackageTesting.Tests/Microsoft.DotNet.PackageTesting.Tests.csproj index 6adcf914d2d..ed72ff9eaf6 100644 --- a/src/Microsoft.DotNet.PackageTesting.Tests/Microsoft.DotNet.PackageTesting.Tests.csproj +++ b/src/Microsoft.DotNet.PackageTesting.Tests/Microsoft.DotNet.PackageTesting.Tests.csproj @@ -1,12 +1,16 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) + + + + diff --git a/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj b/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj index 92fbc5f27c6..74b80da5bfe 100644 --- a/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj +++ b/src/Microsoft.DotNet.PackageTesting/Microsoft.DotNet.PackageTesting.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) MSBuildSdk true true @@ -13,6 +13,10 @@ + + + + diff --git a/src/Microsoft.DotNet.PackageTesting/build/Microsoft.DotNet.PackageTesting.props b/src/Microsoft.DotNet.PackageTesting/build/Microsoft.DotNet.PackageTesting.props index b59d540b0fb..9eabfb1d4c4 100644 --- a/src/Microsoft.DotNet.PackageTesting/build/Microsoft.DotNet.PackageTesting.props +++ b/src/Microsoft.DotNet.PackageTesting/build/Microsoft.DotNet.PackageTesting.props @@ -2,11 +2,12 @@ - $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.PackageTesting.dll + $(MSBuildThisFileDirectory)..\tools\netframework\Microsoft.DotNet.PackageTesting.dll + $(MSBuildThisFileDirectory)..\tools\net\Microsoft.DotNet.PackageTesting.dll - - - + + + diff --git a/src/Microsoft.DotNet.RemoteExecutor/tests/Microsoft.DotNet.RemoteExecutor.Tests.csproj b/src/Microsoft.DotNet.RemoteExecutor/tests/Microsoft.DotNet.RemoteExecutor.Tests.csproj index e7e45e6574c..0db92001071 100644 --- a/src/Microsoft.DotNet.RemoteExecutor/tests/Microsoft.DotNet.RemoteExecutor.Tests.csproj +++ b/src/Microsoft.DotNet.RemoteExecutor/tests/Microsoft.DotNet.RemoteExecutor.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/BoundaryConditionTests.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/BoundaryConditionTests.cs index 2ed009d5620..96366159bed 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/BoundaryConditionTests.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/BoundaryConditionTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.DotNet.XUnitExtensions; using Xunit; diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/CredentialHandlingTests.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/CredentialHandlingTests.cs index accb4518aaf..a9cf811728b 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/CredentialHandlingTests.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/CredentialHandlingTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.DotNet.XUnitExtensions; using Xunit; diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/FeedEnablingTests.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/FeedEnablingTests.cs index dcbbcc161cc..d38cab4887b 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/FeedEnablingTests.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/FeedEnablingTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Xunit; namespace Microsoft.DotNet.SetupNugetSources.Tests diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/InternalFeedAdditionTests.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/InternalFeedAdditionTests.cs index e205d494cf7..3427526ae02 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/InternalFeedAdditionTests.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/InternalFeedAdditionTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -143,6 +143,129 @@ public async Task ConfigWithExistingInternalFeed_DoesNotDuplicate() // Should have 4 total sources (3 original + 1 added transport) modifiedConfig.GetPackageSourceCount().Should().Be(4, "should not duplicate existing sources"); } + + [Fact] + public async Task ConfigWithDotNetEng_AddsDotNetEngInternal() + { + // Arrange + var originalConfig = @" + + + + + +"; + var configPath = Path.Combine(_testOutputDirectory, "nuget.config"); + await Task.Run(() => File.WriteAllText(configPath, originalConfig)); + + // Act + var result = await _scriptRunner.RunScript(configPath); + + // Assert + result.exitCode.Should().Be(0, "Script should succeed, but got error: {0}", result.error); + var modifiedConfig = await Task.Run(() => File.ReadAllText(configPath)); + + modifiedConfig.ShouldContainPackageSource("dotnet-eng-internal", + "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/v3/index.json", + "should add dotnet-eng-internal feed"); + + // Should have 3 total sources (2 original + 1 added internal) + modifiedConfig.GetPackageSourceCount().Should().Be(3, "should add internal feed"); + } + + [Fact] + public async Task ConfigWithDotNetTools_AddsDotNetToolsInternal() + { + // Arrange + var originalConfig = @" + + + + + +"; + var configPath = Path.Combine(_testOutputDirectory, "nuget.config"); + await Task.Run(() => File.WriteAllText(configPath, originalConfig)); + + // Act + var result = await _scriptRunner.RunScript(configPath); + + // Assert + result.exitCode.Should().Be(0, "Script should succeed, but got error: {0}", result.error); + var modifiedConfig = await Task.Run(() => File.ReadAllText(configPath)); + + modifiedConfig.ShouldContainPackageSource("dotnet-tools-internal", + "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json", + "should add dotnet-tools-internal feed"); + + // Should have 3 total sources (2 original + 1 added internal) + modifiedConfig.GetPackageSourceCount().Should().Be(3, "should add internal feed"); + } + + [Fact] + public async Task ConfigWithDotNetEngAndTools_AddsBothInternalFeeds() + { + // Arrange + var originalConfig = @" + + + + + + +"; + var configPath = Path.Combine(_testOutputDirectory, "nuget.config"); + await Task.Run(() => File.WriteAllText(configPath, originalConfig)); + + // Act + var result = await _scriptRunner.RunScript(configPath); + + // Assert + result.exitCode.Should().Be(0, "Script should succeed, but got error: {0}", result.error); + var modifiedConfig = await Task.Run(() => File.ReadAllText(configPath)); + + modifiedConfig.ShouldContainPackageSource("dotnet-eng-internal", + "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/v3/index.json", + "should add dotnet-eng-internal feed"); + + modifiedConfig.ShouldContainPackageSource("dotnet-tools-internal", + "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json", + "should add dotnet-tools-internal feed"); + + // Should have 5 total sources (3 original + 2 added internal) + modifiedConfig.GetPackageSourceCount().Should().Be(5, "should add both internal feeds"); + } + + [Fact] + public async Task ConfigWithoutDotNetEngOrTools_DoesNotAddInternalFeeds() + { + // Arrange + var originalConfig = @" + + + + + +"; + var configPath = Path.Combine(_testOutputDirectory, "nuget.config"); + await Task.Run(() => File.WriteAllText(configPath, originalConfig)); + + // Act + var result = await _scriptRunner.RunScript(configPath); + + // Assert + result.exitCode.Should().Be(0, "Script should succeed, but got error: {0}", result.error); + var modifiedConfig = await Task.Run(() => File.ReadAllText(configPath)); + + modifiedConfig.ShouldNotContainPackageSource("dotnet-eng-internal", + "should not add dotnet-eng-internal when dotnet-eng is not present"); + modifiedConfig.ShouldNotContainPackageSource("dotnet-tools-internal", + "should not add dotnet-tools-internal when dotnet-tools is not present"); + + // Should add dotnet9 internal feeds + modifiedConfig.ShouldContainPackageSource("dotnet9-internal"); + modifiedConfig.ShouldContainPackageSource("dotnet9-internal-transport"); + } } } diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/Microsoft.DotNet.SetupNugetSources.Tests.csproj b/src/Microsoft.DotNet.SetupNugetSources.Tests/Microsoft.DotNet.SetupNugetSources.Tests.csproj index b337ab7e629..83d868c3bdc 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/Microsoft.DotNet.SetupNugetSources.Tests.csproj +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/Microsoft.DotNet.SetupNugetSources.Tests.csproj @@ -1,12 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) true - + diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/NoChangeScenarioTests.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/NoChangeScenarioTests.cs index 40aa7c29759..632511d3441 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/NoChangeScenarioTests.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/NoChangeScenarioTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.DotNet.XUnitExtensions; using Xunit; diff --git a/src/Microsoft.DotNet.SetupNugetSources.Tests/NuGetConfigAssertions.cs b/src/Microsoft.DotNet.SetupNugetSources.Tests/NuGetConfigAssertions.cs index c4ba85f5c23..0bd0f5b5dfd 100644 --- a/src/Microsoft.DotNet.SetupNugetSources.Tests/NuGetConfigAssertions.cs +++ b/src/Microsoft.DotNet.SetupNugetSources.Tests/NuGetConfigAssertions.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; -using AwesomeAssertions; +using FluentAssertions; namespace Microsoft.DotNet.SetupNugetSources.Tests { diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj b/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj index a0d9eca3837..ab867e7a4a4 100644 --- a/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true Common toolset for building shared frameworks and framework packs. @@ -30,5 +30,12 @@ + + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/BuildTask.props b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/BuildTask.props index 1d595945de5..3314587321b 100644 --- a/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/BuildTask.props +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/BuildTask.props @@ -4,17 +4,13 @@ + + $(MSBuildThisFileDirectory)../tools/net/ + $(MSBuildThisFileDirectory)../tools/netframework/ + + - $(MSBuildThisFileDirectory)../tools/net/ $(DotNetSharedFrameworkTaskDir)Microsoft.DotNet.SharedFramework.Sdk.dll - - - - - - - - diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets index 58f1d207ad2..ba044812766 100644 --- a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets @@ -7,7 +7,7 @@ true true - true + true true $(AllowedOutputExtensionsInSymbolsPackageBuildOutputFolder);.map;.r2rmap;.dbg;.debug;.dwarf <_DefaultHostJsonTargetPath>runtimes/$(RuntimeIdentifier)/lib/$(TargetFramework) @@ -62,10 +62,11 @@ - + @@ -181,6 +182,7 @@ + @@ -304,6 +306,7 @@ Text="The following files are missing entries in the templated manifest: @(_FilesMissingInManifestEntries, ' '). Add these file names with extensions to the 'PlatformManifestFileEntry' item group for the runtime pack and corresponding ref pack to include them in the platform manifest." /> + @@ -333,6 +336,7 @@ If the chosen RID doesn't have a superset of files for all shipping platforms, then you may have unexpected behavior when using the produced targeting pack. --> + @@ -390,6 +394,7 @@ + @@ -462,6 +467,7 @@ + @@ -490,6 +496,7 @@ DependencyGraphFilePath="$(IntermediateOutputPath)assembly-graph.dgml" /> + @@ -510,6 +517,7 @@ IgnoredTypes="@(IgnoredDuplicateType)" /> + diff --git a/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs b/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs index bbe40913f7e..74fc9d120ad 100644 --- a/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs +++ b/src/Microsoft.DotNet.SignTool.Tests/FakeSignTool.cs @@ -34,7 +34,7 @@ public override SigningStatus VerifySignedPEFile(Stream stream) public override SigningStatus VerifyStrongNameSign(string fileFullPath) => SigningStatus.Signed; - public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath, bool suppressErrors = false) + public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath) => buildEngine.BuildProjectFile(projectFilePath, null, null, null); internal static void SignFile(string path) diff --git a/src/Microsoft.DotNet.SignTool.Tests/Microsoft.DotNet.SignTool.Tests.csproj b/src/Microsoft.DotNet.SignTool.Tests/Microsoft.DotNet.SignTool.Tests.csproj index 590f52be7b7..d5266900d99 100644 --- a/src/Microsoft.DotNet.SignTool.Tests/Microsoft.DotNet.SignTool.Tests.csproj +++ b/src/Microsoft.DotNet.SignTool.Tests/Microsoft.DotNet.SignTool.Tests.csproj @@ -1,11 +1,11 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) - + @@ -16,9 +16,16 @@ + + @@ -48,6 +55,18 @@ Link="tools\sn\1033\%(RecursiveDir)%(Filename)%(Extension)" /> + + + <_TarToolPattern>@(_TarToolPath->'%(RootDir)%(Directory)')**\*.* + + + <_TarToolFiles Include="$(_TarToolPattern)" /> + + + + + + @@ -61,5 +80,4 @@ - diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.deb b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.deb index 6540f194a86..33b084d5bb4 100644 Binary files a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.deb and b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.deb differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm index c076c99e8d7..25f662be03a 100644 Binary files a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm and b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.rpm differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.tgz b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.tgz index 94f01e350df..20ac2fdbd5c 100644 Binary files a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.tgz and b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.tgz differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/testHardlinks.tgz b/src/Microsoft.DotNet.SignTool.Tests/Resources/testHardlinks.tgz deleted file mode 100644 index b458840f848..00000000000 Binary files a/src/Microsoft.DotNet.SignTool.Tests/Resources/testHardlinks.tgz and /dev/null differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/testSymlinks.tgz b/src/Microsoft.DotNet.SignTool.Tests/Resources/testSymlinks.tgz deleted file mode 100644 index 675a155771c..00000000000 Binary files a/src/Microsoft.DotNet.SignTool.Tests/Resources/testSymlinks.tgz and /dev/null differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs index bb25bde1135..1e53899ba88 100644 --- a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs +++ b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs @@ -9,7 +9,7 @@ using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; using System.Security.Cryptography; -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Test.Common; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -246,7 +246,9 @@ public class SignToolTests : IDisposable ".py", ".pyd", +#if !NETFRAMEWORK ".deb", +#endif }; public static IEnumerable GetSignableExtensions() @@ -277,7 +279,8 @@ private string GetWixToolPath() } private static string s_snPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "sn", "sn.exe"); - private static string s_pkgToolPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "pkg", "Microsoft.Dotnet.MacOsPkg.Cli.dll"); + private static string s_tarToolPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "tar", "Microsoft.Dotnet.Tar.dll"); + private static string s_pkgToolPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "pkg", "Microsoft.Dotnet.MacOsPkg.dll"); private string GetResourcePath(string name, string relativePath = null) { @@ -338,11 +341,11 @@ private void ValidateGeneratedProject( // The path to DotNet will always be null in these tests, this will force // the signing logic to call our FakeBuildEngine.BuildProjectFile with a path // to the XML that store the content of the would be Microbuild sign request. - var signToolArgs = new SignToolArgs(_tmpDir, microBuildCorePath: "MicroBuildCorePath", testSign: true, dotnetPath: null, msbuildVerbosity: "quiet", _tmpDir, enclosingDir: "", "", wix3ToolsPath: wix3ToolsPath, wixToolsPath: wixToolsPath, pkgToolPath: s_pkgToolPath, dotnetTimeout: -1); + var signToolArgs = new SignToolArgs(_tmpDir, microBuildCorePath: "MicroBuildCorePath", testSign: true, dotnetPath: null, msbuildVerbosity: "quiet", _tmpDir, enclosingDir: "", "", wix3ToolsPath: wix3ToolsPath, wixToolsPath: wixToolsPath, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, dotnetTimeout: -1); var signTool = new FakeSignTool(signToolArgs, task.Log); // Passing null for the 3rd party check skip as this doesn't affect the generated project. - var configuration = new Configuration(signToolArgs.TempDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, additionalCertificateInfo, null, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log); + var configuration = new Configuration(signToolArgs.TempDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, additionalCertificateInfo, null, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log); var signingInput = configuration.GenerateListOfFiles(); var util = new BatchSignUtil( task.BuildEngine, @@ -390,7 +393,7 @@ private void ValidateFileSignInfos( var engine = new FakeBuildEngine(); var task = new SignToolTask { BuildEngine = engine }; var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, additionalCertificateInfo, - skip3rdPartyCheckFiles, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); + skip3rdPartyCheckFiles, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); signingInput.FilesToSign.Select(f => f.ToString()).Should().BeEquivalentTo(expected); signingInput.FilesToCopy.Select(f => $"{f.Key} -> {f.Value}").Should().BeEquivalentTo(expectedCopyFiles ?? Array.Empty()); @@ -398,12 +401,12 @@ private void ValidateFileSignInfos( engine.LogWarningEvents.Select(w => $"{w.Code}: {w.Message}").Should().BeEquivalentTo(expectedWarnings ?? Array.Empty()); } +#if !NETFRAMEWORK private void ValidateProducedDebContent( string debianPackage, (string, string)[] expectedFilesOriginalHashes, string[] signableFiles, - string expectedControlFileContent, - (string path, string target)[] expectedSymlinks = null) + string expectedControlFileContent) { string tempDir = Path.Combine(_tmpDir, "verification"); Directory.CreateDirectory(tempDir); @@ -452,23 +455,6 @@ private void ValidateProducedDebContent( } } - // Checks: Symbolic links are preserved and point to the correct targets - if (expectedSymlinks != null) - { - foreach ((string symlinkPath, string expectedTarget) in expectedSymlinks) - { - string layoutPath = Path.Combine(dataLayout, symlinkPath); - var fileInfo = new FileInfo(layoutPath); - fileInfo.Exists.Should().BeTrue($"symlink '{symlinkPath}' should exist"); - fileInfo.LinkTarget.Should().Be(expectedTarget, $"symlink '{symlinkPath}' should point to '{expectedTarget}'"); - - // Verify the symlink resolves to a valid file with the same content as its target - string resolvedTarget = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(layoutPath)!, expectedTarget)); - File.ReadAllBytes(layoutPath).Should().BeEquivalentTo(File.ReadAllBytes(resolvedTarget), - $"symlink '{symlinkPath}' should resolve to the signed file"); - } - } - // Check: control file contents matches the expected contents string controlFileContents = File.ReadAllText(Path.Combine(controlLayout, "control")); controlFileContents.Should().Be(expectedControlFileContent); @@ -486,8 +472,7 @@ private void ValidateProducedRpmContent( string rpmPackage, (string, string)[] expectedFilesOriginalHashes, string[] signableFiles, - string originalUncompressedPayloadChecksum, - (string path, string target)[] expectedSymlinks = null) + string originalUncompressedPayloadChecksum) { string tempDir = Path.Combine(_tmpDir, "verification"); Directory.CreateDirectory(tempDir); @@ -521,23 +506,6 @@ private void ValidateProducedRpmContent( } } - // Checks: Symbolic links are preserved and point to the correct targets - if (expectedSymlinks != null) - { - foreach ((string symlinkPath, string expectedTarget) in expectedSymlinks) - { - string layoutPath = Path.Combine(layout, symlinkPath); - var fileInfo = new FileInfo(layoutPath); - fileInfo.Exists.Should().BeTrue($"symlink '{symlinkPath}' should exist"); - fileInfo.LinkTarget.Should().Be(expectedTarget, $"symlink '{symlinkPath}' should point to '{expectedTarget}'"); - - // Verify the symlink resolves to a valid file with the same content as its target - string resolvedTarget = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(layoutPath)!, expectedTarget)); - File.ReadAllBytes(layoutPath).Should().BeEquivalentTo(File.ReadAllBytes(resolvedTarget), - $"symlink '{symlinkPath}' should resolve to the signed file"); - } - } - // Checks: // Header payload digest matches the hash of the payload // Header payload digest is different than the hash of the original payload @@ -555,35 +523,7 @@ private void ValidateProducedRpmContent( checksum.Should().Be(uncompressedPayloadDigest); } } - - private void ValidateProducedTarGZipContent( - string tarGZipPath, - (string path, string target)[] expectedSymlinks) - { - string tempDir = Path.Combine(_tmpDir, "verification"); - Directory.CreateDirectory(tempDir); - - string layout = Path.Combine(tempDir, "tgz"); - Directory.CreateDirectory(layout); - - var fakeBuildEngine = new FakeBuildEngine(_output); - var fakeLog = new TaskLoggingHelper(fakeBuildEngine, "TestLog"); - ZipData.ExtractTarballContents(fakeLog, tarGZipPath, layout, skipSymlinks: false); - - foreach ((string symlinkPath, string expectedTarget) in expectedSymlinks) - { - string layoutPath = Path.Combine(layout, symlinkPath); - var fileInfo = new FileInfo(layoutPath); - fileInfo.Exists.Should().BeTrue($"symlink '{symlinkPath}' should exist"); - fileInfo.LinkTarget.Should().Be(expectedTarget, $"symlink '{symlinkPath}' should point to '{expectedTarget}'"); - - // Verify the symlink resolves to a valid file with the same content as its target - string resolvedTarget = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(layoutPath)!, expectedTarget)); - File.ReadAllBytes(layoutPath).Should().BeEquivalentTo(File.ReadAllBytes(resolvedTarget), - $"symlink '{symlinkPath}' should resolve to the signed file"); - } - } - +#endif [Fact] public void EmptySigningList() { @@ -594,7 +534,7 @@ public void EmptySigningList() var fileSignInfo = new Dictionary(); var task = new SignToolTask { BuildEngine = new FakeBuildEngine() }; - var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, null, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); + var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, null, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); signingInput.FilesToSign.Should().BeEmpty(); signingInput.ZipDataMap.Should().BeEmpty(); @@ -1545,7 +1485,7 @@ public void SignAndNotarizePkgFile() { { "MacDeveloperHardenWithNotarization", new List() { - new AdditionalCertificateInformation() { MacNotarizationAppName = "com.microsoft.dotnet", MacSigningOperation = "MacDeveloperHarden" } + new AdditionalCertificateInformation() { MacNotarizationAppName = "dotnet", MacSigningOperation = "MacDeveloperHarden" } } } }; @@ -1584,7 +1524,6 @@ public void SignAndNotarizePkgFile() Microsoft400 ", - // Signing rounds use .pkg.zip because MicroBuild expects zipped packages $@" MacDeveloperHarden @@ -1594,10 +1533,8 @@ public void SignAndNotarizePkgFile() MacDeveloperHarden ", - // Notarization round uses the unzipped .pkg path — files are unzipped - // before notarization (see SignTool.cs: "Notarization does not expect zipped packages") $@" - + 8020 com.microsoft.dotnet ", @@ -1754,99 +1691,6 @@ public void SignTarGZipFile() }); } - /// - /// Validates that tar.gz archives containing symbolic links are handled correctly. - /// On Windows, ReadTarGZipEntriesWithExternalTar throws when symlinks are detected, - /// so this test is skipped on Windows. - /// - [UnixOnlyFactAttribute] - public void SignTarGZipFileWithSymlinks() - { - // List of files to be considered for signing - var itemsToSign = new List() - { - new ItemToSign(GetResourcePath("testSymlinks.tgz")) - }; - - // Default signing information - var strongNameSignInfo = new Dictionary>() - { - { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } - }; - - // Overriding information - var fileSignInfo = new Dictionary(); - - // The symlink (test/this_is_a_big_folder_name_look/NativeLibrary.dll -> ../NativeLibrary.dll) - // is filtered out by System.Formats.Tar on non-Windows, so only regular files are signed. - ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] - { - "File 'NativeLibrary.dll' Certificate='Microsoft400'", - "File 'SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", - "File 'Nested.NativeLibrary.dll' Certificate='Microsoft400'", - "File 'Nested.SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", - "File 'testSymlinks.tgz'", - }); - - ValidateProducedTarGZipContent(Path.Combine(_tmpDir, "testSymlinks.tgz"), new[] - { - ("test/this_is_a_big_folder_name_look/NativeLibrary.dll", "../NativeLibrary.dll") - }); - } - - // TODO: Remove WindowsOnlyFact once https://github.com/dotnet/arcade/issues/16484 is resolved. - [WindowsOnlyFact] - public void SignTarGZipFileWithHardlinks() - { - // List of files to be considered for signing - var itemsToSign = new List() - { - new ItemToSign(GetResourcePath("testHardlinks.tgz")) - }; - - // Default signing information - var strongNameSignInfo = new Dictionary>() - { - { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } - }; - - // Overriding information - var fileSignInfo = new Dictionary(); - - // All three files (original + 2 hardlinks) should be detected for signing - ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] - { - "File 'hardlink1.dll' TargetFramework='.NETStandard,Version=v2.0' Certificate='ArcadeCertTest' StrongName='ArcadeStrongTest'", - "File 'hardlink2.dll' TargetFramework='.NETStandard,Version=v2.0' Certificate='ArcadeCertTest' StrongName='ArcadeStrongTest'", - "File 'original.dll' TargetFramework='.NETStandard,Version=v2.0' Certificate='ArcadeCertTest' StrongName='ArcadeStrongTest'", - "File 'testHardlinks.tgz'", - }, - expectedWarnings: new[] - { - $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "0", "hardlink1.dll")}' with Microsoft certificate 'ArcadeCertTest'. The library is considered 3rd party library due to its copyright: ''.", - $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "1", "hardlink2.dll")}' with Microsoft certificate 'ArcadeCertTest'. The library is considered 3rd party library due to its copyright: ''.", - $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "2", "original.dll")}' with Microsoft certificate 'ArcadeCertTest'. The library is considered 3rd party library due to its copyright: ''.", - }); - - ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] - { -$@" - - ArcadeCertTest - ArcadeStrongTest - - - ArcadeCertTest - ArcadeStrongTest - - - ArcadeCertTest - ArcadeStrongTest - -" - }); - } - [Fact] public void SymbolsNupkg() { @@ -1952,7 +1796,8 @@ public void SignedSymbolsNupkg() }); } - [UnixOnlyFact] +#if !NETFRAMEWORK + [Fact] public void CheckDebSigning() { // List of files to be considered for signing @@ -1992,12 +1837,8 @@ public void CheckDebSigning() string[] signableFiles = ["usr/local/bin/mscorlib.dll"]; string expectedControlFileContent = "Package: test\nVersion: 1.0\nSection: base\nPriority: optional\nArchitecture: all\n"; expectedControlFileContent +="Maintainer: Arcade \nInstalled-Size: 48\nDescription: A simple test package\n This is a simple generated .deb package for testing purposes.\n"; - var expectedSymlinks = new (string, string)[] - { - ("usr/local/bin/mscorlib-link.dll", "./mscorlib.dll") - }; - ValidateProducedDebContent(Path.Combine(_tmpDir, "test.deb"), expectedFilesOriginalHashes, signableFiles, expectedControlFileContent, expectedSymlinks); + ValidateProducedDebContent(Path.Combine(_tmpDir, "test.deb"), expectedFilesOriginalHashes, signableFiles, expectedControlFileContent); } [WindowsOnlyFact] @@ -2066,12 +1907,8 @@ public void CheckRpmSigning() }; string[] signableFiles = ["usr/local/bin/mscorlib.dll"]; string originalUncompressedPayloadChecksum = "216c2a99006d2e14d28a40c0f14a63f6462f533e89789a6f294186e0a0aad3fd"; - var expectedSymlinks = new (string, string)[] - { - ("usr/local/bin/mscorlib-link.dll", "mscorlib.dll") - }; - ValidateProducedRpmContent(Path.Combine(_tmpDir, "test.rpm"), expectedFilesOriginalHashes, signableFiles, originalUncompressedPayloadChecksum, expectedSymlinks); + ValidateProducedRpmContent(Path.Combine(_tmpDir, "test.rpm"), expectedFilesOriginalHashes, signableFiles, originalUncompressedPayloadChecksum); } [Fact] @@ -2095,9 +1932,9 @@ public void VerifyDebIntegrity() "File 'IncorrectlySignedDeb.deb' Certificate='LinuxSign'" }; - // If running on a platform other than Linux, both packages will be submitted for signing - // because the CL verification tool (gpg) is not available. - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + // If on windows, both packages will be submitted for signing + // because the CL verification tool (gpg) is not available on Windows. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { expectedFilesToBeSigned.Add("File 'SignedDeb.deb' Certificate='LinuxSign'"); } @@ -2126,15 +1963,16 @@ public void VerifyRpmIntegrity() "File 'IncorrectlySignedRpm.rpm' Certificate='LinuxSign'" }; - // If running on a platform other than Linux, both packages will be submitted for signing - // because the CL verification tool (gpg) is not available. - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + // If on windows, both packages will be submitted for signing + // because the CL verification tool (gpg) is not available on Windows. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { expectedFilesToBeSigned.Add("File 'SignedRpm.rpm' Certificate='LinuxSign'"); } ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, expectedFilesToBeSigned.ToArray()); } +#endif [Fact] public void CheckPowershellSigning() @@ -2158,12 +1996,11 @@ public void CheckPowershellSigning() }); } - /* NuGet package integrity verification behaves differently on .NET Core vs .NET Framework. - * On .NET Core, NuGet's SignedPackageArchiveUtility.IsSigned() treats packages with - * incorrect signatures as signed (only checks for signature markers, not validity). - * On .NET Framework, it correctly detects the invalid signature. - * See: https://github.com/NuGet/NuGet.Client/blob/e88a5a03a1b26099f8be225d3ee3a897b2edb1d0/build/common.targets#L18-L25 + /* These tests return different results on netcoreapp. ie, we can only truly validate nuget integrity when running on framework. + * NuGet behaves differently on core vs framework + * - https://github.com/NuGet/NuGet.Client/blob/e88a5a03a1b26099f8be225d3ee3a897b2edb1d0/build/common.targets#L18-L25 */ +#if NETFRAMEWORK [Fact] public void VerifyNupkgIntegrity() { @@ -2173,12 +2010,11 @@ public void VerifyNupkgIntegrity() new ItemToSign(GetResourcePath("IncorrectlySignedPackage.1.0.0.nupkg")) }; - // On .NET Core, both packages appear as already signed so nothing needs signing. ValidateFileSignInfos(itemsToSign, new Dictionary>(), new Dictionary(), s_fileExtensionSignInfo, - Array.Empty()); + new[] { "File 'IncorrectlySignedPackage.1.0.0.nupkg' Certificate='NuGet'" }); } [Fact] @@ -2200,12 +2036,11 @@ public void SignNupkgWithUnsignedContents() ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] { "File 'UnsignedScript.ps1' Certificate='PSCertificate'", - // FakeSignedContents.nupkg appears as already signed on .NET Core - // (NuGet only checks for signature markers, not validity). - "File 'UnsignedContents.nupkg' Certificate='NuGet'" + "File 'UnsignedContents.nupkg' Certificate='NuGet'", + "File 'FakeSignedContents.nupkg' Certificate='NuGet'" }); } - +#endif [WindowsOnlyFact] [Trait("Category", "SkipWhenLiveUnitTesting")] public void SignMsiEngine() @@ -2373,7 +2208,7 @@ public void BadWixToolsetPath() task.Execute().Should().BeFalse(); task.Log.HasLoggedErrors.Should().BeTrue(); - fakeBuildEngine.LogErrorEvents.ForEach(a => a.Message.Should().EndWith(" does not exist." )); + fakeBuildEngine.LogErrorEvents.ForEach(a => a.Message.Should().EndWithEquivalent(" does not exist." )); } [Fact] @@ -2923,6 +2758,7 @@ public void ValidateSignToolTaskParsing() MicroBuildCorePath = "MicroBuildCorePath", DoStrongNameCheck = false, SNBinaryPath = null, + TarToolPath = s_tarToolPath, PkgToolPath = s_pkgToolPath, }; @@ -3293,6 +3129,7 @@ public void MissingCertificateName(string extension) new Dictionary>(), new(), null, + tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log) @@ -3305,12 +3142,6 @@ public void MissingCertificateName(string extension) [MemberData(nameof(GetSignableExtensions))] public void MissingCertificateNameButExtensionIsIgnored(string extension) { - // test.deb contains symbolic links which aren't supported on windows. - if (extension == ".deb" && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return; - } - var needContent = new Dictionary(StringComparer.OrdinalIgnoreCase) { { ".dll", ("EmptyPKT.dll", []) }, @@ -3349,6 +3180,7 @@ public void MissingCertificateNameButExtensionIsIgnored(string extension) extensionSignInfo, new(), null, + tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log) @@ -3710,6 +3542,7 @@ public void TestSignShouldNotValidateNuGetSignatures() snBinaryPath: "MockSnPath", wix3ToolsPath: null, wixToolsPath: null, + tarToolPath: null, pkgToolPath: null, dotnetTimeout: 300000); @@ -3841,6 +3674,7 @@ public void NotarizationRetriesOnFailure() s_fileExtensionSignInfo, additionalCertificateInfo, itemsToSkip3rdPartyCheck: null, + tarToolPath: null, pkgToolPath: null, snPath: null, new TaskLoggingHelper(new FakeBuildEngine(_output), "SignToolTests"), @@ -3863,6 +3697,7 @@ public void NotarizationRetriesOnFailure() snBinaryPath: null, wix3ToolsPath: null, wixToolsPath: null, + tarToolPath: null, pkgToolPath: null, dotnetTimeout: -1); diff --git a/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj b/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj index b52c825816c..6e87e071b39 100644 --- a/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj +++ b/src/Microsoft.DotNet.SignTool/Microsoft.DotNet.SignTool.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true true @@ -11,9 +11,11 @@ $(NoWarn);NU5128 lib - - true + + + + @@ -26,7 +28,11 @@ - + + + + + diff --git a/src/Microsoft.DotNet.SignTool/build/Microsoft.DotNet.SignTool.props b/src/Microsoft.DotNet.SignTool/build/Microsoft.DotNet.SignTool.props index 7daf24cbdb1..125962ae353 100644 --- a/src/Microsoft.DotNet.SignTool/build/Microsoft.DotNet.SignTool.props +++ b/src/Microsoft.DotNet.SignTool/build/Microsoft.DotNet.SignTool.props @@ -2,9 +2,10 @@ - $(MSBuildThisFileDirectory)..\lib\net\Microsoft.DotNet.SignTool.dll + $(MSBuildThisFileDirectory)..\lib\net\Microsoft.DotNet.SignTool.dll + $(MSBuildThisFileDirectory)..\lib\netframework\Microsoft.DotNet.SignTool.dll - + diff --git a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs index 7f4fcfba4d1..b3500c39d96 100644 --- a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs +++ b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs @@ -284,7 +284,7 @@ void repackContainer(FileSignInfo file) if (_batchData.ZipDataMap.TryGetValue(file.FileContentKey, out var zipData)) { _log.LogMessage($"Repacking container: '{file.FileName}'"); - zipData.Repack(_log, _signTool.TempDir, _signTool.Wix3ToolsPath, _signTool.WixToolsPath, _signTool.PkgToolPath); + zipData.Repack(_log, _signTool.TempDir, _signTool.Wix3ToolsPath, _signTool.WixToolsPath, _signTool.TarToolPath, _signTool.PkgToolPath); } else { diff --git a/src/Microsoft.DotNet.SignTool/src/Configuration.cs b/src/Microsoft.DotNet.SignTool/src/Configuration.cs index 94a02252f66..df7f373d942 100644 --- a/src/Microsoft.DotNet.SignTool/src/Configuration.cs +++ b/src/Microsoft.DotNet.SignTool/src/Configuration.cs @@ -98,6 +98,8 @@ internal class Configuration private Telemetry _telemetry; + private string _tarToolPath; + private string _pkgToolPath; private string _snPath; @@ -113,6 +115,7 @@ public Configuration( Dictionary> extensionSignInfo, Dictionary> additionalCertificateInformation, HashSet itemsToSkip3rdPartyCheck, + string tarToolPath, string pkgToolPath, string snPath, TaskLoggingHelper log, @@ -142,6 +145,7 @@ public Configuration( _wixPacks = _itemsToSign.Where(w => WixPackInfo.IsWixPack(w.FullPath))?.Select(s => new WixPackInfo(s.FullPath)).ToList(); _hashToCollisionIdMap = new Dictionary(); _telemetry = telemetry; + _tarToolPath = tarToolPath; _pkgToolPath = pkgToolPath; _snPath = snPath; _itemsToSkip3rdPartyCheck = itemsToSkip3rdPartyCheck; @@ -825,7 +829,7 @@ private bool TryBuildZipData(FileSignInfo zipFileSignInfo, out ZipData zipData, { var nestedParts = new Dictionary(); - foreach (var entry in ZipData.ReadEntries(archivePath, _pathToContainerUnpackingDirectory, _pkgToolPath, _log)) + foreach (var entry in ZipData.ReadEntries(archivePath, _pathToContainerUnpackingDirectory, _tarToolPath, _pkgToolPath)) { using (entry) { diff --git a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs index 27ca130fce3..04c9a7c97c4 100644 --- a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs @@ -46,7 +46,7 @@ internal RealSignTool(SignToolArgs args, TaskLoggingHelper log) : base(args, log _dotnetTimeout = args.DotNetTimeout; } - public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath, bool suppressErrors = false) + public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath) { if (_dotnetPath == null) { @@ -80,10 +80,7 @@ public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath bool success = true; if (!process.WaitForExit(_dotnetTimeout)) { - if (suppressErrors) - _log.LogMessage(MessageImportance.High, $"MSBuild process did not exit within '{_dotnetTimeout}' ms."); - else - _log.LogError($"MSBuild process did not exit within '{_dotnetTimeout}' ms."); + _log.LogError($"MSBuild process did not exit within '{_dotnetTimeout}' ms."); process.Kill(); process.WaitForExit(); success = false; @@ -91,12 +88,8 @@ public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath if (process.ExitCode != 0) { - if (suppressErrors) - _log.LogMessage(MessageImportance.High, $"Failed to execute MSBuild on the project file '{projectFilePath}'" + - $" with exit code '{process.ExitCode}'."); - else - _log.LogError($"Failed to execute MSBuild on the project file '{projectFilePath}'" + - $" with exit code '{process.ExitCode}'."); + _log.LogError($"Failed to execute MSBuild on the project file '{projectFilePath}'" + + $" with exit code '{process.ExitCode}'."); success = false; } diff --git a/src/Microsoft.DotNet.SignTool/src/SignTool.cs b/src/Microsoft.DotNet.SignTool/src/SignTool.cs index d4211f9371d..e1eecfe9efa 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignTool.cs @@ -26,6 +26,7 @@ internal abstract class SignTool internal string Wix3ToolsPath => _args.Wix3ToolsPath; internal string WixToolsPath => _args.WixToolsPath; + internal string TarToolPath => _args.TarToolPath; internal string PkgToolPath => _args.PkgToolPath; internal SignTool(SignToolArgs args, TaskLoggingHelper log) @@ -48,7 +49,7 @@ internal SignTool(SignToolArgs args, TaskLoggingHelper log) public abstract SigningStatus VerifyStrongNameSign(string fileFullPath); - public abstract bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath, bool suppressErrors = false); + public abstract bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath); public bool Sign(IBuildEngine buildEngine, int round, IEnumerable files) { @@ -125,7 +126,14 @@ private void UnzipMacFiles(Dictionary zippedOSXFiles) } else { + // Delete the file first so that we can overwrite it. ExtractToDirectory's overwrite is not + // available on framework. +#if NETFRAMEWORK + File.Delete(item.Key); + ZipFile.ExtractToDirectory(item.Value, Path.GetDirectoryName(item.Key)); +#else ZipFile.ExtractToDirectory(item.Value, Path.GetDirectoryName(item.Key), true); +#endif } File.Delete(item.Value); @@ -183,7 +191,7 @@ private bool AuthenticodeSignAndNotarize(IBuildEngine buildEngine, int round, IE { var notarizeProjectPath = Path.Combine(dir, $"Round{round}-Notarize.proj"); File.WriteAllText(notarizeProjectPath, GenerateBuildFileContent(filesToNotarize, null, true)); - + // Notarization can be flaky, so retry up to 5 times with no wait between retries const int maxRetries = 5; int attempt = 0; @@ -200,8 +208,7 @@ private bool AuthenticodeSignAndNotarize(IBuildEngine buildEngine, int round, IE notarizationSucceeded = RunMSBuild(buildEngine, notarizeProjectPath, Path.Combine(_args.LogDir, $"{notarizeLogName}.binlog"), Path.Combine(_args.LogDir, $"{notarizeLogName}.log"), - Path.Combine(_args.LogDir, $"{notarizeLogName}.error.log"), - suppressErrors: attempt < maxRetries); + Path.Combine(_args.LogDir, $"{notarizeLogName}.error.log")); if (!notarizationSucceeded && attempt < maxRetries) { diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs b/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs index 5c011713237..c65a2def62c 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs @@ -15,10 +15,11 @@ internal readonly struct SignToolArgs internal string EnclosingDir { get; } internal string Wix3ToolsPath { get; } internal string WixToolsPath { get; } + internal string TarToolPath { get; } internal string PkgToolPath { get; } internal int DotNetTimeout { get; } - internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, string dotnetPath, string msbuildVerbosity, string logDir, string enclosingDir, string snBinaryPath, string wix3ToolsPath, string wixToolsPath, string pkgToolPath, int dotnetTimeout) + internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, string dotnetPath, string msbuildVerbosity, string logDir, string enclosingDir, string snBinaryPath, string wix3ToolsPath, string wixToolsPath, string tarToolPath, string pkgToolPath, int dotnetTimeout) { TempDir = tempPath; MicroBuildCorePath = microBuildCorePath; @@ -30,6 +31,7 @@ internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, SNBinaryPath = snBinaryPath; Wix3ToolsPath = wix3ToolsPath; WixToolsPath = wixToolsPath; + TarToolPath = tarToolPath; PkgToolPath = pkgToolPath; DotNetTimeout = dotnetTimeout; } diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs b/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs index 7767fcbd5b7..a5d2cea441a 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs @@ -2,7 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Framework; +#if NET472 +using AppDomainIsolatedTask = Microsoft.Build.Utilities.AppDomainIsolatedTask; +#else using BuildTask = Microsoft.Build.Utilities.Task; +#endif using System; using System.Collections.Generic; using System.IO; @@ -12,8 +16,15 @@ namespace Microsoft.DotNet.SignTool { +#if NET472 + [LoadInSeparateAppDomain] + public class SignToolTask : AppDomainIsolatedTask + { + static SignToolTask() => AssemblyResolution.Initialize(); +#else public class SignToolTask : BuildTask { +#endif /// /// Perform validation but do not actually send signing request to the server. /// @@ -133,6 +144,11 @@ public class SignToolTask : BuildTask /// public string SNBinaryPath { get; set; } + /// + /// Path to Microsoft.DotNet.Tar.dll. Required for signing tar files on .NET Framework. + /// + public string TarToolPath { get; set; } + /// /// Path to Microsoft.DotNet.MacOsPkg.dll. Required for signing pkg files on MacOS. /// @@ -165,6 +181,9 @@ public class SignToolTask : BuildTask public override bool Execute() { +#if NET472 + AssemblyResolution.Log = Log; +#endif try { ExecuteImpl(); @@ -172,6 +191,9 @@ public override bool Execute() } finally { +#if NET472 + AssemblyResolution.Log = null; +#endif Log.LogMessage(MessageImportance.High, "SignToolTask execution finished."); } } @@ -231,7 +253,7 @@ public void ExecuteImpl() if (Log.HasLoggedErrors) return; - var signToolArgs = new SignToolArgs(TempDir, MicroBuildCorePath, TestSign, DotNetPath, MSBuildVerbosity, LogDir, enclosingDir, SNBinaryPath, Wix3ToolsPath, WixToolsPath, PkgToolPath, DotNetTimeout); + var signToolArgs = new SignToolArgs(TempDir, MicroBuildCorePath, TestSign, DotNetPath, MSBuildVerbosity, LogDir, enclosingDir, SNBinaryPath, Wix3ToolsPath, WixToolsPath, TarToolPath, PkgToolPath, DotNetTimeout); var signTool = DryRun ? new ValidationOnlySignTool(signToolArgs, Log) : (SignTool)new RealSignTool(signToolArgs, Log); var itemsToSign = ItemsToSign.Select(i => new ItemToSign(i.ItemSpec, i.GetMetadata(SignToolConstants.CollisionPriorityId))).OrderBy(i => i.CollisionPriorityId).ToList(); @@ -247,6 +269,7 @@ public void ExecuteImpl() extensionSignInfo, dualCertificates, filesToSkip3rdPartyCheck, + tarToolPath: TarToolPath, pkgToolPath: PkgToolPath, snPath: SNBinaryPath, Log, diff --git a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs index aae9ac59d9b..98bdd73b160 100644 --- a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs @@ -55,7 +55,7 @@ public override SigningStatus VerifySignedPEFile(Stream assemblyStream) public override SigningStatus VerifyStrongNameSign(string fileFullPath) => SigningStatus.Signed; - public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath, bool suppressErrors = false) + public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath, string logPath, string errorLogPath) { if (TestSign) { diff --git a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs index 4df6c975ac2..da4b1ae2610 100644 --- a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs +++ b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs @@ -25,9 +25,16 @@ namespace Microsoft.DotNet.SignTool { internal class VerifySignatures { +#if !NET472 private static readonly HttpClient client = new(new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(10) }); +#endif internal static SigningStatus IsSignedDeb(TaskLoggingHelper log, string filePath) { +# if NET472 + // Debian unpack tooling is not supported on .NET Framework + log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for .NET Framework"); + return SigningStatus.Unknown; +# else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for Windows."); @@ -59,10 +66,16 @@ internal static SigningStatus IsSignedDeb(TaskLoggingHelper log, string filePath { Directory.Delete(tempDir, true); } +# endif } internal static SigningStatus IsSignedRpm(TaskLoggingHelper log, string filePath) { +# if NET472 + // RPM unpack tooling is not supported on .NET Framework + log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for .NET Framework"); + return SigningStatus.Unknown; +# else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for Windows."); @@ -109,6 +122,7 @@ internal static SigningStatus IsSignedRpm(TaskLoggingHelper log, string filePath { Directory.Delete(tempDir, true); } +# endif } internal static SigningStatus IsSignedPowershellFile(string filePath) @@ -123,6 +137,32 @@ internal static SigningStatus IsSignedNupkg(string filePath) using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(filePath))) { isSigned = SignedPackageArchiveUtility.IsSigned(binaryReader); +#if NETFRAMEWORK + if (isSigned) + { + try + { + // A package will fail integrity checks if, for example, the package is signed and then: + // - it is repacked + // - it has its symbols stripped + // - it is otherwise modified + using (Stream stream = SignedPackageArchiveUtility.OpenPackageSignatureFileStream(binaryReader)) + { + using (PackageArchiveReader par = new PackageArchiveReader(filePath)) + { + var signature = par.GetPrimarySignatureAsync(CancellationToken.None).Result; + + var task = par.ValidateIntegrityAsync(signature.SignatureContent, CancellationToken.None); + task.Wait(); + } + } + } + catch (Exception) + { + isSigned = false; + } + } +#endif } return isSigned ? SigningStatus.Signed : SigningStatus.NotSigned; } @@ -217,6 +257,7 @@ private static string RunCommand(string command, bool throwOnError = true) } } +# if !NET472 private static void DownloadAndConfigurePublicKeys(string tempDir) { string[] keyUrls = new string[] @@ -260,5 +301,6 @@ private static string ExtractDebContainerEntry(string debianPackage, string entr return entryPath; } +# endif } } diff --git a/src/Microsoft.DotNet.SignTool/src/ZipData.cs b/src/Microsoft.DotNet.SignTool/src/ZipData.cs index ae9676caf85..8f8a0ffb0cb 100644 --- a/src/Microsoft.DotNet.SignTool/src/ZipData.cs +++ b/src/Microsoft.DotNet.SignTool/src/ZipData.cs @@ -8,15 +8,18 @@ using System.Collections.Immutable; using System.IO; using System.IO.Compression; -using System.IO.Packaging; using System.Linq; using System.Data; -using System.Text; using System.Diagnostics; using System.Runtime.InteropServices; using NuGet.Packaging; using Microsoft.DotNet.Build.Tasks.Installers; + +#if NET472 +using System.IO.Packaging; +#else using System.Formats.Tar; +#endif namespace Microsoft.DotNet.SignTool { @@ -51,24 +54,20 @@ internal ZipData(FileSignInfo fileSignInfo, ImmutableDictionary return null; } - public static IEnumerable ReadEntries(string archivePath, string tempDir, string pkgToolPath, TaskLoggingHelper log, bool ignoreContent = false) + public static IEnumerable ReadEntries(string archivePath, string tempDir, string tarToolPath, string pkgToolPath, bool ignoreContent = false) { if (FileSignInfo.IsTarGZip(archivePath)) { - // TODO: Remove workaround for https://github.com/dotnet/arcade/issues/16484 - // Hardlinks are used on Windows but System.Formats.Tar doesn't fully support them yet. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return ReadTarGZipEntriesWithExternalTar(archivePath, tempDir, log, ignoreContent); - } - + // Tar APIs not available on .NET FX. We need sign tool to run on desktop msbuild because building VSIX packages requires desktop. +#if NET472 + return ReadTarGZipEntries(archivePath, tempDir, tarToolPath, ignoreContent); +#else return ReadTarGZipEntries(archivePath) - .Where(static entry => entry.EntryType != TarEntryType.SymbolicLink && - entry.EntryType != TarEntryType.Directory) .Select(static entry => new ZipDataEntry(entry.Name, entry.DataStream, entry.Length) { UnixFileMode = (uint)entry.Mode, }); +#endif } else if (FileSignInfo.IsPkg(archivePath) || FileSignInfo.IsAppBundle(archivePath)) { @@ -76,16 +75,24 @@ public static IEnumerable ReadEntries(string archivePath, string t } else if (FileSignInfo.IsDeb(archivePath)) { +#if NET472 + throw new NotImplementedException("Debian signing is not supported on .NET Framework"); +#else return ReadDebContainerEntries(archivePath, "data.tar"); +#endif } else if (FileSignInfo.IsRpm(archivePath)) { +#if NET472 + throw new NotImplementedException("RPM signing is not supported on .NET Framework"); +#else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { throw new NotImplementedException("RPM signing is only supported on Linux platform"); } return ReadRpmContainerEntries(archivePath); +#endif } else { @@ -96,15 +103,18 @@ public static IEnumerable ReadEntries(string archivePath, string t /// /// Repack the zip container with the signed files. /// - public void Repack(TaskLoggingHelper log, string tempDir, string wix3ToolsPath, string wixToolsPath, string pkgToolPath) + public void Repack(TaskLoggingHelper log, string tempDir, string wix3ToolsPath, string wixToolsPath, string tarToolPath, string pkgToolPath) { +#if NET472 if (FileSignInfo.IsVsix()) { RepackPackage(log); } - else if (FileSignInfo.IsTarGZip()) + else +#endif + if (FileSignInfo.IsTarGZip()) { - RepackTarGZip(log, tempDir); + RepackTarGZip(log, tempDir, tarToolPath); } else if (FileSignInfo.IsUnpackableWixContainer()) { @@ -116,16 +126,24 @@ public void Repack(TaskLoggingHelper log, string tempDir, string wix3ToolsPath, } else if (FileSignInfo.IsDeb()) { +#if NET472 + throw new NotImplementedException("Debian signing is not supported on .NET Framework"); +#else RepackDebContainer(log, tempDir); +#endif } else if (FileSignInfo.IsRpm()) { +#if NET472 + throw new NotImplementedException("RPM signing is not supported on .NET Framework"); +#else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { throw new NotImplementedException("RPM signing is only supported on Linux platform"); } RepackRpmContainer(log, tempDir); +#endif } else { @@ -133,6 +151,7 @@ public void Repack(TaskLoggingHelper log, string tempDir, string wix3ToolsPath, } } +#if NET472 /// /// Repack a zip container with a package structure. /// @@ -172,6 +191,7 @@ string getPartRelativeFileName(PackagePart part) } } } +#endif private static IEnumerable ReadZipEntries(string archivePath) { @@ -314,11 +334,6 @@ private static IEnumerable ReadPkgOrAppBundleEntries(string archiv foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) { - // Skip symbolic links - they reference files that are processed at their real paths. - if (new FileInfo(path).LinkTarget != null) - { - continue; - } var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); using var stream = ignoreContent ? null : (Stream)File.Open(path, FileMode.Open); yield return new ZipDataEntry(relativePath, stream) @@ -338,6 +353,9 @@ private static IEnumerable ReadPkgOrAppBundleEntries(string archiv private void RepackPkgOrAppBundles(TaskLoggingHelper log, string tempDir, string pkgToolPath) { +#if NET472 + throw new NotImplementedException("PKG manipulation is not supported on .NET Framework"); +#else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { log.LogError("Pkg/AppBundle repackaging is not supported on Windows."); @@ -354,13 +372,6 @@ private void RepackPkgOrAppBundles(TaskLoggingHelper log, string tempDir, string foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) { - // Skip symbolic links - they are preserved from extraction and point to - // the real files which are updated in place. - if (new FileInfo(path).LinkTarget != null) - { - continue; - } - var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); var signedPart = FindNestedPart(relativePath); @@ -390,148 +401,123 @@ private void RepackPkgOrAppBundles(TaskLoggingHelper log, string tempDir, string Directory.Delete(extractDir, recursive: true); } } +#endif } - private void RepackTarGZip(TaskLoggingHelper log, string tempDir) +#if NETFRAMEWORK + private static bool RunTarProcess(string srcPath, string dstPath, string tarToolPath) { - // TODO: Remove workaround for https://github.com/dotnet/arcade/issues/16484 - // Hardlinks are used on Windows but System.Formats.Tar doesn't fully support them yet. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - RepackTarGZipWithExternalTar(log, tempDir); - return; - } - - using MemoryStream streamToCompress = new(); - using (TarWriter writer = new(streamToCompress, leaveOpen: true)) + var process = Process.Start(new ProcessStartInfo() { - foreach (TarEntry entry in ReadTarGZipEntries(FileSignInfo.FullPath)) - { - if (entry.DataStream != null) - { - string relativeName = entry.Name; - ZipPart? signedPart = FindNestedPart(relativeName); - - if (signedPart.HasValue) - { - using FileStream signedStream = File.OpenRead(signedPart.Value.FileSignInfo.FullPath); - entry.DataStream = signedStream; - entry.DataStream.Position = 0; - writer.WriteEntry(entry); - log.LogMessage(MessageImportance.Low, $"Copying signed stream from {signedPart.Value.FileSignInfo.FullPath} to {FileSignInfo.FullPath} -> {relativeName} (perms: {Convert.ToString((uint)entry.Mode, 8)})."); - continue; - } - - log.LogMessage(MessageImportance.Low, $"Didn't find signed part for nested file: {FileSignInfo.FullPath} -> {relativeName}"); - } - - writer.WriteEntry(entry); - } - } + FileName = "dotnet", + Arguments = $@"exec ""{tarToolPath}"" ""{srcPath}"" ""{dstPath}""", + UseShellExecute = false + }); - streamToCompress.Position = 0; - using (FileStream outputStream = File.Open(FileSignInfo.FullPath, FileMode.Truncate, FileAccess.Write)) - { - using GZipStream compressor = new(outputStream, CompressionMode.Compress); - streamToCompress.CopyTo(compressor); - } + process.WaitForExit(); + return process.ExitCode == 0; } - /// - /// Read tar.gz entries using external tar.exe to properly handle hardlinks. - /// Windows tarballs use hardlinks for deduplication, which System.Formats.Tar doesn't yet support. - /// When tar.exe extracts hardlinks, they become regular files with the same content. - /// - private static IEnumerable ReadTarGZipEntriesWithExternalTar(string archivePath, string tempDir, TaskLoggingHelper log, bool ignoreContent) + private static IEnumerable ReadTarGZipEntries(string archivePath, string tempDir, string tarToolPath, bool ignoreContent) { - string extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); - Directory.CreateDirectory(extractDir); - + var extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); try { - // Extract the tarball - tar.exe will resolve hardlinks to regular files - if (!RunExternalProcess(log, "tar", $"-xzf \"{archivePath}\" -C \"{extractDir}\"", out _)) + Directory.CreateDirectory(extractDir); + + if (!RunTarProcess(archivePath, extractDir, tarToolPath)) { - throw new Exception($"Failed to extract tar archive: {archivePath}"); + throw new Exception($"Failed to unpack tar archive: {archivePath}"); } - foreach (var path in Directory.EnumerateFiles(extractDir, "*", SearchOption.AllDirectories)) + foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) { - // Symbolic links require elevated permissions to create on Windows. They should not be used therefore they are not supported. - if (new FileInfo(path).LinkTarget != null) - { - throw new InvalidOperationException( - $"Symbolic link detected in tar archive '{archivePath}': '{path}'. " + - $"Tarballs containing symbolic links are not supported for signing on Windows."); - } - - string relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); + var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); using var stream = ignoreContent ? null : (Stream)File.Open(path, FileMode.Open); yield return new ZipDataEntry(relativePath, stream); } } finally { - if (Directory.Exists(extractDir)) - { - Directory.Delete(extractDir, recursive: true); - } + Directory.Delete(extractDir, recursive: true); } } - /// - /// Repack tar.gz using external tar.exe to preserve hardlinks. - /// Windows tarballs use hardlinks for deduplication, which System.Formats.Tar doesn't yet support. - /// - private void RepackTarGZipWithExternalTar(TaskLoggingHelper log, string tempDir) + private void RepackTarGZip(TaskLoggingHelper log, string tempDir, string tarToolPath) { - string extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); - Directory.CreateDirectory(extractDir); - + var extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); try { - // Extract the tarball - tar.exe will recreate hardlinks - if (!RunExternalProcess(log, "tar", $"-xzf \"{FileSignInfo.FullPath}\" -C \"{extractDir}\"", out _)) + Directory.CreateDirectory(extractDir); + + if (!RunTarProcess(srcPath: FileSignInfo.FullPath, dstPath: extractDir, tarToolPath)) { - log.LogError($"Failed to extract tar archive: {FileSignInfo.FullPath}"); + log.LogMessage(MessageImportance.Low, $"Failed to unpack tar archive: dotnet {tarToolPath} {FileSignInfo.FullPath}"); return; } - // Replace signed files in the extracted directory - foreach (var path in Directory.EnumerateFiles(extractDir, "*", SearchOption.AllDirectories)) + foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) { - // Symbolic links are not supported by the external tar.exe signing path. - if (new FileInfo(path).LinkTarget != null) - { - throw new InvalidOperationException( - $"Symbolic link detected in tar archive '{FileSignInfo.FullPath}': '{path}'. " + - $"Tarballs containing symbolic links are not supported for signing on Windows."); - } - - string relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); - ZipPart? signedPart = FindNestedPart(relativePath); + var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); - if (signedPart.HasValue) + var signedPart = FindNestedPart(relativePath); + if (!signedPart.HasValue) { - log.LogMessage(MessageImportance.Low, $"Copying signed file from {signedPart.Value.FileSignInfo.FullPath} to {FileSignInfo.FullPath} -> {relativePath}"); - File.Copy(signedPart.Value.FileSignInfo.FullPath, path, overwrite: true); + log.LogMessage(MessageImportance.Low, $"Didn't find signed part for nested file: {FileSignInfo.FullPath} -> {relativePath}"); + continue; } + + log.LogMessage(MessageImportance.Low, $"Copying signed stream from {signedPart.Value.FileSignInfo.FullPath} to {FileSignInfo.FullPath} -> {relativePath}."); + File.Copy(signedPart.Value.FileSignInfo.FullPath, path, overwrite: true); } - // Repack the tarball - tar.exe will detect and preserve hardlinks - if (!RunExternalProcess(log, "tar", $"-czf \"{FileSignInfo.FullPath}\" -C \"{extractDir}\" .", out _)) + if (!RunTarProcess(srcPath: extractDir, dstPath: FileSignInfo.FullPath, tarToolPath)) { - log.LogError($"Failed to create tar archive: {FileSignInfo.FullPath}"); + log.LogMessage(MessageImportance.Low, $"Failed to pack tar archive: dotnet {tarToolPath} {FileSignInfo.FullPath}"); return; } } finally { - if (Directory.Exists(extractDir)) + Directory.Delete(extractDir, recursive: true); + } + } +#else + private void RepackTarGZip(TaskLoggingHelper log, string tempDir, string tarToolPath) + { + using MemoryStream streamToCompress = new(); + using (TarWriter writer = new(streamToCompress, leaveOpen: true)) + { + foreach (TarEntry entry in ReadTarGZipEntries(FileSignInfo.FullPath)) { - Directory.Delete(extractDir, recursive: true); + if (entry.DataStream != null) + { + string relativeName = entry.Name; + ZipPart? signedPart = FindNestedPart(relativeName); + + if (signedPart.HasValue) + { + using FileStream signedStream = File.OpenRead(signedPart.Value.FileSignInfo.FullPath); + entry.DataStream = signedStream; + entry.DataStream.Position = 0; + writer.WriteEntry(entry); + log.LogMessage(MessageImportance.Low, $"Copying signed stream from {signedPart.Value.FileSignInfo.FullPath} to {FileSignInfo.FullPath} -> {relativeName} (perms: {Convert.ToString((uint)entry.Mode, 8)})."); + continue; + } + + log.LogMessage(MessageImportance.Low, $"Didn't find signed part for nested file: {FileSignInfo.FullPath} -> {relativeName}"); + } + + writer.WriteEntry(entry); } } + + streamToCompress.Position = 0; + using (FileStream outputStream = File.Open(FileSignInfo.FullPath, FileMode.Truncate, FileAccess.Write)) + { + using GZipStream compressor = new(outputStream, CompressionMode.Compress); + streamToCompress.CopyTo(compressor); + } } private static IEnumerable ReadTarGZipEntries(string path) @@ -678,18 +664,11 @@ internal static void ExtractTarballContents(TaskLoggingHelper log, string file, string outputPath = Path.Join(destination, tar.Name); Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!); - if (tar.EntryType == TarEntryType.SymbolicLink) - { - File.CreateSymbolicLink(outputPath, tar.LinkName); - } - else + using (FileStream outputFileStream = File.Create(outputPath)) { - using (FileStream outputFileStream = File.Create(outputPath)) - { - tar.DataStream?.CopyTo(outputFileStream); - } - SetUnixFileMode(log, (uint)tar.Mode, outputPath); + tar.DataStream?.CopyTo(outputFileStream); } + SetUnixFileMode(log, (uint)tar.Mode, outputPath); } } @@ -718,20 +697,7 @@ internal static IEnumerable ReadDebContainerEntries(string archive } } - /// - /// Read entries from an RPM container. - /// - /// Path to the RPM package. - /// - /// When true (the default), symbolic links are excluded from the returned entries. - /// This is used during the read/signing phase where only regular files need to be inspected and signed. - /// When false, symbolic links are included (with their target paths captured) so that - /// can recreate them on disk. This is necessary because - /// repacking rebuilds the cpio payload from the extracted disk layout rather than copying - /// streams from the original archive, so symlinks must be physically present or they - /// would be dropped from the repacked RPM. - /// - private static IEnumerable ReadRpmContainerEntries(string archivePath, bool skipSymlinks = true) + private static IEnumerable ReadRpmContainerEntries(string archivePath) { using var stream = File.Open(archivePath, FileMode.Open); using RpmPackage rpmPackage = RpmPackage.Read(stream); @@ -739,26 +705,9 @@ private static IEnumerable ReadRpmContainerEntries(string archiveP while (archive.GetNextEntry() is CpioEntry entry) { - uint fileKind = entry.Mode & CpioEntry.FileKindMask; - if (fileKind == CpioEntry.Directory || - (skipSymlinks && fileKind == CpioEntry.SymbolicLink)) - { - continue; - } - - bool isSymlink = fileKind == CpioEntry.SymbolicLink; - string linkTarget = null; - if (isSymlink) - { - using StreamReader reader = new(entry.DataStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: -1, leaveOpen: true); - linkTarget = reader.ReadToEnd().TrimEnd(); - } - - yield return new ZipDataEntry(entry.Name, isSymlink ? null : entry.DataStream) + yield return new ZipDataEntry(entry.Name, entry.DataStream) { UnixFileMode = entry.Mode & CpioEntry.FilePermissionMask, - IsSymbolicLink = isSymlink, - SymbolicLinkTarget = linkTarget, }; } } @@ -847,16 +796,12 @@ private void RepackRpmContainer(TaskLoggingHelper log, string tempDir) internal static void ExtractRpmPayloadContents(TaskLoggingHelper log, string rpmPackage, string layout) { - foreach (var entry in ReadRpmContainerEntries(rpmPackage, skipSymlinks: false)) + foreach (var entry in ReadRpmContainerEntries(rpmPackage)) { string outputPath = Path.Combine(layout, entry.RelativePath); Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!); - if (entry.IsSymbolicLink) - { - File.CreateSymbolicLink(outputPath, entry.SymbolicLinkTarget); - } - else + if (entry != null) { entry.WriteToFile(outputPath); SetUnixFileMode(log, entry.UnixFileMode, outputPath); @@ -896,20 +841,27 @@ private static bool RunExternalProcess(TaskLoggingHelper log, string cmd, string return process.ExitCode == 0; } +#endif internal static void SetUnixFileMode(TaskLoggingHelper log, uint? unixFileMode, string outputPath) { +#if NET // Set file mode if not the default. if (!OperatingSystem.IsWindows() && unixFileMode is { } mode and not /* 0644 */ 420) { log.LogMessage(MessageImportance.Low, $"Setting file mode {Convert.ToString(mode, 8)} on: {outputPath}"); File.SetUnixFileMode(outputPath, (UnixFileMode)mode); } +#endif } private static uint? GetUnixFileMode(string filePath) { +#if NET return OperatingSystem.IsWindows() ? null : (uint)File.GetUnixFileMode(filePath); +#else + return null; +#endif } } } diff --git a/src/Microsoft.DotNet.SignTool/src/ZipDataEntry.cs b/src/Microsoft.DotNet.SignTool/src/ZipDataEntry.cs index 15c4c231bf2..0bdac8c92e2 100644 --- a/src/Microsoft.DotNet.SignTool/src/ZipDataEntry.cs +++ b/src/Microsoft.DotNet.SignTool/src/ZipDataEntry.cs @@ -68,10 +68,6 @@ public ZipDataEntry(ZipArchiveEntry entry) public uint? UnixFileMode { get; set; } - public bool IsSymbolicLink { get; set; } - - public string SymbolicLinkTarget { get; set; } - public void WriteToFile(string path) { using var fs = File.Create(path); diff --git a/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj b/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj index 87dc8190b2d..7f234c442ca 100644 --- a/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj +++ b/src/Microsoft.DotNet.SourceBuild/tasks/Microsoft.DotNet.SourceBuild.Tasks.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) MSBuildSdk true diff --git a/src/Microsoft.DotNet.SourceBuild/tasks/build/Microsoft.DotNet.SourceBuild.Tasks.props b/src/Microsoft.DotNet.SourceBuild/tasks/build/Microsoft.DotNet.SourceBuild.Tasks.props index 73f7ae83dcf..2f4a50b0697 100644 --- a/src/Microsoft.DotNet.SourceBuild/tasks/build/Microsoft.DotNet.SourceBuild.Tasks.props +++ b/src/Microsoft.DotNet.SourceBuild/tasks/build/Microsoft.DotNet.SourceBuild.Tasks.props @@ -2,11 +2,12 @@ - $(MSBuildThisFileDirectory)..\tools\net\$(MSBuildThisFileName).dll + $(MSBuildThisFileDirectory)..\tools\net\$(MSBuildThisFileName).dll + $(MSBuildThisFileDirectory)..\tools\netframework\$(MSBuildThisFileName).dll - - - + + + diff --git a/src/Microsoft.DotNet.SourceBuild/tests/Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj b/src/Microsoft.DotNet.SourceBuild/tests/Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj index d2d0f8318d5..983f8ca0ec5 100644 --- a/src/Microsoft.DotNet.SourceBuild/tests/Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj +++ b/src/Microsoft.DotNet.SourceBuild/tests/Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) false diff --git a/src/Microsoft.DotNet.StrongName/Microsoft.DotNet.StrongName.csproj b/src/Microsoft.DotNet.StrongName/Microsoft.DotNet.StrongName.csproj index 457e5d1cdea..fd542d9a6c3 100644 --- a/src/Microsoft.DotNet.StrongName/Microsoft.DotNet.StrongName.csproj +++ b/src/Microsoft.DotNet.StrongName/Microsoft.DotNet.StrongName.csproj @@ -1,11 +1,29 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) + Library + true true + true Strong name signing and verification + Arcade Build Library Strong Name + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.StrongName/Signing.cs b/src/Microsoft.DotNet.StrongName/Signing.cs index d7e629d7ab3..97e81ef11ba 100644 --- a/src/Microsoft.DotNet.StrongName/Signing.cs +++ b/src/Microsoft.DotNet.StrongName/Signing.cs @@ -232,7 +232,7 @@ internal static void Sign(Stream peStream, string keyFile) /// /// Returns true if the PE file meets all of the pre-conditions to be Open Source Signed. - /// Returns false otherwise. + /// Returns false and logs msbuild errors otherwise. /// private static bool IsPublicSigned(PEReader peReader) { diff --git a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CmdLine/Microsoft.DotNet.SwaggerGenerator.CmdLine.csproj b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CmdLine/Microsoft.DotNet.SwaggerGenerator.CmdLine.csproj index 8318a390413..1131225c365 100644 --- a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CmdLine/Microsoft.DotNet.SwaggerGenerator.CmdLine.csproj +++ b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CmdLine/Microsoft.DotNet.SwaggerGenerator.CmdLine.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Exe true true diff --git a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator.csproj b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator.csproj index 1486a8c8a1f..5287daca560 100644 --- a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator.csproj +++ b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator/Microsoft.DotNet.SwaggerGenerator.CodeGenerator.csproj @@ -1,7 +1,7 @@ - $(NetMinimum) + $(NetMinimum);netstandard2.0;$(NetFrameworkMinimum) Microsoft.DotNet.SwaggerGenerator true @@ -11,6 +11,15 @@ + + + + + + + + + @@ -19,4 +28,7 @@ Pack="true" /> + + + diff --git a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/Microsoft.DotNet.SwaggerGenerator.MSBuild.csproj b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/Microsoft.DotNet.SwaggerGenerator.MSBuild.csproj index ba2a71ccec9..93477f78700 100644 --- a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/Microsoft.DotNet.SwaggerGenerator.MSBuild.csproj +++ b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/Microsoft.DotNet.SwaggerGenerator.MSBuild.csproj @@ -1,13 +1,11 @@ - $(BundledNETCoreAppTargetFramework) - false + $(NetToolCurrent);$(NetFrameworkToolCurrent) true true This package provides support for generating client library code from a swagger document. true - true @@ -15,4 +13,8 @@ + + + + diff --git a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.props b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.props index 4e06d275305..5c6c055458f 100644 --- a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.props +++ b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.props @@ -2,8 +2,10 @@ - $(MSBuildThisFileDirectory)../tools/net/ + $(MSBuildThisFileDirectory)../tools/net/ + $(MSBuildThisFileDirectory)../tools/netframework/ $(MicrosoftDotNetSwaggerGeneratorMSBuildDirectory)Microsoft.DotNet.SwaggerGenerator.MSBuild.dll + AssemblyTaskFactory $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', 'generated-code')) diff --git a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.targets b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.targets index 767c52be40e..b2af860ee86 100644 --- a/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.targets +++ b/src/Microsoft.DotNet.SwaggerGenerator/Microsoft.DotNet.SwaggerGenerator.MSBuild/build/Microsoft.DotNet.SwaggerGenerator.MSBuild.targets @@ -1,7 +1,7 @@ - + + + + $(NetToolCurrent);$(NetFrameworkToolCurrent) + Exe + true + true + Tar + Arcade Build Tool Tar + false + + + + true + dotnet-tar + + + diff --git a/src/Microsoft.DotNet.Tar/Program.cs b/src/Microsoft.DotNet.Tar/Program.cs new file mode 100644 index 00000000000..8d68eb73096 --- /dev/null +++ b/src/Microsoft.DotNet.Tar/Program.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. + +#if NETFRAMEWORK + +System.Console.Error.WriteLine("Not supported on .NET Framework"); +return 1; + +#else + +using System; +using System.Formats.Tar; +using System.IO; +using System.IO.Compression; + +if (args is not [var srcPath, var dstPath]) +{ + Console.Error.WriteLine("Usage: "); + return 1; +} + +try +{ + if (File.Exists(srcPath)) + { + Directory.CreateDirectory(dstPath); + + using var srcStream = File.Open(srcPath, FileMode.Open); + using var gzip = new GZipStream(srcStream, CompressionMode.Decompress); + TarFile.ExtractToDirectory(gzip, dstPath, overwriteFiles: false); + + } + else if (Directory.Exists(srcPath)) + { + using var dstStream = File.Open(dstPath, FileMode.Create); + using var gzip = new GZipStream(dstStream, CompressionMode.Compress); + TarFile.CreateFromDirectory(srcPath, gzip, includeBaseDirectory: false); + } + else + { + Console.Error.WriteLine($"File or directory must exist: '{srcPath}'"); + return 1; + } +} +catch (Exception e) +{ + Console.Error.Write(e.Message); + return 1; +} + +return 0; + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig b/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig index c6af9b3bdb5..b636900dd44 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig +++ b/src/Microsoft.DotNet.XUnitAssert/src/.editorconfig @@ -220,9 +220,16 @@ dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -#### Code quality rules #### - -# Analyzer severity - -dotnet_diagnostic.IDE0040.severity = none # Add accessibility modifiers -dotnet_diagnostic.IDE0079.severity = none # Remove unnecessary suppression +# Build Checks +[*.{csproj,proj,metaproj}] +build_check.BC0101.Severity=suggestion +build_check.BC0102.Severity=suggestion +build_check.BC0103.Severity=suggestion +build_check.BC0104.Severity=suggestion +build_check.BC0105.Severity=suggestion +build_check.BC0106.Severity=suggestion +build_check.BC0107.Severity=suggestion + +build_check.BC0201.Severity=suggestion +build_check.BC0202.Severity=suggestion +build_check.BC0203.Severity=suggestion \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml b/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml deleted file mode 100644 index 0a99d30a482..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: xunit diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs b/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs index 3e9d4a26ff8..ca5d9661a53 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Assert.cs @@ -1,4 +1,7 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -8,8 +11,8 @@ #endif using System; -using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace Xunit { @@ -24,9 +27,6 @@ namespace Xunit #endif partial class Assert { - static readonly Type typeofDictionary = typeof(Dictionary<,>); - static readonly Type typeofHashSet = typeof(HashSet<>); - /// /// Initializes a new instance of the class. /// @@ -53,24 +53,37 @@ protected Assert() { } } /// - /// Safely perform , returning when the + /// Safely perform , returning null when the /// type is not generic. /// /// The potentially generic type - /// The generic type definition, when is generic; , otherwise. + /// The generic type definition, when is generic; null, otherwise. + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] + [UnconditionalSuppressMessage("Trimmability", "IL2073", Justification = "The interfaces on a generic type definition won't be trimmed if they're preserved for an instantation.")] #if XUNIT_NULLABLE - static Type? SafeGetGenericTypeDefinition(Type? type) + static Type? SafeGetGenericTypeDefinition([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? type) #else - static Type SafeGetGenericTypeDefinition(Type type) + static Type SafeGetGenericTypeDefinition([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) #endif { if (type == null) return null; +#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NETFRAMEWORK if (!type.IsGenericType) return null; +#endif - return type.GetGenericTypeDefinition(); + // We need try/catch for target frameworks that don't support IsGenericType; notably, this + // would include .NET Core 1.x and .NET Standard 1.x, which are still supported for v2. + try + { + return type.GetGenericTypeDefinition(); + } + catch (InvalidOperationException) + { + return null; + } } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs index 1e3b915b66e..28d6c75101b 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/AsyncCollectionAsserts.cs @@ -1,7 +1,8 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0161 // Convert to file-scoped namespace -#if NET8_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER #if XUNIT_NULLABLE #nullable enable @@ -9,12 +10,18 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit.Internal; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -25,7 +32,7 @@ partial class Assert /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static void All( + public static void All<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Action action) => All(AssertHelper.ToEnumerable(collection), action); @@ -38,7 +45,7 @@ public static void All( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static void All( + public static void All<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Action action) => All(AssertHelper.ToEnumerable(collection), action); @@ -51,7 +58,7 @@ public static void All( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static Task AllAsync( + public static Task AllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Func action) => AllAsync(AssertHelper.ToEnumerable(collection), action); @@ -64,7 +71,7 @@ public static Task AllAsync( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static Task AllAsync( + public static Task AllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Func action) => AllAsync(AssertHelper.ToEnumerable(collection), action); @@ -77,7 +84,7 @@ public static Task AllAsync( /// The collection to be inspected /// The element inspectors, which inspect each element in turn. The /// total number of element inspectors must exactly match the number of elements in the collection. - public static void Collection( + public static void Collection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, params Action[] elementInspectors) => Collection(AssertHelper.ToEnumerable(collection), elementInspectors); @@ -90,7 +97,7 @@ public static void Collection( /// The collection to be inspected /// The element inspectors, which inspect each element in turn. The /// total number of element inspectors must exactly match the number of elements in the collection. - public static Task CollectionAsync( + public static Task CollectionAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, params Func[] elementInspectors) => CollectionAsync(AssertHelper.ToEnumerable(collection), elementInspectors); @@ -102,7 +109,7 @@ public static Task CollectionAsync( /// The object expected to be in the collection /// The collection to be inspected /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IAsyncEnumerable collection) => Contains(expected, AssertHelper.ToEnumerable(collection)); @@ -115,7 +122,7 @@ public static void Contains( /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IAsyncEnumerable collection, IEqualityComparer comparer) => @@ -128,7 +135,7 @@ public static void Contains( /// The collection to be inspected /// The filter used to find the item you're ensuring the collection contains /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Predicate filter) => Contains(AssertHelper.ToEnumerable(collection), filter); @@ -139,7 +146,7 @@ public static void Contains( /// The type of the object to be compared /// The collection to be inspected /// Thrown when an object is present inside the collection more than once - public static void Distinct(IAsyncEnumerable collection) => + public static void Distinct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IAsyncEnumerable collection) => Distinct(AssertHelper.ToEnumerable(collection), EqualityComparer.Default); /// @@ -149,7 +156,7 @@ public static void Distinct(IAsyncEnumerable collection) => /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when an object is present inside the collection more than once - public static void Distinct( + public static void Distinct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, IEqualityComparer comparer) => Distinct(AssertHelper.ToEnumerable(collection), comparer); @@ -161,7 +168,7 @@ public static void Distinct( /// The object that is expected not to be in the collection /// The collection to be inspected /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IAsyncEnumerable collection) => DoesNotContain(expected, AssertHelper.ToEnumerable(collection)); @@ -174,7 +181,7 @@ public static void DoesNotContain( /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IAsyncEnumerable collection, IEqualityComparer comparer) => @@ -187,7 +194,7 @@ public static void DoesNotContain( /// The collection to be inspected /// The filter used to find the item you're ensuring the collection does not contain /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Predicate filter) => DoesNotContain(AssertHelper.ToEnumerable(collection), filter); @@ -198,7 +205,7 @@ public static void DoesNotContain( /// The collection to be inspected /// Thrown when the collection is null /// Thrown when the collection is not empty - public static void Empty(IAsyncEnumerable collection) => + public static void Empty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IAsyncEnumerable collection) => Empty(AssertHelper.ToEnumerable(collection)); /// @@ -208,7 +215,7 @@ public static void Empty(IAsyncEnumerable collection) => /// The expected value /// The value to be compared against /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual) => @@ -225,7 +232,7 @@ public static void Equal( /// The expected value /// The value to be compared against /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual) => @@ -243,7 +250,7 @@ public static void Equal( /// The value to be compared against /// The comparer used to compare the two objects /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual, @@ -262,7 +269,7 @@ public static void Equal( /// The value to be compared against /// The comparer used to compare the two objects /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual, @@ -281,7 +288,7 @@ public static void Equal( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual, @@ -300,7 +307,7 @@ public static void Equal( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual, @@ -317,7 +324,7 @@ public static void Equal( /// The collection to be inspected /// Thrown when a null collection is passed /// Thrown when the collection is empty - public static void NotEmpty(IAsyncEnumerable collection) => + public static void NotEmpty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IAsyncEnumerable collection) => NotEmpty(AssertHelper.ToEnumerable(collection)); /// @@ -327,7 +334,7 @@ public static void NotEmpty(IAsyncEnumerable collection) => /// The expected object /// The actual object /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual) => @@ -344,7 +351,7 @@ public static void NotEqual( /// The expected object /// The actual object /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual) => @@ -362,7 +369,7 @@ public static void NotEqual( /// The actual object /// The comparer used to compare the two objects /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual, @@ -381,7 +388,7 @@ public static void NotEqual( /// The actual object /// The comparer used to compare the two objects /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual, @@ -400,7 +407,7 @@ public static void NotEqual( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IAsyncEnumerable? actual, @@ -419,7 +426,7 @@ public static void NotEqual( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IAsyncEnumerable? expected, IAsyncEnumerable? actual, @@ -439,7 +446,7 @@ public static void NotEqual( /// The single item in the collection. /// Thrown when the collection does not contain /// exactly one element. - public static T Single(IAsyncEnumerable collection) => + public static T Single<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IAsyncEnumerable collection) => Single(AssertHelper.ToEnumerable(collection)); /// @@ -454,7 +461,7 @@ public static T Single(IAsyncEnumerable collection) => /// The single item in the filtered collection. /// Thrown when the filtered collection does /// not contain exactly one element. - public static T Single( + public static T Single<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IAsyncEnumerable collection, Predicate predicate) => Single(AssertHelper.ToEnumerable(collection), predicate); diff --git a/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs index 4c2a4c02308..1182a31cfb9 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/BooleanAsserts.cs @@ -1,4 +1,5 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -15,6 +16,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// diff --git a/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs index 331a9a5ea01..afe1147c5a3 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/CollectionAsserts.cs @@ -2,8 +2,13 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1720 // Identifier contains type name #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0019 // Use pattern matching +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0063 // Use simple 'using' statement #pragma warning disable IDE0066 // Convert switch statement to expression +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0305 // Simplify collection initialization #if XUNIT_NULLABLE @@ -18,12 +23,18 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -34,35 +45,14 @@ partial class Assert /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static void All( + public static void All<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Action action) { GuardArgumentNotNull(nameof(collection), collection); GuardArgumentNotNull(nameof(action), action); - All(collection, (item, index) => action(item), throwIfEmpty: false); - } - - /// - /// Verifies that all items in the collection pass when executed against - /// action. - /// - /// The type of the object to be verified - /// The collection - /// The action to test each item against - /// Indicates whether to throw an exception if the collection is empty - /// Thrown when the collection contains at least one non-matching element - /// Also thrown when collection is empty and is set to true - public static void All( - IEnumerable collection, - Action action, - bool throwIfEmpty) - { - GuardArgumentNotNull(nameof(collection), collection); - GuardArgumentNotNull(nameof(action), action); - - All(collection, (item, index) => action(item), throwIfEmpty); + All(collection, (item, index) => action(item)); } /// @@ -73,34 +63,13 @@ public static void All( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static void All( + public static void All<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Action action) { GuardArgumentNotNull(nameof(collection), collection); GuardArgumentNotNull(nameof(action), action); - All(collection, action, throwIfEmpty: false); - } - - /// - /// Verifies that all items in the collection pass when executed against - /// action. The item index is provided to the action, in addition to the item. - /// - /// The type of the object to be verified - /// The collection - /// The action to test each item against - /// Indicates whether to throw an exception if the collection is empty - /// Thrown when the collection contains at least one non-matching element - /// Also thrown when collection is empty and is set to true - public static void All( - IEnumerable collection, - Action action, - bool throwIfEmpty) - { - GuardArgumentNotNull(nameof(collection), collection); - GuardArgumentNotNull(nameof(action), action); - var errors = new List>(); var idx = 0; @@ -118,9 +87,6 @@ public static void All( ++idx; } - if (throwIfEmpty && idx == 0) - throw AllException.ForEmptyCollection(); - if (errors.Count > 0) throw AllException.ForFailures(idx, errors); } @@ -133,35 +99,14 @@ public static void All( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static async Task AllAsync( + public static async Task AllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Func action) { GuardArgumentNotNull(nameof(collection), collection); GuardArgumentNotNull(nameof(action), action); - await AllAsync(collection, async (item, index) => await action(item), throwIfEmpty: false); - } - - /// - /// Verifies that all items in the collection pass when executed against - /// action. - /// - /// The type of the object to be verified - /// The collection - /// The action to test each item against - /// Indicates whether to throw an exception if the collection is empty - /// Thrown when the collection contains at least one non-matching element - /// Also thrown when collection is empty and is set to true - public static async Task AllAsync( - IEnumerable collection, - Func action, - bool throwIfEmpty) - { - GuardArgumentNotNull(nameof(collection), collection); - GuardArgumentNotNull(nameof(action), action); - - await AllAsync(collection, (item, index) => action(item), throwIfEmpty); + await AllAsync(collection, async (item, index) => await action(item)); } /// @@ -172,34 +117,13 @@ public static async Task AllAsync( /// The collection /// The action to test each item against /// Thrown when the collection contains at least one non-matching element - public static async Task AllAsync( + public static async Task AllAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Func action) { GuardArgumentNotNull(nameof(collection), collection); GuardArgumentNotNull(nameof(action), action); - await AllAsync(collection, action, throwIfEmpty: false); - } - - /// - /// Verifies that all items in the collection pass when executed against - /// action. The item index is provided to the action, in addition to the item. - /// - /// The type of the object to be verified - /// The collection - /// The action to test each item against - /// Indicates whether to throw an exception if the collection is empty - /// Thrown when the collection contains at least one non-matching element - /// Also thrown when collection is empty and is set to true - public static async Task AllAsync( - IEnumerable collection, - Func action, - bool throwIfEmpty) - { - GuardArgumentNotNull(nameof(collection), collection); - GuardArgumentNotNull(nameof(action), action); - var errors = new List>(); var idx = 0; @@ -217,9 +141,6 @@ public static async Task AllAsync( ++idx; } - if (throwIfEmpty && idx == 0) - throw AllException.ForEmptyCollection(); - if (errors.Count > 0) throw AllException.ForFailures(idx, errors.ToArray()); } @@ -232,7 +153,7 @@ public static async Task AllAsync( /// The collection to be inspected /// The element inspectors, which inspect each element in turn. The /// total number of element inspectors must exactly match the number of elements in the collection. - public static void Collection( + public static void Collection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, params Action[] elementInspectors) { @@ -252,7 +173,8 @@ public static void Collection( } catch (Exception ex) { - var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent); + int? pointerIndent; + var formattedCollection = tracker.FormatIndexedMismatch(index, out pointerIndent); throw CollectionException.ForMismatchedItem(ex, index, pointerIndent, formattedCollection); } @@ -272,7 +194,7 @@ public static void Collection( /// The collection to be inspected /// The element inspectors, which inspect each element in turn. The /// total number of element inspectors must exactly match the number of elements in the collection. - public static async Task CollectionAsync( + public static async Task CollectionAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, params Func[] elementInspectors) { @@ -292,7 +214,8 @@ public static async Task CollectionAsync( } catch (Exception ex) { - var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent); + int? pointerIndent; + var formattedCollection = tracker.FormatIndexedMismatch(index, out pointerIndent); throw CollectionException.ForMismatchedItem(ex, index, pointerIndent, formattedCollection); } @@ -311,7 +234,7 @@ public static async Task CollectionAsync( /// The object expected to be in the collection /// The collection to be inspected /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IEnumerable collection) { @@ -319,13 +242,15 @@ public static void Contains( // We special case sets because they are constructed with their comparers, which we don't have access to. // We want to let them do their normal logic when appropriate, and not try to use our default comparer. - if (collection is ISet set) + var set = collection as ISet; + if (set != null) { Contains(expected, set); return; } -#if NET8_0_OR_GREATER - if (collection is IReadOnlySet readOnlySet) +#if NET5_0_OR_GREATER + var readOnlySet = collection as IReadOnlySet; + if (readOnlySet != null) { Contains(expected, readOnlySet); return; @@ -344,7 +269,7 @@ public static void Contains( /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IEnumerable collection, IEqualityComparer comparer) @@ -364,7 +289,7 @@ public static void Contains( /// The collection to be inspected /// The filter used to find the item you're ensuring the collection contains /// Thrown when the object is not present in the collection - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Predicate filter) { @@ -387,7 +312,7 @@ public static void Contains( /// The type of the object to be compared /// The collection to be inspected /// Thrown when an object is present inside the collection more than once - public static void Distinct(IEnumerable collection) => + public static void Distinct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IEnumerable collection) => Distinct(collection, EqualityComparer.Default); /// @@ -397,7 +322,7 @@ public static void Distinct(IEnumerable collection) => /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when an object is present inside the collection more than once - public static void Distinct( + public static void Distinct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, IEqualityComparer comparer) { @@ -420,8 +345,8 @@ public static void Distinct( /// The type of the object to be compared /// The object that is expected not to be in the collection /// The collection to be inspected - /// Thrown when the object is present inside the collection - public static void DoesNotContain( + /// Thrown when the object is present inside the container + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IEnumerable collection) { @@ -429,13 +354,15 @@ public static void DoesNotContain( // We special case sets because they are constructed with their comparers, which we don't have access to. // We want to let them do their normal logic when appropriate, and not try to use our default comparer. - if (collection is ISet set) + var set = collection as ISet; + if (set != null) { DoesNotContain(expected, set); return; } -#if NET8_0_OR_GREATER - if (collection is IReadOnlySet readOnlySet) +#if NET5_0_OR_GREATER + var readOnlySet = collection as IReadOnlySet; + if (readOnlySet != null) { DoesNotContain(expected, readOnlySet); return; @@ -454,7 +381,7 @@ public static void DoesNotContain( /// The collection to be inspected /// The comparer used to equate objects in the collection with the expected object /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IEnumerable collection, IEqualityComparer comparer) @@ -470,7 +397,8 @@ public static void DoesNotContain( { if (comparer.Equals(item, expected)) { - var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent); + int? pointerIndent; + var formattedCollection = tracker.FormatIndexedMismatch(index, out pointerIndent); throw DoesNotContainException.ForCollectionItemFound( ArgumentFormatter.Format(expected), @@ -492,7 +420,7 @@ public static void DoesNotContain( /// The collection to be inspected /// The filter used to find the item you're ensuring the collection does not contain /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Predicate filter) { @@ -507,7 +435,8 @@ public static void DoesNotContain( { if (filter(item)) { - var formattedCollection = tracker.FormatIndexedMismatch(index, out var pointerIndent); + int? pointerIndent; + var formattedCollection = tracker.FormatIndexedMismatch(index, out pointerIndent); throw DoesNotContainException.ForCollectionFilterMatched( index, @@ -546,7 +475,7 @@ public static void Empty(IEnumerable collection) /// The expected value /// The value to be compared against /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual) => @@ -564,7 +493,7 @@ public static void Equal( /// The value to be compared against /// The comparer used to compare the two objects /// Thrown when the objects are not equal - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual, @@ -583,7 +512,12 @@ public static void Equal( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual, @@ -623,7 +557,7 @@ public static void NotEmpty(IEnumerable collection) /// The expected object /// The actual object /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual) => @@ -641,7 +575,7 @@ public static void NotEqual( /// The actual object /// The comparer used to compare the two objects /// Thrown when the objects are equal - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual, @@ -660,7 +594,12 @@ public static void NotEqual( /// The expected value /// The value to be compared against /// The function to compare two items for equality - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE IEnumerable? expected, IEnumerable? actual, @@ -722,7 +661,7 @@ public static void Single( /// The single item in the collection. /// Thrown when the collection does not contain /// exactly one element. - public static T Single(IEnumerable collection) + public static T Single<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>(IEnumerable collection) { GuardArgumentNotNull(nameof(collection), collection); @@ -741,7 +680,7 @@ public static T Single(IEnumerable collection) /// The single item in the filtered collection. /// Thrown when the filtered collection does /// not contain exactly one element. - public static T Single( + public static T Single<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, Predicate predicate) { @@ -751,7 +690,7 @@ public static T Single( return GetSingleResult(collection, predicate, "(predicate expression)"); } - static T GetSingleResult( + static T GetSingleResult<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( IEnumerable collection, #if XUNIT_NULLABLE Predicate? predicate, diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Comparers.cs b/src/Microsoft.DotNet.XUnitAssert/src/Comparers.cs index 90ac761d2bc..d8c16d2cc57 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Comparers.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Comparers.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1859 // Use concrete types when possible for improved performance +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -11,17 +13,33 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { #if XUNIT_NULLABLE - static IEqualityComparer GetEqualityComparer(IEqualityComparer? innerComparer = null) => + static IEqualityComparer GetEqualityComparer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>(IEqualityComparer? innerComparer = null) => new AssertEqualityComparer(innerComparer); #else - static IEqualityComparer GetEqualityComparer(IEqualityComparer innerComparer = null) => + static IEqualityComparer GetEqualityComparer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>(IEqualityComparer innerComparer = null) => new AssertEqualityComparer(innerComparer); #endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/DictionaryAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/DictionaryAsserts.cs index 41b71f2829a..7236ff0637b 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/DictionaryAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/DictionaryAsserts.cs @@ -1,4 +1,9 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0059 // Unnecessary assignment of a value +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -10,12 +15,21 @@ using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using Xunit.Sdk; +#if XUNIT_IMMUTABLE_COLLECTIONS +using System.Collections.Immutable; +#endif + namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -27,7 +41,12 @@ partial class Assert /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, IDictionary collection) #if XUNIT_NULLABLE @@ -37,7 +56,8 @@ public static TValue Contains( GuardArgumentNotNull(nameof(expected), expected); GuardArgumentNotNull(nameof(collection), collection); - if (!collection.TryGetValue(expected, out var value)) + var value = default(TValue); + if (!collection.TryGetValue(expected, out value)) throw ContainsException.ForKeyNotFound( ArgumentFormatter.Format(expected), CollectionTracker.FormatStart(collection.Keys) @@ -55,7 +75,12 @@ public static TValue Contains( /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, IReadOnlyDictionary collection) #if XUNIT_NULLABLE @@ -65,7 +90,8 @@ public static TValue Contains( GuardArgumentNotNull(nameof(expected), expected); GuardArgumentNotNull(nameof(collection), collection); - if (!collection.TryGetValue(expected, out var value)) + var value = default(TValue); + if (!collection.TryGetValue(expected, out value)) throw ContainsException.ForKeyNotFound( ArgumentFormatter.Format(expected), CollectionTracker.FormatStart(collection.Keys) @@ -83,7 +109,12 @@ public static TValue Contains( /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, ConcurrentDictionary collection) #if XUNIT_NULLABLE @@ -100,7 +131,12 @@ public static TValue Contains( /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, Dictionary collection) #if XUNIT_NULLABLE @@ -117,7 +153,12 @@ public static TValue Contains( /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, ReadOnlyDictionary collection) #if XUNIT_NULLABLE @@ -125,6 +166,7 @@ public static TValue Contains( #endif => Contains(expected, (IReadOnlyDictionary)collection); +#if XUNIT_IMMUTABLE_COLLECTIONS /// /// Verifies that a dictionary contains a given key. /// @@ -134,13 +176,19 @@ public static TValue Contains( /// The collection to be inspected. /// The value associated with . /// Thrown when the object is not present in the collection - public static TValue Contains( + public static TValue Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)]TKey, TValue>( TKey expected, ImmutableDictionary collection) #if XUNIT_NULLABLE where TKey : notnull #endif => Contains(expected, (IReadOnlyDictionary)collection); +#endif /// /// Verifies that a dictionary does not contain a given key. @@ -150,7 +198,12 @@ public static TValue Contains( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, IDictionary collection) #if XUNIT_NULLABLE @@ -176,7 +229,7 @@ public static void DoesNotContain( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, IReadOnlyDictionary collection) #if XUNIT_NULLABLE @@ -202,7 +255,7 @@ public static void DoesNotContain( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, ConcurrentDictionary collection) #if XUNIT_NULLABLE @@ -218,7 +271,7 @@ public static void DoesNotContain( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, Dictionary collection) #if XUNIT_NULLABLE @@ -234,7 +287,7 @@ public static void DoesNotContain( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] TKey, TValue>( TKey expected, ReadOnlyDictionary collection) #if XUNIT_NULLABLE @@ -242,6 +295,7 @@ public static void DoesNotContain( #endif => DoesNotContain(expected, (IReadOnlyDictionary)collection); +#if XUNIT_IMMUTABLE_COLLECTIONS /// /// Verifies that a dictionary does not contain a given key. /// @@ -250,12 +304,18 @@ public static void DoesNotContain( /// The object expected to be in the collection. /// The collection to be inspected. /// Thrown when the object is present in the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)]TKey, TValue>( TKey expected, ImmutableDictionary collection) #if XUNIT_NULLABLE where TKey : notnull #endif => DoesNotContain(expected, (IReadOnlyDictionary)collection); +#endif } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs index be9cc27044a..56c94bedcd6 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts.cs @@ -1,4 +1,8 @@ #pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0045 // Convert to conditional expression +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -12,23 +16,79 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Reflection; using Xunit.Internal; using Xunit.Sdk; +#if XUNIT_NULLABLE +using System.Diagnostics.CodeAnalysis; +#endif + namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { + static readonly Type typeofDictionary = typeof(Dictionary<,>); + static readonly Type typeofHashSet = typeof(HashSet<>); + static readonly Type typeofSet = typeof(ISet<>); + +#if XUNIT_SPAN + /// + /// Verifies that two arrays of un-managed type T are equal, using Span<T>.SequenceEqual. + /// This can be significantly faster than generic enumerables, when the collections are actually + /// equal, because the system can optimize packed-memory comparisons for value type arrays. + /// + /// The type of items whose arrays are to be compared + /// The expected value + /// The value to be compared against + /// + /// If fails, a call + /// to is made, to provide a more meaningful error message. + /// + public static void Equal( +#if XUNIT_NULLABLE + [AllowNull] T[] expected, + [AllowNull] T[] actual) + where T : unmanaged, IEquatable +#else + T[] expected, + T[] actual) + where T : IEquatable +#endif + { + if (expected == null && actual == null) + return; + + if (expected == null || actual == null || !expected.AsSpan().SequenceEqual(actual)) + // Call into Equal (even though we'll re-enumerate) so we get proper formatting + // of the sequence, including the "first mismatch" pointer + Equal(expected, actual); + } +#endif + /// /// Verifies that two objects are equal, using a default comparer. /// /// The type of the objects to be compared /// The expected value /// The value to be compared against - public static void Equal( + /// Thrown when the objects are not equal + public static void Equal<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.Interfaces + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual) => + [AllowNull] T expected, + [AllowNull] T actual) => #else T expected, T actual) => @@ -42,10 +102,16 @@ public static void Equal( /// The expected value /// The value to be compared against /// The comparer used to compare the two objects - public static void Equal( + public static void Equal<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.Interfaces + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual, + [AllowNull] T expected, + [AllowNull] T actual, #else T expected, T actual, @@ -54,16 +120,23 @@ public static void Equal( Equal(expected, actual, AssertEqualityComparer.FromComparer(comparer)); /// - /// Verifies that two objects are equal, using a custom equality comparer. + /// Verifies that two objects are equal, using a custom equatable comparer. /// /// The type of the objects to be compared /// The expected value /// The value to be compared against /// The comparer used to compare the two objects - public static void Equal( + /// Thrown when the objects are not equal + public static void Equal<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.Interfaces + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual, + [AllowNull] T expected, + [AllowNull] T actual, #else T expected, T actual, @@ -78,7 +151,6 @@ public static void Equal( var expectedTracker = expected.AsNonStringTracker(); var actualTracker = actual.AsNonStringTracker(); var exception = default(Exception); - var mismatchedIndex = default(int?); try { @@ -99,14 +171,12 @@ public static void Equal( exception = ex; } - throw EqualException.ForMismatchedValuesWithError( - expected as string ?? ArgumentFormatter.Format(expected), - actual as string ?? ArgumentFormatter.Format(actual), - exception - ); + throw EqualException.ForMismatchedValuesWithError(expected, actual, exception); } else { + int? mismatchedIndex = null; + // If we have "known" comparers, we can ignore them and instead do our own thing, since we know // we want to be able to consume the tracker, and that's not type compatible. var itemComparer = default(IEqualityComparer); @@ -131,37 +201,24 @@ public static void Equal( if (itemComparer != null) { - AssertEqualityResult result; + try + { + bool result; - // Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker - if (aec != null) - result = aec.Equals(expected, expectedTracker, actual, actualTracker); - else - result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer.DefaultInnerComparer); + // Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker + if (aec != null) + result = aec.Equals(expected, expectedTracker, actual, actualTracker, out mismatchedIndex); + else + result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer.DefaultInnerComparer, out mismatchedIndex); - if (result.Equal) - return; - - if (result.InnerResult is AssertEqualityResult innerResult) + if (result) + return; + } + catch (Exception ex) { - var innerExpectedString = innerResult.X as string; - var innerExpectedMismatch = innerResult.MismatchIndexX; - var innerActualString = innerResult.Y as string; - var innerActualMismatch = innerResult.MismatchIndexY; - - if ((innerExpectedString != null || innerActualString != null) && innerExpectedMismatch.HasValue && innerActualMismatch.HasValue) - throw EqualException.ForMismatchedStringsWithHeader( - innerExpectedString, - innerActualString, - innerExpectedMismatch.Value, - innerActualMismatch.Value, - "Collections differ at index " + result.MismatchIndexX - ); + exception = ex; } - exception = result.Exception; - mismatchedIndex = result.MismatchIndexX; - var expectedStartIdx = -1; var expectedEndIdx = -1; expectedTracker?.GetMismatchExtents(mismatchedIndex, out expectedStartIdx, out expectedEndIdx); @@ -206,13 +263,14 @@ public static void Equal( formattedActual = ArgumentFormatter.Format(actual); } - var expectedType = expected?.GetType(); - var expectedTypeDefinition = SafeGetGenericTypeDefinition(expectedType); +#if XUNIT_NULLABLE + string? collectionDisplay = GetCollectionDisplay(expected, actual); +#else + string collectionDisplay = GetCollectionDisplay(expected, actual); +#endif + var expectedType = expected?.GetType(); var actualType = actual?.GetType(); - var actualTypeDefinition = SafeGetGenericTypeDefinition(actualType); - - var collectionDisplay = GetCollectionDisplay(expectedType, expectedTypeDefinition, actualType, actualTypeDefinition); if (expectedType != actualType) { @@ -240,6 +298,39 @@ public static void Equal( } } + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ISet<>))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary<,>))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(HashSet<>))] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "We only check for the types listed above.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075", Justification = "We only check for the types listed above.")] +#if XUNIT_NULLABLE + private static string? GetCollectionDisplay(object? expected, object? actual) +#else + private static string GetCollectionDisplay(object expected, object actual) +#endif + { +#if XUNIT_NULLABLE + string? collectionDisplay = null; +#else + string collectionDisplay = null; +#endif + var expectedType = expected?.GetType(); + var actualType = actual?.GetType(); + var expectedTypeDefinition = SafeGetGenericTypeDefinition(expectedType); + var expectedInterfaceTypeDefinitions = expectedType?.GetTypeInfo().ImplementedInterfaces.Where(i => i.GetTypeInfo().IsGenericType).Select(i => i.GetGenericTypeDefinition()); + + var actualTypeDefinition = SafeGetGenericTypeDefinition(actualType); + var actualInterfaceTypeDefinitions = actualType?.GetTypeInfo().ImplementedInterfaces.Where(i => i.GetTypeInfo().IsGenericType).Select(i => i.GetGenericTypeDefinition()); + + if (expectedTypeDefinition == typeofDictionary && actualTypeDefinition == typeofDictionary) + collectionDisplay = "Dictionaries"; + else if (expectedTypeDefinition == typeofHashSet && actualTypeDefinition == typeofHashSet) + collectionDisplay = "HashSets"; + else if (expectedInterfaceTypeDefinitions != null && actualInterfaceTypeDefinitions != null && expectedInterfaceTypeDefinitions.Contains(typeofSet) && actualInterfaceTypeDefinitions.Contains(typeofSet)) + collectionDisplay = "Sets"; + return collectionDisplay; + } + /// /// Verifies that two values are equal, within the number of decimal /// places given by . The values are rounded before comparison. @@ -436,7 +527,7 @@ public static void Equal( ArgumentFormatter.Format(actual) + (precision == TimeSpan.Zero ? "" : string.Format(CultureInfo.CurrentCulture, " (difference {0} is larger than {1})", difference, precision)); - throw EqualException.ForMismatchedValues(ArgumentFormatter.Format(expected), actualValue); + throw EqualException.ForMismatchedValues(expected, actualValue); } } @@ -470,20 +561,54 @@ public static void Equal( ArgumentFormatter.Format(actual) + (precision == TimeSpan.Zero ? "" : string.Format(CultureInfo.CurrentCulture, " (difference {0} is larger than {1})", difference, precision)); - throw EqualException.ForMismatchedValues(ArgumentFormatter.Format(expected), actualValue); + throw EqualException.ForMismatchedValues(expected, actualValue); } } +#if XUNIT_SPAN + /// + /// Verifies that two arrays of un-managed type T are not equal, using Span<T>.SequenceEqual. + /// + /// The type of items whose arrays are to be compared + /// The expected value + /// The value to be compared against + public static void NotEqual( +#if XUNIT_NULLABLE + [AllowNull] T[] expected, + [AllowNull] T[] actual) + where T : unmanaged, IEquatable +#else + T[] expected, + T[] actual) + where T : IEquatable +#endif + { + // Call into NotEqual so we get proper formatting of the sequence + if (expected == null && actual == null) + NotEqual(expected, actual); + if (expected == null || actual == null) + return; + if (expected.AsSpan().SequenceEqual(actual)) + NotEqual(expected, actual); + } +#endif + /// /// Verifies that two objects are not equal, using a default comparer. /// /// The type of the objects to be compared /// The expected object /// The actual object - public static void NotEqual( + /// Thrown when the objects are equal + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual) => + [AllowNull] T expected, + [AllowNull] T actual) => #else T expected, T actual) => @@ -497,10 +622,15 @@ public static void NotEqual( /// The expected object /// The actual object /// The comparer used to examine the objects - public static void NotEqual( + public static void NotEqual<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual, + [AllowNull] T expected, + [AllowNull] T actual, #else T expected, T actual, @@ -515,10 +645,16 @@ public static void NotEqual( /// The expected object /// The actual object /// The comparer used to examine the objects - public static void NotEqual( + public static void NotEqual< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>( #if XUNIT_NULLABLE - T? expected, - T? actual, + [AllowNull] T expected, + [AllowNull] T actual, #else T expected, T actual, @@ -530,7 +666,6 @@ public static void NotEqual( var expectedTracker = expected.AsNonStringTracker(); var actualTracker = actual.AsNonStringTracker(); var exception = default(Exception); - var mismatchedIndex = default(int?); try { @@ -568,6 +703,8 @@ public static void NotEqual( } else { + int? mismatchedIndex = null; + // If we have "known" comparers, we can ignore them and instead do our own thing, since we know // we want to be able to consume the tracker, and that's not type compatible. var itemComparer = default(IEqualityComparer); @@ -585,29 +722,27 @@ public static void NotEqual( if (itemComparer != null) { - AssertEqualityResult result; - - // Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker - if (aec != null) - result = aec.Equals(expected, expectedTracker, actual, actualTracker); - else - result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer.DefaultInnerComparer); + try + { + bool result; - if (!result.Equal && result.Exception is null) - return; + // Call AssertEqualityComparer.Equals because it checks for IEquatable<> before using CollectionTracker + if (aec != null) + result = aec.Equals(expected, expectedTracker, actual, actualTracker, out mismatchedIndex); + else + result = CollectionTracker.AreCollectionsEqual(expectedTracker, actualTracker, itemComparer, itemComparer == AssertEqualityComparer.DefaultInnerComparer, out mismatchedIndex); - mismatchedIndex = result.MismatchIndexX; + if (!result) + return; - if (result.Exception is null) - { // For NotEqual that doesn't throw, pointers are irrelevant, because // the values are considered to be equal formattedExpected = expectedTracker?.FormatStart() ?? "null"; formattedActual = actualTracker?.FormatStart() ?? "null"; } - else + catch (Exception ex) { - exception = result.Exception; + exception = ex; // When an exception was thrown, we want to provide a pointer so the user knows // which item was being inspected when the exception was thrown @@ -642,13 +777,14 @@ public static void NotEqual( formattedActual = ArgumentFormatter.Format(actual); } - var expectedType = expected?.GetType(); - var expectedTypeDefinition = SafeGetGenericTypeDefinition(expectedType); +#if XUNIT_NULLABLE + string? collectionDisplay = GetCollectionDisplay(expected, actual); +#else + string collectionDisplay = GetCollectionDisplay(expected, actual); +#endif + var expectedType = expected?.GetType(); var actualType = actual?.GetType(); - var actualTypeDefinition = SafeGetGenericTypeDefinition(actualType); - - var collectionDisplay = GetCollectionDisplay(expectedType, expectedTypeDefinition, actualType, actualTypeDefinition); if (expectedType != actualType) { @@ -841,5 +977,63 @@ public static void NotEqual( string.Format(CultureInfo.CurrentCulture, "{0} (rounded from {1})", actualRounded, actual) ); } + + /// + /// Verifies that two objects are strictly not equal, using the type's default comparer. + /// + /// The type of the objects to be compared + /// The expected object + /// The actual object + public static void NotStrictEqual<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( +#if XUNIT_NULLABLE + [AllowNull] T expected, + [AllowNull] T actual) +#else + T expected, + T actual) +#endif + { + if (!EqualityComparer.Default.Equals(expected, actual)) + return; + + throw NotStrictEqualException.ForEqualValues( + ArgumentFormatter.Format(expected), + ArgumentFormatter.Format(actual) + ); + } + + /// + /// Verifies that two objects are strictly equal, using the type's default comparer. + /// + /// The type of the objects to be compared + /// The expected value + /// The value to be compared against + public static void StrictEqual<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( +#if XUNIT_NULLABLE + [AllowNull] T expected, + [AllowNull] T actual) +#else + T expected, + T actual) +#endif + { + if (EqualityComparer.Default.Equals(expected, actual)) + return; + + throw StrictEqualException.ForEqualValues( + ArgumentFormatter.Format(expected), + ArgumentFormatter.Format(actual) + ); + } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_aot.cs deleted file mode 100644 index 1a00ec54b1f..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_aot.cs +++ /dev/null @@ -1,167 +0,0 @@ -#if XUNIT_AOT - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Xunit.Sdk; - -namespace Xunit -{ - partial class Assert - { - /// - /// Verifies that two values in a are equal. - /// - /// The type of the key. - /// The type of the value. - /// The expected key/value pair - /// The actual key/value pair - [OverloadResolutionPriority(1)] - public static void Equal( - KeyValuePair? expected, - KeyValuePair? actual) - { - if (!expected.HasValue) - { - if (!actual.HasValue) - return; - - throw EqualException.ForMismatchedValues(ArgumentFormatter.Format(expected), ArgumentFormatter.Format(actual)); - } - - if (actual == null) - throw EqualException.ForMismatchedValues(ArgumentFormatter.Format(expected), ArgumentFormatter.Format(actual)); - - var keyComparer = new AssertEqualityComparer(); - if (!keyComparer.Equals(expected.Value.Key, actual.Value.Key)) - throw EqualException.ForMismatchedValues( - ArgumentFormatter.Format(expected.Value.Key), - ArgumentFormatter.Format(actual.Value.Key), - "Keys differ in KeyValuePair" - ); - - var valueComparer = new AssertEqualityComparer(); - if (!valueComparer.Equals(expected.Value.Value, actual.Value.Value)) - throw EqualException.ForMismatchedValues( - ArgumentFormatter.Format(expected.Value.Value), - ArgumentFormatter.Format(actual.Value.Value), - "Values differ in KeyValuePair" - ); - } - -#pragma warning disable IDE0060 // Remove unused parameter - -#if XUNIT_NULLABLE - static string? GetCollectionDisplay( - Type? expectedType, - Type? expectedTypeDefinition, - Type? actualType, - Type? actualTypeDefinition) -#else - static string? GetCollectionDisplay( - Type expectedType, - Type expectedTypeDefinition, - Type actualType, - Type actualTypeDefinition) -#endif - { - if (expectedTypeDefinition == typeofDictionary && actualTypeDefinition == typeofDictionary) - return "Dictionaries"; - else if (expectedTypeDefinition == typeofHashSet && actualTypeDefinition == typeofHashSet) - return "HashSets"; - - return null; - } - -#pragma warning restore IDE0060 // Remove unused parameter - - /// - /// Verifies that two values in a are not equal. - /// - /// The type of the key. - /// The type of the value. - /// The expected key/value pair - /// The actual key/value pair - [OverloadResolutionPriority(1)] - public static void NotEqual( - KeyValuePair? expected, - KeyValuePair? actual) - { - if (expected == null) - { - if (actual != null) - return; - - throw NotEqualException.ForEqualValues("null", "null"); - } - - if (actual == null) - return; - - var keyComparer = new AssertEqualityComparer(); - if (!keyComparer.Equals(expected.Value.Key, actual.Value.Key)) - return; - - var valueComparer = new AssertEqualityComparer(); - if (!valueComparer.Equals(expected.Value.Value, actual.Value.Value)) - return; - - throw NotEqualException.ForEqualValues(ArgumentFormatter.Format(expected.Value), ArgumentFormatter.Format(actual.Value)); - } - - /// - /// Verifies that two objects are strictly not equal, using . - /// - /// The expected object - /// The actual object - public static void NotStrictEqual( -#if XUNIT_NULLABLE - object? expected, - object? actual) -#else - object expected, - object actual) -#endif - { - if (!object.Equals(expected, actual)) - return; - - throw NotStrictEqualException.ForEqualValues( - ArgumentFormatter.Format(expected), - ArgumentFormatter.Format(actual) - ); - } - - /// - /// Verifies that two objects are strictly equal, using . - /// - /// The expected object - /// The actual object - public static void StrictEqual( -#if XUNIT_NULLABLE - object? expected, - object? actual) -#else - object expected, - object actual) -#endif - { - if (object.Equals(expected, actual)) - return; - - throw StrictEqualException.ForEqualValues( - ArgumentFormatter.Format(expected), - ArgumentFormatter.Format(actual) - ); - } - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_reflection.cs deleted file mode 100644 index 3d069d7321c..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/EqualityAsserts_reflection.cs +++ /dev/null @@ -1,165 +0,0 @@ -#if !XUNIT_AOT - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Xunit.Internal; -using Xunit.Sdk; - -#if XUNIT_OVERLOAD_RESOLUTION_PRIORITY -using System.Runtime.CompilerServices; -#endif - -namespace Xunit -{ - partial class Assert - { - static readonly Type typeofSet = typeof(ISet<>); - - /// - /// Verifies that two arrays of un-managed type T are equal, using Span<T>.SequenceEqual. - /// This can be significantly faster than generic enumerables, when the collections are actually - /// equal, because the system can optimize packed-memory comparisons for value type arrays. - /// - /// The type of items whose arrays are to be compared - /// The expected value - /// The value to be compared against - /// - /// If fails, a call - /// to is made, to provide a more meaningful error message. - /// - public static void Equal( -#if XUNIT_NULLABLE - T[]? expected, - T[]? actual) - where T : unmanaged, IEquatable -#else - T[] expected, - T[] actual) - where T : IEquatable -#endif - { - if (expected == null && actual == null) - return; - - if (expected == null || actual == null || !expected.AsSpan().SequenceEqual(actual)) - // Call into Equal (even though we'll re-enumerate) so we get proper formatting - // of the sequence, including the "first mismatch" pointer - Equal(expected, actual); - } - -#if XUNIT_NULLABLE - static string? GetCollectionDisplay( - Type? expectedType, - Type? expectedTypeDefinition, - Type? actualType, - Type? actualTypeDefinition) -#else - static string GetCollectionDisplay( - Type expectedType, - Type expectedTypeDefinition, - Type actualType, - Type actualTypeDefinition) -#endif - { - var expectedInterfaceTypeDefinitions = expectedType?.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - var actualInterfaceTypeDefinitions = actualType?.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); - - if (expectedTypeDefinition == typeofDictionary && actualTypeDefinition == typeofDictionary) - return "Dictionaries"; - else if (expectedTypeDefinition == typeofHashSet && actualTypeDefinition == typeofHashSet) - return "HashSets"; -#pragma warning disable CA1508 - else if (expectedInterfaceTypeDefinitions != null && actualInterfaceTypeDefinitions != null && expectedInterfaceTypeDefinitions.Contains(typeofSet) && actualInterfaceTypeDefinitions.Contains(typeofSet)) -#pragma warning restore CA1508 - return "Sets"; - - return null; - } - - /// - /// Verifies that two arrays of un-managed type T are not equal, using Span<T>.SequenceEqual. - /// - /// The type of items whose arrays are to be compared - /// The expected value - /// The value to be compared against - public static void NotEqual( -#if XUNIT_NULLABLE - T[]? expected, - T[]? actual) - where T : unmanaged, IEquatable -#else - T[] expected, - T[] actual) - where T : IEquatable -#endif - { - // Call into NotEqual so we get proper formatting of the sequence - if (expected == null && actual == null) - NotEqual(expected, actual); - if (expected == null || actual == null) - return; - if (expected.AsSpan().SequenceEqual(actual)) - NotEqual(expected, actual); - } - - /// - /// Verifies that two objects are strictly not equal, using the type's default comparer. - /// - /// The type of the objects to be compared - /// The expected object - /// The actual object - public static void NotStrictEqual( -#if XUNIT_NULLABLE - T? expected, - T? actual) -#else - T expected, - T actual) -#endif - { - if (!EqualityComparer.Default.Equals(expected, actual)) - return; - - throw NotStrictEqualException.ForEqualValues( - ArgumentFormatter.Format(expected), - ArgumentFormatter.Format(actual) - ); - } - - /// - /// Verifies that two objects are strictly equal, using the type's default comparer. - /// - /// The type of the objects to be compared - /// The expected value - /// The value to be compared against - public static void StrictEqual( -#if XUNIT_NULLABLE - T? expected, - T? actual) -#else - T expected, - T actual) -#endif - { - if (EqualityComparer.Default.Equals(expected, actual)) - return; - - throw StrictEqualException.ForEqualValues( - ArgumentFormatter.Format(expected), - ArgumentFormatter.Format(actual) - ); - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts.cs new file mode 100644 index 00000000000..8c960a9db41 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts.cs @@ -0,0 +1,47 @@ +#pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0161 // Convert to file-scoped namespace + +#if XUNIT_NULLABLE +#nullable enable +#endif + +using Xunit.Internal; + +namespace Xunit +{ +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif + partial class Assert + { + /// + /// Verifies that two objects are equivalent, using a default comparer. This comparison is done + /// without regard to type, and only inspects public property and field values for individual + /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex + /// types) are supported. With strict mode off, object comparison allows + /// to have extra public members that aren't part of , and collection + /// comparison allows to have more data in it than is present in + /// ; with strict mode on, those rules are tightened to require exact + /// member list (for objects) or data (for collections). + /// + /// The expected value + /// The actual value + /// A flag which enables strict comparison mode + public static void Equivalent( +#if XUNIT_NULLABLE + object? expected, + object? actual, +#else + object expected, + object actual, +#endif + bool strict = false) + { + var ex = AssertHelper.VerifyEquivalence(expected, actual, strict); + if (ex != null) + throw ex; + } + } +} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_aot.cs deleted file mode 100644 index 093ff3c544c..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_aot.cs +++ /dev/null @@ -1,105 +0,0 @@ -#if XUNIT_AOT - -#if XUNIT_NULLABLE -#nullable enable -#endif - -using System; -using System.ComponentModel; -using System.Linq.Expressions; - -namespace Xunit -{ - partial class Assert - { - /// - /// Assert.Equivalent is not supported in Native AOT due to reflection requirements. - /// - [Obsolete("Assert.Equivalent is not supported in Native AOT", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void Equivalent( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - bool strict = false) => - throw new NotSupportedException("Assert.Equivalent is not supported in Native AOT"); - - /// - /// Assert.EquivalentWithExclusions is not supported in Native AOT due to reflection requirements. - /// - [Obsolete("Assert.EquivalentWithExclusions is not supported in Native AOT", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, -#else - object expected, -#endif - T actual, -#if XUNIT_NULLABLE - params Expression>[] exclusionExpressions) => -#else - params Expression>[] exclusionExpressions) => -#endif - throw new NotSupportedException("Assert.Equivalent is not supported in Native AOT"); - - /// - /// Assert.EquivalentWithExclusions is not supported in Native AOT due to reflection requirements. - /// - [Obsolete("Assert.EquivalentWithExclusions is not supported in Native AOT", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, -#else - object expected, -#endif - T actual, - bool strict, -#if XUNIT_NULLABLE - params Expression>[] exclusionExpressions) => -#else - params Expression>[] exclusionExpressions) => -#endif - throw new NotSupportedException("Assert.Equivalent is not supported in Native AOT"); - - /// - /// Assert.EquivalentWithExclusions is not supported in Native AOT due to reflection requirements. - /// - [Obsolete("Assert.EquivalentWithExclusions is not supported in Native AOT", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - params string[] exclusionExpressions) => - throw new NotSupportedException("Assert.Equivalent is not supported in Native AOT"); - - /// - /// Assert.EquivalentWithExclusions is not supported in Native AOT due to reflection requirements. - /// - [Obsolete("Assert.EquivalentWithExclusions is not supported in Native AOT", error: true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - bool strict, - params string[] exclusionExpressions) => - throw new NotSupportedException("Assert.Equivalent is not supported in Native AOT"); - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_reflection.cs deleted file mode 100644 index 17a3a35367b..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/EquivalenceAsserts_reflection.cs +++ /dev/null @@ -1,179 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable CA1052 // Static holder types should be static - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#endif - -using System; -using System.Linq.Expressions; -using Xunit.Internal; - -namespace Xunit -{ - partial class Assert - { - /// - /// Verifies that two objects are equivalent, using a default comparer. This comparison is done - /// without regard to type, and only inspects public property and field values for individual - /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex - /// types) are supported. - /// - /// - /// With strict mode off, object comparison allows to have extra public - /// members that aren't part of , and collection comparison allows - /// to have more data in it than is present in ; - /// with strict mode on, those rules are tightened to require exact member list (for objects) or - /// data (for collections). - /// - /// The expected value - /// The actual value - /// A flag which enables strict comparison mode - public static void Equivalent( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - bool strict = false) - { - var ex = AssertHelper.VerifyEquivalence(expected, actual, strict); - if (ex != null) - throw ex; - } - - /// - /// Verifies that two objects are equivalent, using a default comparer. This comparison is done - /// without regard to type, and only inspects public property and field values for individual - /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex - /// types) are supported. Members can be excluded from the comparison by passing them as - /// expressions via (using lambda expressions). - /// - /// The type of the actual value - /// The expected value - /// The actual value - /// The expressions for exclusions - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, -#else - object expected, -#endif - T actual, -#if XUNIT_NULLABLE - params Expression>[] exclusionExpressions) => -#else - params Expression>[] exclusionExpressions) => -#endif - EquivalentWithExclusions(expected, actual, strict: false, exclusionExpressions); - - /// - /// Verifies that two objects are equivalent, using a default comparer. This comparison is done - /// without regard to type, and only inspects public property and field values for individual - /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex - /// types) are supported. Members can be excluded from the comparison by passing them as - /// expressions via (using lambda expressions). - /// - /// - /// With strict mode off, object comparison allows to have extra public - /// members that aren't part of , and collection comparison allows - /// to have more data in it than is present in ; - /// with strict mode on, those rules are tightened to require exact member list (for objects) or - /// data (for collections). - /// - /// The type of the actual value - /// The expected value - /// The actual value - /// A flag which enables strict comparison mode - /// The expressions for exclusions - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, -#else - object expected, -#endif - T actual, - bool strict, -#if XUNIT_NULLABLE - params Expression>[] exclusionExpressions) -#else - params Expression>[] exclusionExpressions) -#endif - { - var exclusions = AssertHelper.ParseExclusionExpressions(exclusionExpressions); - - var ex = AssertHelper.VerifyEquivalence(expected, actual, strict, exclusions); - if (ex != null) - throw ex; - } - - /// - /// Verifies that two objects are equivalent, using a default comparer. This comparison is done - /// without regard to type, and only inspects public property and field values for individual - /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex - /// types) are supported. Members can be excluded from the comparison by passing them as - /// expressions via (using "Member.SubMember.SubSubMember" - /// form). - /// - /// The expected value - /// The actual value - /// The expressions for exclusions. This should be provided - /// in "Member.SubMember.SubSubMember" form for deep exclusions. - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - params string[] exclusionExpressions) => - EquivalentWithExclusions(expected, actual, strict: false, exclusionExpressions); - - /// - /// Verifies that two objects are equivalent, using a default comparer. This comparison is done - /// without regard to type, and only inspects public property and field values for individual - /// equality. Deep equivalence tests (meaning, property or fields which are themselves complex - /// types) are supported. Members can be excluded from the comparison by passing them as - /// expressions via (using "Member.SubMember.SubSubMember" - /// form). - /// - /// - /// With strict mode off, object comparison allows to have extra public - /// members that aren't part of , and collection comparison allows - /// to have more data in it than is present in ; - /// with strict mode on, those rules are tightened to require exact member list (for objects) or - /// data (for collections). - /// - /// The expected value - /// The actual value - /// A flag which enables strict comparison mode - /// The expressions for exclusions. This should be provided - /// in "Member1.Member2.Member3" form for deep exclusions. - public static void EquivalentWithExclusions( -#if XUNIT_NULLABLE - object? expected, - object? actual, -#else - object expected, - object actual, -#endif - bool strict, - params string[] exclusionExpressions) - { - var exclusions = AssertHelper.ParseExclusionExpressions(exclusionExpressions); - - var ex = AssertHelper.VerifyEquivalence(expected, actual, strict, exclusions); - if (ex != null) - throw ex; - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/EventAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/EventAsserts.cs index 355bf6d492c..3560b1332fe 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/EventAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/EventAsserts.cs @@ -1,6 +1,12 @@ #pragma warning disable CA1034 // Nested types should not be visible #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0039 // Use local function +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace +#pragma warning disable IDE0270 // Use coalesce expression #pragma warning disable IDE0290 // Use primary constructor #if XUNIT_NULLABLE @@ -19,6 +25,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -51,7 +62,10 @@ public static RaisedEvent Raises( Action> detach, Action testCode) { - var raisedEvent = RaisesInternal(attach, detach, testCode) ?? throw RaisesException.ForNoEvent(typeof(T)); + var raisedEvent = RaisesInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesException.ForNoEvent(typeof(T)); if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T))) throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType()); @@ -73,7 +87,10 @@ public static RaisedEvent Raises( Action> detach, Action testCode) { - var raisedEvent = RaisesInternal(attach, detach, testCode) ?? throw RaisesException.ForNoEvent(typeof(T)); + var raisedEvent = RaisesInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesException.ForNoEvent(typeof(T)); if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T))) throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType()); @@ -101,7 +118,10 @@ public static RaisedEvent Raises( Action detach, Action testCode) { - var raisedEvent = RaisesInternal(handler, attach, detach, testCode) ?? throw RaisesException.ForNoEvent(typeof(T)); + var raisedEvent = RaisesInternal(handler, attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesException.ForNoEvent(typeof(T)); if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T))) throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType()); @@ -120,8 +140,15 @@ public static RaisedEvent Raises( public static RaisedEvent RaisesAny( Action attach, Action detach, - Action testCode) => - RaisesInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(EventArgs)); + Action testCode) + { + var raisedEvent = RaisesInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(EventArgs)); + + return raisedEvent; + } /// /// Verifies that an event with the exact or a derived event args is raised. @@ -135,8 +162,15 @@ public static RaisedEvent RaisesAny( public static RaisedEvent RaisesAny( Action> attach, Action> detach, - Action testCode) => - RaisesInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(T)); + Action testCode) + { + var raisedEvent = RaisesInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(T)); + + return raisedEvent; + } /// /// Verifies that an event with the exact or a derived event args is raised. @@ -150,8 +184,15 @@ public static RaisedEvent RaisesAny( public static RaisedEvent RaisesAny( Action> attach, Action> detach, - Action testCode) => - RaisesInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(T)); + Action testCode) + { + var raisedEvent = RaisesInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(T)); + + return raisedEvent; + } /// /// Verifies that an event is raised. @@ -164,8 +205,15 @@ public static RaisedEvent RaisesAny( public static async Task> RaisesAnyAsync( Action attach, Action detach, - Func testCode) => - await RaisesAsyncInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(EventArgs)); + Func testCode) + { + var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(EventArgs)); + + return raisedEvent; + } /// /// Verifies that an event with the exact or a derived event args is raised. @@ -179,8 +227,15 @@ public static async Task> RaisesAnyAsync( public static async Task> RaisesAnyAsync( Action> attach, Action> detach, - Func testCode) => - await RaisesAsyncInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(T)); + Func testCode) + { + var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(T)); + + return raisedEvent; + } /// /// Verifies that an event with the exact or a derived event args is raised. @@ -194,8 +249,15 @@ public static async Task> RaisesAnyAsync( public static async Task> RaisesAnyAsync( Action> attach, Action> detach, - Func testCode) => - await RaisesAsyncInternal(attach, detach, testCode) ?? throw RaisesAnyException.ForNoEvent(typeof(T)); + Func testCode) + { + var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesAnyException.ForNoEvent(typeof(T)); + + return raisedEvent; + } /// /// Verifies that an event is raised. @@ -228,7 +290,10 @@ public static async Task> RaisesAsync( Action> detach, Func testCode) { - var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode) ?? throw RaisesException.ForNoEvent(typeof(T)); + var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesException.ForNoEvent(typeof(T)); if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T))) throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType()); @@ -250,7 +315,10 @@ public static async Task> RaisesAsync( Action> detach, Func testCode) { - var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode) ?? throw RaisesException.ForNoEvent(typeof(T)); + var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode); + + if (raisedEvent == null) + throw RaisesException.ForNoEvent(typeof(T)); if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T))) throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType()); @@ -270,7 +338,7 @@ static bool RaisesInternal( GuardArgumentNotNull(nameof(testCode), testCode); var result = false; - void handler() => result = true; + Action handler = () => result = true; attach(handler); testCode(); @@ -293,12 +361,10 @@ static RaisedEvent RaisesInternal( var raisedEvent = default(RaisedEvent); #if XUNIT_NULLABLE - void handler(object? s, EventArgs args) => + void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent(s, args); #else - void handler(object s, EventArgs args) => + EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent(s, args); #endif - raisedEvent = new RaisedEvent(s, args); - attach(handler); testCode(); detach(handler); @@ -315,7 +381,7 @@ static RaisedEvent RaisesInternal( Action testCode) { var raisedEvent = default(RaisedEvent); - void handler(T args) => raisedEvent = new RaisedEvent(args); + Action handler = (T args) => raisedEvent = new RaisedEvent(args); return RaisesInternal( () => raisedEvent, @@ -335,12 +401,10 @@ static RaisedEvent RaisesInternal( { var raisedEvent = default(RaisedEvent); #if XUNIT_NULLABLE - void handler(object? s, T args) => + void handler(object? s, T args) => raisedEvent = new RaisedEvent(s, args); #else - void handler(object s, T args) => + EventHandler handler = (object s, T args) => raisedEvent = new RaisedEvent(s, args); #endif - raisedEvent = new RaisedEvent(s, args); - return RaisesInternal( () => raisedEvent, () => attach(handler), @@ -380,7 +444,7 @@ static async Task RaisesAsyncInternal( GuardArgumentNotNull(nameof(testCode), testCode); var result = false; - void handler() => result = true; + Action handler = () => result = true; attach(handler); await testCode(); @@ -403,12 +467,10 @@ static async Task> RaisesAsyncInternal( var raisedEvent = default(RaisedEvent); #if XUNIT_NULLABLE - void handler(object? s, EventArgs args) => + void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent(s, args); #else - void handler(object s, EventArgs args) => + EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent(s, args); #endif - raisedEvent = new RaisedEvent(s, args); - attach(handler); await testCode(); detach(handler); @@ -429,7 +491,7 @@ static async Task> RaisesAsyncInternal( GuardArgumentNotNull(nameof(testCode), testCode); var raisedEvent = default(RaisedEvent); - void handler(T args) => raisedEvent = new RaisedEvent(args); + Action handler = (T args) => raisedEvent = new RaisedEvent(args); attach(handler); await testCode(); @@ -452,155 +514,25 @@ static async Task> RaisesAsyncInternal( var raisedEvent = default(RaisedEvent); #if XUNIT_NULLABLE - void handler(object? s, T args) => + void handler(object? s, T args) => raisedEvent = new RaisedEvent(s, args); #else - void handler(object s, T args) => + EventHandler handler = (object s, T args) => raisedEvent = new RaisedEvent(s, args); #endif - raisedEvent = new RaisedEvent(s, args); - attach(handler); await testCode(); detach(handler); return raisedEvent; } - /// - /// Verifies that an event is not raised. - /// - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static void NotRaisedAny( - Action attach, - Action detach, - Action testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - - if (RaisesInternal(attach, detach, testCode)) - throw NotRaisesException.ForUnexpectedEvent(); - } - - /// - /// Verifies that an event with the exact or a derived event args is not raised. - /// - /// The type of the event arguments to expect - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static void NotRaisedAny( - Action> attach, - Action> detach, - Action testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - - if (RaisesInternal(attach, detach, testCode) != null) - throw NotRaisesException.ForUnexpectedEvent(typeof(T)); - } - - /// - /// Verifies that an event with the exact or a derived event args is not raised. - /// - /// The type of the event arguments to expect - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static void NotRaisedAny( - Action> attach, - Action> detach, - Action testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - - if (RaisesInternal(attach, detach, testCode) != null) - throw NotRaisesException.ForUnexpectedEvent(typeof(T)); - } - - /// - /// Verifies that an event is not raised. - /// - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static async Task NotRaisedAnyAsync( - Action attach, - Action detach, - Func testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - - if (await RaisesAsyncInternal(attach, detach, testCode)) - throw NotRaisesException.ForUnexpectedEvent(); - } - - /// - /// Verifies that an event with the exact or a derived event args is not raised. - /// - /// The type of the event arguments to expect - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static async Task NotRaisedAnyAsync( - Action> attach, - Action> detach, - Func testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - if (await RaisesAsyncInternal(attach, detach, testCode) != null) - throw NotRaisesException.ForUnexpectedEvent(typeof(T)); - } - - /// - /// Verifies that an event with the exact or a derived event args is not raised. - /// - /// The type of the event arguments to expect - /// Code to attach the event handler - /// Code to detach the event handler - /// A delegate to the code to be tested - /// Thrown when an unexpected event was raised. - public static async Task NotRaisedAnyAsync( - Action> attach, - Action> detach, - Func testCode) - { - GuardArgumentNotNull(nameof(attach), attach); - GuardArgumentNotNull(nameof(detach), detach); - GuardArgumentNotNull(nameof(testCode), testCode); - - if (await RaisesAsyncInternal(attach, detach, testCode) != null) - throw NotRaisesException.ForUnexpectedEvent(typeof(T)); - } - /// /// Represents a raised event after the fact. /// /// The type of the event arguments. -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - class RaisedEvent + public class RaisedEvent { /// /// The sender of the event. When the event is recorded via rather - /// than , this value will always be , + /// than , this value will always be null, /// since there is no sender value when using actions. /// #if XUNIT_NULLABLE diff --git a/src/Microsoft.DotNet.XUnitAssert/src/ExceptionAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/ExceptionAsserts.cs index e2608f30804..f75baf16f90 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/ExceptionAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/ExceptionAsserts.cs @@ -1,68 +1,62 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 #pragma warning disable CS8604 -#pragma warning disable CS8625 #endif using System; using System.ComponentModel; +using System.Reflection; using System.Threading.Tasks; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// The exception that was thrown, when successful - public static Exception Throws( + static Exception Throws( Type exceptionType, - Action testCode) => - ThrowsImpl(exceptionType, RecordException(testCode)); - - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static Exception Throws( - Type exceptionType, - Action testCode, #if XUNIT_NULLABLE - Func inspector) => + Exception? exception) #else - Func inspector) => + Exception exception) #endif - ThrowsImpl(exceptionType, RecordException(testCode), inspector); + { + GuardArgumentNotNull(nameof(exceptionType), exceptionType); + + if (exception == null) + throw ThrowsException.ForNoException(exceptionType); + + if (!exceptionType.Equals(exception.GetType())) + throw ThrowsException.ForIncorrectExceptionType(exceptionType, exception); + + return exception; + } /// /// Verifies that the exact exception is thrown (and not a derived exception type). - /// Generally used to test property accessors. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful public static Exception Throws( Type exceptionType, -#if XUNIT_NULLABLE - Func testCode) => -#else - Func testCode) => -#endif - ThrowsImpl(exceptionType, RecordException(testCode, nameof(ThrowsAsync))); + Action testCode) => + Throws(exceptionType, RecordException(testCode)); /// /// Verifies that the exact exception is thrown (and not a derived exception type). @@ -70,19 +64,15 @@ public static Exception Throws( /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. /// The exception that was thrown, when successful public static Exception Throws( Type exceptionType, #if XUNIT_NULLABLE - Func testCode, - Func inspector) => + Func testCode) => #else - Func testCode, - Func inspector) => + Func testCode) => #endif - ThrowsImpl(exceptionType, RecordException(testCode, nameof(ThrowsAsync)), inspector); + Throws(exceptionType, RecordException(testCode, nameof(ThrowsAsync))); /// [EditorBrowsable(EditorBrowsableState.Never)] @@ -91,22 +81,7 @@ public static Exception Throws( Type exceptionType, Func testCode) { - throw new NotSupportedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); - } - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("You must call Assert.ThrowsAsync (and await the result) when testing async code.", true)] - public static Exception Throws( - Type exceptionType, - Func testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - { - throw new NotSupportedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); } /// @@ -117,25 +92,9 @@ public static Exception Throws( /// The exception that was thrown, when successful public static T Throws(Action testCode) where T : Exception => - (T)ThrowsImpl(typeof(T), RecordException(testCode)); - - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static T Throws( - Action testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - where T : Exception => - (T)ThrowsImpl(typeof(T), RecordException(testCode), ex => inspector((T)ex)); +#pragma warning disable xUnit2015 + (T)Throws(typeof(T), RecordException(testCode)); +#pragma warning restore xUnit2015 /// /// Verifies that the exact exception is thrown (and not a derived exception type). @@ -150,27 +109,9 @@ public static T Throws(Func testCode) public static T Throws(Func testCode) #endif where T : Exception => - (T)ThrowsImpl(typeof(T), RecordException(testCode, nameof(ThrowsAsync))); - - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// Generally used to test property accessors. - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static T Throws( -#if XUNIT_NULLABLE - Func testCode, - Func inspector) -#else - Func testCode, - Func inspector) -#endif - where T : Exception => - (T)ThrowsImpl(typeof(T), RecordException(testCode, nameof(ThrowsAsync)), ex => inspector((T)ex)); +#pragma warning disable xUnit2015 + (T)Throws(typeof(T), RecordException(testCode, nameof(ThrowsAsync))); +#pragma warning restore xUnit2015 /// [EditorBrowsable(EditorBrowsableState.Never)] @@ -178,22 +119,7 @@ public static T Throws( public static T Throws(Func testCode) where T : Exception { - throw new NotSupportedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); - } - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("You must call Assert.ThrowsAsync (and await the result) when testing async code.", true)] - public static T Throws( - Func testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - where T : Exception - { - throw new NotSupportedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); } /// @@ -257,51 +183,37 @@ public static T Throws( Func testCode) where T : ArgumentException { - throw new NotSupportedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.ThrowsAsync (and await the result) when testing async code."); } - /// - /// Verifies that the exact exception or a derived exception type is thrown. - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// The exception that was thrown, when successful - public static T ThrowsAny(Action testCode) - where T : Exception => - (T)ThrowsAnyImpl(typeof(T), RecordException(testCode)); - - /// - /// Verifies that the exact exception or a derived exception type is thrown. - /// - /// The type of the exception expected to be thrown - /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static T ThrowsAny( - Action testCode, + static Exception ThrowsAny( + Type exceptionType, #if XUNIT_NULLABLE - Func inspector) + Exception? exception) #else - Func inspector) + Exception exception) #endif - where T : Exception => - (T)ThrowsAnyImpl(typeof(T), RecordException(testCode), ex => inspector((T)ex)); + { + GuardArgumentNotNull(nameof(exceptionType), exceptionType); + + if (exception == null) + throw ThrowsAnyException.ForNoException(exceptionType); + + if (!exceptionType.GetTypeInfo().IsAssignableFrom(exception.GetType().GetTypeInfo())) + throw ThrowsAnyException.ForIncorrectExceptionType(exceptionType, exception); + + return exception; + } /// /// Verifies that the exact exception or a derived exception type is thrown. - /// Generally used to test property accessors. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful -#if XUNIT_NULLABLE - public static T ThrowsAny(Func testCode) -#else - public static T ThrowsAny(Func testCode) -#endif + public static T ThrowsAny(Action testCode) where T : Exception => - (T)ThrowsAnyImpl(typeof(T), RecordException(testCode, nameof(ThrowsAnyAsync))); + (T)ThrowsAny(typeof(T), RecordException(testCode)); /// /// Verifies that the exact exception or a derived exception type is thrown. @@ -309,19 +221,14 @@ public static T ThrowsAny(Func testCode) /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. /// The exception that was thrown, when successful - public static T ThrowsAny( #if XUNIT_NULLABLE - Func testCode, - Func inspector) + public static T ThrowsAny(Func testCode) #else - Func testCode, - Func inspector) + public static T ThrowsAny(Func testCode) #endif where T : Exception => - (T)ThrowsAnyImpl(typeof(T), RecordException(testCode, nameof(ThrowsAnyAsync)), ex => inspector((T)ex)); + (T)ThrowsAny(typeof(T), RecordException(testCode, nameof(ThrowsAnyAsync))); /// [EditorBrowsable(EditorBrowsableState.Never)] @@ -329,22 +236,7 @@ public static T ThrowsAny( public static T ThrowsAny(Func testCode) where T : Exception { - throw new NotSupportedException("You must call Assert.ThrowsAnyAsync (and await the result) when testing async code."); - } - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("You must call Assert.ThrowsAnyAsync (and await the result) when testing async code.", true)] - public static T ThrowsAny( - Func testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - where T : Exception - { - throw new NotSupportedException("You must call Assert.ThrowsAnyAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.ThrowsAnyAsync (and await the result) when testing async code."); } /// @@ -355,25 +247,7 @@ public static T ThrowsAny( /// The exception that was thrown, when successful public static async Task ThrowsAnyAsync(Func testCode) where T : Exception => - (T)ThrowsAnyImpl(typeof(T), await RecordExceptionAsync(testCode)); - - /// - /// Verifies that the exact exception or a derived exception type is thrown. - /// - /// The type of the exception expected to be thrown - /// A delegate to the task to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static async Task ThrowsAnyAsync( - Func testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - where T : Exception => - (T)ThrowsAnyImpl(typeof(T), await RecordExceptionAsync(testCode), ex => inspector((T)ex)); + (T)ThrowsAny(typeof(T), await RecordExceptionAsync(testCode)); /// /// Verifies that the exact exception is thrown (and not a derived exception type). @@ -384,25 +258,7 @@ public static async Task ThrowsAnyAsync( public static async Task ThrowsAsync( Type exceptionType, Func testCode) => - ThrowsImpl(exceptionType, await RecordExceptionAsync(testCode)); - - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// - /// The type of the exception expected to be thrown - /// A delegate to the task to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static async Task ThrowsAsync( - Type exceptionType, - Func testCode, -#if XUNIT_NULLABLE - Func inspector) => -#else - Func inspector) => -#endif - ThrowsImpl(exceptionType, await RecordExceptionAsync(testCode), inspector); + Throws(exceptionType, await RecordExceptionAsync(testCode)); /// /// Verifies that the exact exception is thrown (and not a derived exception type). @@ -412,25 +268,9 @@ public static async Task ThrowsAsync( /// The exception that was thrown, when successful public static async Task ThrowsAsync(Func testCode) where T : Exception => - (T)ThrowsImpl(typeof(T), await RecordExceptionAsync(testCode)); - - /// - /// Verifies that the exact exception is thrown (and not a derived exception type). - /// - /// The type of the exception expected to be thrown - /// A delegate to the task to be tested - /// A function which inspects the exception to determine if it's - /// valid or not. Returns if the exception is valid, or a message if it's not. - /// The exception that was thrown, when successful - public static async Task ThrowsAsync( - Func testCode, -#if XUNIT_NULLABLE - Func inspector) -#else - Func inspector) -#endif - where T : Exception => - (T)ThrowsImpl(typeof(T), await RecordExceptionAsync(testCode), ex => inspector((T)ex)); +#pragma warning disable xUnit2015 + (T)Throws(typeof(T), await RecordExceptionAsync(testCode)); +#pragma warning restore xUnit2015 /// /// Verifies that the exact exception is thrown (and not a derived exception type), where the exception @@ -455,73 +295,5 @@ public static async Task ThrowsAsync( return ex; } - - static Exception ThrowsAnyImpl( - Type exceptionType, -#if XUNIT_NULLABLE - Exception? exception, - Func? inspector = null) -#else - Exception exception, - Func inspector = null) -#endif - { - GuardArgumentNotNull(nameof(exceptionType), exceptionType); - - if (exception == null) - throw ThrowsAnyException.ForNoException(exceptionType); - - if (!exceptionType.IsAssignableFrom(exception.GetType())) - throw ThrowsAnyException.ForIncorrectExceptionType(exceptionType, exception); - - var message = default(string); - try - { - message = inspector?.Invoke(exception); - } - catch (Exception ex) - { - throw ThrowsAnyException.ForInspectorFailure("Exception thrown by inspector", ex); - } - - if (message != null) - throw ThrowsAnyException.ForInspectorFailure(message); - - return exception; - } - - static Exception ThrowsImpl( - Type exceptionType, -#if XUNIT_NULLABLE - Exception? exception, - Func? inspector = null) -#else - Exception exception, - Func inspector = null) -#endif - { - GuardArgumentNotNull(nameof(exceptionType), exceptionType); - - if (exception == null) - throw ThrowsException.ForNoException(exceptionType); - - if (!exceptionType.Equals(exception.GetType())) - throw ThrowsException.ForIncorrectExceptionType(exceptionType, exception); - - var message = default(string); - try - { - message = inspector?.Invoke(exception); - } - catch (Exception ex) - { - throw ThrowsException.ForInspectorFailure("Exception thrown by inspector", ex); - } - - if (message != null) - throw ThrowsException.ForInspectorFailure(message); - - return exception; - } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/FailAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/FailAsserts.cs index 04bd4c6c3f1..29232d009d4 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/FailAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/FailAsserts.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -15,6 +17,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -23,10 +30,12 @@ partial class Assert /// The optional failure message #if XUNIT_NULLABLE [DoesNotReturn] - public static void Fail(string? message = null) => + public static void Fail(string? message = null) #else - public static void Fail(string message = null) => + public static void Fail(string message = null) #endif + { throw FailException.ForFailure(message); + } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Guards.cs b/src/Microsoft.DotNet.XUnitAssert/src/Guards.cs index 614cfcd9f56..a756851d9a1 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Guards.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Guards.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -12,6 +14,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// diff --git a/src/Microsoft.DotNet.XUnitAssert/src/IdentityAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/IdentityAsserts.cs index c96e4891231..d67214394e3 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/IdentityAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/IdentityAsserts.cs @@ -1,4 +1,5 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -8,6 +9,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// diff --git a/src/Microsoft.DotNet.XUnitAssert/src/MemoryAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/MemoryAsserts.cs index 1eae48a4d1e..a8c49004bb6 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/MemoryAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/MemoryAsserts.cs @@ -1,14 +1,25 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace + +#if XUNIT_SPAN #if XUNIT_NULLABLE #nullable enable #endif using System; +using System.Diagnostics.CodeAnalysis; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { // While there is an implicit conversion operator from Memory to ReadOnlyMemory, the @@ -24,7 +35,12 @@ partial class Assert /// The sub-Memory expected to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is not present inside the Memory - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Memory expectedSubMemory, Memory actualMemory) where T : IEquatable => @@ -36,7 +52,12 @@ public static void Contains( /// The sub-Memory expected to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is not present inside the Memory - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Memory expectedSubMemory, ReadOnlyMemory actualMemory) where T : IEquatable => @@ -48,7 +69,12 @@ public static void Contains( /// The sub-Memory expected to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is not present inside the Memory - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlyMemory expectedSubMemory, Memory actualMemory) where T : IEquatable => @@ -60,7 +86,12 @@ public static void Contains( /// The sub-Memory expected to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is not present inside the Memory - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlyMemory expectedSubMemory, ReadOnlyMemory actualMemory) where T : IEquatable @@ -80,7 +111,12 @@ public static void Contains( /// The sub-Memory expected not to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is present inside the Memory - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Memory expectedSubMemory, Memory actualMemory) where T : IEquatable => @@ -92,7 +128,12 @@ public static void DoesNotContain( /// The sub-Memory expected not to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is present inside the Memory - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Memory expectedSubMemory, ReadOnlyMemory actualMemory) where T : IEquatable => @@ -104,7 +145,12 @@ public static void DoesNotContain( /// The sub-Memory expected not to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is present inside the Memory - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlyMemory expectedSubMemory, Memory actualMemory) where T : IEquatable => @@ -116,7 +162,12 @@ public static void DoesNotContain( /// The sub-Memory expected not to be in the Memory /// The Memory to be inspected /// Thrown when the sub-Memory is present inside the Memory - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlyMemory expectedSubMemory, ReadOnlyMemory actualMemory) where T : IEquatable @@ -129,8 +180,9 @@ public static void DoesNotContain( if (idx > -1) { + int? failurePointerIndent; var formattedExpected = CollectionTracker.FormatStart(expectedSpan); - var formattedActual = CollectionTracker.FormatIndexedMismatch(actualSpan, idx, out var failurePointerIndent); + var formattedActual = CollectionTracker.FormatIndexedMismatch(actualSpan, idx, out failurePointerIndent); throw DoesNotContainException.ForSubMemoryFound(formattedExpected, idx, failurePointerIndent, formattedActual); } @@ -186,7 +238,9 @@ public static void Equal( GuardArgumentNotNull(nameof(expectedMemory), expectedMemory); if (!expectedMemory.Span.SequenceEqual(actualMemory.Span)) - Equal(expectedMemory.Span.ToArray(), actualMemory.Span.ToArray(), new AssertEqualityComparer()); + Equal(expectedMemory.Span.ToArray(), actualMemory.Span.ToArray()); } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Microsoft.DotNet.XUnitAssert.csproj b/src/Microsoft.DotNet.XUnitAssert/src/Microsoft.DotNet.XUnitAssert.csproj index 123e73b8ada..cb5aed4c476 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Microsoft.DotNet.XUnitAssert.csproj +++ b/src/Microsoft.DotNet.XUnitAssert/src/Microsoft.DotNet.XUnitAssert.csproj @@ -18,7 +18,7 @@ 0024000004800000940000000602000000240000525341310004000001000100252e049addea87f30f99d6ed8ebc189bc05b8c9168765df08f86e0214471dc89844f1f4b9c4a26894d029465848771bc758fed20371280eda223a9f64ae05f48b320e4f0e20c4282dd701e985711bc33b5b9e6ab3fafab6cb78e220ee2b8e1550573e03f8ad665c051c63fbc5359d495d4b1c61024ef76ed9c1ebb471fed59c9 8d05b1bb7a6fdb6c - $(XUnitV3Version.Split('-')[0]) + $(XUnitVersion.Split('-')[0]) diff --git a/src/Microsoft.DotNet.XUnitAssert/src/MultipleAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/MultipleAsserts.cs index 82dd4960f35..06a393b75bc 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/MultipleAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/MultipleAsserts.cs @@ -1,6 +1,6 @@ #pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable CA1052 // Static holder types should be static -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -9,11 +9,15 @@ using System; using System.Collections.Generic; using System.Runtime.ExceptionServices; -using System.Threading.Tasks; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -45,35 +49,5 @@ public static void Multiple(params Action[] checks) throw MultipleException.ForFailures(exceptions); } - - /// - /// Asynchronously runs multiple checks, collecting the exceptions from each one, and then bundles all failures - /// up into a single assertion failure. - /// - /// The individual assertions to run, as async actions. - public static async Task MultipleAsync(params Func[] checks) - { - if (checks == null || checks.Length == 0) - return; - - var exceptions = new List(); - - foreach (var check in checks) - try - { - await check(); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - - if (exceptions.Count == 0) - return; - if (exceptions.Count == 1) - ExceptionDispatchInfo.Capture(exceptions[0]).Throw(); - - throw MultipleException.ForFailuresAsync(exceptions); - } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/NullAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/NullAsserts.cs index 28d7387beb2..f34e4f77220 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/NullAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/NullAsserts.cs @@ -1,14 +1,12 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable #endif -#if XUNIT_POINTERS -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type -#endif - using Xunit.Sdk; #if XUNIT_NULLABLE @@ -17,6 +15,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -34,31 +37,12 @@ public static void NotNull(object @object) throw NotNullException.ForNullValue(); } -#if XUNIT_POINTERS - - /// - /// Verifies that an unmanaged pointer is not null. - /// - /// The type of the pointer - /// The pointer value -#if XUNIT_NULLABLE - public static unsafe void NotNull([NotNull] T* value) -#else - public static unsafe void NotNull(T* value) -#endif - { - if (value == null) - throw NotNullException.ForNullPointer(typeof(T)); - } - -#endif // XUNIT_POINTERS - /// /// Verifies that a nullable struct value is not null. /// /// The type of the struct /// The value to e validated - /// The non- value + /// The non-null value /// Thrown when the value is null #if XUNIT_NULLABLE public static T NotNull([NotNull] T? value) @@ -93,30 +77,16 @@ public static void Null(object @object) /// /// The value to be inspected /// Thrown when the value is not null - public static void Null(T? value) + public static void Null<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>(T? value) where T : struct { if (value.HasValue) throw NullException.ForNonNullStruct(typeof(T), value); } - -#if XUNIT_POINTERS - - /// - /// Verifies that an unmanaged pointer is null. - /// - /// The type of the pointer - /// The pointer value -#if XUNIT_NULLABLE - public static unsafe void Null([NotNull] T* value) -#else - public static unsafe void Null(T* value) -#endif - { - if (value != null) - throw NullException.ForNonNullPointer(typeof(T)); - } - -#endif // XUNIT_POINTERS } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/PropertyAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/PropertyAsserts.cs index 1c9c3b12a52..272248cdcb1 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/PropertyAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/PropertyAsserts.cs @@ -1,12 +1,13 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1720 // Identifier contains type name #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0039 // Use local function +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8622 #endif using System; @@ -16,6 +17,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -37,11 +43,7 @@ public static void PropertyChanged( var propertyChangeHappened = false; -#if XUNIT_NULLABLE - void handler(object? sender, PropertyChangedEventArgs args) => -#else - void handler(object sender, PropertyChangedEventArgs args) => -#endif + PropertyChangedEventHandler handler = (sender, args) => propertyChangeHappened = propertyChangeHappened || string.IsNullOrEmpty(args.PropertyName) || propertyName.Equals(args.PropertyName, StringComparison.OrdinalIgnoreCase); @object.PropertyChanged += handler; @@ -66,7 +68,7 @@ public static void PropertyChanged( string propertyName, Func testCode) { - throw new NotSupportedException("You must call Assert.PropertyChangedAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.PropertyChangedAsync (and await the result) when testing async code."); } /// @@ -88,11 +90,7 @@ public static async Task PropertyChangedAsync( var propertyChangeHappened = false; -#if XUNIT_NULLABLE - void handler(object? sender, PropertyChangedEventArgs args) => -#else - void handler(object sender, PropertyChangedEventArgs args) => -#endif + PropertyChangedEventHandler handler = (sender, args) => propertyChangeHappened = propertyChangeHappened || string.IsNullOrEmpty(args.PropertyName) || propertyName.Equals(args.PropertyName, StringComparison.OrdinalIgnoreCase); @object.PropertyChanged += handler; diff --git a/src/Microsoft.DotNet.XUnitAssert/src/README.md b/src/Microsoft.DotNet.XUnitAssert/src/README.md index f2fe5c04532..dd97568ac01 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/README.md +++ b/src/Microsoft.DotNet.XUnitAssert/src/README.md @@ -1,17 +1,8 @@ # About This Project -This project contains the xUnit.net assertion library source code, intended to be used as a Git submodule (or via the `xunit.v3.assert.source` NuGet package). +This project contains the xUnit.net assertion library source code, intended to be used as a Git submodule. -Code here is built with `netstandard2.0` and `net8.0` within xUnit.net v3. At a minimum the code needs to be able to support `net472` and later for .NET Framework, and `net8.0` and later for .NET. The minimum (and default) C# version is 7.3, unless specific features require targeting later compilers. Additionally, we compile with the full Roslyn analyzer set enabled when building for v3, so you will frequently see conditional code and/or rules being disabled as appropriate. These constraints are supported by the [suggested contribution workflow](#suggested-contribution-workflow), which aims to make it easy to know when you've used unavailable features. - -This code includes assertions for immutable collections as well as the `Span` and `Memory` family of types. If you experience compiler errors related to these types, you may need to add references to the following NuGet packages: - -```xml - - - - -``` +Code here is built with several target frameworks: `netstandard1.1` and `net6.0` for xUnit.net v2; and `netstandard2.0` and `net6.0` for xUnit.net v3. At a minimum the code needs to be able to support `net452` and later for .NET Framework, `netcoreapp1.0` and later for .NET Core, and `net5.0` and later for .NET. The minimum (and default) C# version is 6.0, unless specific features require targeting later compilers. Additionally, we compile with the full Roslyn analyzer set enabled when building for v3, so you will frequently see conditional code and/or rules being disabled as appropriate. These constraints are supported by the [suggested contribution workflow](#suggested-contribution-workflow), which aims to make it easy to know when you've used unavailable features. > _**Note:** If your PR requires a newer target framework or a newer C# language to build, please start a discussion in the related issue(s) before starting any work. PRs that arbitrarily use newer target frameworks and/or newer C# language features will need to be fixed; you may be asked to fix them, or we may fix them for you, or we may decline the PR (at our discretion)._ @@ -21,27 +12,23 @@ To open an issue for this project, please visit the [core xUnit.net project issu Whether you are using this repository via Git submodule or via the [source-based NuGet package](https://www.nuget.org/packages/xunit.assert.source), the following pre-processor directives can be used to influence the code contained in this repository: -### `XUNIT_AOT` (min: C# 13, .NET 9) - -Define this compilation symbol to use assertions that are compatible with Native AOT. +### `XUNIT_IMMUTABLE_COLLECTIONS` (min: C# 6.0, xUnit.net v2) -_Note: you must add_ `true` _to the property group of your project file._ +There are assertions that target immutable collections. If you are using a target framework that is compatible with [`System.Collections.Immutable`](https://www.nuget.org/packages/System.Collections.Immutable), you should define `XUNIT_IMMUTABLE_COLLECTIONS` to enable the additional versions of those assertions that will consume immutable collections. -### `XUNIT_NULLABLE` (min: C# 9.0) +### `XUNIT_NULLABLE` (min: C# 9.0, xUnit.net v2) -Define this compilation symbol to opt-in to support for nullable reference types and to enable the relevant nullability analysis annotations on method signatures. +Projects that consume this repository as source, which wish to use nullable reference type annotations should define the `XUNIT_NULLABLE` compilation symbol to opt-in to the relevant nullability analysis annotations on method signatures. -_Note: you must add_ `enable` _to the property group of your project file._ +### `XUNIT_SKIP` (min: C# 10.0, xUnit.net v3) -### `XUNIT_OVERLOAD_RESOLUTION_PRIORITY` (min: C# 13.0) +The Skip family of assertions (like `Assert.Skip`) require xUnit.net v3. Define this to enable the Skip assertions. -Define this compilation symbol to opt-in to decorating assertion functions with [`[OverloadResolutionPriority]`](https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.overloadresolutionpriorityattribute) to help the compiler resolve competing ambiguous overloads. +> _**Note**: If you enable try to use it from xUnit.net v2, the test will show up as failed rather than skipped. Runtime support in the core library is required to make this feature work properly, which is why it's not supported for v2._ -### `XUNIT_POINTERS` +### `XUNIT_SPAN` (min: C# 6.0, xUnit.net v2) -Define this compilation symbol to enable support for assertions related to unsafe pointers. - -_Note: you must add_ `true` _to the property group of your project file._ +There are optimized versions of `Assert.Equal` for arrays which use `Span`- and/or `Memory`-based comparison options. If you are using a target framework that supports `Span` and `Memory`, you should define `XUNIT_SPAN` to enable these new assertions. You may need to add a reference to [`System.Memory`](https://www.nuget.org/packages/System.Memory) for older target frameworks. ### `XUNIT_VISIBILITY_INTERNAL` @@ -108,32 +95,8 @@ _Please remember that all PRs require associated unit tests. You may be asked to # About xUnit.net -xUnit.net is a free, open source, community-focused unit testing tool for C#, F#, and Visual Basic. - -xUnit.net works with the [.NET SDK](https://dotnet.microsoft.com/download) command line tools, [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/), [JetBrains Rider](https://www.jetbrains.com/rider/), [NCrunch](https://www.ncrunch.net/), and any development environment compatible with [Microsoft Testing Platform](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-intro) (xUnit.net v3) or [VSTest](https://github.com/microsoft/vstest) (all versions of xUnit.net). +[](https://dotnetfoundation.org/projects/project-detail/xunit) -xUnit.net is part of the [.NET Foundation](https://www.dotnetfoundation.org/) and operates under their [code of conduct](https://www.dotnetfoundation.org/code-of-conduct). It is licensed under [Apache 2](https://opensource.org/licenses/Apache-2.0) (an OSI approved license). The project is [governed](https://xunit.net/governance) by a Project Lead. +xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages. xUnit.net works with ReSharper, CodeRush, TestDriven.NET and Xamarin. It is part of the [.NET Foundation](https://www.dotnetfoundation.org/), and operates under their [code of conduct](http://www.dotnetfoundation.org/code-of-conduct). It is licensed under [Apache 2](https://opensource.org/licenses/Apache-2.0) (an OSI approved license). For project documentation, please visit the [xUnit.net project home](https://xunit.net/). - -* _New to xUnit.net? Get started with the [.NET SDK](https://xunit.net/docs/getting-started/v3/getting-started)._ -* _Need some help building the source? See [BUILDING.md](https://github.com/xunit/xunit/tree/main/BUILDING.md)._ -* _Want to contribute to the project? See [CONTRIBUTING.md](https://github.com/xunit/.github/tree/main/CONTRIBUTING.md)._ -* _Want to contribute to the assertion library? See the [suggested contribution workflow](https://github.com/xunit/assert.xunit/tree/main/README.md#suggested-contribution-workflow) in the assertion library project, as it is slightly more complex due to code being spread across two GitHub repositories._ - -[![Powered by NDepend](https://raw.github.com/xunit/media/main/powered-by-ndepend-transparent.png)](http://www.ndepend.com/) - -## Latest Builds - -| | Latest stable | Latest CI ([how to use](https://xunit.net/docs/using-ci-builds)) | Build status -| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ -| `xunit.v3` | [![](https://img.shields.io/nuget/v/xunit.v3.svg?logo=nuget)](https://www.nuget.org/packages/xunit.v3) | [![](https://img.shields.io/endpoint.svg?url=https://f.feedz.io/xunit/xunit/shield/xunit.v3/latest&logo=nuget&color=f58142)](https://feedz.io/org/xunit/repository/xunit/packages/xunit.v3) | [![](https://img.shields.io/endpoint.svg?url=https://actions-badge.atrox.dev/xunit/xunit/badge%3Fref%3Dmain&label=build)](https://actions-badge.atrox.dev/xunit/xunit/goto?ref=main) -| `xunit` | [![](https://img.shields.io/nuget/v/xunit.svg?logo=nuget)](https://www.nuget.org/packages/xunit) | [![](https://img.shields.io/endpoint.svg?url=https://f.feedz.io/xunit/xunit/shield/xunit/latest&logo=nuget&color=f58142)](https://feedz.io/org/xunit/repository/xunit/packages/xunit) | [![](https://img.shields.io/endpoint.svg?url=https://actions-badge.atrox.dev/xunit/xunit/badge%3Fref%3Dv2&label=build)](https://actions-badge.atrox.dev/xunit/xunit/goto?ref=v2) -| `xunit.analyzers` | [![](https://img.shields.io/nuget/v/xunit.analyzers.svg?logo=nuget)](https://www.nuget.org/packages/xunit.analyzers) | [![](https://img.shields.io/endpoint.svg?url=https://f.feedz.io/xunit/xunit/shield/xunit.analyzers/latest&logo=nuget&color=f58142)](https://feedz.io/org/xunit/repository/xunit/packages/xunit.analyzers) | [![](https://img.shields.io/endpoint.svg?url=https://actions-badge.atrox.dev/xunit/xunit.analyzers/badge%3Fref%3Dmain&label=build)](https://actions-badge.atrox.dev/xunit/xunit.analyzers/goto?ref=main) -| `xunit.runner.visualstudio` | [![](https://img.shields.io/nuget/v/xunit.runner.visualstudio.svg?logo=nuget)](https://www.nuget.org/packages/xunit.runner.visualstudio) | [![](https://img.shields.io/endpoint.svg?url=https://f.feedz.io/xunit/xunit/shield/xunit.runner.visualstudio/latest&logo=nuget&color=f58142)](https://feedz.io/org/xunit/repository/xunit/packages/xunit.runner.visualstudio) | [![](https://img.shields.io/endpoint.svg?url=https://actions-badge.atrox.dev/xunit/visualstudio.xunit/badge%3Fref%3Dmain&label=build)](https://actions-badge.atrox.dev/xunit/visualstudio.xunit/goto?ref=main) - -*For complete CI package lists, please visit the [feedz.io package search](https://feedz.io/org/xunit/repository/xunit/search). A free login is required.* - -## Sponsors - -Help support this project by becoming a sponsor through [GitHub Sponsors](https://github.com/sponsors/xunit). diff --git a/src/Microsoft.DotNet.XUnitAssert/src/RangeAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/RangeAsserts.cs index d3928345044..eb2c05d109f 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/RangeAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/RangeAsserts.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -13,6 +15,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Record.cs b/src/Microsoft.DotNet.XUnitAssert/src/Record.cs index 5ca5411d0ab..edf2abccd92 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Record.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Record.cs @@ -1,7 +1,11 @@ #pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0059 // Unnecessary assignment of a value +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -17,26 +21,18 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { - /// - /// The contract for exceptions which indicate that something should be skipped rather than - /// failed is that exception message should start with this, and that any text following this - /// will be treated as the skip reason (for example, "$XunitDynamicSkip$This code can only run - /// on Linux") will result in a skipped test with the reason of "This code can only run - /// on Linux". - /// - const string DynamicSkipToken = "$XunitDynamicSkip$"; - /// /// Records any exception which is thrown by the given code. /// /// The code which may thrown an exception. /// Returns the exception that was thrown by the code; null, otherwise. - /// - /// If the thrown exception is determined to be a "skip exception", it's not recorded, but - /// instead is allowed to escape this function uncaught. - /// #if XUNIT_NULLABLE protected static Exception? RecordException(Action testCode) #else @@ -52,9 +48,6 @@ protected static Exception RecordException(Action testCode) } catch (Exception ex) { - if (ex.Message?.StartsWith(DynamicSkipToken, StringComparison.Ordinal) == true) - throw; - return ex; } } @@ -67,10 +60,6 @@ protected static Exception RecordException(Action testCode) /// The name of the async method the user should've called if they accidentally /// passed in an async function /// Returns the exception that was thrown by the code; null, otherwise. - /// - /// If the thrown exception is determined to be a "skip exception", it's not recorded, but - /// instead is allowed to escape this function uncaught. - /// #if XUNIT_NULLABLE protected static Exception? RecordException( Func testCode, @@ -90,20 +79,11 @@ protected static Exception RecordException( } catch (Exception ex) { - if (ex.Message?.StartsWith(DynamicSkipToken, StringComparison.Ordinal) == true) - throw; - return ex; } if (result is Task) - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - "You must call Assert.{0} when testing async code", - asyncMethodName - ) - ); + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "You must call Assert.{0} when testing async code", asyncMethodName)); return null; } @@ -113,7 +93,7 @@ protected static Exception RecordException( [Obsolete("You must call Assert.RecordExceptionAsync (and await the result) when testing async code.", true)] protected static Exception RecordException(Func testCode) { - throw new NotSupportedException("You must call Assert.RecordExceptionAsync (and await the result) when testing async code."); + throw new NotImplementedException("You must call Assert.RecordExceptionAsync (and await the result) when testing async code."); } /// @@ -121,10 +101,6 @@ protected static Exception RecordException(Func testCode) /// /// The task which may thrown an exception. /// Returns the exception that was thrown by the code; null, otherwise. - /// - /// If the thrown exception is determined to be a "skip exception", it's not recorded, but - /// instead is allowed to escape this function uncaught. - /// #if XUNIT_NULLABLE protected static async Task RecordExceptionAsync(Func testCode) #else @@ -140,9 +116,6 @@ protected static async Task RecordExceptionAsync(Func testCode) } catch (Exception ex) { - if (ex.Message?.StartsWith(DynamicSkipToken, StringComparison.Ordinal) == true) - throw; - return ex; } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter.cs index 13637138815..ab2370c64c2 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter.cs @@ -1,7 +1,19 @@ #pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable CA1707 // Identifiers should not contain underscores +#pragma warning disable CA1810 // Initialize reference type static fields inline +#pragma warning disable CA1825 // Avoid zero-length array allocations +#pragma warning disable CA2263 // Prefer generic overload when type is known +#pragma warning disable IDE0018 // Inline variable declaration #pragma warning disable IDE0019 // Use pattern matching +#pragma warning disable IDE0038 // Use pattern matching +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0045 // Convert to conditional expression +#pragma warning disable IDE0046 // Convert to conditional expression #pragma warning disable IDE0057 // Use range operator +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0078 // Use pattern matching #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0300 // Simplify collection initialization #if XUNIT_NULLABLE @@ -9,23 +21,25 @@ #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE #pragma warning disable CS8600 +#pragma warning disable CS8601 #pragma warning disable CS8602 #pragma warning disable CS8603 #pragma warning disable CS8604 #pragma warning disable CS8605 +#pragma warning disable CS8618 #pragma warning disable CS8625 #endif using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; -using Xunit.Internal; #if XUNIT_ARGUMENTFORMATTER_PRIVATE namespace Xunit.Internal @@ -41,65 +55,87 @@ namespace Xunit.Sdk #else public #endif - static partial class ArgumentFormatter + static class ArgumentFormatter { - static readonly Lazy maxEnumerableLength = new Lazy( - () => GetEnvironmentValue(EnvironmentVariables.PrintMaxEnumerableLength, EnvironmentVariables.Defaults.PrintMaxEnumerableLength)); - static readonly Lazy maxObjectDepth = new Lazy( - () => GetEnvironmentValue(EnvironmentVariables.PrintMaxObjectDepth, EnvironmentVariables.Defaults.PrintMaxObjectDepth)); - static readonly Lazy maxObjectMemberCount = new Lazy( - () => GetEnvironmentValue(EnvironmentVariables.PrintMaxObjectMemberCount, EnvironmentVariables.Defaults.PrintMaxObjectMemberCount)); - static readonly Lazy maxStringLength = new Lazy( - () => GetEnvironmentValue(EnvironmentVariables.PrintMaxStringLength, EnvironmentVariables.Defaults.PrintMaxStringLength)); - internal static readonly string EllipsisInBrackets = "[" + new string((char)0x00B7, 3) + "]"; - // List of intrinsic types => C# type names - static readonly Dictionary TypeMappings = new Dictionary - { - { typeof(bool), "bool" }, - { typeof(byte), "byte" }, - { typeof(sbyte), "sbyte" }, - { typeof(char), "char" }, - { typeof(decimal), "decimal" }, - { typeof(double), "double" }, - { typeof(float), "float" }, - { typeof(int), "int" }, - { typeof(uint), "uint" }, - { typeof(long), "long" }, - { typeof(ulong), "ulong" }, - { typeof(object), "object" }, - { typeof(short), "short" }, - { typeof(ushort), "ushort" }, - { typeof(string), "string" }, - { typeof(IntPtr), "nint" }, - { typeof(UIntPtr), "nuint" }, - }; - /// - /// Gets the ellipsis value (three middle dots, aka U+00B7). + /// Gets the maximum printing depth, in terms of objects before truncation. /// - public static string Ellipsis { get; } = new string((char)0x00B7, 3); + public const int MAX_DEPTH = 3; /// /// Gets the maximum number of values printed for collections before truncation. /// - public static int MaxEnumerableLength => maxEnumerableLength.Value; + public const int MAX_ENUMERABLE_LENGTH = 5; /// - /// Gets the maximum printing depth, in terms of objects before truncation. + /// Gets the maximum number of items (properties or fields) printed in an object before truncation. /// - public static int MaxObjectDepth => maxObjectDepth.Value; + public const int MAX_OBJECT_ITEM_COUNT = 5; /// - /// Gets the maximum number of items (properties or fields) printed in an object before truncation. + /// Gets the maximum strength length before truncation. /// - public static int MaxObjectMemberCount => maxObjectMemberCount.Value; + public const int MAX_STRING_LENGTH = 50; + + static readonly object[] EmptyObjects = new object[0]; + static readonly Type[] EmptyTypes = new Type[0]; + +#if !XUNIT_AOT +#if XUNIT_NULLABLE + static readonly PropertyInfo? tupleIndexer; + static readonly Type? tupleInterfaceType; + static readonly PropertyInfo? tupleLength; +#else + static readonly PropertyInfo tupleIndexer; + static readonly Type tupleInterfaceType; + static readonly PropertyInfo tupleLength; +#endif +#endif + + // List of intrinsic types => C# type names + static readonly Dictionary TypeMappings = new Dictionary + { + { typeof(bool).GetTypeInfo(), "bool" }, + { typeof(byte).GetTypeInfo(), "byte" }, + { typeof(sbyte).GetTypeInfo(), "sbyte" }, + { typeof(char).GetTypeInfo(), "char" }, + { typeof(decimal).GetTypeInfo(), "decimal" }, + { typeof(double).GetTypeInfo(), "double" }, + { typeof(float).GetTypeInfo(), "float" }, + { typeof(int).GetTypeInfo(), "int" }, + { typeof(uint).GetTypeInfo(), "uint" }, + { typeof(long).GetTypeInfo(), "long" }, + { typeof(ulong).GetTypeInfo(), "ulong" }, + { typeof(object).GetTypeInfo(), "object" }, + { typeof(short).GetTypeInfo(), "short" }, + { typeof(ushort).GetTypeInfo(), "ushort" }, + { typeof(string).GetTypeInfo(), "string" }, + { typeof(IntPtr).GetTypeInfo(), "nint" }, + { typeof(UIntPtr).GetTypeInfo(), "nuint" }, + }; + +#if !XUNIT_AOT + static ArgumentFormatter() + { + tupleInterfaceType = Type.GetType("System.Runtime.CompilerServices.ITuple"); + + if (tupleInterfaceType != null) + { + tupleIndexer = tupleInterfaceType.GetRuntimeProperty("Item"); + tupleLength = tupleInterfaceType.GetRuntimeProperty("Length"); + } + + if (tupleIndexer == null || tupleLength == null) + tupleInterfaceType = null; + } +#endif /// - /// Gets the maximum strength length before truncation. + /// Gets the ellipsis value (three middle dots, aka U+00B7). /// - public static int MaxStringLength => maxStringLength.Value; + public static string Ellipsis { get; } = new string((char)0x00B7, 3); /// /// Escapes a string for printing, attempting to most closely model the value on how you would @@ -110,7 +146,7 @@ static partial class ArgumentFormatter /// The string value to be escaped public static string EscapeString(string s) { -#if NET8_0_OR_GREATER +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(s); #else if (s == null) @@ -121,8 +157,12 @@ public static string EscapeString(string s) for (var i = 0; i < s.Length; i++) { var ch = s[i]; - - if (TryGetEscapeSequence(ch, out var escapeSequence)) +#if XUNIT_NULLABLE + string? escapeSequence; +#else + string escapeSequence; +#endif + if (TryGetEscapeSequence(ch, out escapeSequence)) builder.Append(escapeSequence); else if (ch < 32) // C0 control char builder.AppendFormat(CultureInfo.CurrentCulture, @"\x{0}", (+ch).ToString("x2", CultureInfo.CurrentCulture)); @@ -143,18 +183,32 @@ public static string EscapeString(string s) return builder.ToString(); } +#if XUNIT_NULLABLE + public static string Format(Type? value) +#else + public static string Format(Type value) +#endif + { + if (value is null) + return "null"; + + return string.Format(CultureInfo.CurrentCulture, "typeof({0})", FormatTypeName(value, fullTypeName: true)); + } + /// /// Formats a value for display. /// /// The value to be formatted /// The optional printing depth (1 indicates a top-level value) - public static string Format( -#if XUNIT_NULLABLE - object? value, -#else - object value, -#endif - int depth = 1) + [DynamicDependency("ToString", typeof(object))] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "We can't easily annotate callers of this type to require them to preserve the ToString method as we need to use the runtime type. We also can't preserve all of the properties and fields for the complex type printing, but any members that are trimmed aren't used and thus don't contribute to the asserts.")] + public static string Format< + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T>(T value, int depth = 1) { if (value == null) return "null"; @@ -165,7 +219,7 @@ public static string Format( try { - if (value.GetType().IsEnum) + if (value.GetType().GetTypeInfo().IsEnum) return FormatEnumValue(value); if (value is char c) @@ -180,33 +234,33 @@ public static string Format( if (value is DateTime || value is DateTimeOffset) return FormatDateTimeValue(value); - if (value is string stringParameter) + var stringParameter = value as string; + if (stringParameter != null) return FormatStringValue(stringParameter); #if !XUNIT_ARGUMENTFORMATTER_PRIVATE - if (value is CollectionTracker tracker) + var tracker = value as CollectionTracker; + if (tracker != null) return tracker.FormatStart(depth); #endif - if (value is IEnumerable enumerable) + var enumerable = value as IEnumerable; + if (enumerable != null) return FormatEnumerableValue(enumerable, depth); var type = value.GetType(); + var typeInfo = type.GetTypeInfo(); -#if NET8_0_OR_GREATER if (value is ITuple tuple) return FormatTupleValue(tuple, depth); -#else - if (tupleInterfaceType != null && type.GetInterfaces().Contains(tupleInterfaceType)) - return FormatTupleValue(value, depth); -#endif - if (type.IsValueType) - return FormatValueTypeValue(value, type); + if (typeInfo.IsValueType) + return FormatValueTypeValue(value, typeInfo); - if (value is Task task) + var task = value as Task; + if (task != null) { - var typeParameters = type.GenericTypeArguments; + var typeParameters = typeInfo.GenericTypeArguments; var typeName = typeParameters.Length == 0 ? "Task" @@ -217,13 +271,25 @@ public static string Format( // TODO: ValueTask? - var isAnonymousType = type.IsAnonymousType(); + var isAnonymousType = typeInfo.IsAnonymousType(); + if (!isAnonymousType) + { + var toString = type.GetRuntimeMethod("ToString", EmptyTypes); + + if (toString != null && toString.DeclaringType != typeof(object)) +#if XUNIT_NULLABLE + return ((string?)toString.Invoke(value, EmptyObjects)) ?? "null"; +#else + return ((string)toString.Invoke(value, EmptyObjects)) ?? "null"; +#endif + } + return FormatComplexValue(value, depth, type, isAnonymousType); } catch (Exception ex) { // Sometimes an exception is thrown when formatting an argument, such as in ToString. - // In these cases, we don't want to crash, as tests may have passed despite this. + // In these cases, we don't want xunit to crash, as tests may have passed despite this. return string.Format(CultureInfo.CurrentCulture, "{0} was thrown formatting an object of type \"{1}\"", ex.GetType().Name, value.GetType()); } } @@ -234,7 +300,12 @@ static string FormatCharValue(char value) return @"'\''"; // Take care of all of the escape sequences - if (TryGetEscapeSequence(value, out var escapeSequence)) +#if XUNIT_NULLABLE + string? escapeSequence; +#else + string escapeSequence; +#endif + if (TryGetEscapeSequence(value, out escapeSequence)) return string.Format(CultureInfo.CurrentCulture, "'{0}'", escapeSequence); if (char.IsLetterOrDigit(value) || char.IsPunctuation(value) || char.IsSymbol(value) || value == ' ') @@ -244,6 +315,51 @@ static string FormatCharValue(char value) return string.Format(CultureInfo.CurrentCulture, "0x{0:x4}", (int)value); } + static string FormatComplexValue( + object value, + int depth, + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, + bool isAnonymousType) + { + var typeName = isAnonymousType ? "" : type.Name + " "; + + if (depth == MAX_DEPTH) + return string.Format(CultureInfo.CurrentCulture, "{0}{{ {1} }}", typeName, Ellipsis); + + var fields = + type + .GetRuntimeFields() + .Where(f => f.IsPublic && !f.IsStatic) + .Select(f => new { name = f.Name, value = WrapAndGetFormattedValue(() => f.GetValue(value), depth) }); + + var properties = + type + .GetRuntimeProperties() + .Where(p => p.GetMethod != null && p.GetMethod.IsPublic && !p.GetMethod.IsStatic) + .Select(p => new { name = p.Name, value = WrapAndGetFormattedValue(() => p.GetValue(value), depth) }); + + var parameters = + fields + .Concat(properties) + .OrderBy(p => p.name) + .Take(MAX_OBJECT_ITEM_COUNT + 1) + .ToList(); + + if (parameters.Count == 0) + return string.Format(CultureInfo.CurrentCulture, "{0}{{ }}", typeName); + + var formattedParameters = string.Join(", ", parameters.Take(MAX_OBJECT_ITEM_COUNT).Select(p => string.Format(CultureInfo.CurrentCulture, "{0} = {1}", p.name, p.value))); + + if (parameters.Count > MAX_OBJECT_ITEM_COUNT) + formattedParameters += ", " + Ellipsis; + + return string.Format(CultureInfo.CurrentCulture, "{0}{{ {1} }}", typeName, formattedParameters); + } + static string FormatDateTimeValue(object value) => string.Format(CultureInfo.CurrentCulture, "{0:o}", value); @@ -251,7 +367,7 @@ static string FormatDoubleValue(object value) => string.Format(CultureInfo.CurrentCulture, "{0:G17}", value); static string FormatEnumValue(object value) => -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER value.ToString()?.Replace(", ", " | ", StringComparison.Ordinal) ?? "null"; #else value.ToString()?.Replace(", ", " | ") ?? "null"; @@ -261,11 +377,23 @@ static string FormatEnumerableValue( IEnumerable enumerable, int depth) { - if (depth > MaxObjectDepth) + if (depth == MAX_DEPTH) return EllipsisInBrackets; - var result = new StringBuilder(GetGroupingKeyPrefix(enumerable)); - if (result.Length == 0 && !SafeToMultiEnumerate(enumerable)) + var result = new StringBuilder(); + +#if !XUNIT_AOT + var groupingTypes = GetGroupingTypes(enumerable); + if (groupingTypes != null) + { + var groupingInterface = typeof(IGrouping<,>).MakeGenericType(groupingTypes); + var key = groupingInterface.GetRuntimeProperty("Key")?.GetValue(enumerable); + result.AppendFormat(CultureInfo.CurrentCulture, "[{0}] = ", key?.ToString() ?? "null"); + } + else if (!SafeToMultiEnumerate(enumerable)) +#else + if (!SafeToMultiEnumerate(enumerable)) +#endif return EllipsisInBrackets; // This should only be used on values that are known to be re-enumerable @@ -280,7 +408,7 @@ static string FormatEnumerableValue( if (idx != 0) result.Append(", "); - if (idx == MaxEnumerableLength) + if (idx == MAX_ENUMERABLE_LENGTH) { result.Append(Ellipsis); break; @@ -303,15 +431,15 @@ static string FormatFloatValue(object value) => static string FormatStringValue(string value) { -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER value = EscapeString(value).Replace(@"""", @"\""", StringComparison.Ordinal); // escape double quotes #else value = EscapeString(value).Replace(@"""", @"\"""); // escape double quotes #endif - if (value.Length > MaxStringLength) + if (value.Length > MAX_STRING_LENGTH) { - var displayed = value.Substring(0, MaxStringLength); + var displayed = value.Substring(0, MAX_STRING_LENGTH); return string.Format(CultureInfo.CurrentCulture, "\"{0}\"{1}", displayed, Ellipsis); } @@ -319,34 +447,18 @@ static string FormatStringValue(string value) } static string FormatTupleValue( -#if NET8_0_OR_GREATER ITuple tupleParameter, -#else - object tupleParameter, -#endif int depth) { var result = new StringBuilder("Tuple ("); -#if NET8_0_OR_GREATER var length = tupleParameter.Length; -#elif XUNIT_NULLABLE - var length = (int)tupleLength!.GetValue(tupleParameter)!; -#else - var length = (int)tupleLength.GetValue(tupleParameter); -#endif for (var idx = 0; idx < length; ++idx) { if (idx != 0) result.Append(", "); -#if NET8_0_OR_GREATER var value = tupleParameter[idx]; -#elif XUNIT_NULLABLE - var value = tupleIndexer!.GetValue(tupleParameter, new object[] { idx }); -#else - var value = tupleIndexer.GetValue(tupleParameter, new object[] { idx }); -#endif result.Append(Format(value, depth + 1)); } @@ -360,28 +472,22 @@ static string FormatTupleValue( /// of "Int32" or "System.Int32"). /// /// The type to get the formatted name of - /// Set to to include the namespace; set to for just the simple type name + /// Set to true to include the namespace; set to false for just the simple type name public static string FormatTypeName( Type type, bool fullTypeName = false) { -#if NET8_0_OR_GREATER - ArgumentNullException.ThrowIfNull(type, nameof(type)); -#else - if (type is null) - throw new ArgumentNullException(nameof(type)); -#endif - + var typeInfo = type.GetTypeInfo(); var arraySuffix = ""; // Deconstruct and re-construct array - while (type.IsArray) + while (typeInfo.IsArray) { - if (type.IsSZArrayType()) + if (typeInfo.IsSZArrayType()) arraySuffix += "[]"; else { - var rank = type.GetArrayRank(); + var rank = typeInfo.GetArrayRank(); if (rank == 1) arraySuffix += "[*]"; else @@ -389,21 +495,26 @@ public static string FormatTypeName( } #if XUNIT_NULLABLE - type = type.GetElementType()!; + typeInfo = typeInfo.GetElementType()!.GetTypeInfo(); #else - type = type.GetElementType(); + typeInfo = typeInfo.GetElementType().GetTypeInfo(); #endif } // Map C# built-in type names - var shortType = type.IsGenericType ? type.GetGenericTypeDefinition() : type; - if (!TypeMappings.TryGetValue(shortType, out var result)) - result = fullTypeName ? type.FullName : type.Name; +#if XUNIT_NULLABLE + string? result; +#else + string result; +#endif + var shortTypeInfo = typeInfo.IsGenericType ? typeInfo.GetGenericTypeDefinition().GetTypeInfo() : typeInfo; + if (!TypeMappings.TryGetValue(shortTypeInfo, out result)) + result = fullTypeName ? typeInfo.FullName : typeInfo.Name; - if (result is null) - return type.Name; + if (result == null) + return typeInfo.Name; -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER var tickIdx = result.IndexOf('`', StringComparison.Ordinal); #else var tickIdx = result.IndexOf('`'); @@ -411,55 +522,121 @@ public static string FormatTypeName( if (tickIdx > 0) result = result.Substring(0, tickIdx); - if (type.IsGenericTypeDefinition) - result = string.Format(CultureInfo.CurrentCulture, "{0}<{1}>", result, new string(',', type.GetGenericArguments().Length - 1)); - else if (type.IsGenericType) + if (typeInfo.IsGenericTypeDefinition) + result = string.Format(CultureInfo.CurrentCulture, "{0}<{1}>", result, new string(',', typeInfo.GenericTypeParameters.Length - 1)); + else if (typeInfo.IsGenericType) { - if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) - result = FormatTypeName(type.GenericTypeArguments[0]) + "?"; + if (typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) + result = FormatTypeName(typeInfo.GenericTypeArguments[0]) + "?"; else - result = string.Format(CultureInfo.CurrentCulture, "{0}<{1}>", result, string.Join(", ", type.GenericTypeArguments.Select(t => FormatTypeName(t)))); + result = string.Format(CultureInfo.CurrentCulture, "{0}<{1}>", result, string.Join(", ", typeInfo.GenericTypeArguments.Select(t => FormatTypeName(t)))); } return result + arraySuffix; } - static int GetEnvironmentValue( - string environmentVariableName, - int defaultValue, - bool allowMaxValue = true) + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(KeyValuePair<,>))] + [DynamicDependency("ToString", typeof(object))] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070", Justification = "We can't easily annotate callers of this type to require them to preserve properties for the one type we need or the ToString method as we need to use the runtime type")] + static string FormatValueTypeValue( + object value, + TypeInfo typeInfo) { - var stringValue = Environment.GetEnvironmentVariable(environmentVariableName); - if (string.IsNullOrWhiteSpace(stringValue) || !int.TryParse(stringValue, out var intValue)) - return defaultValue; + if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + var k = typeInfo.GetProperty("Key")?.GetValue(value, null); + var v = typeInfo.GetProperty("Value")?.GetValue(value, null); + + return string.Format(CultureInfo.CurrentCulture, "[{0}] = {1}", Format(k), Format(v)); + } + + return Convert.ToString(value, CultureInfo.CurrentCulture) ?? "null"; + } + +#if !XUNIT_AOT +#if XUNIT_NULLABLE + internal static Type[]? GetGroupingTypes(object? obj) +#else + internal static Type[] GetGroupingTypes(object obj) +#endif + { + if (obj == null) + return null; + + return + (from @interface in obj.GetType().GetTypeInfo().ImplementedInterfaces + where @interface.GetTypeInfo().IsGenericType + let genericTypeDefinition = @interface.GetGenericTypeDefinition() + where genericTypeDefinition == typeof(IGrouping<,>) + select @interface.GetTypeInfo()).FirstOrDefault()?.GenericTypeArguments; + } +#endif - if (intValue <= 0) - return allowMaxValue ? int.MaxValue : defaultValue; + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ISet<>))] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075", Justification = "We can't easily annotate callers of this type to require them to preserve interfaces, so just preserve the one interface that's checked for.")] +#if XUNIT_NULLABLE + internal static Type? GetSetElementType(object? obj) +#else + internal static Type GetSetElementType(object obj) +#endif + { + if (obj == null) + return null; - return intValue; + return + (from @interface in obj.GetType().GetTypeInfo().ImplementedInterfaces + where @interface.GetTypeInfo().IsGenericType + let genericTypeDefinition = @interface.GetGenericTypeDefinition() + where genericTypeDefinition == typeof(ISet<>) + select @interface.GetTypeInfo()).FirstOrDefault()?.GenericTypeArguments[0]; } - static bool IsAnonymousType(this Type type) + static bool IsAnonymousType(this TypeInfo typeInfo) { // There isn't a sanctioned way to do this, so we look for compiler-generated types that // include "AnonymousType" in their names. - if (type.GetCustomAttribute() == null) + if (typeInfo.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) == null) + return false; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return typeInfo.Name.Contains("AnonymousType", StringComparison.Ordinal); +#else + return typeInfo.Name.Contains("AnonymousType"); +#endif + } + + + static bool IsEnumerableOfGrouping(IEnumerable collection) + { +#if !XUNIT_AOT + var genericEnumerableType = + (from @interface in collection.GetType().GetTypeInfo().ImplementedInterfaces + where @interface.GetTypeInfo().IsGenericType + let genericTypeDefinition = @interface.GetGenericTypeDefinition() + where genericTypeDefinition == typeof(IEnumerable<>) + select @interface.GetTypeInfo()).FirstOrDefault()?.GenericTypeArguments[0]; + + if (genericEnumerableType == null) return false; -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - return type.Name.Contains("AnonymousType", StringComparison.Ordinal); + return + genericEnumerableType + .GetTypeInfo() + .ImplementedInterfaces + .Concat(new[] { genericEnumerableType }) + .Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IGrouping<,>)); #else - return type.Name.Contains("AnonymousType"); + return false; #endif } - static bool IsSZArrayType(this Type type) => -#if NET8_0_OR_GREATER - type.IsSZArray; + static bool IsSZArrayType(this TypeInfo typeInfo) => +#if NETCOREAPP2_0_OR_GREATER + typeInfo.IsSZArray; #elif XUNIT_NULLABLE - type == type.GetElementType()!.MakeArrayType(); + typeInfo == typeInfo.GetElementType()!.MakeArrayType().GetTypeInfo(); #else - type == type.GetElementType().MakeArrayType(); + typeInfo == typeInfo.GetElementType().MakeArrayType().GetTypeInfo(); #endif static bool SafeToMultiEnumerate(IEnumerable collection) => diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_aot.cs deleted file mode 100644 index 369d05b213a..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_aot.cs +++ /dev/null @@ -1,76 +0,0 @@ -#if XUNIT_AOT - -#pragma warning disable IDE0060 // Remove unused parameter - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; - -#if XUNIT_ARGUMENTFORMATTER_PRIVATE -namespace Xunit.Internal -#else -namespace Xunit.Sdk -#endif -{ - partial class ArgumentFormatter - { - /// - /// Formats a value for display. - /// - /// The value to be formatted - public static string Format(KeyValuePair value) => - string.Format( - CultureInfo.CurrentCulture, - "[{0}] = {1}", - Format(value.Key), - Format(value.Value) - ); - - static string FormatComplexValue( - object value, - int depth, - Type type, - bool isAnonymousType) - { - // For objects which implement a custom ToString method, just call that - var toString = value.ToString(); - if (toString is string && toString != type.FullName) - return toString; - - return string.Format(CultureInfo.CurrentCulture, "{0}{{ {1} }}", isAnonymousType ? "" : type.Name + " ", Ellipsis); - } - - static string FormatValueTypeValue( - object value, - Type type) => - Convert.ToString(value, CultureInfo.CurrentCulture) ?? "null"; - -#if XUNIT_NULLABLE - static string? GetGroupingKeyPrefix(IEnumerable enumerable) => -#else - static string GetGroupingKeyPrefix(IEnumerable enumerable) => -#endif - null; - -#if XUNIT_NULLABLE - internal static Type? GetSetElementType(object? obj) => -#else - internal static Type GetSetElementType(object obj) => -#endif - null; - - static bool IsEnumerableOfGrouping(IEnumerable collection) => - false; - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_reflection.cs deleted file mode 100644 index 19232771458..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/ArgumentFormatter_reflection.cs +++ /dev/null @@ -1,198 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable IDE0300 // Simplify collection initialization -#pragma warning disable IDE0301 // Simplify collection initialization -#pragma warning disable IDE0305 // Simplify collection initialization -#pragma warning disable CA1810 // Initialize reference type static fields inline - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8600 -#pragma warning disable CS8601 -#pragma warning disable CS8603 -#pragma warning disable CS8604 -#pragma warning disable CS8618 -#pragma warning disable CS8625 -#endif - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; - -#if XUNIT_ARGUMENTFORMATTER_PRIVATE -namespace Xunit.Internal -#else -namespace Xunit.Sdk -#endif -{ - partial class ArgumentFormatter - { - static readonly object[] EmptyObjects = Array.Empty(); - static readonly Type[] EmptyTypes = Array.Empty(); - -#if !NET8_0_OR_GREATER - -#if XUNIT_NULLABLE - static readonly PropertyInfo? tupleIndexer; - static readonly Type? tupleInterfaceType; - static readonly PropertyInfo? tupleLength; -#else - static readonly PropertyInfo tupleIndexer; - static readonly Type tupleInterfaceType; - static readonly PropertyInfo tupleLength; -#endif - - static ArgumentFormatter() - { - tupleInterfaceType = Type.GetType("System.Runtime.CompilerServices.ITuple"); - - if (tupleInterfaceType != null) - { - tupleIndexer = tupleInterfaceType.GetRuntimeProperty("Item"); - tupleLength = tupleInterfaceType.GetRuntimeProperty("Length"); - } - - if (tupleIndexer == null || tupleLength == null) - tupleInterfaceType = null; - } - -#endif // !NET8_0_OR_GREATER - - static string FormatComplexValue( - object value, - int depth, - Type type, - bool isAnonymousType) - { - // For objects which implement a custom ToString method, just call that - if (!isAnonymousType) - { - var toString = type.GetRuntimeMethod("ToString", EmptyTypes); - if (toString != null && toString.DeclaringType != typeof(object)) - return toString.Invoke(value, EmptyObjects) as string ?? "null"; - } - - var typeName = isAnonymousType ? "" : type.Name + " "; - - if (depth > MaxObjectDepth) - return string.Format(CultureInfo.CurrentCulture, "{0}{{ {1} }}", typeName, Ellipsis); - - var fields = - type - .GetRuntimeFields() - .Where(f => f.IsPublic && !f.IsStatic) - .Select(f => new { name = f.Name, value = WrapAndGetFormattedValue(() => f.GetValue(value), depth + 1) }); - - var properties = - type - .GetRuntimeProperties() - .Where(p => p.GetMethod != null && p.GetMethod.IsPublic && !p.GetMethod.IsStatic) - .Select(p => new { name = p.Name, value = WrapAndGetFormattedValue(() => p.GetValue(value), depth + 1) }); - - var parameters = - MaxObjectMemberCount == int.MaxValue - ? fields.Concat(properties).OrderBy(p => p.name).ToList() - : fields.Concat(properties).OrderBy(p => p.name).Take(MaxObjectMemberCount + 1).ToList(); - - if (parameters.Count == 0) - return string.Format(CultureInfo.CurrentCulture, "{0}{{ }}", typeName); - - var formattedParameters = string.Join(", ", parameters.Take(MaxObjectMemberCount).Select(p => string.Format(CultureInfo.CurrentCulture, "{0} = {1}", p.name, p.value))); - - if (parameters.Count > MaxObjectMemberCount) - formattedParameters += ", " + Ellipsis; - - return string.Format(CultureInfo.CurrentCulture, "{0}{{ {1} }}", typeName, formattedParameters); - } - - static string FormatValueTypeValue( - object value, - Type type) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - var k = type.GetProperty("Key")?.GetValue(value, null); - var v = type.GetProperty("Value")?.GetValue(value, null); - - return string.Format(CultureInfo.CurrentCulture, "[{0}] = {1}", Format(k), Format(v)); - } - - return Convert.ToString(value, CultureInfo.CurrentCulture) ?? "null"; - } - -#if XUNIT_NULLABLE - static string? GetGroupingKeyPrefix(IEnumerable enumerable) -#else - static string GetGroupingKeyPrefix(IEnumerable enumerable) -#endif - { - var groupingTypes = GetGroupingTypes(enumerable); - if (groupingTypes == null) - return null; - - var groupingInterface = typeof(IGrouping<,>).MakeGenericType(groupingTypes); - var key = groupingInterface.GetRuntimeProperty("Key")?.GetValue(enumerable); - return string.Format(CultureInfo.CurrentCulture, "[{0}] = ", key?.ToString() ?? "null"); - } - -#if XUNIT_NULLABLE - internal static Type[]? GetGroupingTypes(object? obj) -#else - internal static Type[] GetGroupingTypes(object obj) -#endif - { - if (obj == null) - return null; - - return - (from @interface in obj.GetType().GetInterfaces() - where @interface.IsGenericType - let genericTypeDefinition = @interface.GetGenericTypeDefinition() - where genericTypeDefinition == typeof(IGrouping<,>) - select @interface).FirstOrDefault()?.GenericTypeArguments; - } - -#if XUNIT_NULLABLE - internal static Type? GetSetElementType(object? obj) -#else - internal static Type GetSetElementType(object obj) -#endif - { - if (obj == null) - return null; - - return - (from @interface in obj.GetType().GetInterfaces() - where @interface.IsGenericType - let genericTypeDefinition = @interface.GetGenericTypeDefinition() - where genericTypeDefinition == typeof(ISet<>) - select @interface).FirstOrDefault()?.GenericTypeArguments[0]; - } - - static bool IsEnumerableOfGrouping(IEnumerable collection) - { - var genericEnumerableType = - (from @interface in collection.GetType().GetInterfaces() - where @interface.IsGenericType - let genericTypeDefinition = @interface.GetGenericTypeDefinition() - where genericTypeDefinition == typeof(IEnumerable<>) - select @interface).FirstOrDefault()?.GenericTypeArguments[0]; - - if (genericEnumerableType == null) - return false; - - return - genericEnumerableType - .GetInterfaces() - .Concat(new[] { genericEnumerableType }) - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGrouping<,>)); - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer.cs index f9162a1d362..44a50d3a9b9 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer.cs @@ -1,20 +1,37 @@ +#pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0016 // Use 'throw' expression +#pragma warning disable IDE0019 // Use pattern matching +#pragma warning disable IDE0022 // Use expression body for method +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression #pragma warning disable IDE0063 // Use simple 'using' statement #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace +#pragma warning disable IDE0270 // Use coalesce expression #pragma warning disable IDE0290 // Use primary constructor +#pragma warning disable IDE0300 // Simplify collection initialization #if XUNIT_NULLABLE #nullable enable #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE +#pragma warning disable CS8601 +#pragma warning disable CS8602 #pragma warning disable CS8604 +#pragma warning disable CS8605 +#pragma warning disable CS8618 #pragma warning disable CS8625 #pragma warning disable CS8767 #endif using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; #if XUNIT_NULLABLE using System.Diagnostics.CodeAnalysis; @@ -22,8 +39,67 @@ namespace Xunit.Sdk { - static partial class AssertEqualityComparer + static class AssertEqualityComparer { +#if !XUNIT_AOT // not supported on AOT due to MakeGenericType + static readonly ConcurrentDictionary cachedDefaultComparers = new ConcurrentDictionary(); + static readonly ConcurrentDictionary cachedDefaultInnerComparers = new ConcurrentDictionary(); +#if XUNIT_NULLABLE + static readonly object?[] singleNullObject = new object?[] { null }; +#else + static readonly object[] singleNullObject = new object[] { null }; +#endif + + /// + /// Gets the default comparer to be used for the provided when a custom one + /// has not been provided. Creates an instance of wrapped + /// by . + /// + /// The type to be compared + internal static IEqualityComparer GetDefaultComparer(Type type) => + cachedDefaultComparers.GetOrAdd(type, itemType => + { + var comparerType = typeof(AssertEqualityComparer<>).MakeGenericType(itemType); + var comparer = Activator.CreateInstance(comparerType, singleNullObject); + if (comparer == null) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not create instance of AssertEqualityComparer<{0}>", itemType.FullName ?? itemType.Name)); + + var wrapperType = typeof(AssertEqualityComparerAdapter<>).MakeGenericType(itemType); + var result = Activator.CreateInstance(wrapperType, new object[] { comparer }) as IEqualityComparer; + if (result == null) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not create instance of AssertEqualityComparerAdapter<{0}>", itemType.FullName ?? itemType.Name)); + + return result; + }); + + /// + /// Gets the default comparer to be used as an inner comparer for the provided + /// when a custom one has not been provided. For non-collections, this defaults to an -based + /// comparer; for collections, this creates an inner comparer based on the item type in the collection. + /// + /// The type to create an inner comparer for + internal static IEqualityComparer GetDefaultInnerComparer(Type type) => + cachedDefaultInnerComparers.GetOrAdd(type, t => + { + var innerType = typeof(object); + + // string is enumerable, but we don't treat it like a collection + if (t != typeof(string)) + { + var enumerableOfT = + t.GetTypeInfo() + .ImplementedInterfaces + .Select(i => i.GetTypeInfo()) + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + + if (enumerableOfT != null) + innerType = enumerableOfT.GenericTypeArguments[0]; + } + + return GetDefaultComparer(innerType); + }); +#endif // !XUNIT_AOT + /// /// This exception is thrown when an operation failure has occured during equality comparison operations. /// This generally indicates that a necessary pre-condition was not met for comparison operations to succeed. @@ -44,14 +120,27 @@ public static OperationalFailureException ForIllegalGetHashCode() => } /// - /// Default implementation of used by the assertion library. + /// Default implementation of used by the xUnit.net equality assertions. /// /// The type that is being compared. - sealed partial class AssertEqualityComparer : IAssertEqualityComparer + sealed class AssertEqualityComparer< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicMethods)] T> : IEqualityComparer { +#if XUNIT_AOT + internal static readonly IEqualityComparer DefaultInnerComparer = new AssertEqualityComparerAdapter(new AssertEqualityComparer()); +#else internal static readonly IEqualityComparer DefaultInnerComparer = AssertEqualityComparer.GetDefaultInnerComparer(typeof(T)); +#endif + static readonly ConcurrentDictionary cacheOfIComparableOfT = new ConcurrentDictionary(); + static readonly ConcurrentDictionary cacheOfIEquatableOfT = new ConcurrentDictionary(); readonly Lazy innerComparer; + static readonly Type typeKeyValuePair = typeof(KeyValuePair<,>); /// /// Initializes a new instance of the class. @@ -64,7 +153,7 @@ public AssertEqualityComparer(IEqualityComparer innerComparer = null) #endif { // Use a thunk to delay evaluation of DefaultInnerComparer - this.innerComparer = new Lazy(() => innerComparer ?? DefaultInnerComparer); + this.innerComparer = new Lazy(() => innerComparer ?? AssertEqualityComparer.DefaultInnerComparer); } public IEqualityComparer InnerComparer => @@ -73,47 +162,229 @@ public AssertEqualityComparer(IEqualityComparer innerComparer = null) /// public bool Equals( #if XUNIT_NULLABLE - T? x, - T? y) + [AllowNull] T x, + [AllowNull] T y) #else T x, T y) #endif { + int? _; + +#if XUNIT_FRAMEWORK + return Equals(x, y, out _); +#else using (var xTracker = x.AsNonStringTracker()) using (var yTracker = y.AsNonStringTracker()) - return Equals(x, xTracker, y, yTracker).Equal; + return Equals(x, xTracker, y, yTracker, out _); +#endif } + internal bool Equals( #if XUNIT_NULLABLE - public static IEqualityComparer FromComparer(Func comparer) => + [AllowNull] T x, +#if !XUNIT_FRAMEWORK + CollectionTracker? xTracker, +#endif + [AllowNull] T y, +#if !XUNIT_FRAMEWORK + CollectionTracker? yTracker, +#endif #else - public static IEqualityComparer FromComparer(Func comparer) => + T x, +#if !XUNIT_FRAMEWORK + CollectionTracker xTracker, #endif - new FuncEqualityComparer(comparer); + T y, +#if !XUNIT_FRAMEWORK + CollectionTracker yTracker, +#endif +#endif + out int? mismatchedIndex) + { + mismatchedIndex = null; - /// - public int GetHashCode(T obj) => - innerComparer.Value.GetHashCode(GuardArgumentNotNull(nameof(obj), obj)); + // Null? + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; + + // If you point at the same thing, you're equal + if (ReferenceEquals(x, y)) + return true; + + // Implements IEquatable? + var equatable = x as IEquatable; + if (equatable != null) + return equatable.Equals(y); + +#if !XUNIT_AOT + var xType = x.GetType(); + var yType = y.GetType(); + var xTypeInfo = xType.GetTypeInfo(); + + // Implements IEquatable? + // Not supported on AOT due to MakeGenericType + if (xType != yType) + { + var iequatableY = cacheOfIEquatableOfT.GetOrAdd(yType, (t) => typeof(IEquatable<>).MakeGenericType(t).GetTypeInfo()); + if (iequatableY.IsAssignableFrom(xTypeInfo)) + { + var equalsMethod = iequatableY.GetDeclaredMethod(nameof(IEquatable.Equals)); + if (equalsMethod == null) + return false; - /// #if XUNIT_NULLABLE - [return: NotNull] + return equalsMethod.Invoke(x, new object[] { y }) is true; +#else + return (bool)equalsMethod.Invoke(x, new object[] { y }); #endif - internal static TArg GuardArgumentNotNull( - string argName, + } + } +#endif // !XUNIT_AOT + +#if !XUNIT_FRAMEWORK + // Special case collections (before IStructuralEquatable because arrays implement that in a way we don't want to call) + if (xTracker != null && yTracker != null) + return CollectionTracker.AreCollectionsEqual(xTracker, yTracker, InnerComparer, InnerComparer == DefaultInnerComparer, out mismatchedIndex); +#endif + + // Implements IStructuralEquatable? + var structuralEquatable = x as IStructuralEquatable; + if (structuralEquatable != null && structuralEquatable.Equals(y, new TypeErasedEqualityComparer(innerComparer.Value))) + return true; + + // Implements IComparable? + var comparableGeneric = x as IComparable; + if (comparableGeneric != null) + { + try + { + return comparableGeneric.CompareTo(y) == 0; + } + catch + { + // Some implementations of IComparable.CompareTo throw exceptions in + // certain situations, such as if x can't compare against y. + // If this happens, just swallow up the exception and continue comparing. + } + } + +#if !XUNIT_AOT + // Implements IComparable? + // Not supported on AOT due to MakeGenericType + if (xType != yType) + { + var icomparableY = cacheOfIComparableOfT.GetOrAdd(yType, (t) => typeof(IComparable<>).MakeGenericType(t).GetTypeInfo()); + if (icomparableY.IsAssignableFrom(xTypeInfo)) + { + var compareToMethod = icomparableY.GetDeclaredMethod(nameof(IComparable.CompareTo)); + if (compareToMethod == null) + return false; + + try + { #if XUNIT_NULLABLE - [NotNull] TArg? argValue) + return compareToMethod.Invoke(x, new object[] { y }) is 0; #else - TArg argValue) + return (int)compareToMethod.Invoke(x, new object[] { y }) == 0; #endif - { - if (argValue == null) - throw new ArgumentNullException(argName.TrimStart('@')); + } + catch + { + // Some implementations of IComparable.CompareTo throw exceptions in + // certain situations, such as if x can't compare against y. + // If this happens, just swallow up the exception and continue comparing. + } + } + } +#endif // !XUNIT_AOT - return argValue; + // Implements IComparable? + var comparable = x as IComparable; + if (comparable != null) + { + try + { + return comparable.CompareTo(y) == 0; + } + catch + { + // Some implementations of IComparable.CompareTo throw exceptions in + // certain situations, such as if x can't compare against y. + // If this happens, just swallow up the exception and continue comparing. + } + } + + // Special case KeyValuePair + if (typeof(T).IsConstructedGenericType && + typeof(T).GetGenericTypeDefinition() == typeKeyValuePair) + { +#if XUNIT_AOT + var xKey = typeof(T).GetRuntimeProperty("Key")?.GetValue(x); + var yKey = typeof(T).GetRuntimeProperty("Key")?.GetValue(y); +#else + var xKey = xType.GetRuntimeProperty("Key")?.GetValue(x); + var yKey = yType.GetRuntimeProperty("Key")?.GetValue(y); +#endif + + if (xKey == null) + { + if (yKey != null) + return false; + } + else + { + var xKeyType = xKey.GetType(); + var yKeyType = yKey?.GetType(); + +#if XUNIT_AOT + var keyComparer = innerComparer.Value; +#else + var keyComparer = AssertEqualityComparer.GetDefaultComparer(xKeyType == yKeyType ? xKeyType : typeof(object)); +#endif + if (!keyComparer.Equals(xKey, yKey)) + return false; + } + +#if XUNIT_AOT + var xValue = typeof(T).GetRuntimeProperty("Value")?.GetValue(x); + var yValue = typeof(T).GetRuntimeProperty("Value")?.GetValue(y); +#else + var xValue = xType.GetRuntimeProperty("Value")?.GetValue(x); + var yValue = yType.GetRuntimeProperty("Value")?.GetValue(y); +#endif + + if (xValue == null) + return yValue == null; + + var xValueType = xValue.GetType(); + var yValueType = yValue?.GetType(); + +#if XUNIT_AOT + var valueComparer = innerComparer.Value; +#else + var valueComparer = AssertEqualityComparer.GetDefaultComparer(xValueType == yValueType ? xValueType : typeof(object)); +#endif + return valueComparer.Equals(xValue, yValue); + } + + // Last case, rely on object.Equals + return object.Equals(x, y); } +#if XUNIT_NULLABLE + public static IEqualityComparer FromComparer(Func comparer) => +#else + public static IEqualityComparer FromComparer(Func comparer) => +#endif + new FuncEqualityComparer(comparer); + + /// + public int GetHashCode(T obj) => + innerComparer.Value.GetHashCode(GuardArgumentNotNull(nameof(obj), obj)); + #if XUNIT_NULLABLE sealed class FuncEqualityComparer : IEqualityComparer #else @@ -122,8 +393,17 @@ sealed class FuncEqualityComparer : IEqualityComparer { readonly Func comparer; - public FuncEqualityComparer(Func comparer) => - this.comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + public FuncEqualityComparer(Func comparer) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(comparer); +#else + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); +#endif + + this.comparer = comparer; + } public bool Equals( #if XUNIT_NULLABLE @@ -154,5 +434,97 @@ public int GetHashCode(T obj) #pragma warning restore CA1065 } } + + sealed class TypeErasedEqualityComparer : IEqualityComparer + { + readonly IEqualityComparer innerComparer; + + public TypeErasedEqualityComparer(IEqualityComparer innerComparer) + { + this.innerComparer = innerComparer; + } + +#if !XUNIT_AOT +#if XUNIT_NULLABLE + static MethodInfo? s_equalsMethod; +#else + static MethodInfo s_equalsMethod; +#endif +#endif // XUNIT_AOT + + public new bool Equals( +#if XUNIT_NULLABLE + object? x, + object? y) +#else + object x, + object y) +#endif + { + if (x == null) + return y == null; + if (y == null) + return false; + +#if XUNIT_AOT + // Can't use MakeGenericType, have to use object + return EqualsGeneric(x, y); +#else + // Delegate checking of whether two objects are equal to AssertEqualityComparer. + // To get the best result out of AssertEqualityComparer, we attempt to specialize the + // comparer for the objects that we are checking. + // If the objects are the same, great! If not, assume they are objects. + // This is more naive than the C# compiler which tries to see if they share any interfaces + // etc. but that's likely overkill here as AssertEqualityComparer is smart enough. + var objectType = x.GetType() == y.GetType() ? x.GetType() : typeof(object); + + // Lazily initialize and cache the EqualsGeneric method. + if (s_equalsMethod == null) + { + s_equalsMethod = typeof(TypeErasedEqualityComparer).GetTypeInfo().GetDeclaredMethod(nameof(EqualsGeneric)); + if (s_equalsMethod == null) + return false; + } + +#if XUNIT_NULLABLE + return s_equalsMethod.MakeGenericMethod(objectType).Invoke(this, new object[] { x, y }) is true; +#else + return (bool)s_equalsMethod.MakeGenericMethod(objectType).Invoke(this, new object[] { x, y }); +#endif // XUNIT_NULLABLE +#endif // XUNIT_AOT + } + + bool EqualsGeneric<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.Interfaces + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] U>( + U x, + U y) => + new AssertEqualityComparer(innerComparer: innerComparer).Equals(x, y); + + public int GetHashCode(object obj) => + GuardArgumentNotNull(nameof(obj), obj).GetHashCode(); + } + + /// +#if XUNIT_NULLABLE + [return: NotNull] +#endif + internal static TArg GuardArgumentNotNull( + string argName, +#if XUNIT_NULLABLE + [NotNull] TArg? argValue) +#else + TArg argValue) +#endif + { + if (argValue == null) + throw new ArgumentNullException(argName.TrimStart('@')); + + return argValue; + } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparerAdapter.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparerAdapter.cs index a8493033bdc..e8aa05015b2 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparerAdapter.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparerAdapter.cs @@ -1,11 +1,11 @@ -#pragma warning disable CA1031 // Do not catch general exception types -#pragma warning disable IDE0290 // Use primary constructor +#pragma warning disable IDE0016 // Use 'throw' expression +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 #pragma warning disable CS8767 #endif @@ -19,7 +19,7 @@ namespace Xunit.Sdk /// A class that wraps to add . /// /// The type that is being compared. - sealed class AssertEqualityComparerAdapter : IEqualityComparer, IAssertEqualityComparer + sealed class AssertEqualityComparerAdapter : IEqualityComparer, IEqualityComparer { readonly IEqualityComparer innerComparer; @@ -27,8 +27,17 @@ sealed class AssertEqualityComparerAdapter : IEqualityComparer, IAssertEquali /// Initializes a new instance of the class. /// /// The comparer that is being adapted. - public AssertEqualityComparerAdapter(IEqualityComparer innerComparer) => - this.innerComparer = innerComparer ?? throw new ArgumentNullException(nameof(innerComparer)); + public AssertEqualityComparerAdapter(IEqualityComparer innerComparer) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(innerComparer); +#else + if (innerComparer == null) + throw new ArgumentNullException(nameof(innerComparer)); +#endif + + this.innerComparer = innerComparer; + } /// public new bool Equals( @@ -53,35 +62,17 @@ public bool Equals( #endif innerComparer.Equals(x, y); - public AssertEqualityResult Equals( -#if XUNIT_NULLABLE - T? x, - CollectionTracker? xTracker, - T? y, - CollectionTracker? yTracker) -#else - T x, - CollectionTracker xTracker, - T y, - CollectionTracker yTracker) -#endif - { - if (innerComparer is IAssertEqualityComparer innerAssertEqualityComparer) - return innerAssertEqualityComparer.Equals(x, xTracker, y, yTracker); - - return AssertEqualityResult.ForResult(Equals(x, y), x, y); - } /// public int GetHashCode(object obj) => innerComparer.GetHashCode((T)obj); + // This warning disable is here because sometimes IEqualityComparer.GetHashCode marks the obj parameter + // with [DisallowNull] and sometimes it doesn't, and we need to be able to support both scenarios when + // someone brings in the assertion library via source. +#pragma warning disable CS8607 /// public int GetHashCode(T obj) => - // This warning disable is here because sometimes IEqualityComparer.GetHashCode marks the obj parameter - // with [DisallowNull] and sometimes it doesn't, and we need to be able to support both scenarios when - // someone brings in the assertion library via source. -#pragma warning disable CS8607 innerComparer.GetHashCode(obj); #pragma warning restore CS8607 } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_aot.cs deleted file mode 100644 index 8d5e48e5911..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_aot.cs +++ /dev/null @@ -1,116 +0,0 @@ -#if XUNIT_AOT - -#pragma warning disable CA1031 // Do not catch general exception types - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#pragma warning disable CS8767 -#endif - -using System; -using System.Collections; -using System.Collections.Immutable; - -namespace Xunit.Sdk -{ - partial class AssertEqualityComparer - { - // Create a new instance each call (matching the non-AOT/reflection path behavior) - // rather than caching in a static field. A cached static field causes a circular - // static initialization dependency: the field initializer triggers - // AssertEqualityComparer's static initializer, which reads the field back - // via GetDefaultInnerComparer before it has been assigned. On Mono/WASM, this - // causes DefaultInnerComparer to be permanently null, leading to - // NullReferenceException when comparing value types via IStructuralEquatable. - internal static IEqualityComparer GetDefaultComparer(Type _) => - new AssertEqualityComparerAdapter(new AssertEqualityComparer()); - - internal static IEqualityComparer GetDefaultInnerComparer(Type _) => - new AssertEqualityComparerAdapter(new AssertEqualityComparer()); - } - - partial class AssertEqualityComparer - { - public AssertEqualityResult Equals( -#if XUNIT_NULLABLE - T? x, - CollectionTracker? xTracker, - T? y, - CollectionTracker? yTracker) -#else - T x, - CollectionTracker xTracker, - T y, - CollectionTracker yTracker) -#endif - { - // Null? - if (x == null && y == null) - return AssertEqualityResult.ForResult(true, x, y); - if (x == null || y == null) - return AssertEqualityResult.ForResult(false, x, y); - - // If you point at the same thing, you're equal - if (ReferenceEquals(x, y)) - return AssertEqualityResult.ForResult(true, x, y); - - // We want the inequality indices for strings - if (x is string xString && y is string yString) - return StringAssertEqualityComparer.Equivalent(xString, yString); - - var xType = x.GetType(); - var yType = y.GetType(); - - // ImmutableArray defines IEquatable> in a way that isn't consistent with the - // needs of an assertion library. https://github.com/xunit/xunit/issues/3137 - if (!xType.IsGenericType || xType.GetGenericTypeDefinition() != typeof(ImmutableArray<>)) - { - // Implements IEquatable? - if (x is IEquatable equatable) - return AssertEqualityResult.ForResult(equatable.Equals(y), x, y); - } - - // Special case collections (before IStructuralEquatable because arrays implement that in a way we don't want to call) - if (xTracker != null && yTracker != null) - return CollectionTracker.AreCollectionsEqual(xTracker, yTracker, InnerComparer, InnerComparer == DefaultInnerComparer); - - // Implements IStructuralEquatable? - if (x is IStructuralEquatable structuralEquatable && structuralEquatable.Equals(y, innerComparer.Value)) - return AssertEqualityResult.ForResult(true, x, y); - - // Implements IComparable? - if (x is IComparable comparableGeneric) - try - { - return AssertEqualityResult.ForResult(comparableGeneric.CompareTo(y) == 0, x, y); - } - catch - { - // Some implementations of IComparable.CompareTo throw exceptions in - // certain situations, such as if x can't compare against y. - // If this happens, just swallow up the exception and continue comparing. - } - - // Implements IComparable? - if (x is IComparable comparable) - try - { - return AssertEqualityResult.ForResult(comparable.CompareTo(y) == 0, x, y); - } - catch - { - // Some implementations of IComparable.CompareTo throw exceptions in - // certain situations, such as if x can't compare against y. - // If this happens, just swallow up the exception and continue comparing. - } - - // Last case, rely on object.Equals - return AssertEqualityResult.ForResult(object.Equals(x, y), x, y); - } - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_reflection.cs deleted file mode 100644 index 6cd660fd72d..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityComparer_reflection.cs +++ /dev/null @@ -1,320 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable CA1031 // Do not catch general exception types -#pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0290 // Use primary constructor -#pragma warning disable IDE0300 // Simplify collection initialization -#pragma warning disable IDE0340 // Use unbound generic type - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8601 -#pragma warning disable CS8604 -#pragma warning disable CS8605 -#pragma warning disable CS8618 -#pragma warning disable CS8625 -#pragma warning disable CS8767 -#endif - -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Reflection; - -namespace Xunit.Sdk -{ - partial class AssertEqualityComparer - { - static readonly ConcurrentDictionary cachedDefaultComparers = new ConcurrentDictionary(); - static readonly ConcurrentDictionary cachedDefaultInnerComparers = new ConcurrentDictionary(); -#if XUNIT_NULLABLE - static readonly object?[] singleNullObject = new object?[] { null }; -#else - static readonly object[] singleNullObject = new object[] { null }; -#endif - - /// - /// Gets the default comparer to be used for the provided when a custom one - /// has not been provided. Creates an instance of wrapped - /// by . - /// - /// The type to be compared - internal static IEqualityComparer GetDefaultComparer(Type type) => - cachedDefaultComparers.GetOrAdd(type, itemType => - { - var comparerType = typeof(AssertEqualityComparer<>).MakeGenericType(itemType); - var comparer = - Activator.CreateInstance(comparerType, singleNullObject) - ?? throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not create instance of AssertEqualityComparer<{0}>", itemType.FullName ?? itemType.Name)); - - var wrapperType = typeof(AssertEqualityComparerAdapter<>).MakeGenericType(itemType); - var result = - Activator.CreateInstance(wrapperType, new object[] { comparer }) as IEqualityComparer - ?? throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not create instance of AssertEqualityComparerAdapter<{0}>", itemType.FullName ?? itemType.Name)); - - return result; - }); - - /// - /// Gets the default comparer to be used as an inner comparer for the provided - /// when a custom one has not been provided. For non-collections, this defaults to an -based - /// comparer; for collections, this creates an inner comparer based on the item type in the collection. - /// - /// The type to create an inner comparer for - internal static IEqualityComparer GetDefaultInnerComparer(Type type) => - cachedDefaultInnerComparers.GetOrAdd(type, t => - { - var innerType = typeof(object); - - // string is enumerable, but we don't treat it like a collection - if (t != typeof(string)) - { - var enumerableOfT = - t - .GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - - if (enumerableOfT != null) - innerType = enumerableOfT.GenericTypeArguments[0]; - } - - return GetDefaultComparer(innerType); - }); - } - - partial class AssertEqualityComparer - { - static readonly ConcurrentDictionary cacheOfIComparableOfT = new ConcurrentDictionary(); - static readonly ConcurrentDictionary cacheOfIEquatableOfT = new ConcurrentDictionary(); - static readonly Type typeKeyValuePair = typeof(KeyValuePair<,>); - - /// - public AssertEqualityResult Equals( -#if XUNIT_NULLABLE - T? x, - CollectionTracker? xTracker, - T? y, - CollectionTracker? yTracker) -#else - T x, - CollectionTracker xTracker, - T y, - CollectionTracker yTracker) -#endif - { - // Null? - if (x == null && y == null) - return AssertEqualityResult.ForResult(true, x, y); - if (x == null || y == null) - return AssertEqualityResult.ForResult(false, x, y); - - // If you point at the same thing, you're equal - if (ReferenceEquals(x, y)) - return AssertEqualityResult.ForResult(true, x, y); - - // We want the inequality indices for strings - if (x is string xString && y is string yString) - return StringAssertEqualityComparer.Equivalent(xString, yString); - - var xType = x.GetType(); - var yType = y.GetType(); - - // ImmutableArray defines IEquatable> in a way that isn't consistent with the - // needs of an assertion library. https://github.com/xunit/xunit/issues/3137 - if (!xType.IsGenericType || xType.GetGenericTypeDefinition() != typeof(ImmutableArray<>)) - { - // Implements IEquatable? - if (x is IEquatable equatable) - return AssertEqualityResult.ForResult(equatable.Equals(y), x, y); - - // Implements IEquatable? - if (xType != yType) - { - var iequatableY = cacheOfIEquatableOfT.GetOrAdd(yType, (t) => typeof(IEquatable<>).MakeGenericType(t)); - if (iequatableY.IsAssignableFrom(xType)) - { - var equalsMethod = iequatableY.GetMethod(nameof(IEquatable.Equals)); - if (equalsMethod == null) - return AssertEqualityResult.ForResult(false, x, y); - -#if XUNIT_NULLABLE - return AssertEqualityResult.ForResult(equalsMethod.Invoke(x, new object[] { y }) is true, x, y); -#else - return AssertEqualityResult.ForResult((bool)equalsMethod.Invoke(x, new object[] { y }), x, y); -#endif - } - } - } - - // Special case collections (before IStructuralEquatable because arrays implement that in a way we don't want to call) - if (xTracker != null && yTracker != null) - return CollectionTracker.AreCollectionsEqual(xTracker, yTracker, InnerComparer, InnerComparer == DefaultInnerComparer); - - // Implements IStructuralEquatable? - if (x is IStructuralEquatable structuralEquatable && structuralEquatable.Equals(y, new TypeErasedEqualityComparer(innerComparer.Value))) - return AssertEqualityResult.ForResult(true, x, y); - - // Implements IComparable? - if (x is IComparable comparableGeneric) - try - { - return AssertEqualityResult.ForResult(comparableGeneric.CompareTo(y) == 0, x, y); - } - catch - { - // Some implementations of IComparable.CompareTo throw exceptions in - // certain situations, such as if x can't compare against y. - // If this happens, just swallow up the exception and continue comparing. - } - - // Implements IComparable? - if (xType != yType) - { - var icomparableY = cacheOfIComparableOfT.GetOrAdd(yType, (t) => typeof(IComparable<>).MakeGenericType(t)); - if (icomparableY.IsAssignableFrom(xType)) - { - var compareToMethod = icomparableY.GetMethod(nameof(IComparable.CompareTo)); - if (compareToMethod == null) - return AssertEqualityResult.ForResult(false, x, y); - - try - { -#if XUNIT_NULLABLE - return AssertEqualityResult.ForResult(compareToMethod.Invoke(x, new object[] { y }) is 0, x, y); -#else - return AssertEqualityResult.ForResult((int)compareToMethod.Invoke(x, new object[] { y }) == 0, x, y); -#endif - } - catch - { - // Some implementations of IComparable.CompareTo throw exceptions in - // certain situations, such as if x can't compare against y. - // If this happens, just swallow up the exception and continue comparing. - } - } - } - - // Implements IComparable? - if (x is IComparable comparable) - try - { - return AssertEqualityResult.ForResult(comparable.CompareTo(y) == 0, x, y); - } - catch - { - // Some implementations of IComparable.CompareTo throw exceptions in - // certain situations, such as if x can't compare against y. - // If this happens, just swallow up the exception and continue comparing. - } - - // Special case KeyValuePair - if (xType.IsConstructedGenericType && - xType.GetGenericTypeDefinition() == typeKeyValuePair && - yType.IsConstructedGenericType && - yType.GetGenericTypeDefinition() == typeKeyValuePair) - { - var xKey = xType.GetRuntimeProperty("Key")?.GetValue(x); - var yKey = yType.GetRuntimeProperty("Key")?.GetValue(y); - - if (xKey == null) - { - if (yKey != null) - return AssertEqualityResult.ForResult(false, x, y); - } - else - { - var xKeyType = xKey.GetType(); - var yKeyType = yKey?.GetType(); - - var keyComparer = AssertEqualityComparer.GetDefaultComparer(xKeyType == yKeyType ? xKeyType : typeof(object)); - if (!keyComparer.Equals(xKey, yKey)) - return AssertEqualityResult.ForResult(false, x, y); - } - - var xValue = xType.GetRuntimeProperty("Value")?.GetValue(x); - var yValue = yType.GetRuntimeProperty("Value")?.GetValue(y); - - if (xValue == null) - return AssertEqualityResult.ForResult(yValue is null, x, y); - - var xValueType = xValue.GetType(); - var yValueType = yValue?.GetType(); - - var valueComparer = AssertEqualityComparer.GetDefaultComparer(xValueType == yValueType ? xValueType : typeof(object)); - return AssertEqualityResult.ForResult(valueComparer.Equals(xValue, yValue), x, y); - } - - // Last case, rely on object.Equals - return AssertEqualityResult.ForResult(object.Equals(x, y), x, y); - } - - sealed class TypeErasedEqualityComparer : IEqualityComparer - { - readonly IEqualityComparer innerComparer; - - public TypeErasedEqualityComparer(IEqualityComparer innerComparer) - { - this.innerComparer = innerComparer; - } - -#if XUNIT_NULLABLE - static MethodInfo? equalsMethod; -#else - static MethodInfo equalsMethod; -#endif - - public new bool Equals( -#if XUNIT_NULLABLE - object? x, - object? y) -#else - object x, - object y) -#endif - { - if (x == null) - return y == null; - if (y == null) - return false; - - // Delegate checking of whether two objects are equal to AssertEqualityComparer. - // To get the best result out of AssertEqualityComparer, we attempt to specialize the - // comparer for the objects that we are checking. - // If the objects are the same, great! If not, assume they are objects. - // This is more naive than the C# compiler which tries to see if they share any interfaces - // etc. but that's likely overkill here as AssertEqualityComparer is smart enough. - var objectType = x.GetType() == y.GetType() ? x.GetType() : typeof(object); - - // Lazily initialize and cache the EqualsGeneric method. - if (equalsMethod == null) - { - equalsMethod = typeof(TypeErasedEqualityComparer).GetMethod(nameof(EqualsGeneric), BindingFlags.NonPublic | BindingFlags.Instance); - if (equalsMethod == null) - return false; - } - -#if XUNIT_NULLABLE - return equalsMethod.MakeGenericMethod(objectType).Invoke(this, new object[] { x, y }) is true; -#else - return (bool)equalsMethod.MakeGenericMethod(objectType).Invoke(this, new object[] { x, y }); -#endif - } - - bool EqualsGeneric( - U x, - U y) => - new AssertEqualityComparer(innerComparer: innerComparer).Equals(x, y); - - public int GetHashCode(object obj) => - GuardArgumentNotNull(nameof(obj), obj).GetHashCode(); - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityResult.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityResult.cs deleted file mode 100644 index 0d6a71872df..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEqualityResult.cs +++ /dev/null @@ -1,271 +0,0 @@ -#pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0290 // Use primary constructor - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8601 -#pragma warning disable CS8618 -#pragma warning disable CS8625 -#pragma warning disable CS8765 -#pragma warning disable CS8767 -#endif - -using System; - -namespace Xunit.Sdk -{ - /// - /// Indicates the result of comparing two values for equality. Includes success/failure information, as well - /// as indices where the values differ, if the values are indexed (e.g., collections or strings). - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - class AssertEqualityResult : IEquatable - { - AssertEqualityResult( - bool equal, -#if XUNIT_NULLABLE - object? x, - object? y, -#else - object x, - object y, -#endif - int? mismatchIndexX = null, - int? mismatchIndexY = null, -#if XUNIT_NULLABLE - Exception? exception = null, - AssertEqualityResult? innerResult = null) -#else - Exception exception = null, - AssertEqualityResult innerResult = null) -#endif - { - Equal = equal; - X = x; - Y = y; - Exception = exception; - InnerResult = innerResult; - MismatchIndexX = mismatchIndexX; - MismatchIndexY = mismatchIndexY; - } - - /// - /// Returns if the values were equal; , otherwise. - /// - public bool Equal { get; } - - /// - /// Returns the exception that caused the failure, if it was based on an exception. - /// -#if XUNIT_NULLABLE - public Exception? Exception { get; } -#else - public Exception Exception { get; } -#endif - - /// - /// Returns the comparer result for any inner comparison that caused this result - /// to fail; returns if there was no inner comparison. - /// - /// - /// If this value is set, then it generally indicates that this comparison was a - /// failed collection comparison, and the inner result indicates the specific - /// item comparison that caused the failure. - /// -#if XUNIT_NULLABLE - public AssertEqualityResult? InnerResult { get; } -#else - public AssertEqualityResult InnerResult { get; } -#endif - - /// - /// Returns the index of the mismatch for the X value, if the comparison - /// failed on a specific index. - /// - public int? MismatchIndexX { get; } - - /// - /// Returns the index of the mismatch for the Y value, if the comparison - /// failed on a specific index. - /// - public int? MismatchIndexY { get; } - - /// - /// The left-hand value in the comparison - /// -#if XUNIT_NULLABLE - public object? X { get; } -#else - public object X { get; } -#endif - - /// - /// The right-hand value in the comparison - /// -#if XUNIT_NULLABLE - public object? Y { get; } -#else - public object Y { get; } -#endif - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// Returns if the values are equal; , otherwise. -#if XUNIT_NULLABLE - public override bool Equals(object? obj) => -#else - public override bool Equals(object obj) => -#endif - obj is AssertEqualityResult other && Equals(other); - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// Returns if the values are equal; , otherwise. -#if XUNIT_NULLABLE - public bool Equals(AssertEqualityResult? other) -#else - public bool Equals(AssertEqualityResult other) -#endif - { - if (other is null) - return false; - - return - Equal.Equals(other) && - X?.Equals(other.X) != false && - Y?.Equals(other.Y) != false && - InnerResult?.Equals(other.InnerResult) != false && - MismatchIndexX.Equals(other.MismatchIndexY) && - MismatchIndexY.Equals(other.MismatchIndexY); - } - - /// - /// Creates an instance of where the values were - /// not equal, and there is a single mismatch index (for example, when comparing two - /// collections). - /// - /// The left-hand value in the comparison - /// The right-hand value in the comparison - /// The mismatch index for both X and Y values - /// The optional exception that was thrown to cause the failure - /// The optional inner result that caused the equality failure - public static AssertEqualityResult ForMismatch( -#if XUNIT_NULLABLE - object? x, - object? y, -#else - object x, - object y, -#endif - int mismatchIndex, -#if XUNIT_NULLABLE - Exception? exception = null, - AssertEqualityResult? innerResult = null) => -#else - Exception exception = null, - AssertEqualityResult innerResult = null) => -#endif - new AssertEqualityResult(false, x, y, mismatchIndex, mismatchIndex, exception, innerResult); - - /// - /// Creates an instance of where the values were - /// not equal, and there are separate mismatch indices (for example, when comparing two - /// strings under special circumstances). - /// - /// The left-hand value in the comparison - /// The right-hand value in the comparison - /// The mismatch index for the X value - /// The mismatch index for the Y value - /// The optional exception that was thrown to cause the failure - /// The optional inner result that caused the equality failure - public static AssertEqualityResult ForMismatch( -#if XUNIT_NULLABLE - object? x, - object? y, -#else - object x, - object y, -#endif - int mismatchIndexX, - int mismatchIndexY, -#if XUNIT_NULLABLE - Exception? exception = null, - AssertEqualityResult? innerResult = null) => -#else - Exception exception = null, - AssertEqualityResult innerResult = null) => -#endif - new AssertEqualityResult(false, x, y, mismatchIndexX, mismatchIndexY, exception, innerResult); - - /// - /// Creates an instance of . - /// - /// A flag which indicates whether the values were equal - /// The left-hand value in the comparison - /// The right-hand value in the comparison - /// The optional exception that was thrown to cause the failure - /// The optional inner result that caused the equality failure - public static AssertEqualityResult ForResult( - bool equal, -#if XUNIT_NULLABLE - object? x, - object? y, - Exception? exception = null, - AssertEqualityResult? innerResult = null) => -#else - object x, - object y, - Exception exception = null, - AssertEqualityResult innerResult = null) => -#endif - new AssertEqualityResult(equal, x, y, exception: exception, innerResult: innerResult); - - /// - /// Gets a hash code for the object, to be used in hashed containers. - /// - public override int GetHashCode() => - (Equal, MismatchIndexX, MismatchIndexY).GetHashCode(); - - /// - /// Determines whether two instances of are equal. - /// - /// The first value - /// The second value - /// Returns if the values are equal; , otherwise. - public static bool operator ==( -#if XUNIT_NULLABLE - AssertEqualityResult? left, - AssertEqualityResult? right) => -#else - AssertEqualityResult left, - AssertEqualityResult right) => -#endif - left?.Equals(right) == true; - - /// - /// Determines whether two instances of are not equal. - /// - /// The first value - /// The second value - /// Returns if the values are not equal; , otherwise. - public static bool operator !=( -#if XUNIT_NULLABLE - AssertEqualityResult? left, - AssertEqualityResult? right) => -#else - AssertEqualityResult left, - AssertEqualityResult right) => -#endif - left?.Equals(right) == false; - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEquivalenceComparer.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEquivalenceComparer.cs deleted file mode 100644 index 10bdd63f2c0..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertEquivalenceComparer.cs +++ /dev/null @@ -1,104 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable IDE0290 // Use primary constructor - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#pragma warning disable CS8767 -#endif - -using System.Collections; -using System.Collections.Generic; - -namespace Xunit -{ - /// - /// An implementation of that uses the same logic - /// from . - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - class AssertEquivalenceComparer : IEqualityComparer - { - readonly bool strict; - - /// - /// Initializes a new instance of the class. - /// - /// A flag indicating whether comparisons should be strict. - public AssertEquivalenceComparer(bool strict) => - this.strict = strict; - - /// - public new bool Equals( -#if XUNIT_NULLABLE - object? x, - object? y) -#else - object x, - object y) -#endif - { - Assert.Equivalent(x, y, strict); - return true; - } - - /// - public int GetHashCode(object obj) => - obj?.GetHashCode() ?? 0; - } - - /// - /// An implementation of that uses the same logic - /// from . - /// - /// The item type being compared - /// - /// A generic version of this is provided so that it can be used with - /// - /// to ensure strict ordering of collections while doing equivalence comparisons for - /// the items inside the collection, per . - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - class AssertEquivalenceComparer : IEqualityComparer - { - readonly bool strict; - - /// - /// Initializes a new instance of the class. - /// - /// A flag indicating whether comparisons should be strict. - public AssertEquivalenceComparer(bool strict) => - this.strict = strict; - - /// - public bool Equals( -#if XUNIT_NULLABLE - T? x, - T? y) -#else - T x, - T y) -#endif - { - Assert.Equivalent(x, y, strict); - return true; - } - - /// - public int GetHashCode(T obj) => - obj?.GetHashCode() ?? 0; - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper.cs index 82d4f5c9918..f720d5ec419 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper.cs @@ -1,21 +1,36 @@ -#pragma warning disable IDE0057 // Use range operator +#pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable CA2263 // Prefer generic overload when type is known +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0019 // Use pattern matching +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0305 // Simplify collection initialization +#pragma warning disable IDE0161 // Convert to file-scoped namespace +#pragma warning disable IDE0270 // Null check can be simplified +#pragma warning disable IDE0300 // Collection initialization can be simplified #if XUNIT_NULLABLE #nullable enable #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE +#pragma warning disable CS8600 +#pragma warning disable CS8601 #pragma warning disable CS8603 +#pragma warning disable CS8604 +#pragma warning disable CS8621 #pragma warning disable CS8625 #pragma warning disable CS8767 #endif using System; +using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; +using System.Reflection; using System.Text; using Xunit.Sdk; @@ -23,21 +38,13 @@ using System.Diagnostics.CodeAnalysis; #endif -#if NET8_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER using System.Threading.Tasks; #endif namespace Xunit.Internal { - /// - /// INTERNAL CLASS. DO NOT USE. - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - static partial class AssertHelper + internal static class AssertHelper { static readonly Dictionary encodings = new Dictionary { @@ -52,170 +59,153 @@ static partial class AssertHelper { '\\', @"\\" }, // Backslash }; - internal static (int start, int end) GetStartEndForString( #if XUNIT_NULLABLE - string? value, + static readonly ConcurrentDictionary>> gettersByType = new ConcurrentDictionary>>(); #else - string value, + static readonly ConcurrentDictionary>> gettersByType = new ConcurrentDictionary>>(); #endif - int index) - { - if (value is null) - return (0, 0); - - if (ArgumentFormatter.MaxStringLength == int.MaxValue) - return (0, value.Length); - - var halfMaxLength = ArgumentFormatter.MaxStringLength / 2; - var start = Math.Max(index - halfMaxLength, 0); - var end = Math.Min(start + ArgumentFormatter.MaxStringLength, value.Length); - start = Math.Max(end - ArgumentFormatter.MaxStringLength, 0); - - return (start, end); - } - - internal static bool IsCompilerGenerated(Type type) => - type.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute"); - - /// - public static IReadOnlyList<(string Prefix, string Member)> ParseExclusionExpressions(params string[] exclusionExpressions) - { - var result = new List<(string Prefix, string Member)>(); - foreach (var expression in exclusionExpressions ?? throw new ArgumentNullException(nameof(exclusionExpressions))) - { - if (expression is null || expression.Length is 0) - throw new ArgumentException("Null/empty expressions are not valid.", nameof(exclusionExpressions)); - - var lastDotIdx = expression.LastIndexOf('.'); - if (lastDotIdx == 0) - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - "Expression '{0}' is not valid. Expressions may not start with a period.", - expression - ), - nameof(exclusionExpressions) - ); - - if (lastDotIdx == expression.Length - 1) - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - "Expression '{0}' is not valid. Expressions may not end with a period.", - expression - ), - nameof(exclusionExpressions) - ); - - if (lastDotIdx < 0) - result.Add((string.Empty, expression)); - else - result.Add((expression.Substring(0, lastDotIdx), expression.Substring(lastDotIdx + 1))); - } + const string fileSystemInfoFqn = "System.IO.FileSystemInfo, System.Runtime"; +#if XUNIT_NULLABLE + static readonly Lazy fileSystemInfoTypeInfo = new Lazy(() => Type.GetType(fileSystemInfoFqn)?.GetTypeInfo()); + static readonly Lazy fileSystemInfoFullNameProperty = new Lazy(() => Type.GetType(fileSystemInfoFqn)?.GetTypeInfo().GetDeclaredProperty("FullName")); +#else + static readonly Lazy fileSystemInfoTypeInfo = new Lazy(() => GetTypeInfo(fileSystemInfoFqn)?.GetTypeInfo()); + static readonly Lazy fileSystemInfoFullNameProperty = new Lazy(() => fileSystemInfoTypeInfo.Value?.GetDeclaredProperty("FullName")); +#endif - return result; - } +#pragma warning disable IDE0200 // The lambda expression here is conditionally necessary, but the analyzer isn't smart enough to know that - /// - public static IReadOnlyList<(string Prefix, string Member)> ParseExclusionExpressions(params LambdaExpression[] exclusionExpressions) + static readonly Lazy getAssemblies = new Lazy(() => { - var result = new List<(string Prefix, string Member)>(); - - foreach (var expression in exclusionExpressions ?? throw new ArgumentNullException(nameof(exclusionExpressions))) +#if NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 + var appDomainType = Type.GetType("System.AppDomain"); + if (appDomainType != null) { - if (expression is null) - throw new ArgumentException("Null expression is not valid.", nameof(exclusionExpressions)); - - var memberExpression = default(MemberExpression); - - // The incoming expressions are T => object?, so any boxed struct starts with a conversion - if (expression.Body.NodeType == ExpressionType.Convert && expression.Body is UnaryExpression unaryExpression) - memberExpression = unaryExpression.Operand as MemberExpression; - else - memberExpression = expression.Body as MemberExpression; - - if (memberExpression is null) - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - "Expression '{0}' is not supported. Only property or field expressions from the lambda parameter are supported.", - expression - ), - nameof(exclusionExpressions) - ); - - var pieces = new LinkedList(); - - while (true) + var currentDomainProperty = appDomainType.GetRuntimeProperty("CurrentDomain"); + if (currentDomainProperty != null) { - pieces.AddFirst(memberExpression.Member.Name); - - if (memberExpression.Expression?.NodeType == ExpressionType.Parameter) - break; - - memberExpression = memberExpression.Expression as MemberExpression; - - if (memberExpression is null) - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - "Expression '{0}' is not supported. Only property or field expressions from the lambda parameter are supported.", - expression - ), - nameof(exclusionExpressions) - ); + var getAssembliesMethod = appDomainType.GetRuntimeMethods().FirstOrDefault(m => m.Name == "GetAssemblies"); + if (getAssembliesMethod != null) + { + var currentDomain = currentDomainProperty.GetValue(null); + if (currentDomain != null) + { + var getAssembliesArgs = getAssembliesMethod.GetParameters().Length == 1 ? new object[] { false } : new object[0]; + var assemblies = getAssembliesMethod.Invoke(currentDomain, getAssembliesArgs) as Assembly[]; + if (assemblies != null) + return assemblies; + } + } } + } - if (pieces.Last is null) - continue; - - var member = pieces.Last.Value; - pieces.RemoveLast(); + return new Assembly[0]; +#else + return AppDomain.CurrentDomain.GetAssemblies(); +#endif + }); - var prefix = string.Join(".", pieces.ToArray()); - result.Add((prefix, member)); - } +#pragma warning restore IDE0200 // Remove unnecessary lambda expression - return result; - } +#if !XUNIT_AOT + static readonly Type objectType = typeof(object); + static readonly TypeInfo objectTypeInfo = objectType.GetTypeInfo(); +#endif + static readonly IEqualityComparer referenceEqualityComparer = new ReferenceEqualityComparer(); - internal static string ShortenAndEncodeString( + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111: Method 'lambda expression' with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method.", Justification = "The lambda will only be called by the value in the type parameter, which has the same requirements.")] #if XUNIT_NULLABLE - string? value, + static Dictionary> GetGettersForType([DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) => #else - string value, + static Dictionary> GetGettersForType([DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) => +#endif + gettersByType.GetOrAdd(type, + ([DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type _type) => + { + var fieldGetters = + _type + .GetRuntimeFields() + .Where(f => f.IsPublic && !f.IsStatic) +#if XUNIT_NULLABLE + .Select(f => new { name = f.Name, getter = (Func)f.GetValue }); +#else + .Select(f => new { name = f.Name, getter = (Func)f.GetValue }); #endif - int index, - out int pointerIndent) - { - var (start, end) = GetStartEndForString(value, index); - - return ShortenString(value, start, end, index, out pointerIndent); - } + var propertyGetters = + _type + .GetRuntimeProperties() + .Where(p => + p.CanRead + && p.GetMethod != null + && p.GetMethod.IsPublic + && !p.GetMethod.IsStatic +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + && !p.GetMethod.ReturnType.IsByRefLike +#endif + && p.GetIndexParameters().Length == 0 + && !p.GetCustomAttributes(typeof(ObsoleteAttribute)).Any() + && !p.GetMethod.GetCustomAttributes(typeof(ObsoleteAttribute)).Any() + ) #if XUNIT_NULLABLE - internal static string ShortenAndEncodeString(string? value) => + .Select(p => new { name = p.Name, getter = (Func)p.GetValue }); #else - internal static string ShortenAndEncodeString(string value) => + .Select(p => new { name = p.Name, getter = (Func)p.GetValue }); #endif - ShortenAndEncodeString(value, 0, out var _); + return + fieldGetters + .Concat(propertyGetters) + .ToDictionary(g => g.name, g => g.getter); + }); + +#if !XUNIT_AOT #if XUNIT_NULLABLE - internal static string ShortenAndEncodeStringEnd(string? value) => + static TypeInfo? GetTypeInfo(string typeName) #else - internal static string ShortenAndEncodeStringEnd(string value) => + static TypeInfo GetTypeInfo(string typeName) #endif - ShortenAndEncodeString(value, (value?.Length - 1) ?? 0, out var _); + { + try + { + foreach (var assembly in getAssemblies.Value) + { + var type = assembly.GetType(typeName); + if (type != null) + return type.GetTypeInfo(); + } - internal static string ShortenString( + return null; + } + catch (Exception ex) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Fatal error: Exception occurred while trying to retrieve type '{0}'", typeName), ex); + } + } +#endif + + internal static bool IsCompilerGenerated(Type type) => + type.GetTypeInfo().CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute"); + + internal static string ShortenAndEncodeString( #if XUNIT_NULLABLE string? value, #else string value, #endif - int start, - int end, int index, out int pointerIndent) { @@ -225,9 +215,10 @@ internal static string ShortenString( return "null"; } - // Set the initial buffer to include the possibility of quotes and ellipses, plus a few extra - // characters for encoding before needing reallocation. - var printedValue = new StringBuilder(end - start + 10); + var start = Math.Max(index - 20, 0); + var end = Math.Min(start + 41, value.Length); + start = Math.Max(end - 41, 0); + var printedValue = new StringBuilder(100); pointerIndent = 0; if (start > 0) @@ -244,7 +235,13 @@ internal static string ShortenString( var c = value[idx]; var paddingLength = 1; - if (encodings.TryGetValue(c, out var encoding)) +#if XUNIT_NULLABLE + string? encoding; +#else + string encoding; +#endif + + if (encodings.TryGetValue(c, out encoding)) { printedValue.Append(encoding); paddingLength = encoding.Length; @@ -264,7 +261,29 @@ internal static string ShortenString( return printedValue.ToString(); } -#if NET8_0_OR_GREATER +#if XUNIT_NULLABLE + internal static string ShortenAndEncodeString(string? value) +#else + internal static string ShortenAndEncodeString(string value) +#endif + { + int pointerIndent; + + return ShortenAndEncodeString(value, 0, out pointerIndent); + } + +#if XUNIT_NULLABLE + internal static string ShortenAndEncodeStringEnd(string? value) +#else + internal static string ShortenAndEncodeStringEnd(string value) +#endif + { + int pointerIndent; + + return ShortenAndEncodeString(value, (value?.Length - 1) ?? 0, out pointerIndent); + } + +#if NETCOREAPP3_0_OR_GREATER #if XUNIT_NULLABLE [return: NotNullIfNotNull(nameof(data))] @@ -289,6 +308,447 @@ static IEnumerable ToEnumerableImpl(IAsyncEnumerable data) } } +#endif + + static bool TryConvert( + object value, + Type targetType, +#if XUNIT_NULLABLE + [NotNullWhen(true)] out object? converted) +#else + out object converted) +#endif + { + try + { + converted = Convert.ChangeType(value, targetType, CultureInfo.CurrentCulture); + return converted != null; + } + catch (InvalidCastException) + { + converted = null; + return false; + } + } + +#if !XUNIT_AOT +#if XUNIT_NULLABLE + static object? UnwrapLazy( + object? value, +#else + static object UnwrapLazy( + object value, +#endif + out Type valueType, + out TypeInfo valueTypeInfo) + { + if (value == null) + { + valueType = objectType; + valueTypeInfo = objectTypeInfo; + + return null; + } + + valueType = value.GetType(); + valueTypeInfo = valueType.GetTypeInfo(); + + if (valueTypeInfo.IsGenericType && valueTypeInfo.GetGenericTypeDefinition() == typeof(Lazy<>)) + { + var property = valueType.GetRuntimeProperty("Value"); + if (property != null) + { + valueType = valueTypeInfo.GenericTypeArguments[0]; + valueTypeInfo = valueType.GetTypeInfo(); + return property.GetValue(value); + } + } + + return value; + } +#endif + +#if XUNIT_NULLABLE + public static EquivalentException? VerifyEquivalence( + object? expected, + object? actual, +#else + public static EquivalentException VerifyEquivalence( + object expected, + object actual, +#endif + bool strict) => + VerifyEquivalence(expected, actual, strict, string.Empty, new HashSet(referenceEqualityComparer), new HashSet(referenceEqualityComparer), 1); + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalence<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] U>( + T? expected, + U? actual, +#else + static EquivalentException VerifyEquivalence<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties)] U>( + T expected, + U actual, +#endif + bool strict, + string prefix, + HashSet expectedRefs, + HashSet actualRefs, + int depth) + { + // Check for exceeded depth + if (depth == 50) + return EquivalentException.ForExceededDepth(50, prefix); + +#if !XUNIT_AOT + // Unwrap Lazy + Type expectedType; + TypeInfo expectedTypeInfo; + expected = UnwrapLazy(expected, out expectedType, out expectedTypeInfo); + + Type actualType; + TypeInfo actualTypeInfo; + actual = UnwrapLazy(actual, out actualType, out actualTypeInfo); +#endif + + // Check for null equivalence + if (expected == null) + return + actual == null + ? null + : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + + if (actual == null) + return EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + + // Check for identical references + if (ReferenceEquals(expected, actual)) + return null; + + // Prevent circular references + if (expectedRefs.Contains(expected)) + return EquivalentException.ForCircularReference(string.Format(CultureInfo.CurrentCulture, "{0}.{1}", nameof(expected), prefix)); + + if (actualRefs.Contains(actual)) + return EquivalentException.ForCircularReference(string.Format(CultureInfo.CurrentCulture, "{0}.{1}", nameof(actual), prefix)); + + try + { +#if XUNIT_AOT + var expectedType = expected.GetType(); + var expectedTypeInfo = expectedType.GetTypeInfo(); + var actualType = actual.GetType(); + var actualTypeInfo = actualType.GetTypeInfo(); +#endif + expectedRefs.Add(expected); + actualRefs.Add(actual); + + // Primitive types, enums and strings should just fall back to their Equals implementation + if (expectedTypeInfo.IsPrimitive || expectedTypeInfo.IsEnum || expectedType == typeof(string) || expectedType == typeof(decimal) || expectedType == typeof(Guid)) + return VerifyEquivalenceIntrinsics(expected, actual, prefix); + + // DateTime and DateTimeOffset need to be compared via IComparable (because of a circular + // reference via the Date property). + if (expectedType == typeof(DateTime) || expectedType == typeof(DateTimeOffset)) + return VerifyEquivalenceDateTime(expected, actual, prefix); + + // FileSystemInfo has a recursion problem when getting the root directory + if (fileSystemInfoTypeInfo.Value != null) + if (fileSystemInfoTypeInfo.Value.IsAssignableFrom(expectedTypeInfo) && fileSystemInfoTypeInfo.Value.IsAssignableFrom(actualTypeInfo)) + return VerifyEquivalenceFileSystemInfo(expected, actual, strict, prefix, expectedRefs, actualRefs, depth); + + // Uri can throw for relative URIs + var expectedUri = expected as Uri; + var actualUri = actual as Uri; + if (expectedUri != null && actualUri != null) + return VerifyEquivalenceUri(expectedUri, actualUri, prefix); + +#if !XUNIT_AOT + // IGrouping is special, since it implements IEnumerable + var expectedGroupingTypes = ArgumentFormatter.GetGroupingTypes(expected); + if (expectedGroupingTypes != null) + { + var actualGroupingTypes = ArgumentFormatter.GetGroupingTypes(actual); + if (actualGroupingTypes != null) + return VerifyEquivalenceGroupings(expected, expectedGroupingTypes, actual, actualGroupingTypes, strict); + } +#endif + + // Enumerables? Check equivalence of individual members + var enumerableExpected = expected as IEnumerable; + var enumerableActual = actual as IEnumerable; + if (enumerableExpected != null && enumerableActual != null) + return VerifyEquivalenceEnumerable(enumerableExpected, enumerableActual, strict, prefix, expectedRefs, actualRefs, depth); + + return VerifyEquivalenceReference(expected, actual, strict, prefix, expectedRefs, actualRefs, depth); + } + finally + { + expectedRefs.Remove(expected); + actualRefs.Remove(actual); + } + } + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceDateTime( +#else + static EquivalentException VerifyEquivalenceDateTime( +#endif + object expected, + object actual, + string prefix) + { + try + { + var expectedComparable = expected as IComparable; + if (expectedComparable != null) + return + expectedComparable.CompareTo(actual) == 0 + ? null + : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + } + catch (Exception ex) + { + return EquivalentException.ForMemberValueMismatch(expected, actual, prefix, ex); + } + + try + { + var actualComparable = actual as IComparable; + if (actualComparable != null) + return + actualComparable.CompareTo(expected) == 0 + ? null + : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + } + catch (Exception ex) + { + return EquivalentException.ForMemberValueMismatch(expected, actual, prefix, ex); + } + + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + "VerifyEquivalenceDateTime was given non-DateTime(Offset) objects; typeof(expected) = {0}, typeof(actual) = {1}", + ArgumentFormatter.FormatTypeName(expected.GetType()), + ArgumentFormatter.FormatTypeName(actual.GetType()) + ) + ); + } + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceEnumerable( +#else + static EquivalentException VerifyEquivalenceEnumerable( +#endif + IEnumerable expected, + IEnumerable actual, + bool strict, + string prefix, + HashSet expectedRefs, + HashSet actualRefs, + int depth) + { +#if XUNIT_NULLABLE + var expectedValues = expected.Cast().ToList(); + var actualValues = actual.Cast().ToList(); +#else + var expectedValues = expected.Cast().ToList(); + var actualValues = actual.Cast().ToList(); +#endif + var actualOriginalValues = actualValues.ToList(); + + // Walk the list of expected values, and look for actual values that are equivalent + foreach (var expectedValue in expectedValues) + { + var actualIdx = 0; + + for (; actualIdx < actualValues.Count; ++actualIdx) + if (VerifyEquivalence(expectedValue, actualValues[actualIdx], strict, "", expectedRefs, actualRefs, depth) == null) + break; + + if (actualIdx == actualValues.Count) + return EquivalentException.ForMissingCollectionValue(expectedValue, actualOriginalValues, prefix); + + actualValues.RemoveAt(actualIdx); + } + + if (strict && actualValues.Count != 0) + return EquivalentException.ForExtraCollectionValue(expectedValues, actualOriginalValues, actualValues, prefix); + + return null; + } + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceFileSystemInfo( +#else + static EquivalentException VerifyEquivalenceFileSystemInfo( +#endif + object expected, + object actual, + bool strict, + string prefix, + HashSet expectedRefs, + HashSet actualRefs, + int depth) + { + if (fileSystemInfoFullNameProperty.Value == null) + throw new InvalidOperationException("Could not find 'FullName' property on type 'System.IO.FileSystemInfo'"); + + var expectedType = expected.GetType(); + var actualType = actual.GetType(); + + if (expectedType != actualType) + return EquivalentException.ForMismatchedTypes(expectedType, actualType, prefix); + + var fullName = fileSystemInfoFullNameProperty.Value.GetValue(expected); + var expectedAnonymous = new { FullName = fullName }; + + return VerifyEquivalenceReference(expectedAnonymous, actual, strict, prefix, expectedRefs, actualRefs, depth); + } + +#if !XUNIT_AOT +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceGroupings( +#else + static EquivalentException VerifyEquivalenceGroupings( +#endif + object expected, + Type[] expectedGroupingTypes, + object actual, + Type[] actualGroupingTypes, + bool strict) + { + var expectedKey = typeof(IGrouping<,>).MakeGenericType(expectedGroupingTypes).GetRuntimeProperty("Key")?.GetValue(expected); + var actualKey = typeof(IGrouping<,>).MakeGenericType(actualGroupingTypes).GetRuntimeProperty("Key")?.GetValue(actual); + + var keyException = VerifyEquivalence(expectedKey, actualKey, strict: false); + if (keyException != null) + return keyException; + + var toArrayMethod = + typeof(Enumerable) + .GetRuntimeMethods() + .FirstOrDefault(m => m.IsStatic && m.IsPublic && m.Name == nameof(Enumerable.ToArray) && m.GetParameters().Length == 1); + + if (toArrayMethod == null) + throw new InvalidOperationException("Could not find method Enumerable.ToArray<>"); + + // Convert everything to an array so it doesn't endlessly loop on the IGrouping<> test + var expectedToArrayMethod = toArrayMethod.MakeGenericMethod(expectedGroupingTypes[1]); + var expectedValues = expectedToArrayMethod.Invoke(null, new[] { expected }); + + var actualToArrayMethod = toArrayMethod.MakeGenericMethod(actualGroupingTypes[1]); + var actualValues = actualToArrayMethod.Invoke(null, new[] { actual }); + + if (VerifyEquivalence(expectedValues, actualValues, strict) != null) + throw EquivalentException.ForGroupingWithMismatchedValues(expectedValues, actualValues, ArgumentFormatter.Format(expectedKey)); + + return null; + } +#endif + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceIntrinsics( +#else + static EquivalentException VerifyEquivalenceIntrinsics( +#endif + object expected, + object actual, + string prefix) + { + var result = expected.Equals(actual); + + var converted = default(object); + if (!result && TryConvert(expected, actual.GetType(), out converted)) + result = converted.Equals(actual); + if (!result && TryConvert(actual, expected.GetType(), out converted)) + result = converted.Equals(expected); + + return result ? null : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "We need to use the runtime type for getting the getters as we can't recursively preserve them. Any members that are trimmed were not touched by the test and likely are not important for equivalence.")] +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceReference( +#else + static EquivalentException VerifyEquivalenceReference( +#endif + object expected, + object actual, + bool strict, + string prefix, + HashSet expectedRefs, + HashSet actualRefs, + int depth) + { + Assert.GuardArgumentNotNull(nameof(prefix), prefix); + + var prefixDot = prefix.Length == 0 ? string.Empty : prefix + "."; + + // Enumerate over public instance fields and properties and validate equivalence + var expectedGetters = GetGettersForType(expected.GetType()); + var actualGetters = GetGettersForType(actual.GetType()); + + if (strict && expectedGetters.Count != actualGetters.Count) + return EquivalentException.ForMemberListMismatch(expectedGetters.Keys, actualGetters.Keys, prefixDot); + + foreach (var kvp in expectedGetters) + { +#if XUNIT_NULLABLE + Func? actualGetter; +#else + Func actualGetter; +#endif + + if (!actualGetters.TryGetValue(kvp.Key, out actualGetter)) + return EquivalentException.ForMemberListMismatch(expectedGetters.Keys, actualGetters.Keys, prefixDot); + + var expectedMemberValue = kvp.Value(expected); + var actualMemberValue = actualGetter(actual); + + var ex = VerifyEquivalence(expectedMemberValue, actualMemberValue, strict, prefixDot + kvp.Key, expectedRefs, actualRefs, depth + 1); + if (ex != null) + return ex; + } + + return null; + } + +#if XUNIT_NULLABLE + static EquivalentException? VerifyEquivalenceUri( +#else + static EquivalentException VerifyEquivalenceUri( +#endif + Uri expected, + Uri actual, + string prefix) + { + if (expected.OriginalString != actual.OriginalString) + return EquivalentException.ForMemberValueMismatch(expected, actual, prefix); + + return null; + } + +#if NETCOREAPP3_0_OR_GREATER + static void WaitForValueTask(ValueTask valueTask) { var valueTaskAwaiter = valueTask.GetAwaiter(); @@ -309,6 +769,26 @@ static T WaitForValueTask(ValueTask valueTask) return Task.Run(valueTask.AsTask).GetAwaiter().GetResult(); } -#endif // NET8_0_OR_GREATER +#endif + } + + sealed class ReferenceEqualityComparer : IEqualityComparer + { + public new bool Equals( +#if XUNIT_NULLABLE + object? x, + object? y) => +#else + object x, + object y) => +#endif + ReferenceEquals(x, y); + +#if XUNIT_NULLABLE + public int GetHashCode([DisallowNull] object obj) => +#else + public int GetHashCode(object obj) => +#endif + obj.GetHashCode(); } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper_reflection.cs deleted file mode 100644 index 5cf3531ca93..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertHelper_reflection.cs +++ /dev/null @@ -1,576 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable CA1031 // Do not catch general exception types -#pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0300 // Collection initialization can be simplified -#pragma warning disable IDE0301 // Simplify collection initialization - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#pragma warning disable CS8604 -#pragma warning disable CS8621 -#pragma warning disable CS8625 -#pragma warning disable CS8767 -#endif - -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using Xunit.Sdk; - -#if XUNIT_NULLABLE -using System.Diagnostics.CodeAnalysis; -#endif - -namespace Xunit.Internal -{ - partial class AssertHelper - { -#if XUNIT_NULLABLE - static readonly Lazy fileSystemInfoType = new Lazy(() => GetTypeByName("System.IO.FileSystemInfo")); - static readonly Lazy fileSystemInfoFullNameProperty = new Lazy(() => fileSystemInfoType.Value?.GetProperty("FullName")); - static readonly ConcurrentDictionary>> gettersByType = new ConcurrentDictionary>>(); -#else - static readonly Lazy fileSystemInfoType = new Lazy(() => GetTypeByName("System.IO.FileSystemInfo")); - static readonly Lazy fileSystemInfoFullNameProperty = new Lazy(() => fileSystemInfoType.Value?.GetProperty("FullName")); - static readonly ConcurrentDictionary>> gettersByType = new ConcurrentDictionary>>(); -#endif - - static readonly IReadOnlyList<(string Prefix, string Member)> emptyExclusions = Array.Empty<(string Prefix, string Member)>(); - static readonly Lazy getAssemblies = new Lazy(AppDomain.CurrentDomain.GetAssemblies); - static readonly Lazy maxCompareDepth = new Lazy(() => - { - var stringValue = Environment.GetEnvironmentVariable(EnvironmentVariables.AssertEquivalentMaxDepth); - if (stringValue is null || !int.TryParse(stringValue, out var intValue) || intValue <= 0) - return EnvironmentVariables.Defaults.AssertEquivalentMaxDepth; - return intValue; - }); - static readonly Type objectType = typeof(object); - static readonly IEqualityComparer referenceEqualityComparer = new ReferenceEqualityComparer(); - -#if XUNIT_NULLABLE - static Dictionary> GetGettersForType(Type type) => -#else - static Dictionary> GetGettersForType(Type type) => -#endif - gettersByType.GetOrAdd(type, _type => - { - var fieldGetters = - _type - .GetRuntimeFields() - .Where(f => f.IsPublic && !f.IsStatic) -#if XUNIT_NULLABLE - .Select(f => new { name = f.Name, getter = (Func)f.GetValue }); -#else - .Select(f => new { name = f.Name, getter = (Func)f.GetValue }); -#endif - - var propertyGetters = - _type - .GetRuntimeProperties() - .Where(p => - p.CanRead - && p.GetMethod != null - && p.GetMethod.IsPublic - && !p.GetMethod.IsStatic -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - && !p.GetMethod.ReturnType.IsByRefLike -#endif - && p.GetIndexParameters().Length == 0 - && !p.GetCustomAttributes().Any() - && !p.GetMethod.GetCustomAttributes().Any() - ) - .GroupBy(p => p.Name) - .Select(group => - { - // When there is more than one property with the same name, we take the one from - // the most derived class. Start assuming the first one is the correct one, and then - // visit each in turn to see whether it's more derived or not. - var targetProperty = group.First(); - - foreach (var candidateProperty in group.Skip(1)) - for (var candidateType = candidateProperty.DeclaringType?.BaseType; candidateType != null; candidateType = candidateType.BaseType) - if (targetProperty.DeclaringType == candidateType) - { - targetProperty = candidateProperty; - break; - } - -#if XUNIT_NULLABLE - return new { name = targetProperty.Name, getter = (Func)targetProperty.GetValue }; -#else - return new { name = targetProperty.Name, getter = (Func)targetProperty.GetValue }; -#endif - }); - - return - fieldGetters - .Concat(propertyGetters) - .ToDictionary(g => g.name, g => g.getter); - }); - -#if XUNIT_NULLABLE - static Type? GetTypeByName(string typeName) -#else - static Type GetTypeByName(string typeName) -#endif - { - try - { - foreach (var assembly in getAssemblies.Value) - { - var type = assembly.GetType(typeName); - if (type != null) - return type; - } - - return null; - } - catch (Exception ex) - { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Fatal error: Exception occurred while trying to retrieve type '{0}'", typeName), ex); - } - } - - static bool TryConvert( - object value, - Type targetType, -#if XUNIT_NULLABLE - [NotNullWhen(true)] out object? converted) -#else - out object converted) -#endif - { - try - { - converted = Convert.ChangeType(value, targetType, CultureInfo.CurrentCulture); - return converted != null; - } - catch (InvalidCastException) - { - converted = null; - return false; - } - } - -#if XUNIT_NULLABLE - static object? UnwrapLazy( - object? value, -#else - static object UnwrapLazy( - object value, -#endif - out Type valueType) - { - if (value == null) - { - valueType = objectType; - - return null; - } - - valueType = value.GetType(); - - if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Lazy<>)) - { - var property = valueType.GetRuntimeProperty("Value"); - if (property != null) - { - valueType = valueType.GenericTypeArguments[0]; - return property.GetValue(value); - } - } - - return value; - } - - /// -#if XUNIT_NULLABLE - public static EquivalentException? VerifyEquivalence( - object? expected, - object? actual, -#else - public static EquivalentException VerifyEquivalence( - object expected, - object actual, -#endif - bool strict, -#if XUNIT_NULLABLE - IReadOnlyList<(string Prefix, string Member)>? exclusions = null) => -#else - IReadOnlyList<(string Prefix, string Member)> exclusions = null) => -#endif - VerifyEquivalence( - expected, - actual, - strict, - string.Empty, - new HashSet(referenceEqualityComparer), - new HashSet(referenceEqualityComparer), - 1, - exclusions ?? emptyExclusions - ); - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalence( - object? expected, - object? actual, -#else - static EquivalentException VerifyEquivalence( - object expected, - object actual, -#endif - bool strict, - string prefix, - HashSet expectedRefs, - HashSet actualRefs, - int depth, - IReadOnlyList<(string Prefix, string Member)> exclusions) - { - // Check for exceeded depth - if (depth > maxCompareDepth.Value) - return EquivalentException.ForExceededDepth(maxCompareDepth.Value, prefix); - - // Unwrap Lazy - expected = UnwrapLazy(expected, out var expectedType); - actual = UnwrapLazy(actual, out var actualType); - - // Check for null equivalence - if (expected == null) - return - actual == null - ? null - : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - - if (actual == null) - return EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - - // Check for identical references - if (ReferenceEquals(expected, actual)) - return null; - - // Prevent circular references - if (expectedRefs.Contains(expected)) - return EquivalentException.ForCircularReference(string.Format(CultureInfo.CurrentCulture, "{0}.{1}", nameof(expected), prefix)); - - if (actualRefs.Contains(actual)) - return EquivalentException.ForCircularReference(string.Format(CultureInfo.CurrentCulture, "{0}.{1}", nameof(actual), prefix)); - - try - { - expectedRefs.Add(expected); - actualRefs.Add(actual); - - // Primitive types, enums and strings should just fall back to their Equals implementation - if (expectedType.IsPrimitive || expectedType.IsEnum || expectedType == typeof(string) || expectedType == typeof(decimal) || expectedType == typeof(Guid)) - return VerifyEquivalenceIntrinsics(expected, actual, prefix); - - // DateTime and DateTimeOffset need to be compared via IComparable (because of a circular - // reference via the Date property). - if (expectedType == typeof(DateTime) || expectedType == typeof(DateTimeOffset)) - return VerifyEquivalenceDateTime(expected, actual, prefix); - - // FileSystemInfo has a recursion problem when getting the root directory - if (fileSystemInfoType.Value != null) - if (fileSystemInfoType.Value.IsAssignableFrom(expectedType) && fileSystemInfoType.Value.IsAssignableFrom(actualType)) - return VerifyEquivalenceFileSystemInfo(expected, actual, strict, prefix, expectedRefs, actualRefs, depth, exclusions); - - // Uri can throw for relative URIs - var expectedUri = expected as Uri; - var actualUri = actual as Uri; - if (expectedUri != null && actualUri != null) - return VerifyEquivalenceUri(expectedUri, actualUri, prefix); - - // IGrouping is special, since it implements IEnumerable - var expectedGroupingTypes = ArgumentFormatter.GetGroupingTypes(expected); - if (expectedGroupingTypes != null) - { - var actualGroupingTypes = ArgumentFormatter.GetGroupingTypes(actual); - if (actualGroupingTypes != null) - return VerifyEquivalenceGroupings(expected, expectedGroupingTypes, actual, actualGroupingTypes, strict); - } - - // Enumerables? Check equivalence of individual members - if (expected is IEnumerable enumerableExpected && actual is IEnumerable enumerableActual) - return VerifyEquivalenceEnumerable(enumerableExpected, enumerableActual, strict, prefix, expectedRefs, actualRefs, depth, exclusions); - - return VerifyEquivalenceReference(expected, actual, strict, prefix, expectedRefs, actualRefs, depth, exclusions); - } - finally - { - expectedRefs.Remove(expected); - actualRefs.Remove(actual); - } - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceDateTime( -#else - static EquivalentException VerifyEquivalenceDateTime( -#endif - object expected, - object actual, - string prefix) - { - try - { - if (expected is IComparable expectedComparable) - return - expectedComparable.CompareTo(actual) == 0 - ? null - : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - } - catch (Exception ex) - { - return EquivalentException.ForMemberValueMismatch(expected, actual, prefix, ex); - } - - try - { - if (actual is IComparable actualComparable) - return - actualComparable.CompareTo(expected) == 0 - ? null - : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - } - catch (Exception ex) - { - return EquivalentException.ForMemberValueMismatch(expected, actual, prefix, ex); - } - - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - "VerifyEquivalenceDateTime was given non-DateTime(Offset) objects; typeof(expected) = {0}, typeof(actual) = {1}", - ArgumentFormatter.FormatTypeName(expected.GetType()), - ArgumentFormatter.FormatTypeName(actual.GetType()) - ) - ); - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceEnumerable( -#else - static EquivalentException VerifyEquivalenceEnumerable( -#endif - IEnumerable expected, - IEnumerable actual, - bool strict, - string prefix, - HashSet expectedRefs, - HashSet actualRefs, - int depth, - IReadOnlyList<(string Prefix, string Member)> exclusions) - { -#if XUNIT_NULLABLE - var expectedValues = expected.Cast().ToList(); - var actualValues = actual.Cast().ToList(); -#else - var expectedValues = expected.Cast().ToList(); - var actualValues = actual.Cast().ToList(); -#endif - var actualOriginalValues = actualValues.ToList(); - var collectionPrefix = prefix.Length == 0 ? string.Empty : prefix + "[]"; - - // Walk the list of expected values, and look for actual values that are equivalent - foreach (var expectedValue in expectedValues) - { - var actualIdx = 0; - - for (; actualIdx < actualValues.Count; ++actualIdx) - if (VerifyEquivalence(expectedValue, actualValues[actualIdx], strict, collectionPrefix, expectedRefs, actualRefs, depth, exclusions) == null) - break; - - if (actualIdx == actualValues.Count) - return EquivalentException.ForMissingCollectionValue(expectedValue, actualOriginalValues, collectionPrefix); - - actualValues.RemoveAt(actualIdx); - } - - if (strict && actualValues.Count != 0) - return EquivalentException.ForExtraCollectionValue(expectedValues, actualOriginalValues, actualValues, collectionPrefix); - - return null; - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceFileSystemInfo( -#else - static EquivalentException VerifyEquivalenceFileSystemInfo( -#endif - object expected, - object actual, - bool strict, - string prefix, - HashSet expectedRefs, - HashSet actualRefs, - int depth, - IReadOnlyList<(string Prefix, string Member)> exclusions) - { - if (fileSystemInfoFullNameProperty.Value == null) - throw new InvalidOperationException("Could not find 'FullName' property on type 'System.IO.FileSystemInfo'"); - - var expectedType = expected.GetType(); - var actualType = actual.GetType(); - - if (expectedType != actualType) - return EquivalentException.ForMismatchedTypes(expectedType, actualType, prefix); - - var fullName = fileSystemInfoFullNameProperty.Value.GetValue(expected); - var expectedAnonymous = new { FullName = fullName }; - - return VerifyEquivalenceReference(expectedAnonymous, actual, strict, prefix, expectedRefs, actualRefs, depth, exclusions); - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceGroupings( -#else - static EquivalentException VerifyEquivalenceGroupings( -#endif - object expected, - Type[] expectedGroupingTypes, - object actual, - Type[] actualGroupingTypes, - bool strict) - { - var expectedKey = typeof(IGrouping<,>).MakeGenericType(expectedGroupingTypes).GetRuntimeProperty("Key")?.GetValue(expected); - var actualKey = typeof(IGrouping<,>).MakeGenericType(actualGroupingTypes).GetRuntimeProperty("Key")?.GetValue(actual); - - var keyException = VerifyEquivalence(expectedKey, actualKey, strict: false); - if (keyException != null) - return keyException; - - var toArrayMethod = - typeof(Enumerable) - .GetRuntimeMethods() - .FirstOrDefault(m => m.IsStatic && m.IsPublic && m.Name == nameof(Enumerable.ToArray) && m.GetParameters().Length == 1) - ?? throw new InvalidOperationException("Could not find method Enumerable.ToArray<>"); - - // Convert everything to an array so it doesn't endlessly loop on the IGrouping<> test - var expectedToArrayMethod = toArrayMethod.MakeGenericMethod(expectedGroupingTypes[1]); - var expectedValues = expectedToArrayMethod.Invoke(null, new[] { expected }); - - var actualToArrayMethod = toArrayMethod.MakeGenericMethod(actualGroupingTypes[1]); - var actualValues = actualToArrayMethod.Invoke(null, new[] { actual }); - - if (VerifyEquivalence(expectedValues, actualValues, strict) != null) - throw EquivalentException.ForGroupingWithMismatchedValues(expectedValues, actualValues, ArgumentFormatter.Format(expectedKey)); - - return null; - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceIntrinsics( -#else - static EquivalentException VerifyEquivalenceIntrinsics( -#endif - object expected, - object actual, - string prefix) - { - var result = expected.Equals(actual); - - if (!result && TryConvert(expected, actual.GetType(), out var converted)) - result = converted.Equals(actual); - if (!result && TryConvert(actual, expected.GetType(), out converted)) - result = converted.Equals(expected); - - return result ? null : EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceReference( -#else - static EquivalentException VerifyEquivalenceReference( -#endif - object expected, - object actual, - bool strict, - string prefix, - HashSet expectedRefs, - HashSet actualRefs, - int depth, - IReadOnlyList<(string Prefix, string Member)> exclusions) - { - Assert.GuardArgumentNotNull(nameof(prefix), prefix); - - var prefixDot = prefix.Length == 0 ? string.Empty : prefix + "."; - - // Enumerate over public instance fields and properties and validate equivalence - var expectedGetters = GetGettersForType(expected.GetType()); - var actualGetters = GetGettersForType(actual.GetType()); - - if (strict && expectedGetters.Count != actualGetters.Count) - return EquivalentException.ForMemberListMismatch(expectedGetters.Keys, actualGetters.Keys, prefixDot); - - var excludedAtThisLevel = - new HashSet( - exclusions - .Where(e => e.Prefix == prefix) - .Select(e => e.Member) - ); - - foreach (var kvp in expectedGetters) - { - if (excludedAtThisLevel.Contains(kvp.Key)) - continue; - - if (!actualGetters.TryGetValue(kvp.Key, out var actualGetter)) - return EquivalentException.ForMemberListMismatch(expectedGetters.Keys, actualGetters.Keys, prefixDot); - - var expectedMemberValue = kvp.Value(expected); - var actualMemberValue = actualGetter(actual); - - var ex = VerifyEquivalence(expectedMemberValue, actualMemberValue, strict, prefixDot + kvp.Key, expectedRefs, actualRefs, depth + 1, exclusions); - if (ex != null) - return ex; - } - - return null; - } - -#if XUNIT_NULLABLE - static EquivalentException? VerifyEquivalenceUri( -#else - static EquivalentException VerifyEquivalenceUri( -#endif - Uri expected, - Uri actual, - string prefix) - { - if (expected.OriginalString != actual.OriginalString) - return EquivalentException.ForMemberValueMismatch(expected, actual, prefix); - - return null; - } - } - - sealed class ReferenceEqualityComparer : IEqualityComparer - { - public new bool Equals( -#if XUNIT_NULLABLE - object? x, - object? y) => -#else - object x, - object y) => -#endif - ReferenceEquals(x, y); - -#if XUNIT_NULLABLE - public int GetHashCode([DisallowNull] object obj) => -#else - public int GetHashCode(object obj) => -#endif - obj.GetHashCode(); - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertRangeComparer.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertRangeComparer.cs index b2160ea5608..4acd486f934 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertRangeComparer.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/AssertRangeComparer.cs @@ -1,3 +1,7 @@ +#pragma warning disable IDE0019 // Use pattern matching +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace + #if XUNIT_NULLABLE #nullable enable #else @@ -44,7 +48,8 @@ public int Compare( return -1; // Implements IComparable? - if (x is IComparable comparable1) + var comparable1 = x as IComparable; + if (comparable1 != null) return comparable1.CompareTo(y); // Implements IComparable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker.cs index 7a6046dfec5..83ecceac8d8 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker.cs @@ -1,12 +1,17 @@ +#pragma warning disable CA1063 // Implement IDisposable Correctly #pragma warning disable CA1000 // Do not declare static members on generic types -#pragma warning disable CA1031 // Do not catch general exception types -#pragma warning disable CA1508 // Avoid dead conditional code -#pragma warning disable CA2213 // We move disposal to DisposeInternal, due to https://github.com/xunit/xunit/issues/2762 +#pragma warning disable IDE0016 // Use 'throw' expression +#pragma warning disable IDE0018 // Inline variable declaration #pragma warning disable IDE0019 // Use pattern matching #pragma warning disable IDE0028 // Simplify collection initialization +#pragma warning disable IDE0034 // Simplify 'default' expression +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0063 // Use simple 'using' statement #pragma warning disable IDE0074 // Use compound assignment #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0290 // Use primary constructor #pragma warning disable IDE0300 // Simplify collection initialization @@ -17,14 +22,15 @@ #pragma warning disable CS8601 #pragma warning disable CS8603 #pragma warning disable CS8604 +#pragma warning disable CS8605 #pragma warning disable CS8618 -#pragma warning disable CS8625 #endif using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; #if XUNIT_NULLABLE @@ -42,20 +48,34 @@ namespace Xunit.Sdk #else public #endif - abstract partial class CollectionTracker : IDisposable + abstract class CollectionTracker : IDisposable { /// /// Initializes a new instance of the class. /// /// /// - protected CollectionTracker(IEnumerable innerEnumerable) => - InnerEnumerable = innerEnumerable ?? throw new ArgumentNullException(nameof(innerEnumerable)); + protected CollectionTracker(IEnumerable innerEnumerable) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(innerEnumerable); +#else + if (innerEnumerable == null) + throw new ArgumentNullException(nameof(innerEnumerable)); +#endif + + InnerEnumerable = innerEnumerable; + } + + static readonly MethodInfo openGenericCompareTypedSetsMethod = + typeof(CollectionTracker) + .GetRuntimeMethods() + .Single(m => m.Name == nameof(CompareTypedSets)); /// /// Gets the inner enumerable that this collection track is wrapping. This is mostly /// provided for simplifying other APIs which require both the tracker and the collection - /// (for example, ). + /// (for example, ). /// protected internal IEnumerable InnerEnumerable { get; protected set; } @@ -66,10 +86,11 @@ protected CollectionTracker(IEnumerable innerEnumerable) => /// First value to compare /// Second value to comare /// The comparer used for individual item comparisons - /// Pass if the is the default item - /// comparer from ; pass , otherwise. - /// Returns if the collections are equal; , otherwise. - public static AssertEqualityResult AreCollectionsEqual( + /// Pass true if the is the default item + /// comparer from ; pass false, otherwise. + /// The output mismatched item index when the collections are not equal + /// Returns true if the collections are equal; false, otherwise. + public static bool AreCollectionsEqual( #if XUNIT_NULLABLE CollectionTracker? x, CollectionTracker? y, @@ -78,36 +99,38 @@ public static AssertEqualityResult AreCollectionsEqual( CollectionTracker y, #endif IEqualityComparer itemComparer, - bool isDefaultItemComparer) + bool isDefaultItemComparer, + out int? mismatchedIndex) { Assert.GuardArgumentNotNull(nameof(itemComparer), itemComparer); - try - { - return - CheckIfDictionariesAreEqual(x, y) ?? - CheckIfSetsAreEqual(x, y, isDefaultItemComparer ? null : itemComparer) ?? - CheckIfArraysAreEqual(x, y, itemComparer, isDefaultItemComparer) ?? - CheckIfEnumerablesAreEqual(x, y, itemComparer, isDefaultItemComparer); - } - catch (Exception ex) - { - return AssertEqualityResult.ForResult(false, x?.InnerEnumerable, y?.InnerEnumerable, ex); - } + mismatchedIndex = null; + + return +#if XUNIT_AOT + CheckIfDictionariesAreEqual(x, y, itemComparer) ?? +#else + CheckIfDictionariesAreEqual(x, y) ?? +#endif + CheckIfSetsAreEqual(x, y, isDefaultItemComparer ? null : itemComparer) ?? + CheckIfArraysAreEqual(x, y, itemComparer, isDefaultItemComparer, out mismatchedIndex) ?? + CheckIfEnumerablesAreEqual(x, y, itemComparer, isDefaultItemComparer, out mismatchedIndex); } + static bool? CheckIfArraysAreEqual( #if XUNIT_NULLABLE - static AssertEqualityResult? CheckIfArraysAreEqual( CollectionTracker? x, CollectionTracker? y, #else - static AssertEqualityResult CheckIfArraysAreEqual( CollectionTracker x, CollectionTracker y, #endif IEqualityComparer itemComparer, - bool isDefaultItemComparer) + bool isDefaultItemComparer, + out int? mismatchedIndex) { + mismatchedIndex = null; + if (x == null || y == null) return null; @@ -121,16 +144,16 @@ static AssertEqualityResult CheckIfArraysAreEqual( // version, since that's uses the trackers and gets us the mismatch pointer. if (expectedArray.Rank == 1 && expectedArray.GetLowerBound(0) == 0 && actualArray.Rank == 1 && actualArray.GetLowerBound(0) == 0) - return CheckIfEnumerablesAreEqual(x, y, itemComparer, isDefaultItemComparer); + return CheckIfEnumerablesAreEqual(x, y, itemComparer, isDefaultItemComparer, out mismatchedIndex); if (expectedArray.Rank != actualArray.Rank) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; // Differing bounds, aka object[2,1] vs. object[1,2] // You can also have non-zero-based arrays, so we don't just check lengths for (var rank = 0; rank < expectedArray.Rank; rank++) if (expectedArray.GetLowerBound(rank) != actualArray.GetLowerBound(rank) || expectedArray.GetUpperBound(rank) != actualArray.GetUpperBound(rank)) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; // Enumeration will flatten everything identically, so just enumerate at this point var expectedEnumerator = x.GetSafeEnumerator(); @@ -142,21 +165,25 @@ static AssertEqualityResult CheckIfArraysAreEqual( var hasActual = actualEnumerator.MoveNext(); if (!hasExpected || !hasActual) - return AssertEqualityResult.ForResult(hasExpected == hasActual, x.InnerEnumerable, y.InnerEnumerable); + return hasExpected == hasActual; if (!itemComparer.Equals(expectedEnumerator.Current, actualEnumerator.Current)) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; } } + static bool? CheckIfDictionariesAreEqual( #if XUNIT_NULLABLE - static AssertEqualityResult? CheckIfDictionariesAreEqual( CollectionTracker? x, - CollectionTracker? y) + CollectionTracker? y #else - static AssertEqualityResult CheckIfDictionariesAreEqual( CollectionTracker x, - CollectionTracker y) + CollectionTracker y +#endif +#if XUNIT_AOT + , IEqualityComparer itemComparer) +#else + ) #endif { if (x == null || y == null) @@ -169,17 +196,19 @@ static AssertEqualityResult CheckIfDictionariesAreEqual( return null; if (dictionaryX.Count != dictionaryY.Count) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; var dictionaryYKeys = new HashSet(dictionaryY.Keys.Cast()); +#if !XUNIT_AOT // We don't pass along the itemComparer from AreCollectionsEqual because we aren't directly // comparing the KeyValuePair<> objects. Instead we rely on Contains() on the dictionary to // match up keys, and then create type-appropriate comparers for the values. +#endif foreach (var key in dictionaryX.Keys.Cast()) { if (!dictionaryYKeys.Contains(key)) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; var valueX = dictionaryX[key]; var valueY = dictionaryY[key]; @@ -187,27 +216,31 @@ static AssertEqualityResult CheckIfDictionariesAreEqual( if (valueX == null) { if (valueY != null) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; } else if (valueY == null) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; else { var valueXType = valueX.GetType(); var valueYType = valueY.GetType(); +#if XUNIT_AOT + var comparer = itemComparer; +#else var comparer = AssertEqualityComparer.GetDefaultComparer(valueXType == valueYType ? valueXType : typeof(object)); +#endif if (!comparer.Equals(valueX, valueY)) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); + return false; } dictionaryYKeys.Remove(key); } - return AssertEqualityResult.ForResult(dictionaryYKeys.Count == 0, x.InnerEnumerable, y.InnerEnumerable); + return dictionaryYKeys.Count == 0; } - static AssertEqualityResult CheckIfEnumerablesAreEqual( + static bool CheckIfEnumerablesAreEqual( #if XUNIT_NULLABLE CollectionTracker? x, CollectionTracker? y, @@ -216,17 +249,20 @@ static AssertEqualityResult CheckIfEnumerablesAreEqual( CollectionTracker y, #endif IEqualityComparer itemComparer, - bool isDefaultItemComparer) + bool isDefaultItemComparer, + out int? mismatchIndex) { + mismatchIndex = null; + if (x == null) - return AssertEqualityResult.ForResult(y == null, null, y?.InnerEnumerable); + return y == null; if (y == null) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, null); + return false; - var (comparisonType, equalsMethod) = GetAssertEqualityComparerMetadata(itemComparer); var enumeratorX = x.GetSafeEnumerator(); var enumeratorY = y.GetSafeEnumerator(); - var mismatchIndex = 0; + + mismatchIndex = 0; while (true) { @@ -234,9 +270,15 @@ static AssertEqualityResult CheckIfEnumerablesAreEqual( var hasNextY = enumeratorY.MoveNext(); if (!hasNextX || !hasNextY) - return hasNextX == hasNextY - ? AssertEqualityResult.ForResult(true, x.InnerEnumerable, y.InnerEnumerable) - : AssertEqualityResult.ForMismatch(x.InnerEnumerable, y.InnerEnumerable, mismatchIndex); + { + if (hasNextX == hasNextY) + { + mismatchIndex = null; + return true; + } + + return false; + } var xCurrent = enumeratorX.Current; var yCurrent = enumeratorY.Current; @@ -244,39 +286,67 @@ static AssertEqualityResult CheckIfEnumerablesAreEqual( using (var xCurrentTracker = isDefaultItemComparer ? xCurrent.AsNonStringTracker() : null) using (var yCurrentTracker = isDefaultItemComparer ? yCurrent.AsNonStringTracker() : null) { - try + if (xCurrentTracker != null && yCurrentTracker != null) { - if (xCurrentTracker != null && yCurrentTracker != null) - { - var innerCompare = AreCollectionsEqual(xCurrentTracker, yCurrentTracker, AssertEqualityComparer.DefaultInnerComparer, true); - if (!innerCompare.Equal) - return AssertEqualityResult.ForMismatch(x.InnerEnumerable, y.InnerEnumerable, mismatchIndex, innerResult: innerCompare); - } - else - { - var assertEqualityResult = default(AssertEqualityResult); - if (comparisonType?.IsAssignableFrom(xCurrent?.GetType()) == true && comparisonType?.IsAssignableFrom(yCurrent?.GetType()) == true) - assertEqualityResult = equalsMethod?.Invoke(itemComparer, new[] { xCurrent, null, yCurrent, null }) as AssertEqualityResult; - - if (assertEqualityResult != null) - { - if (!assertEqualityResult.Equal) - return AssertEqualityResult.ForMismatch(x.InnerEnumerable, y.InnerEnumerable, mismatchIndex, innerResult: assertEqualityResult); - } - else if (!itemComparer.Equals(xCurrent, yCurrent)) - return AssertEqualityResult.ForMismatch(x.InnerEnumerable, y.InnerEnumerable, mismatchIndex); - } - } - catch (Exception ex) - { - return AssertEqualityResult.ForMismatch(x.InnerEnumerable, y.InnerEnumerable, mismatchIndex, ex); + int? _; + var innerCompare = AreCollectionsEqual(xCurrentTracker, yCurrentTracker, AssertEqualityComparer.DefaultInnerComparer, true, out _); + if (!innerCompare) + return false; } + else if (!itemComparer.Equals(xCurrent, yCurrent)) + return false; mismatchIndex++; } } } + static bool? CheckIfSetsAreEqual( +#if XUNIT_NULLABLE + CollectionTracker? x, + CollectionTracker? y, + IEqualityComparer? itemComparer) +#else + CollectionTracker x, + CollectionTracker y, + IEqualityComparer itemComparer) +#endif + { + if (x == null || y == null) + return null; + + var elementTypeX = ArgumentFormatter.GetSetElementType(x.InnerEnumerable); + var elementTypeY = ArgumentFormatter.GetSetElementType(y.InnerEnumerable); + + if (elementTypeX == null || elementTypeY == null) + return null; + + if (elementTypeX != elementTypeY) + return false; + +#if XUNIT_AOT + // Can't use MakeGenericType in AOT + return CompareUntypedSets(x.InnerEnumerable, y.InnerEnumerable); +#else + var genericCompareMethod = openGenericCompareTypedSetsMethod.MakeGenericMethod(elementTypeX); +#if XUNIT_NULLABLE + return (bool)genericCompareMethod.Invoke(null, new object?[] { x.InnerEnumerable, y.InnerEnumerable, itemComparer })!; +#else + return (bool)genericCompareMethod.Invoke(null, new object[] { x.InnerEnumerable, y.InnerEnumerable, itemComparer }); +#endif +#endif // XUNIT_AOT + } + + static bool CompareUntypedSets( + IEnumerable enumX, + IEnumerable enumY) + { + var setX = new HashSet(enumX.Cast()); + var setY = new HashSet(enumY.Cast()); + + return setX.SetEquals(setY); + } + static bool CompareTypedSets( ISet setX, ISet setY, @@ -299,18 +369,7 @@ static bool CompareTypedSets( } /// - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - - /// - /// Override to provide an implementation of . - /// - /// - protected abstract void Dispose(bool disposing); + public abstract void Dispose(); /// /// Formats the collection when you have a mismatched index. The formatted result will be the section of the @@ -353,7 +412,7 @@ public abstract string FormatIndexedMismatch( /// /// Gets the extents to print when you find a mismatched index, in the form of /// a and . If the mismatched - /// index is , the extents will start at index 0. + /// index is null, the extents will start at index 0. /// /// The mismatched item index /// The start index that should be used for printing @@ -372,11 +431,11 @@ public abstract void GetMismatchExtents( /// /// Gets the full name of the type of the element at the given index, if known. - /// Since this uses the item cache produced by enumeration, it may return + /// Since this uses the item cache produced by enumeration, it may return null /// when we haven't enumerated enough to see the given element, or if we enumerated /// so much that the item has left the cache, or if the item at the given index - /// is . It will also return when the - /// is . + /// is null. It will also return null when the + /// is null. /// /// The item index #if XUNIT_NULLABLE @@ -403,14 +462,23 @@ public static CollectionTracker Wrap(IEnumerable enumerable) => #else public #endif - sealed class CollectionTracker : CollectionTracker, IEnumerable + sealed class CollectionTracker<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T> : CollectionTracker, IEnumerable { + const int MAX_ENUMERABLE_LENGTH_HALF = ArgumentFormatter.MAX_ENUMERABLE_LENGTH / 2; + readonly IEnumerable collection; +#pragma warning disable CA2213 // We move disposal to DisposeInternal, due to https://github.com/xunit/xunit/issues/2762 #if XUNIT_NULLABLE - BufferedEnumerator? enumerator; + Enumerator? enumerator; #else - BufferedEnumerator enumerator; + Enumerator enumerator; #endif +#pragma warning restore CA2213 /// /// INTERNAL CONSTRUCTOR. DO NOT CALL. @@ -418,12 +486,23 @@ sealed class CollectionTracker : CollectionTracker, IEnumerable internal CollectionTracker( IEnumerable collection, IEnumerable castCollection) : - base(collection) => - this.collection = castCollection ?? throw new ArgumentNullException(nameof(castCollection)); + base(collection) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(castCollection); +#else + if (castCollection == null) + throw new ArgumentNullException(nameof(castCollection)); +#endif + + this.collection = castCollection; + } CollectionTracker(IEnumerable collection) : - base(collection) => - this.collection = collection; + base(collection) + { + this.collection = collection; + } /// /// Gets the number of iterations that have happened so far. @@ -432,7 +511,7 @@ internal CollectionTracker( enumerator == null ? 0 : enumerator.CurrentIndex + 1; /// - protected override void Dispose(bool disposing) => + public override void Dispose() => enumerator?.DisposeInternal(); /// @@ -441,19 +520,22 @@ public override string FormatIndexedMismatch( out int? pointerIndent, int depth = 1) { - if (depth > ArgumentFormatter.MaxEnumerableLength) + if (depth == ArgumentFormatter.MAX_DEPTH) { pointerIndent = 1; return ArgumentFormatter.EllipsisInBrackets; } - GetMismatchExtents(mismatchedIndex, out var startIndex, out var endIndex); + int startIndex; + int endIndex; + + GetMismatchExtents(mismatchedIndex, out startIndex, out endIndex); return FormatIndexedMismatch( #if XUNIT_NULLABLE - enumerator!.CurrentItemsIndexer, + enumerator!.CurrentItems, #else - enumerator.CurrentItemsIndexer, + enumerator.CurrentItems, #endif enumerator.MoveNext, startIndex, @@ -476,7 +558,7 @@ public override string FormatIndexedMismatch( throw new InvalidOperationException("Called FormatIndexedMismatch with indices without calling GetMismatchExtents first"); return FormatIndexedMismatch( - enumerator.CurrentItemsIndexer, + enumerator.CurrentItems, enumerator.MoveNext, startIndex, endIndex, @@ -486,6 +568,7 @@ public override string FormatIndexedMismatch( ); } +#if XUNIT_SPAN /// /// Formats a span with a mismatched index. /// @@ -500,25 +583,15 @@ public static string FormatIndexedMismatch( out int? pointerIndent, int depth = 1) { - if (depth > ArgumentFormatter.MaxEnumerableLength) + if (depth == ArgumentFormatter.MAX_DEPTH) { pointerIndent = 1; return ArgumentFormatter.EllipsisInBrackets; } - int startIndex, endIndex; - - if (ArgumentFormatter.MaxEnumerableLength == int.MaxValue) - { - startIndex = 0; - endIndex = span.Length - 1; - } - else - { - startIndex = Math.Max(0, (mismatchedIndex ?? 0) - ArgumentFormatter.MaxEnumerableLength / 2); - endIndex = Math.Min(span.Length - 1, startIndex + ArgumentFormatter.MaxEnumerableLength - 1); - startIndex = Math.Max(0, endIndex - ArgumentFormatter.MaxEnumerableLength + 1); - } + var startIndex = Math.Max(0, (mismatchedIndex ?? 0) - MAX_ENUMERABLE_LENGTH_HALF); + var endIndex = Math.Min(span.Length - 1, startIndex + ArgumentFormatter.MAX_ENUMERABLE_LENGTH - 1); + startIndex = Math.Max(0, endIndex - ArgumentFormatter.MAX_ENUMERABLE_LENGTH + 1); var moreItemsPastEndIndex = endIndex < span.Length - 1; var items = new Dictionary(); @@ -527,7 +600,7 @@ public static string FormatIndexedMismatch( items[idx] = span[idx]; return FormatIndexedMismatch( - idx => items[idx], + items, () => moreItemsPastEndIndex, startIndex, endIndex, @@ -536,9 +609,10 @@ public static string FormatIndexedMismatch( depth ); } +#endif static string FormatIndexedMismatch( - Func indexer, + Dictionary items, Func moreItemsPastEndIndex, int startIndex, int endIndex, @@ -560,7 +634,7 @@ static string FormatIndexedMismatch( if (idx == mismatchedIndex) pointerIndent = printedValues.Length; - printedValues.Append(ArgumentFormatter.Format(indexer(idx), depth)); + printedValues.Append(ArgumentFormatter.Format(items[idx], depth)); } if (moreItemsPastEndIndex()) @@ -573,18 +647,18 @@ static string FormatIndexedMismatch( /// public override string FormatStart(int depth = 1) { - if (depth > ArgumentFormatter.MaxEnumerableLength) + if (depth == ArgumentFormatter.MAX_DEPTH) return ArgumentFormatter.EllipsisInBrackets; if (enumerator == null) - enumerator = BufferedEnumerator.Create(collection.GetEnumerator()); + enumerator = new Enumerator(collection.GetEnumerator()); // Ensure we have already seen enough data to format - while (enumerator.CurrentIndex <= ArgumentFormatter.MaxEnumerableLength) + while (enumerator.CurrentIndex <= ArgumentFormatter.MAX_ENUMERABLE_LENGTH) if (!enumerator.MoveNext()) break; - return FormatStart(enumerator.StartItemsIndexer, enumerator.CurrentIndex, depth); + return FormatStart(enumerator.StartItems, enumerator.CurrentIndex, depth); } /// @@ -599,7 +673,7 @@ public static string FormatStart( { Assert.GuardArgumentNotNull(nameof(collection), collection); - if (depth > ArgumentFormatter.MaxEnumerableLength) + if (depth == ArgumentFormatter.MAX_DEPTH) return ArgumentFormatter.EllipsisInBrackets; var startItems = new List(); @@ -607,7 +681,7 @@ public static string FormatStart( var spanEnumerator = collection.GetEnumerator(); // Ensure we have already seen enough data to format - while (currentIndex <= ArgumentFormatter.MaxEnumerableLength) + while (currentIndex <= ArgumentFormatter.MAX_ENUMERABLE_LENGTH) { if (!spanEnumerator.MoveNext()) break; @@ -616,9 +690,10 @@ public static string FormatStart( ++currentIndex; } - return FormatStart(idx => startItems[idx], currentIndex, depth); + return FormatStart(startItems, currentIndex, depth); } +#if XUNIT_SPAN /// /// Formats the beginning part of a span. /// @@ -629,7 +704,7 @@ public static string FormatStart( ReadOnlySpan span, int depth = 1) { - if (depth > ArgumentFormatter.MaxEnumerableLength) + if (depth == ArgumentFormatter.MAX_DEPTH) return ArgumentFormatter.EllipsisInBrackets; var startItems = new List(); @@ -637,7 +712,7 @@ public static string FormatStart( var spanEnumerator = span.GetEnumerator(); // Ensure we have already seen enough data to format - while (currentIndex <= ArgumentFormatter.MaxEnumerableLength) + while (currentIndex <= ArgumentFormatter.MAX_ENUMERABLE_LENGTH) { if (!spanEnumerator.MoveNext()) break; @@ -646,26 +721,27 @@ public static string FormatStart( ++currentIndex; } - return FormatStart(idx => startItems[idx], currentIndex, depth); + return FormatStart(startItems, currentIndex, depth); } +#endif static string FormatStart( - Func indexer, + List items, int currentIndex, int depth) { var printedValues = new StringBuilder("["); - var printLength = Math.Min(currentIndex + 1, ArgumentFormatter.MaxEnumerableLength); + var printLength = Math.Min(currentIndex + 1, ArgumentFormatter.MAX_ENUMERABLE_LENGTH); for (var idx = 0; idx < printLength; ++idx) { if (idx != 0) printedValues.Append(", "); - printedValues.Append(ArgumentFormatter.Format(indexer(idx), depth)); + printedValues.Append(ArgumentFormatter.Format(items[idx], depth)); } - if (currentIndex >= ArgumentFormatter.MaxEnumerableLength) + if (currentIndex >= ArgumentFormatter.MAX_ENUMERABLE_LENGTH) printedValues.Append(", " + ArgumentFormatter.Ellipsis); printedValues.Append(']'); @@ -678,7 +754,7 @@ public IEnumerator GetEnumerator() if (enumerator != null) throw new InvalidOperationException("Multiple enumeration is not supported"); - enumerator = BufferedEnumerator.Create(collection.GetEnumerator()); + enumerator = new Enumerator(collection.GetEnumerator()); return enumerator; } @@ -696,18 +772,10 @@ public override void GetMismatchExtents( out int endIndex) { if (enumerator == null) - enumerator = BufferedEnumerator.Create(collection.GetEnumerator()); + enumerator = new Enumerator(collection.GetEnumerator()); - if (ArgumentFormatter.MaxEnumerableLength == int.MaxValue) - { - startIndex = 0; - endIndex = int.MaxValue; - } - else - { - startIndex = Math.Max(0, (mismatchedIndex ?? 0) - ArgumentFormatter.MaxEnumerableLength / 2); - endIndex = startIndex + ArgumentFormatter.MaxEnumerableLength - 1; - } + startIndex = Math.Max(0, (mismatchedIndex ?? 0) - MAX_ENUMERABLE_LENGTH_HALF); + endIndex = startIndex + ArgumentFormatter.MAX_ENUMERABLE_LENGTH - 1; // Make sure our window starts with startIndex and ends with endIndex, as appropriate while (enumerator.CurrentIndex < endIndex) @@ -715,9 +783,7 @@ public override void GetMismatchExtents( break; endIndex = enumerator.CurrentIndex; - - if (ArgumentFormatter.MaxEnumerableLength != int.MaxValue) - startIndex = Math.Max(0, endIndex - ArgumentFormatter.MaxEnumerableLength + 1); + startIndex = Math.Max(0, endIndex - ArgumentFormatter.MAX_ENUMERABLE_LENGTH + 1); } /// @@ -730,7 +796,12 @@ public override string TypeAt(int? index) if (enumerator == null || !index.HasValue) return null; - if (!enumerator.TryGetCurrentItemAt(index.Value, out var item)) +#if XUNIT_NULLABLE + T? item; +#else + T item; +#endif + if (!enumerator.TryGetCurrentItemAt(index.Value, out item)) return null; return item?.GetType().FullName; @@ -743,13 +814,19 @@ public override string TypeAt(int? index) public static CollectionTracker Wrap(IEnumerable collection) => new CollectionTracker(collection); - abstract class BufferedEnumerator : IEnumerator + sealed class Enumerator : IEnumerator { - protected BufferedEnumerator(IEnumerator innerEnumerator) => - InnerEnumerator = innerEnumerator; + int currentItemsLastInsertionIndex = -1; + readonly T[] currentItemsRingBuffer = new T[ArgumentFormatter.MAX_ENUMERABLE_LENGTH]; + readonly IEnumerator innerEnumerator; + + public Enumerator(IEnumerator innerEnumerator) + { + this.innerEnumerator = innerEnumerator; + } public T Current => - InnerEnumerator.Current; + innerEnumerator.Current; #if XUNIT_NULLABLE object? IEnumerator.Current => @@ -760,186 +837,90 @@ protected BufferedEnumerator(IEnumerator innerEnumerator) => public int CurrentIndex { get; private set; } = -1; - public abstract Func CurrentItemsIndexer { get; } + public Dictionary CurrentItems + { + get + { + var result = new Dictionary(); - protected IEnumerator InnerEnumerator { get; } + if (CurrentIndex > -1) + { + var itemIndex = Math.Max(0, CurrentIndex - ArgumentFormatter.MAX_ENUMERABLE_LENGTH + 1); - public abstract Func StartItemsIndexer { get; } + var indexInRingBuffer = (currentItemsLastInsertionIndex - CurrentIndex + itemIndex) % ArgumentFormatter.MAX_ENUMERABLE_LENGTH; + if (indexInRingBuffer < 0) + indexInRingBuffer += ArgumentFormatter.MAX_ENUMERABLE_LENGTH; - public static BufferedEnumerator Create(IEnumerator innerEnumerator) => - ArgumentFormatter.MaxEnumerableLength == int.MaxValue - ? (BufferedEnumerator)new ListBufferedEnumerator(innerEnumerator) - : new RingBufferedEnumerator(innerEnumerator); + while (itemIndex <= CurrentIndex) + { + result[itemIndex] = currentItemsRingBuffer[indexInRingBuffer]; - public void Dispose() { } + ++itemIndex; + indexInRingBuffer = (indexInRingBuffer + 1) % ArgumentFormatter.MAX_ENUMERABLE_LENGTH; + } + } + + return result; + } + } + + public List StartItems { get; } = new List(); + + public void Dispose() + { } public void DisposeInternal() => - InnerEnumerator.Dispose(); + innerEnumerator.Dispose(); - public virtual bool MoveNext() + public bool MoveNext() { - if (!InnerEnumerator.MoveNext()) + if (!innerEnumerator.MoveNext()) return false; CurrentIndex++; + var current = innerEnumerator.Current; + + // Keep (MAX_ENUMERABLE_LENGTH + 1) items here, so we can + // print the start of the collection when lengths differ + if (CurrentIndex <= ArgumentFormatter.MAX_ENUMERABLE_LENGTH) + StartItems.Add(current); + + // Keep a ring buffer filled with the most recent MAX_ENUMERABLE_LENGTH items + // so we can print out the items when we've found a bad index + currentItemsLastInsertionIndex = (currentItemsLastInsertionIndex + 1) % ArgumentFormatter.MAX_ENUMERABLE_LENGTH; + currentItemsRingBuffer[currentItemsLastInsertionIndex] = current; + return true; } - public virtual void Reset() + public void Reset() { - InnerEnumerator.Reset(); + innerEnumerator.Reset(); CurrentIndex = -1; + currentItemsLastInsertionIndex = -1; + StartItems.Clear(); } - public abstract bool TryGetCurrentItemAt( + public bool TryGetCurrentItemAt( int index, #if XUNIT_NULLABLE - [MaybeNullWhen(false)] out T item); -#else - out T item); -#endif - - // Used when ArgumentFormatter.MaxEnumerableLength is unlimited (int.MaxValue) - sealed class ListBufferedEnumerator : BufferedEnumerator - { - readonly List buffer = new List(); - - public ListBufferedEnumerator(IEnumerator innerEnumerator) : - base(innerEnumerator) - { } - - public override Func CurrentItemsIndexer => - idx => buffer[idx]; - - public override Func StartItemsIndexer => - idx => buffer[idx]; - - public override bool MoveNext() - { - if (!base.MoveNext()) - return false; - - buffer.Add(InnerEnumerator.Current); - return true; - } - - public override void Reset() - { - base.Reset(); - - buffer.Clear(); - } - - public override bool TryGetCurrentItemAt( - int index, -#if XUNIT_NULLABLE - [MaybeNullWhen(false)] out T item) + [MaybeNullWhen(false)] out T item) #else - out T item) + out T item) #endif - { - if (index < 0 || index > CurrentIndex) - { - item = default; - return false; - } - - item = buffer[index]; - return true; - } - } - - // Used when ArgumentFormatter.MaxEnumerableLength is not unlimited - sealed class RingBufferedEnumerator : BufferedEnumerator { - int currentItemsLastInsertionIndex = -1; - readonly T[] currentItemsRingBuffer = new T[ArgumentFormatter.MaxEnumerableLength]; - readonly List startItems = new List(); - - public override Func CurrentItemsIndexer - { - get - { - var result = new Dictionary(); - - if (CurrentIndex > -1) - { - var itemIndex = Math.Max(0, CurrentIndex - ArgumentFormatter.MaxEnumerableLength + 1); - - var indexInRingBuffer = (currentItemsLastInsertionIndex - CurrentIndex + itemIndex) % ArgumentFormatter.MaxEnumerableLength; - if (indexInRingBuffer < 0) - indexInRingBuffer += ArgumentFormatter.MaxEnumerableLength; - - while (itemIndex <= CurrentIndex) - { - result[itemIndex] = currentItemsRingBuffer[indexInRingBuffer]; - - ++itemIndex; - indexInRingBuffer = (indexInRingBuffer + 1) % ArgumentFormatter.MaxEnumerableLength; - } - } - - return idx => result[idx]; - } - } - - public override Func StartItemsIndexer => - idx => startItems[idx]; - - public RingBufferedEnumerator(IEnumerator innerEnumerator) : - base(innerEnumerator) - { } + item = default(T); - public override bool MoveNext() - { - if (!base.MoveNext()) - return false; - - var current = InnerEnumerator.Current; - - // Keep (MAX_ENUMERABLE_LENGTH + 1) items here, so we can - // print the start of the collection when lengths differ - if (CurrentIndex <= ArgumentFormatter.MaxEnumerableLength) - startItems.Add(current); - - // Keep a ring buffer filled with the most recent MAX_ENUMERABLE_LENGTH items - // so we can print out the items when we've found a bad index - currentItemsLastInsertionIndex = (currentItemsLastInsertionIndex + 1) % ArgumentFormatter.MaxEnumerableLength; - currentItemsRingBuffer[currentItemsLastInsertionIndex] = current; - - return true; - } - - public override void Reset() - { - base.Reset(); - - currentItemsLastInsertionIndex = -1; - startItems.Clear(); - } - - public override bool TryGetCurrentItemAt( - int index, -#if XUNIT_NULLABLE - [MaybeNullWhen(false)] out T item) -#else - out T item) -#endif - { - if (index < 0 || index <= CurrentIndex - ArgumentFormatter.MaxEnumerableLength || index > CurrentIndex) - { - item = default; - return false; - } + if (index < 0 || index <= CurrentIndex - ArgumentFormatter.MAX_ENUMERABLE_LENGTH || index > CurrentIndex) + return false; - var indexInRingBuffer = (currentItemsLastInsertionIndex - CurrentIndex + index) % ArgumentFormatter.MaxEnumerableLength; - if (indexInRingBuffer < 0) - indexInRingBuffer += ArgumentFormatter.MaxEnumerableLength; + var indexInRingBuffer = (currentItemsLastInsertionIndex - CurrentIndex + index) % ArgumentFormatter.MAX_ENUMERABLE_LENGTH; + if (indexInRingBuffer < 0) + indexInRingBuffer += ArgumentFormatter.MAX_ENUMERABLE_LENGTH; - item = currentItemsRingBuffer[indexInRingBuffer]; - return true; - } + item = currentItemsRingBuffer[indexInRingBuffer]; + return true; } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions.cs index 38d56efe2ff..a36ef2a4b21 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions.cs @@ -1,13 +1,26 @@ +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0078 // Use pattern matching +#pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace +#pragma warning disable IDE0300 // Simplify collection initialization + #if XUNIT_NULLABLE #nullable enable #else // In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE +#pragma warning disable CS8601 #pragma warning disable CS8603 #pragma warning disable CS8604 #endif +using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using System.Reflection; #if XUNIT_NULLABLE using System.Diagnostics.CodeAnalysis; @@ -23,8 +36,15 @@ namespace Xunit.Sdk #else public #endif - static partial class CollectionTrackerExtensions + static class CollectionTrackerExtensions { +#if XUNIT_NULLABLE + static readonly MethodInfo? asTrackerOpenGeneric = typeof(CollectionTrackerExtensions).GetRuntimeMethods().FirstOrDefault(m => m.Name == nameof(AsTracker) && m.IsGenericMethod); +#else + static readonly MethodInfo asTrackerOpenGeneric = typeof(CollectionTrackerExtensions).GetRuntimeMethods().FirstOrDefault(m => m.Name == nameof(AsTracker) && m.IsGenericMethod); +#endif + static readonly ConcurrentDictionary cacheOfAsTrackerByType = new ConcurrentDictionary(); + #if XUNIT_NULLABLE internal static CollectionTracker? AsNonStringTracker(this object? value) #else @@ -37,6 +57,46 @@ internal static CollectionTracker AsNonStringTracker(this object value) return AsTracker(value as IEnumerable); } + /// + /// Wraps the given enumerable in an instance of . + /// + /// The enumerable to be wrapped +#if XUNIT_NULLABLE + [return: NotNullIfNotNull(nameof(enumerable))] + public static CollectionTracker? AsTracker(this IEnumerable? enumerable) +#else + public static CollectionTracker AsTracker(this IEnumerable enumerable) +#endif + { + if (enumerable == null) + return null; + + var result = enumerable as CollectionTracker; + if (result != null) + return result; + +#if XUNIT_AOT + return CollectionTracker.Wrap(enumerable); +#else + // CollectionTracker.Wrap for the non-T enumerable uses the CastIterator, which has terrible + // performance during iteration. We do our best to try to get a T and dynamically invoke the + // generic version of AsTracker as we can. + var iEnumerableOfT = enumerable.GetType().GetTypeInfo().ImplementedInterfaces.FirstOrDefault(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + if (iEnumerableOfT == null) + return CollectionTracker.Wrap(enumerable); + + var enumerableType = iEnumerableOfT.GenericTypeArguments[0]; +#if XUNIT_NULLABLE + var method = cacheOfAsTrackerByType.GetOrAdd(enumerableType, t => asTrackerOpenGeneric!.MakeGenericMethod(enumerableType)); +#else + var method = cacheOfAsTrackerByType.GetOrAdd(enumerableType, t => asTrackerOpenGeneric.MakeGenericMethod(enumerableType)); +#endif + + result = method.Invoke(null, new object[] { enumerable }) as CollectionTracker; + return result ?? CollectionTracker.Wrap(enumerable); +#endif // !XUNIT_AOT + } + /// /// Wraps the given enumerable in an instance of . /// @@ -44,7 +104,12 @@ internal static CollectionTracker AsNonStringTracker(this object value) /// The enumerable to be wrapped #if XUNIT_NULLABLE [return: NotNullIfNotNull(nameof(enumerable))] - public static CollectionTracker? AsTracker(this IEnumerable? enumerable) => + public static CollectionTracker? AsTracker<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>(this IEnumerable? enumerable) => #else public static CollectionTracker AsTracker(this IEnumerable enumerable) => #endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_aot.cs deleted file mode 100644 index 61a7e0d0998..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_aot.cs +++ /dev/null @@ -1,42 +0,0 @@ -#if XUNIT_AOT - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#endif - -using System.Collections; - -#if XUNIT_NULLABLE -using System.Diagnostics.CodeAnalysis; -#endif - -namespace Xunit.Sdk -{ - partial class CollectionTrackerExtensions - { - /// - /// Wraps the given enumerable in an instance of . - /// - /// The enumerable to be wrapped -#if XUNIT_NULLABLE - [return: NotNullIfNotNull(nameof(enumerable))] - public static CollectionTracker? AsTracker(this IEnumerable? enumerable) -#else - public static CollectionTracker AsTracker(this IEnumerable enumerable) -#endif - { - if (enumerable == null) - return null; - - if (enumerable is CollectionTracker result) - return result; - - return CollectionTracker.Wrap(enumerable); - } - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_reflection.cs deleted file mode 100644 index 2102c4781e0..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTrackerExtensions_reflection.cs +++ /dev/null @@ -1,78 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0300 // Simplify collection initialization - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8601 -#pragma warning disable CS8603 -#endif - -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -#if XUNIT_NULLABLE -using System.Diagnostics.CodeAnalysis; -#endif - -namespace Xunit.Sdk -{ - partial class CollectionTrackerExtensions - { -#if XUNIT_NULLABLE - static readonly MethodInfo? asTrackerOpenGeneric = -#else - static readonly MethodInfo asTrackerOpenGeneric = -#endif - typeof(CollectionTrackerExtensions).GetRuntimeMethods().FirstOrDefault(m => m.Name == nameof(AsTracker) && m.IsGenericMethod); - - static readonly ConcurrentDictionary cacheOfAsTrackerByType = new ConcurrentDictionary(); - - /// - /// Wraps the given enumerable in an instance of . - /// - /// The enumerable to be wrapped -#if XUNIT_NULLABLE - [return: NotNullIfNotNull(nameof(enumerable))] - public static CollectionTracker? AsTracker(this IEnumerable? enumerable) -#else - public static CollectionTracker AsTracker(this IEnumerable enumerable) -#endif - { - if (enumerable == null) - return null; - - if (enumerable is CollectionTracker result) - return result; - -#if XUNIT_AOT - return CollectionTracker.Wrap(enumerable); -#else - // CollectionTracker.Wrap for the non-T enumerable uses the CastIterator, which has terrible - // performance during iteration. We do our best to try to get a T and dynamically invoke the - // generic version of AsTracker as we can. - var iEnumerableOfT = enumerable.GetType().GetInterfaces().FirstOrDefault(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - if (iEnumerableOfT == null) - return CollectionTracker.Wrap(enumerable); - - var enumerableType = iEnumerableOfT.GenericTypeArguments[0]; -#if XUNIT_NULLABLE - var method = cacheOfAsTrackerByType.GetOrAdd(enumerableType, t => asTrackerOpenGeneric!.MakeGenericMethod(enumerableType)); -#else - var method = cacheOfAsTrackerByType.GetOrAdd(enumerableType, t => asTrackerOpenGeneric.MakeGenericMethod(enumerableType)); -#endif - - return method.Invoke(null, new object[] { enumerable }) as CollectionTracker ?? CollectionTracker.Wrap(enumerable); -#endif // XUNIT_AOT - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_aot.cs deleted file mode 100644 index ac6f20b8153..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_aot.cs +++ /dev/null @@ -1,66 +0,0 @@ -#if XUNIT_AOT -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#pragma warning disable CS8619 -#endif - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; - -namespace Xunit.Sdk -{ - partial class CollectionTracker - { -#if XUNIT_NULLABLE - static AssertEqualityResult? CheckIfSetsAreEqual( - CollectionTracker? x, - CollectionTracker? y, - IEqualityComparer? itemComparer) -#else - static AssertEqualityResult CheckIfSetsAreEqual( - CollectionTracker x, - CollectionTracker y, - IEqualityComparer itemComparer) -#endif - { - if (x == null || y == null) - return null; - - var elementTypeX = ArgumentFormatter.GetSetElementType(x.InnerEnumerable); - var elementTypeY = ArgumentFormatter.GetSetElementType(y.InnerEnumerable); - - if (elementTypeX == null || elementTypeY == null) - return null; - - if (elementTypeX != elementTypeY) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); - - if (itemComparer == null) - return null; - - return AssertEqualityResult.ForResult( - CompareTypedSets( - (ISet)x.InnerEnumerable, - (ISet)y.InnerEnumerable, - (IEqualityComparer)itemComparer - ), - x.InnerEnumerable, - y.InnerEnumerable - ); - } - -#if XUNIT_NULLABLE - static (Type?, MethodInfo?) GetAssertEqualityComparerMetadata(IEqualityComparer itemComparer) => -#else - static (Type, MethodInfo) GetAssertEqualityComparerMetadata(IEqualityComparer itemComparer) => -#endif - (null, null); - } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_reflection.cs deleted file mode 100644 index 3b9d3f81584..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/CollectionTracker_reflection.cs +++ /dev/null @@ -1,79 +0,0 @@ -#if !XUNIT_AOT - -#pragma warning disable IDE0300 // Simplify collection initialization - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8603 -#pragma warning disable CS8605 -#pragma warning disable CS8619 -#endif - -using System; -using System.Collections; -using System.Linq; -using System.Reflection; - -namespace Xunit.Sdk -{ - partial class CollectionTracker - { - static readonly MethodInfo openGenericCompareTypedSetsMethod = - typeof(CollectionTracker) - .GetRuntimeMethods() - .Single(m => m.Name == nameof(CompareTypedSets)); - -#if XUNIT_NULLABLE - static AssertEqualityResult? CheckIfSetsAreEqual( - CollectionTracker? x, - CollectionTracker? y, - IEqualityComparer? itemComparer) -#else - static AssertEqualityResult CheckIfSetsAreEqual( - CollectionTracker x, - CollectionTracker y, - IEqualityComparer itemComparer) -#endif - { - if (x == null || y == null) - return null; - - var elementTypeX = ArgumentFormatter.GetSetElementType(x.InnerEnumerable); - var elementTypeY = ArgumentFormatter.GetSetElementType(y.InnerEnumerable); - - if (elementTypeX == null || elementTypeY == null) - return null; - - if (elementTypeX != elementTypeY) - return AssertEqualityResult.ForResult(false, x.InnerEnumerable, y.InnerEnumerable); - - var genericCompareMethod = openGenericCompareTypedSetsMethod.MakeGenericMethod(elementTypeX); -#if XUNIT_NULLABLE - return AssertEqualityResult.ForResult((bool)genericCompareMethod.Invoke(null, new object?[] { x.InnerEnumerable, y.InnerEnumerable, itemComparer })!, x.InnerEnumerable, y.InnerEnumerable); -#else - return AssertEqualityResult.ForResult((bool)genericCompareMethod.Invoke(null, new object[] { x.InnerEnumerable, y.InnerEnumerable, itemComparer }), x.InnerEnumerable, y.InnerEnumerable); -#endif - } - -#if XUNIT_NULLABLE - static (Type?, MethodInfo?) GetAssertEqualityComparerMetadata(IEqualityComparer itemComparer) -#else - static (Type, MethodInfo) GetAssertEqualityComparerMetadata(IEqualityComparer itemComparer) -#endif - { - var assertQualityComparererType = - itemComparer - .GetType() - .GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAssertEqualityComparer<>)); - var comparisonType = assertQualityComparererType?.GenericTypeArguments[0]; - var equalsMethod = assertQualityComparererType?.GetMethod("Equals"); - - return (comparisonType, equalsMethod); - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/DynamicSkipToken.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/DynamicSkipToken.cs index 43d235b930b..113f6f3dccf 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/DynamicSkipToken.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/DynamicSkipToken.cs @@ -1,3 +1,6 @@ +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace + #if XUNIT_NULLABLE #nullable enable #endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/EnvironmentVariables.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/EnvironmentVariables.cs deleted file mode 100644 index 6472ecb156e..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/EnvironmentVariables.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Xunit.Internal -{ - internal static class EnvironmentVariables - { - public const string AssertEquivalentMaxDepth = "XUNIT_ASSERT_EQUIVALENT_MAX_DEPTH"; - public const string HidePassingOutputDiagnostics = "XUNIT_HIDE_PASSING_OUTPUT_DIAGNOSTICS"; - public const string PrintMaxEnumerableLength = "XUNIT_PRINT_MAX_ENUMERABLE_LENGTH"; - public const string PrintMaxObjectDepth = "XUNIT_PRINT_MAX_OBJECT_DEPTH"; - public const string PrintMaxObjectMemberCount = "XUNIT_PRINT_MAX_OBJECT_MEMBER_COUNT"; - public const string PrintMaxStringLength = "XUNIT_PRINT_MAX_STRING_LENGTH"; - public const string TestingPlatformDebug = "XUNIT_TESTINGPLATFORM_DEBUG"; - - internal static class Defaults - { - public const int AssertEquivalentMaxDepth = 50; - public const int PrintMaxEnumerableLength = 5; - public const int PrintMaxObjectDepth = 3; - public const int PrintMaxObjectMemberCount = 5; - public const int PrintMaxStringLength = 50; - } - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/AllException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/AllException.cs index a982ea3eb08..948e326a1cb 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/AllException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/AllException.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -60,17 +62,17 @@ public static AllException ForFailures( CultureInfo.CurrentCulture, "{0}Item: {1}{2}{3}Error: {4}", string.Format(CultureInfo.CurrentCulture, "[{0}]:", error.Item1).PadRight(maxItemIndexLength), -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER error.Item2.Replace(Environment.NewLine, wrapSpaces, StringComparison.Ordinal), #else error.Item2.Replace(Environment.NewLine, wrapSpaces), #endif Environment.NewLine, indexSpaces, -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - error.Item3.Message?.Replace(Environment.NewLine, wrapSpaces, StringComparison.Ordinal) +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + error.Item3.Message.Replace(Environment.NewLine, wrapSpaces, StringComparison.Ordinal) #else - error.Item3.Message?.Replace(Environment.NewLine, wrapSpaces) + error.Item3.Message.Replace(Environment.NewLine, wrapSpaces) #endif ) ) @@ -79,20 +81,5 @@ public static AllException ForFailures( return new AllException(message); } - - /// - /// Creates a new instance of the class to be thrown when - /// collection is not supposed to be empty - /// during - /// or . - /// - public static AllException ForEmptyCollection() => - new AllException( - string.Format( - CultureInfo.CurrentCulture, - "Assert.All() Failure: The collection was empty.{0}At least one item was expected.", - Environment.NewLine - ) - ); } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/CollectionException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/CollectionException.cs index 98d3d193999..fdf5d952567 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/CollectionException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/CollectionException.cs @@ -1,5 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0300 // Simplify collection initialization #if XUNIT_NULLABLE @@ -38,7 +41,7 @@ partial class CollectionException : XunitException static string FormatInnerException(Exception innerException) { - var text = innerException.Message ?? string.Empty; + var text = innerException.Message; var filteredStack = ExceptionUtilityInternal.TransformStackTrace(ExceptionUtilityInternal.FilterStackTrace(innerException.StackTrace), " "); if (!string.IsNullOrWhiteSpace(filteredStack)) { diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ContainsException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ContainsException.cs index 182a9bb2804..9745e865770 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ContainsException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ContainsException.cs @@ -1,6 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DistinctException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DistinctException.cs index 6e7b162260f..6d8423f4364 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DistinctException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DistinctException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotContainException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotContainException.cs index 458d5fd171d..58b23928934 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotContainException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotContainException.cs @@ -1,6 +1,10 @@ #pragma warning disable CA1032 // Implement standard exception constructors #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -186,7 +190,8 @@ public static DoesNotContainException ForSubStringFound( Assert.GuardArgumentNotNull(nameof(expectedSubString), expectedSubString); Assert.GuardArgumentNotNull(nameof(@string), @string); - var encodedString = AssertHelper.ShortenAndEncodeString(@string, indexFailurePoint, out var failurePointerIndent); + int failurePointerIndent; + var encodedString = AssertHelper.ShortenAndEncodeString(@string, indexFailurePoint, out failurePointerIndent); return new DoesNotContainException( string.Format( diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotMatchException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotMatchException.cs index a1dcd8d0faa..c69224a41fc 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotMatchException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/DoesNotMatchException.cs @@ -1,6 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EmptyException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EmptyException.cs index 59b0daa25e3..0478ef637a7 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EmptyException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EmptyException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EndsWithException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EndsWithException.cs index 2908ca815ef..f8682dafd14 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EndsWithException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EndsWithException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EqualException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EqualException.cs index 6b1a3d7827e..2c6673a87ac 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EqualException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EqualException.cs @@ -1,4 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -9,7 +13,6 @@ #endif using System; -using System.ComponentModel; using System.Globalization; using Xunit.Internal; @@ -128,58 +131,6 @@ public static EqualException ForMismatchedCollectionsWithError( return new EqualException(message, error); } - /// - /// Creates a new instance of to be thrown when two sets - /// are not equal. - /// - /// The expected collection - /// The type of the expected set, when they differ in type - /// The actual collection - /// The type of the actual set, when they differ in type - /// The display name for the collection type - public static EqualException ForMismatchedSets( - string expected, -#if XUNIT_NULLABLE - string? expectedType, -#else - string expectedType, -#endif - string actual, -#if XUNIT_NULLABLE - string? actualType, -#else - string actualType, -#endif - string collectionDisplay) - { - Assert.GuardArgumentNotNull(nameof(expected), expected); - Assert.GuardArgumentNotNull(nameof(actual), actual); - - var message = string.Format(CultureInfo.CurrentCulture, "Assert.Equal() Failure: {0} differ", collectionDisplay); - var expectedTypeText = ""; - var actualTypeText = ""; - if (expectedType != null && actualType != null && expectedType != actualType) - { - var length = Math.Max(expectedType.Length, actualType.Length) + 1; - - expectedTypeText = expectedType.PadRight(length); - actualTypeText = actualType.PadRight(length); - } - - message += string.Format( - CultureInfo.CurrentCulture, - "{0}Expected: {1}{2}{3}Actual: {4}{5}", - Environment.NewLine, - expectedTypeText, - expected, - Environment.NewLine, - actualTypeText, - actual - ); - - return new EqualException(message); - } - /// /// Creates a new instance of to be thrown when two string /// values are not equal. @@ -197,45 +148,15 @@ public static EqualException ForMismatchedStrings( string actual, #endif int expectedIndex, - int actualIndex) => - ForMismatchedStringsWithHeader(expected, actual, expectedIndex, actualIndex, "Strings differ"); - - /// - /// Creates a new instance of to be thrown when two string - /// values are not equal. - /// - /// The expected value - /// The actual value - /// The index point in the expected string where the values differ - /// The index point in the actual string where the values differ - /// The header to display in the assertion heading - public static EqualException ForMismatchedStringsWithHeader( -#if XUNIT_NULLABLE - string? expected, - string? actual, -#else - string expected, - string actual, -#endif - int expectedIndex, - int actualIndex, - string header) + int actualIndex) { - var message = "Assert.Equal() Failure: " + header; - var (expectedStart, expectedEnd) = AssertHelper.GetStartEndForString(expected, expectedIndex); - var (actualStart, actualEnd) = AssertHelper.GetStartEndForString(actual, actualIndex); + var message = "Assert.Equal() Failure: Strings differ"; - if ((expectedStart != 0 || actualStart != 0) && expectedIndex != -1 && actualIndex != -1) - { - // Try to find the correct start point so the positions will come into alignment - var positionDifference = expectedIndex - actualIndex; - var startingPosition = Math.Max(expectedStart, actualStart); - expectedStart = startingPosition; - actualStart = startingPosition - positionDifference; - } + int expectedPointer; + int actualPointer; - var formattedExpected = AssertHelper.ShortenString(expected, expectedStart, expectedEnd, expectedIndex, out var expectedPointer); - var formattedActual = AssertHelper.ShortenString(actual, actualStart, actualEnd, actualIndex, out var actualPointer); + var formattedExpected = AssertHelper.ShortenAndEncodeString(expected, expectedIndex, out expectedPointer); + var formattedActual = AssertHelper.ShortenAndEncodeString(actual, actualIndex, out actualPointer); if (expected != null && expectedIndex > -1 && expectedIndex < expected.Length) message += string.Format(CultureInfo.CurrentCulture, "{0}{1}\u2193 (pos {2})", newLineAndIndent, new string(' ', expectedPointer), expectedIndex); @@ -248,23 +169,6 @@ public static EqualException ForMismatchedStringsWithHeader( return new EqualException(message); } - /// - /// Please use . - /// - [Obsolete("Please use the overload that accepts pre-formatted values as strings")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static EqualException ForMismatchedValues( -#if XUNIT_NULLABLE - object? expected, - object? actual, - string? banner = null) => -#else - object expected, - object actual, - string banner = null) => -#endif - ForMismatchedValues(ArgumentFormatter.Format(expected), ArgumentFormatter.Format(actual), banner); - /// /// Creates a new instance of to be thrown when two values /// are not equal. This may be simple values (like intrinsics) or complex values (like @@ -272,44 +176,19 @@ public static EqualException ForMismatchedValues( /// /// The expected value /// The actual value - /// The banner to show; if , then the standard + /// The banner to show; if null, then the standard /// banner of "Values differ" will be used public static EqualException ForMismatchedValues( - string expected, - string actual, -#if XUNIT_NULLABLE - string? banner = null) => -#else - string banner = null) => -#endif - ForMismatchedValuesWithError(expected, actual, null, banner); - - /// - /// Please use . - /// - [Obsolete("Please use the overload that accepts pre-formatted values as strings")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static EqualException ForMismatchedValuesWithError( #if XUNIT_NULLABLE object? expected, object? actual, - Exception? error = null, string? banner = null) => #else object expected, object actual, - Exception error = null, string banner = null) => #endif - // Strings normally come through ForMismatchedStrings, so we want to make sure any - // string value that comes through here isn't re-formatted/truncated. This allows - // any assertion functions which pre-format string values to preserve those. - ForMismatchedValuesWithError( - expected as string ?? ArgumentFormatter.Format(expected), - actual as string ?? ArgumentFormatter.Format(actual), - error, - banner - ); + ForMismatchedValuesWithError(expected, actual, null, banner); /// /// Creates a new instance of to be thrown when two values @@ -319,23 +198,32 @@ public static EqualException ForMismatchedValuesWithError( /// The expected value /// The actual value /// The optional exception that was thrown during comparison - /// The banner to show; if , then the standard - /// banner of "Values differ" will be used. If is not , + /// The banner to show; if null, then the standard + /// banner of "Values differ" will be used. If is not null, /// then the banner used will always be "Exception thrown during comparison", regardless /// of the value passed here. public static EqualException ForMismatchedValuesWithError( - string expected, - string actual, #if XUNIT_NULLABLE + object? expected, + object? actual, Exception? error = null, string? banner = null) #else + object expected, + object actual, Exception error = null, string banner = null) #endif { - Assert.GuardArgumentNotNull(nameof(expected), expected); - Assert.GuardArgumentNotNull(nameof(actual), actual); + // Strings normally come through ForMismatchedStrings, so we want to make sure any + // string value that comes through here isn't re-formatted/truncated. This is for + // two reasons: (a) to support Assert.Equal(string1, string2) to get a full + // printout of the raw string values, which is useful when debugging; and (b) to + // allow the assertion functions to pre-format the value themselves, perhaps with + // additional information (like DateTime/DateTimeOffset when providing the precision + // of the comparison). + var expectedText = expected as string ?? ArgumentFormatter.Format(expected); + var actualText = actual as string ?? ArgumentFormatter.Format(actual); var message = error == null @@ -348,16 +236,16 @@ public static EqualException ForMismatchedValuesWithError( "{0}{1}Expected: {2}{3}Actual: {4}", message, Environment.NewLine, -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - expected.Replace(Environment.NewLine, newLineAndIndent, StringComparison.Ordinal), +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + expectedText.Replace(Environment.NewLine, newLineAndIndent, StringComparison.Ordinal), #else - expected.Replace(Environment.NewLine, newLineAndIndent), + expectedText.Replace(Environment.NewLine, newLineAndIndent), #endif Environment.NewLine, -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - actual.Replace(Environment.NewLine, newLineAndIndent, StringComparison.Ordinal) +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + actualText.Replace(Environment.NewLine, newLineAndIndent, StringComparison.Ordinal) #else - actual.Replace(Environment.NewLine, newLineAndIndent) + actualText.Replace(Environment.NewLine, newLineAndIndent) #endif ), error diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EquivalentException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EquivalentException.cs index 06ef429e535..6634d922264 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EquivalentException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/EquivalentException.cs @@ -1,6 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors #pragma warning disable CA1200 // Avoid using cref tags with a prefix +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ExceptionUtility.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ExceptionUtility.cs index 6ceec89f347..cbeb36b97b7 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ExceptionUtility.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ExceptionUtility.cs @@ -1,7 +1,9 @@ +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0300 // Simplify collection initialization #pragma warning disable IDE0305 // Simplify collection initialization -#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FailException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FailException.cs index 1b9d48d4a85..75cbb1ce756 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FailException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FailException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FalseException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FalseException.cs index 4f6961b01cd..441b652842b 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FalseException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/FalseException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -26,9 +28,9 @@ partial class FalseException : XunitException /// /// Creates a new instance of the class to be thrown when - /// a non- value was provided. + /// a non-false value was provided. /// - /// The message to be displayed, or for the default message + /// The message to be displayed, or null for the default message /// The actual value public static FalseException ForNonFalseValue( #if XUNIT_NULLABLE diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/InRangeException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/InRangeException.cs index 483df0b1445..5eeb55864ad 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/InRangeException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/InRangeException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsAssignableFromException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsAssignableFromException.cs index fb4842a7368..4fb9486092e 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsAssignableFromException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsAssignableFromException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotAssignableFromException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotAssignableFromException.cs index 58402838cc8..d0643925461 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotAssignableFromException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotAssignableFromException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotTypeException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotTypeException.cs index 25443c06e93..233c9aa9417 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotTypeException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsNotTypeException.cs @@ -1,5 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsTypeException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsTypeException.cs index 9b3f3e2ce0c..4fa4514b8b8 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsTypeException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/IsTypeException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MatchesException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MatchesException.cs index a4c0c959c54..805b7bfcedd 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MatchesException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MatchesException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MultipleException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MultipleException.cs index 6a624ef2323..efe61817eba 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MultipleException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/MultipleException.cs @@ -1,6 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0305 // Simplify collection initialization +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -23,10 +25,8 @@ namespace Xunit.Sdk #endif partial class MultipleException : XunitException { - MultipleException( - string assertionName, - IEnumerable innerExceptions) : - base("Assert." + assertionName + "() Failure: Multiple failures were encountered") + MultipleException(IEnumerable innerExceptions) : + base("Assert.Multiple() Failure: Multiple failures were encountered") { Assert.GuardArgumentNotNull(nameof(innerExceptions), innerExceptions); @@ -52,14 +52,6 @@ partial class MultipleException : XunitException /// /// The inner exceptions public static MultipleException ForFailures(IReadOnlyCollection innerExceptions) => - new MultipleException("Multiple", innerExceptions); - - /// - /// Creates a new instance of the class to be thrown - /// when caught 2 or more exceptions. - /// - /// The inner exceptions - public static MultipleException ForFailuresAsync(IReadOnlyCollection innerExceptions) => - new MultipleException("MultipleAsync", innerExceptions); + new MultipleException(innerExceptions); } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEmptyException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEmptyException.cs index 7c4cedfb808..abe5f7b5407 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEmptyException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEmptyException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEqualException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEqualException.cs index 145a18d29b1..65b967302ec 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEqualException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotEqualException.cs @@ -1,5 +1,8 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -55,14 +58,14 @@ public static NotEqualException ForEqualCollections( /// Creates a new instance of to be thrown when two collections /// are equal, and an error has occurred during comparison. /// - /// The index at which the collections error occurred (should be - /// when is ) + /// The index at which the collections error occurred (should be null + /// when is null) /// The expected collection /// The spacing into the expected collection where the difference occurs - /// (should be when is null) + /// (should be null when is null) /// The actual collection /// The spacing into the actual collection where the difference occurs - /// (should be when is null) + /// (should be null when is null) /// The optional exception that was thrown during comparison /// The display name for the collection type (defaults to "Collections") public static NotEqualException ForEqualCollectionsWithError( @@ -83,7 +86,6 @@ public static NotEqualException ForEqualCollectionsWithError( Assert.GuardArgumentNotNull(nameof(actual), actual); error = ArgumentFormatter.UnwrapException(error); - if (error is AssertEqualityComparer.OperationalFailureException) return new NotEqualException("Assert.NotEqual() Failure: " + error.Message); @@ -103,58 +105,6 @@ public static NotEqualException ForEqualCollectionsWithError( return new NotEqualException(message, error); } - /// - /// Creates a new instance of to be thrown when two sets - /// are equal. - /// - /// The expected collection - /// The type of the expected set, when they differ in type - /// The actual collection - /// The type of the actual set, when they differ in type - /// The display name for the collection type - public static NotEqualException ForEqualSets( - string expected, -#if XUNIT_NULLABLE - string? expectedType, -#else - string expectedType, -#endif - string actual, -#if XUNIT_NULLABLE - string? actualType, -#else - string actualType, -#endif - string collectionDisplay) - { - Assert.GuardArgumentNotNull(nameof(expected), expected); - Assert.GuardArgumentNotNull(nameof(actual), actual); - - var message = string.Format(CultureInfo.CurrentCulture, "Assert.NotEqual() Failure: {0} are equal", collectionDisplay); - var expectedTypeText = ""; - var actualTypeText = ""; - if (expectedType != null && actualType != null && expectedType != actualType) - { - var length = Math.Max(expectedType.Length, actualType.Length) + 1; - - expectedTypeText = expectedType.PadRight(length); - actualTypeText = actualType.PadRight(length); - } - - message += string.Format( - CultureInfo.CurrentCulture, - "{0}Expected: Not {1}{2}{3}Actual: {4}{5}", - Environment.NewLine, - expectedTypeText, - expected, - Environment.NewLine, - actualTypeText, - actual - ); - - return new NotEqualException(message); - } - /// /// Creates a new instance of to be thrown when two values /// are equal. This may be simple values (like intrinsics) or complex values (like @@ -162,7 +112,7 @@ public static NotEqualException ForEqualSets( /// /// The expected value /// The actual value - /// The banner to show; if , then the standard + /// The banner to show; if null, then the standard /// banner of "Values are equal" will be used public static NotEqualException ForEqualValues( string expected, @@ -182,8 +132,8 @@ public static NotEqualException ForEqualValues( /// The expected value /// The actual value /// The optional exception that was thrown during comparison - /// The banner to show; if , then the standard - /// banner of "Values are equal" will be used. If is not , + /// The banner to show; if null, then the standard + /// banner of "Values are equal" will be used. If is not null, /// then the banner used will always be "Exception thrown during comparison", regardless /// of the value passed here. public static NotEqualException ForEqualValuesWithError( diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotInRangeException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotInRangeException.cs index 58679b76ef1..8e5b2ecba90 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotInRangeException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotInRangeException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotNullException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotNullException.cs index f17caafee5c..010d8ab48ed 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotNullException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotNullException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -26,21 +28,7 @@ partial class NotNullException : XunitException /// /// Creates a new instance of the class to be - /// throw when a pointer is . - /// - /// The inner type of the value - public static Exception ForNullPointer(Type type) => - new NotNullException( - string.Format( - CultureInfo.CurrentCulture, - "Assert.NotNull() Failure: Value of type '{0}*' is null", - ArgumentFormatter.FormatTypeName(Assert.GuardArgumentNotNull(nameof(type), type)) - ) - ); - - /// - /// Creates a new instance of the class to be - /// throw when a nullable struct is . + /// throw when a nullable struct is null. /// /// The inner type of the value public static Exception ForNullStruct(Type type) => @@ -54,7 +42,7 @@ public static Exception ForNullStruct(Type type) => /// /// Creates a new instance of the class to be - /// thrown when a reference value is . + /// thrown when a reference value is null. /// public static NotNullException ForNullValue() => new NotNullException("Assert.NotNull() Failure: Value is null"); diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotRaisesException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotRaisesException.cs deleted file mode 100644 index 1116d24fb75..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotRaisesException.cs +++ /dev/null @@ -1,50 +0,0 @@ -#pragma warning disable CA1032 // Implement standard exception constructors -#pragma warning disable IDE0090 // Use 'new(...)' - -#if XUNIT_NULLABLE -#nullable enable -#endif - -using System; -using System.Globalization; - -namespace Xunit.Sdk -{ - /// - /// Exception thrown when Assert.NotRaisedAny fails. - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - partial class NotRaisesException : XunitException - { - NotRaisesException(string message) : - base(message) - { } - - /// - /// Creates a new instance of the class to be thrown when - /// an unexpected event was raised. - /// - public static NotRaisesException ForUnexpectedEvent() => - new NotRaisesException("Assert.NotRaisedAny() Failure: An unexpected event was raised"); - - /// - /// Creates a new instance of the class to be thrown when - /// an unexpected event (with data) was raised. - /// - /// The type of the event args that was unexpected - public static NotRaisesException ForUnexpectedEvent(Type unexpected) => - new NotRaisesException( - string.Format( - CultureInfo.CurrentCulture, - "Assert.NotRaisedAny() Failure: An unexpected event was raised{0}Unexpected: {1}{2}Actual: An event was raised", - Environment.NewLine, - ArgumentFormatter.Format(Assert.GuardArgumentNotNull(nameof(unexpected), unexpected)), - Environment.NewLine - ) - ); - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotSameException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotSameException.cs index 632a5845c2d..951a8bd39d1 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotSameException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotSameException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotStrictEqualException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotStrictEqualException.cs index be318811397..d88b7ccbfbf 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotStrictEqualException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NotStrictEqualException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NullException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NullException.cs index a44f356ec5e..967fb7f020e 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NullException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/NullException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -9,6 +11,7 @@ #endif using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace Xunit.Sdk @@ -27,27 +30,18 @@ partial class NullException : XunitException base(message) { } - /// - /// Creates a new instance of the class to be thrown - /// when the given pointer value was unexpectedly not null. - /// - /// The inner type of the value - public static NullException ForNonNullPointer(Type type) => - new NullException( - string.Format( - CultureInfo.CurrentCulture, - "Assert.Null() Failure: Value of type '{0}*' is not null", - ArgumentFormatter.FormatTypeName(Assert.GuardArgumentNotNull(nameof(type), type)) - ) - ); - /// /// Creates a new instance of the class to be thrown /// when the given nullable struct was unexpectedly not null. /// /// The inner type of the value - /// The actual non- value - public static Exception ForNonNullStruct( + /// The actual non-null value + public static Exception ForNonNullStruct<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Type type, T? actual) where T : struct => @@ -66,8 +60,15 @@ public static Exception ForNonNullStruct( /// Creates a new instance of the class to be thrown /// when the given value was unexpectedly not null. /// - /// The actual non- value - public static NullException ForNonNullValue(object actual) => + /// The actual non-null value + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2019:Mismatched constraints", + Justification = "Assert.GuardArgumentNotNull returns the same type passed in, so the annotations on the T type parameter will work")] + public static NullException ForNonNullValue<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>(T actual) => new NullException( string.Format( CultureInfo.CurrentCulture, diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSubsetException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSubsetException.cs index 492698a5d49..2d86623b4b8 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSubsetException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSubsetException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSupersetException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSupersetException.cs index 28ac605c8ee..c0739448b8f 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSupersetException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ProperSupersetException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/PropertyChangedException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/PropertyChangedException.cs index 343ca3ce7c4..66b05b96a77 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/PropertyChangedException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/PropertyChangedException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesAnyException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesAnyException.cs index 6fb18529add..32eee34b569 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesAnyException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesAnyException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesException.cs index 5943ca65881..91bc979616a 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/RaisesException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SameException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SameException.cs index ae3fd870b81..f8adf4d8713 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SameException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SameException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SingleException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SingleException.cs index 7fbf4645fa7..b420c474300 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SingleException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SingleException.cs @@ -1,5 +1,9 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0046 // Convert to conditional expression +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -29,7 +33,7 @@ partial class SingleException : XunitException /// Creates an new instance of the class to be thrown when /// the collection didn't contain any values (or didn't contain the expected value). /// - /// The expected value (set to for no expected value) + /// The expected value (set to null for no expected value) /// The collection public static SingleException Empty( #if XUNIT_NULLABLE @@ -61,7 +65,7 @@ public static SingleException Empty( /// the collection more than one value (or contained more than one of the expected value). /// /// The number of items, or the number of matching items - /// The expected value (set to for no expected value) + /// The expected value (set to null for no expected value) /// The collection /// The list of indices where matches occurred public static SingleException MoreThanOne( diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SkipException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SkipException.cs index b011d3372ee..da44efba619 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SkipException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SkipException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StartsWithException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StartsWithException.cs index 59ce1ca4370..71a1e798f5e 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StartsWithException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StartsWithException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StrictEqualException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StrictEqualException.cs index 6a04c0925fa..37757239a6d 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StrictEqualException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/StrictEqualException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SubsetException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SubsetException.cs index afdc31dab50..eba4a9ccec3 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SubsetException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SubsetException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SupersetException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SupersetException.cs index 5fbd0504489..80a214e3c0b 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SupersetException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/SupersetException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsAnyException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsAnyException.cs index 8d6f8156fb2..24b7e6fb035 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsAnyException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsAnyException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -54,21 +56,6 @@ public static ThrowsAnyException ForIncorrectExceptionType( actual ); - /// - /// Creates a new instance of the class to be thrown when - /// an inspector rejected the exception. - /// - /// The custom message - /// The optional exception thrown by the inspector - public static Exception ForInspectorFailure( - string message, -#if XUNIT_NULLABLE - Exception? innerException = null) => -#else - Exception innerException = null) => -#endif - new ThrowsAnyException(string.Format(CultureInfo.CurrentCulture, "Assert.ThrowsAny() Failure: {0}", message), innerException); - /// /// Creates a new instance of the class to be thrown when /// an exception wasn't thrown by Assert.ThrowsAny. diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsException.cs index e3160a31c4b..49afecff28a 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/ThrowsException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -83,21 +85,6 @@ public static ThrowsException ForIncorrectParameterName( ) ); - /// - /// Creates a new instance of the class to be thrown when - /// an inspector rejected the exception. - /// - /// The custom message - /// The optional exception thrown by the inspector - public static Exception ForInspectorFailure( - string message, -#if XUNIT_NULLABLE - Exception? innerException = null) => -#else - Exception innerException = null) => -#endif - new ThrowsException(string.Format(CultureInfo.CurrentCulture, "Assert.Throws() Failure: {0}", message), innerException); - /// /// Creates a new instance of the class to be thrown when /// an exception wasn't thrown by Assert.Throws. diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/TrueException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/TrueException.cs index 25c6f95b49f..e2c86b02294 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/TrueException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/TrueException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -26,9 +28,9 @@ partial class TrueException : XunitException /// /// Creates a new instance of the class to be thrown when - /// a non- value was provided. + /// a non-true value was provided. /// - /// The message to be displayed, or for the default message + /// The message to be displayed, or null for the default message /// The actual value public static TrueException ForNonTrueValue( #if XUNIT_NULLABLE diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/XunitException.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/XunitException.cs index 9b60f06b0ca..e51892647d8 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/XunitException.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/Exceptions/XunitException.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable IDE0040 // Add accessibility modifiers #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #pragma warning disable IDE0290 // Use primary constructor #if XUNIT_NULLABLE @@ -23,10 +25,7 @@ namespace Xunit.Sdk #else public #endif - partial class XunitException : Exception -#if !XUNIT_AOT - , IAssertionException -#endif + partial class XunitException : Exception, IAssertionException { /// /// Initializes a new instance of the class. @@ -63,7 +62,7 @@ public override string ToString() var className = GetType().ToString(); var message = Message; var result = - message is null || message.Length == 0 + message == null || message.Length <= 0 ? className : string.Format(CultureInfo.CurrentCulture, "{0}: {1}", className, message); diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertEqualityComparer.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertEqualityComparer.cs deleted file mode 100644 index ad178940846..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertEqualityComparer.cs +++ /dev/null @@ -1,85 +0,0 @@ -#pragma warning disable CA1510 // Use ArgumentNullException throw helper -#pragma warning disable IDE0063 // Use simple 'using' statement - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections.Generic; - -namespace Xunit.Sdk -{ - /// - /// Represents a specialized version of that returns information useful - /// when formatting results for assertion failures. - /// - /// The type of the objects being compared. -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - interface IAssertEqualityComparer : IEqualityComparer - { - /// - /// Compares two values and determines if they are equal. - /// - /// The first value - /// The first value as a (if it's a collection) - /// The second value - /// The second value as a (if it's a collection) - /// Success or failure information - AssertEqualityResult Equals( -#if XUNIT_NULLABLE - T? x, - CollectionTracker? xTracker, - T? y, - CollectionTracker? yTracker); -#else - T x, - CollectionTracker xTracker, - T y, - CollectionTracker yTracker); -#endif - } - - /// - /// Extension methods for - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - static class IAssertEqualityComparerExtensions - { - /// - /// Compares two values and determines if they are equal. - /// - /// The comparer - /// The first value - /// The second value - /// Success or failure information - public static AssertEqualityResult Equals( - this IAssertEqualityComparer comparer, -#if XUNIT_NULLABLE - T? x, - T? y) -#else - T x, - T y) -#endif - { - if (comparer is null) - throw new ArgumentNullException(nameof(comparer)); - - using (var xTracker = x.AsNonStringTracker()) - using (var yTracker = y.AsNonStringTracker()) - return comparer.Equals(x, xTracker, y, yTracker); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_reflection.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException.cs similarity index 89% rename from src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_reflection.cs rename to src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException.cs index fc1c9022192..52f5e3b2403 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_reflection.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException.cs @@ -1,8 +1,7 @@ -#if !XUNIT_AOT - #pragma warning disable CA1040 // Avoid empty interfaces #pragma warning disable CA1200 // Avoid using cref tags with a prefix #pragma warning disable CA1711 // Identifiers should not have incorrect suffix +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -22,5 +21,3 @@ namespace Xunit.Sdk interface IAssertionException { } } - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_aot.cs deleted file mode 100644 index bff648802f5..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/IAssertionException_aot.cs +++ /dev/null @@ -1,28 +0,0 @@ -#if XUNIT_AOT - -#pragma warning disable CA1040 // Avoid empty interfaces -#pragma warning disable CA1711 // Identifiers should not have incorrect suffix - -#if XUNIT_NULLABLE -#nullable enable -#endif - -using System; - -namespace Xunit.Sdk -{ - /// - /// This interface is not supported in Native AOT, because interface-based discovery - /// requires reflection. - /// - [Obsolete("This interface is not supported in Native AOT. Decorating with it is benign and ignored.")] -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - interface IAssertionException - { } -} - -#endif // XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/OverloadResolutionPriorityAttribute.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/OverloadResolutionPriorityAttribute.cs deleted file mode 100644 index 7383536eec1..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/OverloadResolutionPriorityAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#if !NET9_0_OR_GREATER && XUNIT_OVERLOAD_RESOLUTION_PRIORITY - -#pragma warning disable IDE0290 // Use primary constructor - -namespace System.Runtime.CompilerServices -{ - /// - /// Specifies the priority of a member in overload resolution. When unspecified, the default priority is 0. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - internal sealed class OverloadResolutionPriorityAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The priority of the attributed member. Higher numbers are prioritized, lower numbers are deprioritized. 0 is the default if no attribute is present. - public OverloadResolutionPriorityAttribute(int priority) => - Priority = priority; - - /// - /// The priority of the member. - /// - public int Priority { get; } - } -} - -#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringAssertEqualityComparer.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringAssertEqualityComparer.cs deleted file mode 100644 index 2ec597c7d67..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringAssertEqualityComparer.cs +++ /dev/null @@ -1,245 +0,0 @@ -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0090 // Use 'new(...)' -#pragma warning disable IDE0290 // Use primary constructor - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections.Generic; - -namespace Xunit.Sdk -{ - /// - /// This static class offers equivalence comparisons for string values - /// -#if XUNIT_VISIBILITY_INTERNAL - internal -#else - public -#endif - static class StringAssertEqualityComparer - { - static readonly HashSet charsLineEndings = new HashSet() - { - '\r', // Carriage Return - '\n', // Line feed - }; - static readonly HashSet charsWhitespace = new HashSet() - { - '\t', // Tab - ' ', // Space - '\u00A0', // No-Break Space - '\u1680', // Ogham Space Mark - '\u180E', // Mongolian Vowel Separator - '\u2000', // En Quad - '\u2001', // Em Quad - '\u2002', // En Space - '\u2003', // Em Space - '\u2004', // Three-Per-Em Space - '\u2005', // Four-Per-Em Space - '\u2006', // Six-Per-Em Space - '\u2007', // Figure Space - '\u2008', // Punctuation Space - '\u2009', // Thin Space - '\u200A', // Hair Space - '\u200B', // Zero Width Space - '\u202F', // Narrow No-Break Space - '\u205F', // Medium Mathematical Space - '\u3000', // Ideographic Space - '\uFEFF', // Zero Width No-Break Space - }; - - /// - /// Compare two string values for equalivalence. - /// - /// The expected string value. - /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// - public static AssertEqualityResult Equivalent( -#if XUNIT_NULLABLE - string? expected, - string? actual, -#else - string expected, - string actual, -#endif - bool ignoreCase = false, - bool ignoreLineEndingDifferences = false, - bool ignoreWhiteSpaceDifferences = false, - bool ignoreAllWhiteSpace = false) - { - if (expected == null && actual == null) - return AssertEqualityResult.ForResult(true, expected, actual); - if (expected == null || actual == null) - return AssertEqualityResult.ForResult(false, expected, actual); - - return Equivalent(expected.AsSpan(), actual.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); - } - - /// - /// Compare two string values for equalivalence. - /// - /// The expected string value. - /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// - public static AssertEqualityResult Equivalent( - ReadOnlySpan expected, - ReadOnlySpan actual, - bool ignoreCase = false, - bool ignoreLineEndingDifferences = false, - bool ignoreWhiteSpaceDifferences = false, - bool ignoreAllWhiteSpace = false) - { - // Walk the string, keeping separate indices since we can skip variable amounts of - // data based on ignoreLineEndingDifferences and ignoreWhiteSpaceDifferences. - var expectedIndex = 0; - var actualIndex = 0; - var expectedLength = expected.Length; - var actualLength = actual.Length; - - // Block used to fix edge case of Equal("", " ") when ignoreAllWhiteSpace enabled. - if (ignoreAllWhiteSpace) - { - if (expectedLength == 0 && SkipWhitespace(actual, 0) == actualLength) - return AssertEqualityResult.ForResult(true, expected.ToString(), actual.ToString()); - if (actualLength == 0 && SkipWhitespace(expected, 0) == expectedLength) - return AssertEqualityResult.ForResult(true, expected.ToString(), actual.ToString()); - } - - while (expectedIndex < expectedLength && actualIndex < actualLength) - { - var expectedChar = expected[expectedIndex]; - var actualChar = actual[actualIndex]; - - if (ignoreLineEndingDifferences && charsLineEndings.Contains(expectedChar) && charsLineEndings.Contains(actualChar)) - { - expectedIndex = SkipLineEnding(expected, expectedIndex); - actualIndex = SkipLineEnding(actual, actualIndex); - } - else if (ignoreAllWhiteSpace && (charsWhitespace.Contains(expectedChar) || charsWhitespace.Contains(actualChar))) - { - expectedIndex = SkipWhitespace(expected, expectedIndex); - actualIndex = SkipWhitespace(actual, actualIndex); - } - else if (ignoreWhiteSpaceDifferences && charsWhitespace.Contains(expectedChar) && charsWhitespace.Contains(actualChar)) - { - expectedIndex = SkipWhitespace(expected, expectedIndex); - actualIndex = SkipWhitespace(actual, actualIndex); - } - else - { - if (ignoreCase) - { - expectedChar = char.ToUpperInvariant(expectedChar); - actualChar = char.ToUpperInvariant(actualChar); - } - - if (expectedChar != actualChar) - break; - - expectedIndex++; - actualIndex++; - } - } - - if (expectedIndex < expectedLength || actualIndex < actualLength) - return AssertEqualityResult.ForMismatch(expected.ToString(), actual.ToString(), expectedIndex, actualIndex); - - return AssertEqualityResult.ForResult(true, expected.ToString(), actual.ToString()); - } - - static int SkipLineEnding( - ReadOnlySpan value, - int index) - { - if (value[index] == '\r') - ++index; - - if (index < value.Length && value[index] == '\n') - ++index; - - return index; - } - - static int SkipWhitespace( - ReadOnlySpan value, - int index) - { - while (index < value.Length) - { - if (charsWhitespace.Contains(value[index])) - index++; - else - return index; - } - - return index; - } - - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringSyntaxAttribute.cs b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringSyntaxAttribute.cs index 445241d7f95..77d2de253f6 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringSyntaxAttribute.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/Sdk/StringSyntaxAttribute.cs @@ -1,4 +1,6 @@ -#pragma warning disable IDE0301 // Simplify collection initialization +#pragma warning disable CA1825 // Avoid zero-length array allocations +#pragma warning disable IDE0161 // Convert to file-scoped namespace +#pragma warning disable IDE0300 // Simplify collection initialization #if XUNIT_NULLABLE #nullable enable @@ -27,7 +29,7 @@ internal sealed class StringSyntaxAttribute : Attribute public StringSyntaxAttribute(string syntax) { Syntax = syntax; - Arguments = Array.Empty(); + Arguments = new object[0]; } /// diff --git a/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts.cs index 6d3596e114b..b84e6b10ef2 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -8,11 +10,20 @@ #endif using System.Collections.Generic; -using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Xunit.Sdk; +#if XUNIT_IMMUTABLE_COLLECTIONS +using System.Collections.Immutable; +#endif + namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -22,7 +33,7 @@ partial class Assert /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, ISet set) { @@ -36,8 +47,7 @@ public static void Contains( ); } -#if NET8_0_OR_GREATER - +#if NET5_0_OR_GREATER /// /// Verifies that the read-only set contains the given object. /// @@ -45,7 +55,12 @@ public static void Contains( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IReadOnlySet set) { @@ -58,8 +73,7 @@ public static void Contains( CollectionTracker.FormatStart(set) ); } - -#endif // NET8_0_OR_GREATER +#endif /// /// Verifies that the hashset contains the given object. @@ -68,7 +82,7 @@ public static void Contains( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, HashSet set) => Contains(expected, (ISet)set); @@ -80,11 +94,12 @@ public static void Contains( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, SortedSet set) => Contains(expected, (ISet)set); +#if XUNIT_IMMUTABLE_COLLECTIONS /// /// Verifies that the immutable hashset contains the given object. /// @@ -92,7 +107,7 @@ public static void Contains( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, ImmutableHashSet set) => Contains(expected, (ISet)set); @@ -104,10 +119,11 @@ public static void Contains( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, ImmutableSortedSet set) => Contains(expected, (ISet)set); +#endif /// /// Verifies that the set does not contain the given item. @@ -116,7 +132,7 @@ public static void Contains( /// The object that is expected not to be in the set /// The set to be inspected /// Thrown when the object is present inside the set - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, ISet set) { @@ -129,8 +145,7 @@ public static void DoesNotContain( ); } -#if NET8_0_OR_GREATER - +#if NET5_0_OR_GREATER /// /// Verifies that the read-only set does not contain the given item. /// @@ -138,7 +153,12 @@ public static void DoesNotContain( /// The object that is expected not to be in the set /// The set to be inspected /// Thrown when the object is present inside the collection - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, IReadOnlySet set) { @@ -150,8 +170,7 @@ public static void DoesNotContain( CollectionTracker.FormatStart(set) ); } - -#endif // NET8_0_OR_GREATER +#endif /// /// Verifies that the hashset does not contain the given item. @@ -160,7 +179,12 @@ public static void DoesNotContain( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, HashSet set) => DoesNotContain(expected, (ISet)set); @@ -172,11 +196,12 @@ public static void DoesNotContain( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, SortedSet set) => DoesNotContain(expected, (ISet)set); +#if XUNIT_IMMUTABLE_COLLECTIONS /// /// Verifies that the immutable hashset does not contain the given item. /// @@ -184,7 +209,12 @@ public static void DoesNotContain( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)]T>( T expected, ImmutableHashSet set) => DoesNotContain(expected, (ISet)set); @@ -196,10 +226,11 @@ public static void DoesNotContain( /// The object expected to be in the set /// The set to be inspected /// Thrown when the object is not present in the set - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( T expected, ImmutableSortedSet set) => DoesNotContain(expected, (ISet)set); +#endif /// /// Verifies that a set is a proper subset of another set. @@ -208,7 +239,12 @@ public static void DoesNotContain( /// The expected subset /// The set expected to be a proper subset /// Thrown when the actual set is not a proper subset of the expected set - public static void ProperSubset( + public static void ProperSubset<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ISet expectedSubset, #if XUNIT_NULLABLE ISet? actual) @@ -232,7 +268,12 @@ public static void ProperSubset( /// The expected superset /// The set expected to be a proper superset /// Thrown when the actual set is not a proper superset of the expected set - public static void ProperSuperset( + public static void ProperSuperset<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ISet expectedSuperset, #if XUNIT_NULLABLE ISet? actual) @@ -256,7 +297,7 @@ public static void ProperSuperset( /// The expected subset /// The set expected to be a subset /// Thrown when the actual set is not a subset of the expected set - public static void Subset( + public static void Subset<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ISet expectedSubset, #if XUNIT_NULLABLE ISet? actual) @@ -280,7 +321,7 @@ public static void Subset( /// The expected superset /// The set expected to be a superset /// Thrown when the actual set is not a superset of the expected set - public static void Superset( + public static void Superset<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ISet expectedSuperset, #if XUNIT_NULLABLE ISet? actual) diff --git a/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts_aot.cs b/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts_aot.cs deleted file mode 100644 index ed4c23f4837..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/src/SetAsserts_aot.cs +++ /dev/null @@ -1,336 +0,0 @@ -#if XUNIT_AOT - -#if XUNIT_NULLABLE -#nullable enable -#else -// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE -#pragma warning disable CS8604 -#endif - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Xunit.Sdk; - -namespace Xunit -{ - partial class Assert - { - const string SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED = "Set comparisons with comparison functions are not supported. For more information, see https://xunit.net/docs/hash-sets-vs-linear-containers"; - - /// - /// Verifies that a set contains the same items as the collection. - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// Thrown if the set is not equal - [OverloadResolutionPriority(1)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual) -#else - ISet actual) -#endif - { - GuardArgumentNotNull(nameof(expected), expected); - - if (actual != null && actual.SetEquals(expected)) - return; - - SetEqualFailure(expected, actual); - } - - /// - /// Verifies that a set contains the same items as the collection, using - /// the given . - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// The item comparerer to use - /// Thrown if the set is not equal - /// - /// Note that this creates a new hash set with the given comparer, using the items from . - /// Because the comparer may create equality differences from the one was created with, - /// the items in the compared container may differ from the one that was passed, since sets are designed to - /// eliminated duplicate (equal) items. - /// - [OverloadResolutionPriority(1)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual, -#else - ISet actual, -#endif - IEqualityComparer comparer) => - Equal(expected, actual == null ? null : new HashSet(actual, comparer)); - - /// - [Obsolete(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED, error: true)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual, -#else - ISet actual, -#endif - Func comparer) => - throw new NotSupportedException(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED); - - /// - /// Verifies that a set contains the same items as the collection. - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// Thrown if the set is not equal - [OverloadResolutionPriority(2)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual) -#else - IReadOnlySet actual) -#endif - { - GuardArgumentNotNull(nameof(expected), expected); - - if (actual != null && actual.SetEquals(expected)) - return; - - SetEqualFailure(expected, actual); - } - - /// - /// Verifies that a set contains the same items as the collection, using - /// the given . - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// The item comparerer to use - /// Thrown if the set is not equal - /// - /// Note that this creates a new hash set with the given comparer, using the items from . - /// Because the comparer may create equality differences from the one was created with, - /// the items in the compared container may differ from the one that was passed, since sets are designed to - /// eliminated duplicate (equal) items. - /// - [OverloadResolutionPriority(2)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual, -#else - IReadOnlySet actual, -#endif - IEqualityComparer comparer) => - Equal(expected, actual == null ? null : new HashSet(actual, comparer)); - - /// - [Obsolete(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED, error: true)] - [OverloadResolutionPriority(2)] - public static void Equal( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual, -#else - IReadOnlySet actual, -#endif - Func comparer) => - throw new NotSupportedException(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED); - - /// - /// Verifies that a set does not contain the same items as the collection. - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// Thrown if the set is equal - [OverloadResolutionPriority(1)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual) -#else - ISet actual) -#endif - { - GuardArgumentNotNull(nameof(expected), expected); - - if (actual != null && !actual.SetEquals(expected)) - return; - - SetNotEqualFailure(expected, actual); - } - - /// - /// Verifies that a set does not contain the same items as the collection, using - /// the given . - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// The item comparerer to use - /// Thrown if the set is equal - /// - /// Note that this creates a new hash set with the given comparer, using the items from . - /// Because the comparer may create equality differences from the one was created with, - /// the items in the compared container may differ from the one that was passed, since sets are designed to - /// eliminated duplicate (equal) items. - /// - [OverloadResolutionPriority(1)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual, -#else - ISet actual, -#endif - IEqualityComparer comparer) => - NotEqual(expected, actual == null ? null : new HashSet(actual, comparer)); - - /// - [Obsolete(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED, error: true)] - [OverloadResolutionPriority(1)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - ISet? actual, -#else - ISet actual, -#endif - Func comparer) => - throw new NotSupportedException(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED); - - /// - /// Verifies that a set does not contain the same items as the collection. - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// Thrown if the set is equal - [OverloadResolutionPriority(2)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual) -#else - IReadOnlySet actual) -#endif - { - GuardArgumentNotNull(nameof(expected), expected); - - if (actual != null && !actual.SetEquals(expected)) - return; - - SetNotEqualFailure(expected, actual); - } - - /// - /// Verifies that a set does not contain the same items as the collection, using - /// the given . - /// - /// The type of the items in the set - /// The expected items to be in the set - /// The actual set - /// The item comparerer to use - /// Thrown if the set is equal - /// - /// Note that this creates a new hash set with the given comparer, using the items from . - /// Because the comparer may create equality differences from the one was created with, - /// the items in the compared container may differ from the one that was passed, since sets are designed to - /// eliminated duplicate (equal) items. - /// - [OverloadResolutionPriority(2)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual, -#else - IReadOnlySet actual, -#endif - IEqualityComparer comparer) => - NotEqual(expected, actual == null ? null : new HashSet(actual, comparer)); - - /// - [Obsolete(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED, error: true)] - [OverloadResolutionPriority(2)] - public static void NotEqual( - IEnumerable expected, -#if XUNIT_NULLABLE - IReadOnlySet? actual, -#else - IReadOnlySet actual, -#endif - Func comparer) => - throw new NotSupportedException(SET_COMPARISON_WITH_FUNC_NOT_SUPPORTED); - - static void SetEqualFailure( - IEnumerable expected, -#if XUNIT_NULLABLE - IEnumerable? actual) -#else - IEnumerable actual) -#endif - { - var expectedFormatted = CollectionTracker.FormatStart(expected); - var actualFormatted = actual == null ? "null" : CollectionTracker.FormatStart(actual); - var expectedTypeFormatted = default(string); - var actualTypeFormatted = default(string); - var expectedType = expected.GetType(); - var actualType = actual?.GetType(); - - if (actualType != null && expectedType != actualType) - { - expectedTypeFormatted = ArgumentFormatter.FormatTypeName(expectedType); - actualTypeFormatted = ArgumentFormatter.FormatTypeName(actualType); - } - - var expectedTypeDefinition = SafeGetGenericTypeDefinition(expectedType); - var actualTypeDefinition = SafeGetGenericTypeDefinition(actualType); - var collectionDisplay = - expectedTypeDefinition == typeofHashSet && actualTypeDefinition == typeofHashSet - ? "HashSets" - : "Sets"; - - throw EqualException.ForMismatchedSets(expectedFormatted, expectedTypeFormatted, actualFormatted, actualTypeFormatted, collectionDisplay); - } - - static void SetNotEqualFailure( - IEnumerable expected, -#if XUNIT_NULLABLE - IEnumerable? actual) -#else - IEnumerable actual) -#endif - { - var expectedFormatted = CollectionTracker.FormatStart(expected); - var actualFormatted = actual == null ? "null" : CollectionTracker.FormatStart(actual); - var expectedTypeFormatted = default(string); - var actualTypeFormatted = default(string); - var expectedType = expected.GetType(); - var actualType = actual?.GetType(); - - if (actualType != null && expectedType != actualType) - { - expectedTypeFormatted = ArgumentFormatter.FormatTypeName(expectedType); - actualTypeFormatted = ArgumentFormatter.FormatTypeName(actualType); - } - - var expectedTypeDefinition = SafeGetGenericTypeDefinition(expectedType); - var actualTypeDefinition = SafeGetGenericTypeDefinition(actualType); - var collectionDisplay = - expectedTypeDefinition == typeofHashSet && actualTypeDefinition == typeofHashSet - ? "HashSets" - : "Sets"; - - throw NotEqualException.ForEqualSets(expectedFormatted, expectedTypeFormatted, actualFormatted, actualTypeFormatted, collectionDisplay); - } - } -} - -#endif // !XUNIT_AOT diff --git a/src/Microsoft.DotNet.XUnitAssert/src/SkipAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/SkipAsserts.cs index 42acf115183..96e1681f75a 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/SkipAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/SkipAsserts.cs @@ -1,4 +1,8 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace + +#if XUNIT_SKIP #if XUNIT_NULLABLE #nullable enable @@ -12,6 +16,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -30,9 +39,9 @@ public static void Skip(string reason) } /// - /// Will skip the current test unless evaluates to . + /// Will skip the current test unless evaluates to true. /// - /// When , the test will continue to run; when , + /// When true, the test will continue to run; when false, /// the test will be skipped /// The message to indicate why the test was skipped public static void SkipUnless( @@ -50,9 +59,9 @@ public static void SkipUnless( } /// - /// Will skip the current test when evaluates to . + /// Will skip the current test when evaluates to true. /// - /// When , the test will be skipped; when , + /// When true, the test will be skipped; when false, /// the test will continue to run /// The message to indicate why the test was skipped public static void SkipWhen( @@ -70,3 +79,5 @@ public static void SkipWhen( } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/SpanAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/SpanAsserts.cs index 38bfb812265..19842b52590 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/SpanAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/SpanAsserts.cs @@ -1,14 +1,25 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0018 // Inline variable declaration +#pragma warning disable IDE0161 // Convert to file-scoped namespace + +#if XUNIT_SPAN #if XUNIT_NULLABLE #nullable enable #endif using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Xunit.Sdk; namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { // While there is an implicit conversion operator from Span to ReadOnlySpan, the @@ -24,7 +35,12 @@ partial class Assert /// The sub-span expected to be in the span /// The span to be inspected /// Thrown when the sub-span is not present inside the span - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Span expectedSubSpan, Span actualSpan) where T : IEquatable => @@ -36,7 +52,12 @@ public static void Contains( /// The sub-span expected to be in the span /// The span to be inspected /// Thrown when the sub-span is not present inside the span - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Span expectedSubSpan, ReadOnlySpan actualSpan) where T : IEquatable => @@ -48,7 +69,12 @@ public static void Contains( /// The sub-span expected to be in the span /// The span to be inspected /// Thrown when the sub-span is not present inside the span - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlySpan expectedSubSpan, Span actualSpan) where T : IEquatable => @@ -60,7 +86,12 @@ public static void Contains( /// The sub-span expected to be in the span /// The span to be inspected /// Thrown when the sub-span is not present inside the span - public static void Contains( + public static void Contains<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlySpan expectedSubSpan, ReadOnlySpan actualSpan) where T : IEquatable @@ -78,7 +109,12 @@ public static void Contains( /// The sub-span expected not to be in the span /// The span to be inspected /// Thrown when the sub-span is present inside the span - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Span expectedSubSpan, Span actualSpan) where T : IEquatable => @@ -90,7 +126,12 @@ public static void DoesNotContain( /// The sub-span expected not to be in the span /// The span to be inspected /// Thrown when the sub-span is present inside the span - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( Span expectedSubSpan, ReadOnlySpan actualSpan) where T : IEquatable => @@ -102,7 +143,12 @@ public static void DoesNotContain( /// The sub-span expected not to be in the span /// The span to be inspected /// Thrown when the sub-span is present inside the span - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlySpan expectedSubSpan, Span actualSpan) where T : IEquatable => @@ -114,7 +160,12 @@ public static void DoesNotContain( /// The sub-span expected not to be in the span /// The span to be inspected /// Thrown when the sub-span is present inside the span - public static void DoesNotContain( + public static void DoesNotContain<[DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods)] T>( ReadOnlySpan expectedSubSpan, ReadOnlySpan actualSpan) where T : IEquatable @@ -122,8 +173,9 @@ public static void DoesNotContain( var idx = actualSpan.IndexOf(expectedSubSpan); if (idx > -1) { + int? failurePointerIndent; var formattedExpected = CollectionTracker.FormatStart(expectedSubSpan); - var formattedActual = CollectionTracker.FormatIndexedMismatch(actualSpan, idx, out var failurePointerIndent); + var formattedActual = CollectionTracker.FormatIndexedMismatch(actualSpan, idx, out failurePointerIndent); throw DoesNotContainException.ForSubSpanFound( formattedExpected, @@ -195,7 +247,9 @@ public static void Equal( where T : IEquatable { if (!expectedSpan.SequenceEqual(actualSpan)) - Equal(expectedSpan.ToArray(), actualSpan.ToArray(), new AssertEqualityComparer()); + Equal(expectedSpan.ToArray(), actualSpan.ToArray()); } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/src/StringAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/StringAsserts.cs index c6a806224be..106d3ddad62 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/StringAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/StringAsserts.cs @@ -1,6 +1,10 @@ #pragma warning disable CA1052 // Static holder types should be static +#pragma warning disable IDE0018 // Inline variable declaration #pragma warning disable IDE0028 // Simplify collection initialization +#pragma warning disable IDE0040 // Add accessibility modifiers +#pragma warning disable IDE0058 // Expression value is never used #pragma warning disable IDE0090 // Use 'new(...)' +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -10,6 +14,7 @@ #endif using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Xunit.Internal; @@ -17,6 +22,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -56,6 +66,8 @@ public static void Contains( throw ContainsException.ForSubStringNotFound(expectedSubstring, actualString); } +#if XUNIT_SPAN + /// /// Verifies that a string contains a given sub-string, using the current culture. /// @@ -258,6 +270,8 @@ public static void Contains( ReadOnlySpan actualString) => Contains(expectedSubstring, actualString, StringComparison.CurrentCulture); +#endif + /// /// Verifies that a string does not contain a given sub-string, using the current culture. /// @@ -299,6 +313,8 @@ public static void DoesNotContain( } } +#if XUNIT_SPAN + /// /// Verifies that a string does not contain a given sub-string, using the current culture. /// @@ -499,6 +515,8 @@ public static void DoesNotContain( ReadOnlySpan actualString) => DoesNotContain(expectedSubstring, actualString, StringComparison.CurrentCulture); +#endif + /// /// Verifies that a string does not match a regular expression. /// @@ -521,8 +539,9 @@ public static void DoesNotMatch( var match = Regex.Match(actualString, expectedRegexPattern); if (match.Success) { + int pointerIndent; var formattedExpected = AssertHelper.ShortenAndEncodeString(expectedRegexPattern); - var formattedActual = AssertHelper.ShortenAndEncodeString(actualString, match.Index, out var pointerIndent); + var formattedActual = AssertHelper.ShortenAndEncodeString(actualString, match.Index, out pointerIndent); throw DoesNotMatchException.ForMatch(formattedExpected, match.Index, pointerIndent, formattedActual); } @@ -550,8 +569,9 @@ public static void DoesNotMatch( var match = expectedRegex.Match(actualString); if (match.Success) { + int pointerIndent; var formattedExpected = AssertHelper.ShortenAndEncodeString(expectedRegex.ToString()); - var formattedActual = AssertHelper.ShortenAndEncodeString(actualString, match.Index, out var pointerIndent); + var formattedActual = AssertHelper.ShortenAndEncodeString(actualString, match.Index, out pointerIndent); throw DoesNotMatchException.ForMatch(formattedExpected, match.Index, pointerIndent, formattedActual); } @@ -609,6 +629,8 @@ public static void EndsWith( throw EndsWithException.ForStringNotFound(expectedEndString, actualString); } +#if XUNIT_SPAN + /// /// Verifies that a string ends with a given sub-string, using the current culture. /// @@ -808,6 +830,8 @@ public static void EndsWith( throw EndsWithException.ForStringNotFound(expectedEndString.ToString(), actualString.ToString()); } +#endif + /// /// Verifies that two strings are equivalent. /// @@ -829,10 +853,10 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. /// /// The and flags consider @@ -863,18 +887,86 @@ public static void Equal( /// public static void Equal( +#if XUNIT_SPAN ReadOnlySpan expected, ReadOnlySpan actual, +#elif XUNIT_NULLABLE + string? expected, + string? actual, +#else + string expected, + string actual, +#endif bool ignoreCase = false, bool ignoreLineEndingDifferences = false, bool ignoreWhiteSpaceDifferences = false, bool ignoreAllWhiteSpace = false) { - var result = StringAssertEqualityComparer.Equivalent(expected, actual, ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); - if (!result.Equal) - throw EqualException.ForMismatchedStrings(expected.ToString(), actual.ToString(), result.MismatchIndexX ?? -1, result.MismatchIndexY ?? -1); +#if !XUNIT_SPAN + if (expected == null && actual == null) + return; + if (expected == null || actual == null) + throw EqualException.ForMismatchedStrings(expected, actual, -1, -1); +#endif + + // Walk the string, keeping separate indices since we can skip variable amounts of + // data based on ignoreLineEndingDifferences and ignoreWhiteSpaceDifferences. + var expectedIndex = 0; + var actualIndex = 0; + var expectedLength = expected.Length; + var actualLength = actual.Length; + + // Block used to fix edge case of Equal("", " ") when ignoreAllWhiteSpace enabled. + if (ignoreAllWhiteSpace) + { + if (expectedLength == 0 && SkipWhitespace(actual, 0) == actualLength) + return; + if (actualLength == 0 && SkipWhitespace(expected, 0) == expectedLength) + return; + } + + while (expectedIndex < expectedLength && actualIndex < actualLength) + { + var expectedChar = expected[expectedIndex]; + var actualChar = actual[actualIndex]; + + if (ignoreLineEndingDifferences && charsLineEndings.Contains(expectedChar) && charsLineEndings.Contains(actualChar)) + { + expectedIndex = SkipLineEnding(expected, expectedIndex); + actualIndex = SkipLineEnding(actual, actualIndex); + } + else if (ignoreAllWhiteSpace && (charsWhitespace.Contains(expectedChar) || charsWhitespace.Contains(actualChar))) + { + expectedIndex = SkipWhitespace(expected, expectedIndex); + actualIndex = SkipWhitespace(actual, actualIndex); + } + else if (ignoreWhiteSpaceDifferences && charsWhitespace.Contains(expectedChar) && charsWhitespace.Contains(actualChar)) + { + expectedIndex = SkipWhitespace(expected, expectedIndex); + actualIndex = SkipWhitespace(actual, actualIndex); + } + else + { + if (ignoreCase) + { + expectedChar = char.ToUpperInvariant(expectedChar); + actualChar = char.ToUpperInvariant(actualChar); + } + + if (expectedChar != actualChar) + break; + + expectedIndex++; + actualIndex++; + } + } + + if (expectedIndex < expectedLength || actualIndex < actualLength) + throw EqualException.ForMismatchedStrings(expected.ToString(), actual.ToString(), expectedIndex, actualIndex); } +#if XUNIT_SPAN + /// /// Verifies that two strings are equivalent. /// @@ -924,38 +1016,11 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// public static void Equal( Memory expected, Memory actual, @@ -970,38 +1035,11 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// public static void Equal( Memory expected, ReadOnlyMemory actual, @@ -1016,38 +1054,11 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// public static void Equal( ReadOnlyMemory expected, Memory actual, @@ -1062,38 +1073,11 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. - /// - /// The and flags consider - /// the following characters to be white-space: - /// Tab (\t), - /// Space (\u0020), - /// No-Break Space (\u00A0), - /// Ogham Space Mark (\u1680), - /// Mongolian Vowel Separator (\u180E), - /// En Quad (\u2000), - /// Em Quad (\u2001), - /// En Space (\u2002), - /// Em Space (\u2003), - /// Three-Per-Em Space (\u2004), - /// Four-Per-Em Space (\u2004), - /// Six-Per-Em Space (\u2006), - /// Figure Space (\u2007), - /// Punctuation Space (\u2008), - /// Thin Space (\u2009), - /// Hair Space (\u200A), - /// Zero Width Space (\u200B), - /// Narrow No-Break Space (\u202F), - /// Medium Mathematical Space (\u205F), - /// Ideographic Space (\u3000), - /// and Zero Width No-Break Space (\uFEFF). - /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by - /// . - /// public static void Equal( ReadOnlyMemory expected, ReadOnlyMemory actual, @@ -1163,10 +1147,10 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats spaces and tabs (in any non-zero quantity) as equivalent. - /// If set to , ignores all white space differences during comparison. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent. + /// If set to true, ignores all white space differences during comparison. /// Thrown when the strings are not equivalent. /// /// The and flags consider @@ -1209,10 +1193,10 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats spaces and tabs (in any non-zero quantity) as equivalent. - /// If set to , ignores all white space differences during comparison. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent. + /// If set to true, ignores all white space differences during comparison. /// Thrown when the strings are not equivalent. /// /// The and flags consider @@ -1255,10 +1239,10 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats spaces and tabs (in any non-zero quantity) as equivalent. - /// If set to , removes all whitespaces and tabs before comparing. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent. + /// If set to true, removes all whitespaces and tabs before comparing. /// Thrown when the strings are not equivalent. /// /// The and flags consider @@ -1301,10 +1285,10 @@ public static void Equal( /// /// The expected string value. /// The actual string value. - /// If set to , ignores cases differences. The invariant culture is used. - /// If set to , treats \r\n, \r, and \n as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. - /// If set to , treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks) in any non-zero quantity as equivalent. + /// If set to true, treats horizontal white-space (i.e. spaces, tabs, and others; see remarks), including zero quantities, as equivalent. /// Thrown when the strings are not equivalent. /// /// The and flags consider @@ -1333,6 +1317,7 @@ public static void Equal( /// In particular, it does not include carriage return (\r) or line feed (\n), which are covered by /// . /// + public static void Equal( #if XUNIT_NULLABLE string? expected, @@ -1346,6 +1331,9 @@ public static void Equal( bool ignoreWhiteSpaceDifferences = false, bool ignoreAllWhiteSpace = false) { + // This overload is inside #if XUNIT_SPAN because the string version is dynamically converted + // to a span version, so this string version is a backup that then delegates to the span version. + if (expected == null && actual == null) return; if (expected == null || actual == null) @@ -1354,6 +1342,8 @@ public static void Equal( Equal(expected.AsSpan(), actual.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); } +#endif + /// /// Verifies that a string matches a regular expression. /// @@ -1432,6 +1422,8 @@ public static void StartsWith( throw StartsWithException.ForStringNotFound(expectedStartString, actualString); } +#if XUNIT_SPAN + /// /// Verifies that a string starts with a given sub-string, using the current culture. /// @@ -1630,5 +1622,73 @@ public static void StartsWith( if (!actualString.StartsWith(expectedStartString, comparisonType)) throw StartsWithException.ForStringNotFound(expectedStartString.ToString(), actualString.ToString()); } + +#endif + + static readonly HashSet charsLineEndings = new HashSet() + { + '\r', // Carriage Return + '\n', // Line feed + }; + static readonly HashSet charsWhitespace = new HashSet() + { + '\t', // Tab + ' ', // Space + '\u00A0', // No-Break Space + '\u1680', // Ogham Space Mark + '\u180E', // Mongolian Vowel Separator + '\u2000', // En Quad + '\u2001', // Em Quad + '\u2002', // En Space + '\u2003', // Em Space + '\u2004', // Three-Per-Em Space + '\u2005', // Four-Per-Em Space + '\u2006', // Six-Per-Em Space + '\u2007', // Figure Space + '\u2008', // Punctuation Space + '\u2009', // Thin Space + '\u200A', // Hair Space + '\u200B', // Zero Width Space + '\u202F', // Narrow No-Break Space + '\u205F', // Medium Mathematical Space + '\u3000', // Ideographic Space + '\uFEFF', // Zero Width No-Break Space + }; + + static int SkipLineEnding( +#if XUNIT_SPAN + ReadOnlySpan value, +#else + string value, +#endif + int index) + { + if (value[index] == '\r') + ++index; + + if (index < value.Length && value[index] == '\n') + ++index; + + return index; + } + + static int SkipWhitespace( +#if XUNIT_SPAN + ReadOnlySpan value, +#else + string value, +#endif + int index) + { + while (index < value.Length) + { + if (charsWhitespace.Contains(value[index])) + index++; + else + return index; + } + + return index; + } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/src/TypeAsserts.cs b/src/Microsoft.DotNet.XUnitAssert/src/TypeAsserts.cs index 342dcd0be87..f32d9f06722 100644 --- a/src/Microsoft.DotNet.XUnitAssert/src/TypeAsserts.cs +++ b/src/Microsoft.DotNet.XUnitAssert/src/TypeAsserts.cs @@ -1,5 +1,7 @@ #pragma warning disable CA1052 // Static holder types should be static #pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable IDE0058 // Expression value is never used +#pragma warning disable IDE0161 // Convert to file-scoped namespace #if XUNIT_NULLABLE #nullable enable @@ -11,6 +13,7 @@ using System; using System.Globalization; +using System.Reflection; using Xunit.Sdk; #if XUNIT_NULLABLE @@ -19,6 +22,11 @@ namespace Xunit { +#if XUNIT_VISIBILITY_INTERNAL + internal +#else + public +#endif partial class Assert { /// @@ -56,7 +64,7 @@ public static void IsAssignableFrom( { GuardArgumentNotNull(nameof(expectedType), expectedType); - if (@object == null || !expectedType.IsAssignableFrom(@object.GetType())) + if (@object == null || !expectedType.GetTypeInfo().IsAssignableFrom(@object.GetType().GetTypeInfo())) throw IsAssignableFromException.ForIncompatibleType(expectedType, @object); } @@ -90,7 +98,7 @@ public static void IsNotAssignableFrom( { GuardArgumentNotNull(nameof(expectedType), expectedType); - if (@object != null && expectedType.IsAssignableFrom(@object.GetType())) + if (@object != null && expectedType.GetTypeInfo().IsAssignableFrom(@object.GetType().GetTypeInfo())) throw IsNotAssignableFromException.ForCompatibleType(expectedType, @object); } @@ -114,8 +122,8 @@ public static void IsNotType(object @object) => /// /// The type the object should not be /// The object to be evaluated - /// Will only fail with an exact type match when is - /// passed; will fail with a compatible type match when is passed. + /// Will only fail with an exact type match when true is + /// passed; will fail with a compatible type match when false is passed. /// Thrown when the object is the given type #if XUNIT_NULLABLE public static void IsNotType( @@ -149,8 +157,8 @@ public static void IsNotType( /// /// The type the object should not be /// The object to be evaluated - /// Will only fail with an exact type match when is - /// passed; will fail with a compatible type match when is passed. + /// Will only fail with an exact type match when true is + /// passed; will fail with a compatible type match when false is passed. /// Thrown when the object is the given type public static void IsNotType( Type expectedType, @@ -172,7 +180,7 @@ public static void IsNotType( { var actualType = @object?.GetType(); - if (actualType != null && expectedType.IsAssignableFrom(actualType)) + if (actualType != null && expectedType.GetTypeInfo().IsAssignableFrom(actualType.GetTypeInfo())) throw IsNotTypeException.ForCompatibleType(expectedType, actualType); } } @@ -203,8 +211,8 @@ public static T IsType(object @object) /// /// The type the object should be /// The object to be evaluated - /// Will only pass with an exact type match when is - /// passed; will pass with a compatible type match when is passed. + /// Will only pass with an exact type match when true is + /// passed; will pass with a compatible type match when false is passed. /// The object, casted to type T when successful /// Thrown when the object is not the given type #if XUNIT_NULLABLE @@ -242,8 +250,8 @@ public static void IsType( /// /// The type the object should be /// The object to be evaluated - /// Will only pass with an exact type match when is - /// passed; will pass with a compatible type match when is passed. + /// Will only pass with an exact type match when true is + /// passed; will pass with a compatible type match when false is passed. /// Thrown when the object is not the given type public static void IsType( Type expectedType, @@ -268,7 +276,7 @@ public static void IsType( var compatible = exactMatch ? expectedType == actualType - : expectedType.IsAssignableFrom(actualType); + : expectedType.GetTypeInfo().IsAssignableFrom(actualType.GetTypeInfo()); if (!compatible) { @@ -277,8 +285,8 @@ public static void IsType( if (expectedTypeName == actualTypeName) { - expectedTypeName += string.Format(CultureInfo.CurrentCulture, " (from {0})", expectedType.Assembly.GetName().FullName); - actualTypeName += string.Format(CultureInfo.CurrentCulture, " (from {0})", actualType.Assembly.GetName().FullName); + expectedTypeName += string.Format(CultureInfo.CurrentCulture, " (from {0})", expectedType.GetTypeInfo().Assembly.GetName().FullName); + actualTypeName += string.Format(CultureInfo.CurrentCulture, " (from {0})", actualType.GetTypeInfo().Assembly.GetName().FullName); } if (exactMatch) diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/ExceptionAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/ExceptionAssertsTests.cs deleted file mode 100644 index 78b82e22543..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/ExceptionAssertsTests.cs +++ /dev/null @@ -1,1947 +0,0 @@ -using Xunit; -using Xunit.Sdk; -using Xunit.v3; - -public class ExceptionAssertsTests -{ -#pragma warning disable xUnit2015 // Do not use typeof expression to check the exception type - - public class Throws_NonGeneric - { - public class WithAction - { - [Fact] - public static void GuardClauses() - { - static void testCode() { } - - Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode)); - Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Action)!)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static void testCode() => throw new ArgumentException(); - - Assert.Throws(typeof(ArgumentException), testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.Throws(typeof(Exception), testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithActionAndInspector - { - [Fact] - public static void GuardClauses() - { - static void testCode() { } - - Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode, _ => null)); - Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Action)!, _ => null)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static void testCode() => throw new ArgumentException(); - - Assert.Throws(typeof(ArgumentException), testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFails() - { - static void testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static void testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.StartsWith( - "Assert.Throws() Failure: Exception thrown by inspector", - ex.Message - ); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.Throws(typeof(Exception), testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFunc - { - [Fact] - public static void GuardClauses() - { - static object testCode() => 42; - - Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode)); - Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Func)!)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.Throws(typeof(Exception), testCode)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static object testCode() => throw new ArgumentException(); - - Assert.Throws(typeof(ArgumentException), testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.Throws(typeof(Exception), testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFuncAndInspector - { - [Fact] - public static void GuardClauses() - { - static object testCode() => 42; - - Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode, _ => null)); - Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Func)!, _ => null)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.Throws(typeof(Exception), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static object testCode() => throw new ArgumentException(); - - Assert.Throws(typeof(ArgumentException), testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFails() - { - static object testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static object testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.StartsWith( - "Assert.Throws() Failure: Exception thrown by inspector", - ex.Message - ); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.Throws(typeof(Exception), testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - } - -#pragma warning restore xUnit2015 // Do not use typeof expression to check the exception type - - public class Throws_Generic - { - public class WithAction - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Action)!)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static void testCode() => throw new ArgumentException(); - - Assert.Throws(testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.Throws(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithActionAndInspector - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Action)!, _ => null)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static void testCode() => throw new ArgumentException(); - - Assert.Throws(testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFails() - { - static void testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: I don't like this exception", ex.Message); Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static void testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception thrown by inspector", - ex.Message - ); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.Throws(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFunc - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Func)!)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static object testCode() => throw new ArgumentException(); - - Assert.Throws(testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.Throws(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFuncAndInspector - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Func)!, _ => null)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static object testCode() => throw new ArgumentException(); - - Assert.Throws(testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFailed() - { - static object testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static object testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.Throws(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - } - - public class Throws_Generic_ArgumentException - { - public class WithAction - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Action)!)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static void testCode() => throw new ArgumentException("Hello world", "paramName"); - - Assert.Throws("paramName", testCode); - } - - [Fact] - public static void IncorrectParameterName() - { - static void testCode() => throw new ArgumentException("Hello world", "paramName1"); - - var ex = Record.Exception(() => Assert.Throws("paramName2", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + - "Exception: typeof(System.ArgumentException)" + Environment.NewLine + - "Expected: \"paramName2\"" + Environment.NewLine + - "Actual: \"paramName1\"", - ex.Message - ); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - } - - public class WithFunc - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.Throws(default(Func)!)); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static object testCode() => throw new ArgumentException("Hello world", "paramName"); - - Assert.Throws("paramName", testCode); - } - - [Fact] - public static void IncorrectParameterName() - { - static object testCode() => throw new ArgumentException("Hello world", "paramName1"); - - var ex = Record.Exception(() => Assert.Throws("paramName2", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + - "Exception: typeof(System.ArgumentException)" + Environment.NewLine + - "Expected: \"paramName2\"" + Environment.NewLine + - "Actual: \"paramName1\"", - ex.Message - ); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - } - } - - public class ThrowsAny - { - public class WithAction - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.ThrowsAny(default(Action)!)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static void testCode() => throw new ArgumentException(); - - Assert.ThrowsAny(testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - static void testCode() => throw new ArgumentNullException(); - - Assert.ThrowsAny(testCode); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.ThrowsAny(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithActionAndInspector - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.ThrowsAny(default(Action)!, _ => null)); - } - - [Fact] - public static void NoExceptionThrown() - { - static void testCode() { } - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static void testCode() => throw new ArgumentException(); - - Assert.ThrowsAny(testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFails() - { - static void testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static void testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - void testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorPasses() - { - static void testCode() => throw new ArgumentNullException(); - - Assert.ThrowsAny(testCode, _ => null); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorFails() - { - static void testCode() => throw new ArgumentNullException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorThrows() - { - static void testCode() => throw new ArgumentNullException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static void testCode() => Assert.Skip("This is a skipped test"); - - try - { - Assert.ThrowsAny(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFunc - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.ThrowsAny(default(Func)!)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAnyAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown() - { - static object testCode() => throw new ArgumentException(); - - Assert.ThrowsAny(testCode); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown() - { - static object testCode() => throw new ArgumentNullException(); - - Assert.ThrowsAny(testCode); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.ThrowsAny(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithFuncAndInspector - { - [Fact] - public static void GuardClause() - { - Assert.Throws("testCode", () => Assert.ThrowsAny(default(Func)!, _ => null)); - } - - [Fact] - public static void ProtectsAgainstAccidentalTask() - { - static object testCode() => Task.FromResult(42); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal("You must call Assert.ThrowsAnyAsync when testing async code", ex.Message); - } - - [Fact] - public static void NoExceptionThrown() - { - static object testCode() => 42; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorPasses() - { - static object testCode() => throw new ArgumentException(); - - Assert.ThrowsAny(testCode, _ => null); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorFails() - { - static object testCode() => throw new ArgumentException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void CorrectExceptionThrown_InspectorThrows() - { - static object testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - object testCode() => throw thrown; - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorPasses() - { - static object testCode() => throw new ArgumentNullException(); - - Assert.ThrowsAny(testCode, _ => null); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorFails() - { - static object testCode() => throw new ArgumentNullException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static void DerivedExceptionThrown_InspectorThrows() - { - static object testCode() => throw new ArgumentNullException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = Record.Exception(() => Assert.ThrowsAny(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static void SkipExceptionEscapes() - { - static object testCode() { Assert.Skip("This is a skipped test"); return null; } - - try - { - Assert.ThrowsAny(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - } - - public class ThrowsAnyAsync - { - public class WithoutInspector - { - [Fact] - public static async Task GuardClause() - { - await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAnyAsync(default!)); - } - - [Fact] - public static async Task NoExceptionThrown() - { - static Task testCode() => Task.FromResult(42); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown() - { - static Task testCode() => throw new ArgumentException(); - - await Assert.ThrowsAnyAsync(testCode); - } - - [Fact] - public static async Task IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown() - { - static Task testCode() => throw new ArgumentNullException(); - - await Assert.ThrowsAnyAsync(testCode); - } - - [Fact] - public static async Task SkipExceptionEscapes() - { - static Task testCode() { Assert.Skip("This is a skipped test"); return Task.CompletedTask; } - - try - { - await Assert.ThrowsAnyAsync(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithInspector - { - [Fact] - public static async Task GuardClause() - { - await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAnyAsync(default!, _ => null)); - } - - [Fact] - public static async Task NoExceptionThrown() - { - static Task testCode() => Task.FromResult(42); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorPasses() - { - static Task testCode() => throw new ArgumentException(); - - await Assert.ThrowsAnyAsync(testCode, _ => null); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorFails() - { - static Task testCode() => throw new ArgumentException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorThrows() - { - static Task testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static async Task IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown_InspectorPasses() - { - static Task testCode() => throw new ArgumentNullException(); - - await Assert.ThrowsAnyAsync(testCode, _ => null); - } - - [Fact] - public static async Task DerivedExceptionThrown_InspectorFails() - { - static Task testCode() => throw new ArgumentNullException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown_InspectorThrows() - { - static Task testCode() => throw new ArgumentNullException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.ThrowsAny() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static async Task SkipExceptionEscapes() - { - static Task testCode() { Assert.Skip("This is a skipped test"); return Task.CompletedTask; } - - try - { - await Assert.ThrowsAnyAsync(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - } - - public class ThrowsAsync - { - public class WithoutInspector - { - [Fact] - public static async Task GuardClause() - { - await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAsync(default!)); - } - - [Fact] - public static async Task NoExceptionThrown() - { - static Task testCode() => Task.FromResult(42); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown() - { - static Task testCode() => throw new ArgumentException(); - - await Assert.ThrowsAsync(testCode); - } - - [Fact] - public static async Task IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task SkipExceptionEscapes() - { - static Task testCode() { Assert.Skip("This is a skipped test"); return Task.CompletedTask; } - - try - { - await Assert.ThrowsAsync(testCode); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - - public class WithInspector - { - [Fact] - public static async Task GuardClause() - { - await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAsync(default!, _ => null)); - } - - [Fact] - public static async Task NoExceptionThrown() - { - static Task testCode() => Task.FromResult(42); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorPasses() - { - static Task testCode() => throw new ArgumentException(); - - await Assert.ThrowsAsync(testCode, _ => null); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorFails() - { - static Task testCode() => throw new ArgumentException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode, _ => "I don't like this exception")); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: I don't like this exception", ex.Message); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown_InspectorThrows() - { - static Task testCode() => throw new ArgumentException(); - var thrownByInspector = new DivideByZeroException(); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode, _ => throw thrownByInspector)); - - Assert.IsType(ex); - Assert.Equal("Assert.Throws() Failure: Exception thrown by inspector", ex.Message); - Assert.Same(thrownByInspector, ex.InnerException); - } - - [Fact] - public static async Task IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode, _ => null)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task SkipExceptionEscapes() - { - static Task testCode() { Assert.Skip("This is a skipped test"); return Task.CompletedTask; } - - try - { - await Assert.ThrowsAsync(testCode, _ => null); - Assert.Fail("The exception should not be caught"); - } - catch (Exception ex) - { - Assert.Equal(DynamicSkipToken.Value + "This is a skipped test", ex.Message); - } - } - } - } - - public class ThrowsAsync_ArgumentException - { - [Fact] - public static async Task GuardClause() - { - await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAsync("paramName", default!)); - } - - [Fact] - public static async Task NoExceptionThrown() - { - static Task testCode() => Task.FromResult(42); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)", - ex.Message - ); - Assert.Null(ex.InnerException); - } - - [Fact] - public static async Task CorrectExceptionThrown() - { - static Task testCode() => throw new ArgumentException("Hello world", "paramName"); - - await Assert.ThrowsAsync("paramName", testCode); - } - - [Fact] - public static async Task IncorrectParameterName() - { - static Task testCode() => throw new ArgumentException("Hello world", "paramName1"); - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName2", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + - "Exception: typeof(System.ArgumentException)" + Environment.NewLine + - "Expected: \"paramName2\"" + Environment.NewLine + - "Actual: \"paramName1\"", - ex.Message - ); - } - - [Fact] - public static async Task IncorrectExceptionThrown() - { - var thrown = new DivideByZeroException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.DivideByZeroException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - - [Fact] - public static async Task DerivedExceptionThrown() - { - var thrown = new ArgumentNullException(); - Task testCode() => throw thrown; - - var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + - "Expected: typeof(System.ArgumentException)" + Environment.NewLine + - "Actual: typeof(System.ArgumentNullException)", - ex.Message - ); - Assert.Same(thrown, ex.InnerException); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MultipleAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MultipleAssertsTests.cs deleted file mode 100644 index 7570481066e..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MultipleAssertsTests.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Xunit; -using Xunit.Sdk; - -public class MultipleAssertsTests -{ - [Fact] - public void NoActions_DoesNotThrow() - { - Assert.Multiple(); - } - - [Fact] - public void SingleAssert_Success_DoesNotThrow() - { - Assert.Multiple( - static () => Assert.True(true) - ); - } - - [Fact] - public void SingleAssert_Fails_ThrowsFailingAssert() - { - var ex = Record.Exception(() => - Assert.Multiple( - static () => Assert.True(false) - ) - ); - - Assert.IsType(ex); - } - - [Fact] - public void MultipleAssert_Success_DoesNotThrow() - { - Assert.Multiple( - static () => Assert.True(true), - static () => Assert.False(false) - ); - } - - [Fact] - public void MultipleAssert_SingleFailure_ThrowsFailingAssert() - { - var ex = Record.Exception(static () => - Assert.Multiple( - () => Assert.True(true), - () => Assert.False(true) - ) - ); - - Assert.IsType(ex); - } - - [Fact] - public void MultipleAssert_MultipleFailures_ThrowsMultipleException() - { - var ex = Record.Exception(static () => - Assert.Multiple( - () => Assert.True(false), - () => Assert.False(true) - ) - ); - - var multiEx = Assert.IsType(ex); - Assert.Equal( - "Assert.Multiple() Failure: Multiple failures were encountered", - ex.Message - ); - Assert.Collection( - multiEx.InnerExceptions, - innerEx => Assert.IsType(innerEx), - innerEx => Assert.IsType(innerEx) - ); - } - - [Fact] - public async Task MultipleAsync_NoActions_DoesNotThrow() - { - await Assert.MultipleAsync(); - } - - [Fact] - public async Task MultipleAsync_SingleAssert_Success_DoesNotThrow() - { - static Task task(bool isTrue) => Task.FromResult(isTrue); - - await Assert.MultipleAsync( - static async () => Assert.True(await task(true)) - ); - } - - [Fact] - public async Task MultipleAsync_Success_DoesNotThrow() - { - static Task task(bool isTrue) => Task.FromResult(isTrue); - - await Assert.MultipleAsync( - static async () => Assert.True(await task(true)), - static async () => Assert.True(await task(true)), - static async () => Assert.True(await task(true)) - ); - } - - [Fact] - public async Task MultipleAsync_SingleAssert_Fails_ThrowsFailingAssert() - { - static Task task(bool isTrue) => Task.FromResult(isTrue); - - var ex = await Record.ExceptionAsync(static async () => - await Assert.MultipleAsync( - static async () => Assert.False(await task(true)) - ) - ); - - Assert.IsType(ex); - } - - [Fact] - public async Task MultipleAsync_SingleAssert_Multiple_ThrowsFailingAssert() - { - static Task task(bool isTrue) => Task.FromResult(isTrue); - - var ex = await Record.ExceptionAsync(static async () => - await Assert.MultipleAsync( - static async () => Assert.False(await task(true)), - static async () => Assert.False(await task(true)) - ) - ); - - var multiEx = Assert.IsType(ex); - Assert.Collection( - multiEx.InnerExceptions, - static innerEx => Assert.IsType(innerEx), - static innerEx => Assert.IsType(innerEx) - ); - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/AssertHelperTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/AssertHelperTests.cs deleted file mode 100644 index a30788c51ec..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/AssertHelperTests.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System.Linq.Expressions; -using Xunit; - -public class AssertHelperTests -{ - public class ParseExclusionExpressions_LambdaExpression - { - [Fact] - public void NullExpression() - { - var expression = (Expression>)null!; - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith("Null expression is not valid.", ex.Message); - } - - [Fact] - public void Properties() - { - var result = AssertHelper.ParseExclusionExpressions( - (Expression>)(c => c.Property), - (Expression>)(c => c.ParentProperty.Property), - (Expression>)(c => c.GrandparentProperty!.ParentProperty.Property) - ); - - Assert.Collection( - result, - ex => Assert.Equal((string.Empty, "Property"), ex), - ex => Assert.Equal(("ParentProperty", "Property"), ex), - ex => Assert.Equal(("GrandparentProperty.ParentProperty", "Property"), ex) - ); - } - - [Fact] - public void Fields() - { - var result = AssertHelper.ParseExclusionExpressions( - (Expression>)(c => c.Field), - (Expression>)(c => c.ParentField.Field), - (Expression>)(c => c.GrandparentField!.ParentField.Field) - ); - - Assert.Collection( - result, - ex => Assert.Equal((string.Empty, "Field"), ex), - ex => Assert.Equal(("ParentField", "Field"), ex), - ex => Assert.Equal(("GrandparentField.ParentField", "Field"), ex) - ); - } - - [Fact] - public void Mixed() - { - var result = AssertHelper.ParseExclusionExpressions( - (Expression>)(c => c.ParentProperty.Field), - (Expression>)(c => c.GrandparentProperty!.ParentField.Property) - ); - - Assert.Collection( - result, - ex => Assert.Equal(("ParentProperty", "Field"), ex), - ex => Assert.Equal(("GrandparentProperty.ParentField", "Property"), ex) - ); - } - - [Fact] - public void MethodNotSupported() - { - var expression = (Expression>)(c => c.Method()); - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith($"Expression '{expression}' is not supported. Only property or field expressions from the lambda parameter are supported.", ex.Message); - } - - [Fact] - public void ChildMethodNotSupported() - { - var expression = (Expression>)(p => p.ParentProperty.Method()); - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith($"Expression '{expression}' is not supported. Only property or field expressions from the lambda parameter are supported.", ex.Message); - } - - [Fact] - public void IndexerNotSupported() - { - var expression = (Expression>)(c => c[0]); - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith($"Expression '{expression}' is not supported. Only property or field expressions from the lambda parameter are supported.", ex.Message); - } - - [Fact] - public void ChildIndexerNotSupported() - { - var expression = (Expression>)(p => p.ParentField[0]); - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith($"Expression '{expression}' is not supported. Only property or field expressions from the lambda parameter are supported.", ex.Message); - } - - static readonly string foo = "Hello world"; - - [Fact] - public void ExpressionMustOriginateFromParameter() - { - var expression = (Expression>)(c => foo.Length); - - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(expression)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith($"Expression '{expression}' is not supported. Only property or field expressions from the lambda parameter are supported.", ex.Message); - } - - class Child - { - public int Field = 2112; - - public string this[int idx] => idx.ToString(); - - public string? Property => ToString(); - - public string? Method() => ToString(); - } - - class Parent - { - public Child ParentField = new(); - - public Child ParentProperty { get; } = new(); - } - - class Grandparent - { - public Parent? GrandparentField = new(); - - public Parent? GrandparentProperty { get; set; } - } - } - - public class ParseExclusionExpressions_String - { - [Fact] - public void NullExpression() - { - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(default(string)!)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith("Null/empty expressions are not valid.", ex.Message); - } - - [Fact] - public void EmptyExpression() - { - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(string.Empty)); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith("Null/empty expressions are not valid.", ex.Message); - } - - [Fact] - public void StartsWithDot() - { - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions(".Foo")); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith("Expression '.Foo' is not valid. Expressions may not start with a period.", ex.Message); - } - - [Fact] - public void EndsWithDot() - { - var ex = Record.Exception(() => AssertHelper.ParseExclusionExpressions("Foo.")); - - var argEx = Assert.IsType(ex); - Assert.Equal("exclusionExpressions", argEx.ParamName); - Assert.StartsWith("Expression 'Foo.' is not valid. Expressions may not end with a period.", ex.Message); - } - - [Fact] - public void SuccessCases() - { - var result = AssertHelper.ParseExclusionExpressions( - "Child", - "Parent.Child", - "Grandparent.Parent.Child" - ); - - Assert.Collection( - result, - ex => Assert.Equal((string.Empty, "Child"), ex), - ex => Assert.Equal(("Parent", "Child"), ex), - ex => Assert.Equal(("Grandparent.Parent", "Child"), ex) - ); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/AsyncCollectionAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/AsyncCollectionAssertsTests.cs similarity index 88% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/AsyncCollectionAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/AsyncCollectionAssertsTests.cs index adc417d6e93..60b33890d6d 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/AsyncCollectionAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/AsyncCollectionAssertsTests.cs @@ -1,4 +1,4 @@ -#if NET8_0_OR_GREATER +#if NETCOREAPP3_0_OR_GREATER using System; using System.Collections; @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NSubstitute; using Xunit; using Xunit.Sdk; @@ -18,8 +19,8 @@ public class All public static void GuardClauses() { Assert.Throws("collection", () => Assert.All(default(IAsyncEnumerable)!, _ => { })); - Assert.Throws("action", () => Assert.All(Array.Empty().ToAsyncEnumerable(), (Action)null!)); - Assert.Throws("action", () => Assert.All(Array.Empty().ToAsyncEnumerable(), (Action)null!)); + Assert.Throws("action", () => Assert.All(new object[0].ToAsyncEnumerable(), (Action)null!)); + Assert.Throws("action", () => Assert.All(new object[0].ToAsyncEnumerable(), (Action)null!)); } [Fact] @@ -70,8 +71,8 @@ public class AllAsync public static async Task GuardClauses() { await Assert.ThrowsAsync("collection", () => Assert.AllAsync(default(IAsyncEnumerable)!, async _ => await Task.Yield())); - await Assert.ThrowsAsync("action", () => Assert.AllAsync(Array.Empty().ToAsyncEnumerable(), (Func)null!)); - await Assert.ThrowsAsync("action", () => Assert.AllAsync(Array.Empty().ToAsyncEnumerable(), (Func)null!)); + await Assert.ThrowsAsync("action", () => Assert.AllAsync(new object[0].ToAsyncEnumerable(), (Func)null!)); + await Assert.ThrowsAsync("action", () => Assert.AllAsync(new object[0].ToAsyncEnumerable(), (Func)null!)); } [Fact] @@ -134,11 +135,9 @@ public static void MismatchedElementCount() var list = new List().ToAsyncEnumerable(); var ex = Record.Exception( -#pragma warning disable xUnit2023 // Do not use collection methods for single-item collections () => Assert.Collection(list, item => Assert.True(false) ) -#pragma warning restore xUnit2023 // Do not use collection methods for single-item collections ); var collEx = Assert.IsType(ex); @@ -207,11 +206,9 @@ public static async Task MismatchedElementCountAsync() var list = new List().ToAsyncEnumerable(); var ex = await Record.ExceptionAsync( -#pragma warning disable xUnit2023 // Do not use collection methods for single-item collections () => Assert.CollectionAsync(list, async item => await Task.Yield() ) -#pragma warning restore xUnit2023 // Do not use collection methods for single-item collections ); var collEx = Assert.IsType(ex); @@ -332,10 +329,10 @@ public class Contains_Comparer [Fact] public static void GuardClauses() { - var comparer = new MyComparer(); + var comparer = Substitute.For>(); Assert.Throws("collection", () => Assert.Contains(14, default(IAsyncEnumerable)!, comparer)); - Assert.Throws("comparer", () => Assert.Contains(14, Array.Empty().ToAsyncEnumerable(), null!)); + Assert.Throws("comparer", () => Assert.Contains(14, new int[0].ToAsyncEnumerable(), null!)); } [Fact] @@ -348,11 +345,9 @@ public static void CanUseComparer() class MyComparer : IEqualityComparer { - public bool Equals(int x, int y) => - true; + public bool Equals(int x, int y) => true; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public int GetHashCode(int obj) => throw new NotImplementedException(); } } @@ -362,7 +357,7 @@ public class Contains_Predicate public static void GuardClauses() { Assert.Throws("collection", () => Assert.Contains(default(IAsyncEnumerable)!, item => true)); - Assert.Throws("filter", () => Assert.Contains(Array.Empty().ToAsyncEnumerable(), (Predicate)null!)); + Assert.Throws("filter", () => Assert.Contains(new int[0].ToAsyncEnumerable(), (Predicate)null!)); } [Fact] @@ -370,7 +365,7 @@ public static void ItemFound() { var list = new[] { "Hello", "world" }.ToAsyncEnumerable(); - Assert.Contains(list, item => item.StartsWith("wo")); + Assert.Contains(list, item => item.StartsWith("w")); } [Fact] @@ -378,7 +373,7 @@ public static void ItemNotFound() { var list = new[] { "Hello", "world" }.ToAsyncEnumerable(); - var ex = Record.Exception(() => Assert.Contains(list, item => item.StartsWith("qu"))); + var ex = Record.Exception(() => Assert.Contains(list, item => item.StartsWith("q"))); Assert.IsType(ex); Assert.Equal( @@ -395,7 +390,7 @@ public class Distinct public static void GuardClauses() { Assert.Throws("collection", () => Assert.Distinct(default(IAsyncEnumerable)!)); - Assert.Throws("comparer", () => Assert.Distinct(Array.Empty().ToAsyncEnumerable(), null!)); + Assert.Throws("comparer", () => Assert.Distinct(new object[0].ToAsyncEnumerable(), null!)); } [Fact] @@ -519,10 +514,10 @@ public class DoesNotContain_Comparer [Fact] public static void GuardClauses() { - var comparer = new MyComparer(); + var comparer = Substitute.For>(); Assert.Throws("collection", () => Assert.DoesNotContain(14, default(IAsyncEnumerable)!, comparer)); - Assert.Throws("comparer", () => Assert.DoesNotContain(14, Array.Empty().ToAsyncEnumerable(), null!)); + Assert.Throws("comparer", () => Assert.DoesNotContain(14, new int[0].ToAsyncEnumerable(), null!)); } [Fact] @@ -535,11 +530,9 @@ public static void CanUseComparer() class MyComparer : IEqualityComparer { - public bool Equals(int x, int y) => - false; + public bool Equals(int x, int y) => false; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public int GetHashCode(int obj) => throw new NotImplementedException(); } } @@ -549,7 +542,7 @@ public class DoesNotContain_Predicate public static void GuardClauses() { Assert.Throws("collection", () => Assert.DoesNotContain(default(IAsyncEnumerable)!, item => true)); - Assert.Throws("filter", () => Assert.DoesNotContain(Array.Empty().ToAsyncEnumerable(), (Predicate)null!)); + Assert.Throws("filter", () => Assert.DoesNotContain(new int[0].ToAsyncEnumerable(), (Predicate)null!)); } [Fact] @@ -557,7 +550,7 @@ public static void ItemFound() { var list = new[] { "Hello", "world" }.ToAsyncEnumerable(); - var ex = Record.Exception(() => Assert.DoesNotContain(list, item => item.StartsWith("wo"))); + var ex = Record.Exception(() => Assert.DoesNotContain(list, item => item.StartsWith("w"))); Assert.IsType(ex); Assert.Equal( @@ -573,7 +566,7 @@ public static void ItemNotFound() { var list = new[] { "Hello", "world" }.ToAsyncEnumerable(); - Assert.DoesNotContain(list, item => item.StartsWith("qu")); + Assert.DoesNotContain(list, item => item.StartsWith("q")); } } @@ -636,10 +629,10 @@ public static void BothNull() [Fact] public static void EmptyExpectedNullActual() { - var expected = Array.Empty(); + var expected = new int[0]; var actual = default(IAsyncEnumerable); - static void validateError( + void validateError( Action action, string expectedType) { @@ -661,9 +654,9 @@ static void validateError( [Fact] public static void NullExpectedEmptyActual() { - var actual = Array.Empty().ToAsyncEnumerable(); + var actual = new int[0].ToAsyncEnumerable(); - static void validateError(Action action) + void validateError(Action action) { var ex = Record.Exception(action); @@ -790,7 +783,7 @@ public static void AlwaysFalse() var expected = new[] { 1, 2, 3, 4, 5 }; var actual = expected.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -823,9 +816,15 @@ public static void AlwaysTrue() Assert.Equal(expected.ToAsyncEnumerable(), actual, new IntComparer(true)); } - class IntComparer(bool answer) : - IEqualityComparer + class IntComparer : IEqualityComparer { + readonly bool answer; + + public IntComparer(bool answer) + { + this.answer = answer; + } + public bool Equals(int x, int y) => answer; public int GetHashCode(int obj) => throw new NotImplementedException(); @@ -835,8 +834,8 @@ class IntComparer(bool answer) : [Fact] public void CollectionItemIsEnumerable() { - List actual = [new(0), new(2)]; - List expected = [new(1), new(3)]; + List actual = new List { new(0), new(2) }; + List expected = new List { new(1), new(3) }; Assert.Equal(expected, actual.ToAsyncEnumerable(), new EnumerableItemComparer()); Assert.Equal(expected.ToAsyncEnumerable(), actual.ToAsyncEnumerable(), new EnumerableItemComparer()); @@ -851,22 +850,21 @@ public int GetHashCode(EnumerableItem obj) => throw new NotImplementedException(); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } + + public EnumerableItem(int value) => Value = value; - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } [Fact] public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() { - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -886,9 +884,7 @@ static void validateError( Assert.IsType(ex.InnerException); } -#pragma warning disable IDE0300 // Simplify collection initialization validateError(() => Assert.Equal(new[] { 1, 2 }, new[] { 1, 3 }.ToAsyncEnumerable(), new ThrowingComparer()), "int[] ", " "); -#pragma warning restore IDE0300 // Simplify collection initialization validateError(() => Assert.Equal(new[] { 1, 2 }.ToAsyncEnumerable(), new[] { 1, 3 }.ToAsyncEnumerable(), new ThrowingComparer()), "", ""); } @@ -920,7 +916,7 @@ public void NotEqual() var expected = new[] { new EquatableObject { Char = 'a' } }; var actual = new[] { new EquatableObject { Char = 'b' } }.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -932,13 +928,8 @@ static void validateError( Assert.Equal( "Assert.Equal() Failure: Collections differ" + Environment.NewLine + " " + new string(' ', padding) + " ↓ (pos 0)" + Environment.NewLine + -#if XUNIT_AOT - "Expected: " + expectedType.PadRight(padding) + $"[EquatableObject {{ {ArgumentFormatter.Ellipsis} }}]" + Environment.NewLine + - "Actual: " + actualType.PadRight(padding) + $"[EquatableObject {{ {ArgumentFormatter.Ellipsis} }}]" + Environment.NewLine + -#else "Expected: " + expectedType.PadRight(padding) + "[EquatableObject { Char = 'a' }]" + Environment.NewLine + "Actual: " + actualType.PadRight(padding) + "[EquatableObject { Char = 'b' }]" + Environment.NewLine + -#endif " " + new string(' ', padding) + " ↑ (pos 0)", ex.Message ); @@ -954,12 +945,6 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } @@ -969,9 +954,9 @@ public class CollectionsWithFunc public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([1, 2, 3, 4, 5]).ToAsyncEnumerable(); + var actual = new List(new int[] { 1, 2, 3, 4, 5 }).ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -998,7 +983,7 @@ static void validateError( public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([0, 0, 0, 0, 0]).ToAsyncEnumerable(); + var actual = new List(new int[] { 0, 0, 0, 0, 0 }).ToAsyncEnumerable(); Assert.Equal(expected, actual, (x, y) => true); Assert.Equal(expected.ToAsyncEnumerable(), actual, (int x, int y) => true); @@ -1015,16 +1000,15 @@ public void CollectionItemIsEnumerable() Assert.Equal(expected.ToAsyncEnumerable(), actual, (x, y) => x.Value / 2 == y.Value / 2); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } + + public EnumerableItem(int value) => Value = value; - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } [Fact] @@ -1033,7 +1017,7 @@ public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() var expected = new[] { 1, 2 }; var actual = new[] { 1, 3 }.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1053,7 +1037,7 @@ static void validateError( Assert.IsType(ex.InnerException); } - validateError(() => Assert.Equal(expected, actual, (e, a) => throw new DivideByZeroException()), "int[] ", " "); + validateError(() => Assert.Equal(expected, actual, (int e, int a) => throw new DivideByZeroException()), "int[] ", " "); validateError(() => Assert.Equal(expected.ToAsyncEnumerable(), actual, (int e, int a) => throw new DivideByZeroException()), "", ""); } } @@ -1104,7 +1088,7 @@ public static void BothNull() var nullEnumerable = default(IEnumerable); var nullAsyncEnumerable = default(IAsyncEnumerable); - static void validateError(Action action) + void validateError(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); @@ -1123,7 +1107,7 @@ static void validateError(Action action) [Fact] public static void EmptyExpectedNullActual() { - var expected = Array.Empty(); + var expected = new int[0]; var actual = default(IAsyncEnumerable); Assert.NotEqual(expected, actual); @@ -1133,7 +1117,7 @@ public static void EmptyExpectedNullActual() [Fact] public static void NullExpectedEmptyActual() { - var actual = Array.Empty().ToAsyncEnumerable(); + var actual = new int[0].ToAsyncEnumerable(); Assert.NotEqual(default(IEnumerable), actual); Assert.NotEqual(default(IAsyncEnumerable), actual); @@ -1148,7 +1132,7 @@ public static void Equal() var expected = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var actual = new List(expected).ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1173,7 +1157,7 @@ static void validateError( public static void NotEqual() { var expected = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - var actual = new List([1, 2, 3, 4, 0, 6, 7, 8, 9, 10]).ToAsyncEnumerable(); + var actual = new List(new[] { 1, 2, 3, 4, 0, 6, 7, 8, 9, 10 }).ToAsyncEnumerable(); Assert.NotEqual(expected, actual); Assert.NotEqual(expected.ToAsyncEnumerable(), actual); @@ -1186,7 +1170,7 @@ public class CollectionsWithComparer public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([1, 2, 3, 4, 5]).ToAsyncEnumerable(); + var actual = new List(new int[] { 1, 2, 3, 4, 5 }).ToAsyncEnumerable(); Assert.NotEqual(expected, actual, new IntComparer(false)); Assert.NotEqual(expected.ToAsyncEnumerable(), actual, new IntComparer(false)); @@ -1196,9 +1180,9 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([0, 0, 0, 0, 0]).ToAsyncEnumerable(); + var actual = new List(new int[] { 0, 0, 0, 0, 0 }).ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1219,14 +1203,18 @@ static void validateError( validateError(() => Assert.NotEqual(expected.ToAsyncEnumerable(), actual, new IntComparer(true)), "", ""); } - class IntComparer(bool answer) : - IEqualityComparer + class IntComparer : IEqualityComparer { - public bool Equals(int x, int y) => - answer; + readonly bool answer; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public IntComparer(bool answer) + { + this.answer = answer; + } + + public bool Equals(int x, int y) => answer; + + public int GetHashCode(int obj) => throw new NotImplementedException(); } [Fact] @@ -1235,7 +1223,7 @@ public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() var expected = new[] { 1, 2 }; var actual = new[] { 1, 2 }.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1277,7 +1265,7 @@ public void Equal() var expected = new[] { new EquatableObject { Char = 'a' } }; var actual = new[] { new EquatableObject { Char = 'a' } }.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1288,13 +1276,8 @@ static void validateError( Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Collections are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not " + expectedType.PadRight(padding) + $"[EquatableObject {{ {ArgumentFormatter.Ellipsis} }}]" + Environment.NewLine + - "Actual: " + actualType.PadRight(padding) + $"[EquatableObject {{ {ArgumentFormatter.Ellipsis} }}]", -#else "Expected: Not " + expectedType.PadRight(padding) + "[EquatableObject { Char = 'a' }]" + Environment.NewLine + "Actual: " + actualType.PadRight(padding) + "[EquatableObject { Char = 'a' }]", -#endif ex.Message ); } @@ -1319,12 +1302,6 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } @@ -1334,7 +1311,7 @@ public class CollectionsWithFunc public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([1, 2, 3, 4, 5]).ToAsyncEnumerable(); + var actual = new List(new int[] { 1, 2, 3, 4, 5 }).ToAsyncEnumerable(); Assert.NotEqual(expected, actual, (x, y) => false); Assert.NotEqual(expected.ToAsyncEnumerable(), actual, (int x, int y) => false); @@ -1344,9 +1321,9 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List([0, 0, 0, 0, 0]).ToAsyncEnumerable(); + var actual = new List(new int[] { 0, 0, 0, 0, 0 }).ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1373,7 +1350,7 @@ public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() var expected = new[] { 1, 2 }; var actual = new[] { 1, 2 }.ToAsyncEnumerable(); - static void validateError( + void validateError( Action action, string expectedType, string actualType) @@ -1393,7 +1370,7 @@ static void validateError( Assert.IsType(ex.InnerException); } - validateError(() => Assert.NotEqual(expected, actual, (e, a) => throw new DivideByZeroException()), "int[] ", " "); + validateError(() => Assert.NotEqual(expected, actual, (int e, int a) => throw new DivideByZeroException()), "int[] ", " "); validateError(() => Assert.NotEqual(expected.ToAsyncEnumerable(), actual, (int e, int a) => throw new DivideByZeroException()), "", ""); } } @@ -1410,7 +1387,7 @@ public static void GuardClause() [Fact] public static void EmptyCollection() { - var collection = Array.Empty().ToAsyncEnumerable(); + var collection = new object[0].ToAsyncEnumerable(); var ex = Record.Exception(() => Assert.Single(collection)); @@ -1490,7 +1467,7 @@ public class Single_WithPredicate public static void GuardClauses() { Assert.Throws("collection", () => Assert.Single(default(IAsyncEnumerable)!, _ => true)); - Assert.Throws("predicate", () => Assert.Single(Array.Empty().ToAsyncEnumerable(), null!)); + Assert.Throws("predicate", () => Assert.Single(new object[0].ToAsyncEnumerable(), null!)); } [Fact] @@ -1498,7 +1475,7 @@ public static void SingleMatch() { var collection = new[] { "Hello", "World" }.ToAsyncEnumerable(); - var result = Assert.Single(collection, static item => item.StartsWith('H')); + var result = Assert.Single(collection, item => item.StartsWith("H")); Assert.Equal("Hello", result); } @@ -1580,16 +1557,19 @@ public static void StringAsCollection_NoMatch() } } - sealed class SpyEnumerator(IAsyncEnumerable enumerable) : - IAsyncEnumerable, IAsyncEnumerator + sealed class SpyEnumerator : IAsyncEnumerable, IAsyncEnumerator { - IAsyncEnumerator? innerEnumerator = enumerable.GetAsyncEnumerator(); + IAsyncEnumerator? innerEnumerator; + + public SpyEnumerator(IAsyncEnumerable enumerable) + { + innerEnumerator = enumerable.GetAsyncEnumerator(); + } public T Current => GuardNotNull("Tried to get Current on a disposed enumerator", innerEnumerator).Current; - public bool IsDisposed => - innerEnumerator is null; + public bool IsDisposed => innerEnumerator is null; public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => this; @@ -1618,4 +1598,4 @@ static T2 GuardNotNull( } } -#endif +#endif \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/BooleanAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/BooleanAssertsTests.cs similarity index 99% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/BooleanAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/BooleanAssertsTests.cs index ce6ed22d921..4a1a1c4c0d8 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/BooleanAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/BooleanAssertsTests.cs @@ -1,3 +1,4 @@ +using System; using Xunit; using Xunit.Sdk; diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/CollectionAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/CollectionAssertsTests.cs similarity index 85% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/CollectionAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/CollectionAssertsTests.cs index 6167acf6c43..65df5681035 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/CollectionAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/CollectionAssertsTests.cs @@ -1,9 +1,11 @@ -#pragma warning disable CA1825 // Avoid zero-length array allocations -#pragma warning disable CA1865 // Use char overload -#pragma warning disable IDE0034 // Simplify 'default' expression - +using System; using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text; +using System.Threading.Tasks; +using NSubstitute; using Xunit; using Xunit.Sdk; @@ -15,8 +17,8 @@ public class All public static void GuardClauses() { Assert.Throws("collection", () => Assert.All(default(IEnumerable)!, _ => { })); - Assert.Throws("action", () => Assert.All([], (Action)null!)); - Assert.Throws("action", () => Assert.All([], (Action)null!)); + Assert.Throws("action", () => Assert.All(new object[0], (Action)null!)); + Assert.Throws("action", () => Assert.All(new object[0], (Action)null!)); } [Fact] @@ -27,13 +29,6 @@ public static void Success() Assert.All(items, x => Assert.Equal(1, x)); } - [Fact] - public static void EmptyCollection_ThrowIfEmptyFalse_Success() - { - var items = Array.Empty(); - Assert.All(items, item => Assert.Equal(1, item), false); - } - [Fact] public static void Failure() { @@ -56,20 +51,6 @@ public static void Failure() ); } - [Fact] - public static void EmptyCollection_ThrowIfEmptyTrue_Failure() - { - var items = Array.Empty(); - var ex = Record.Exception(() => Assert.All(items, item => Assert.Equal(1, item), true)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.All() Failure: The collection was empty." + Environment.NewLine + - "At least one item was expected.", - ex.Message - ); - } - [Fact] public static void ActionCanReceiveIndex() { @@ -88,8 +69,8 @@ public class AllAsync public static async Task GuardClauses() { await Assert.ThrowsAsync("collection", () => Assert.AllAsync(default(IEnumerable)!, async _ => await Task.Yield())); - await Assert.ThrowsAsync("action", () => Assert.AllAsync([], (Func)null!)); - await Assert.ThrowsAsync("action", () => Assert.AllAsync([], (Func)null!)); + await Assert.ThrowsAsync("action", () => Assert.AllAsync(new object[0], (Func)null!)); + await Assert.ThrowsAsync("action", () => Assert.AllAsync(new object[0], (Func)null!)); } [Fact] @@ -100,13 +81,6 @@ public static async Task Success() await Assert.AllAsync(items, async item => { await Task.Yield(); Assert.Equal(1, item); }); } - [Fact] - public static async Task EmptyCollection_ThrowIfEmptyFalse_Success() - { - var items = Array.Empty(); - await Assert.AllAsync(items, async item => { await Task.Yield(); Assert.Equal(1, item); }, false); - } - [Fact] public static void Failure() { @@ -129,20 +103,6 @@ public static void Failure() ); } - [Fact] - public static async Task EmptyCollection_ThrowIfEmptyTrue_Failure() - { - var items = Array.Empty(); - var ex = await Record.ExceptionAsync(() => Assert.AllAsync(items, async item => { await Task.Yield(); Assert.Equal(1, item); }, true)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.All() Failure: The collection was empty." + Environment.NewLine + - "At least one item was expected.", - ex.Message - ); - } - [Fact] public static async Task ActionCanReceiveIndex() { @@ -173,11 +133,9 @@ public static void MismatchedElementCount() var list = new List(); var ex = Record.Exception( -#pragma warning disable xUnit2023 // Do not use collection methods for single-item collections () => Assert.Collection(list, item => Assert.True(false) ) -#pragma warning restore xUnit2023 // Do not use collection methods for single-item collections ); var collEx = Assert.IsType(ex); @@ -202,6 +160,7 @@ public static void NonEmptyCollection() ); } +#if !NETCOREAPP2_0 // Unclear why this is failing only on .NET Core 2.0, but it passes with .NET 6 and .NET Framework 4.x [Fact] public static void MismatchedElement() { @@ -226,6 +185,7 @@ public static void MismatchedElement() ex.Message ); } +#endif } public class CollectionAsync @@ -246,11 +206,9 @@ public static async Task MismatchedElementCountAsync() var list = new List(); var ex = await Record.ExceptionAsync( -#pragma warning disable xUnit2023 // Do not use collection methods for single-item collections () => Assert.CollectionAsync(list, async item => await Task.Yield() ) -#pragma warning restore xUnit2023 // Do not use collection methods for single-item collections ); var collEx = Assert.IsType(ex); @@ -373,8 +331,7 @@ public static void SetsAreTreatedSpecially() Assert.Contains("HI THERE", set); } -#if NET8_0_OR_GREATER - +#if NET5_0_OR_GREATER [Fact] public static void ReadOnlySetsAreTreatedSpecially() { @@ -382,8 +339,7 @@ public static void ReadOnlySetsAreTreatedSpecially() Assert.Contains("HI THERE", set); } - -#endif // NET8_0_OR_GREATER +#endif } public class Contains_Comparer @@ -391,10 +347,10 @@ public class Contains_Comparer [Fact] public static void GuardClauses() { - var comparer = new MyComparer(); + var comparer = Substitute.For>(); Assert.Throws("collection", () => Assert.Contains(14, default(IEnumerable)!, comparer)); - Assert.Throws("comparer", () => Assert.Contains(14, [], null!)); + Assert.Throws("comparer", () => Assert.Contains(14, new int[0], null!)); } [Fact] @@ -435,7 +391,7 @@ public class Contains_Predicate public static void GuardClauses() { Assert.Throws("collection", () => Assert.Contains(default(IEnumerable)!, item => true)); - Assert.Throws("filter", () => Assert.Contains([], (Predicate)null!)); + Assert.Throws("filter", () => Assert.Contains(new int[0], (Predicate)null!)); } [Fact] @@ -443,7 +399,7 @@ public static void ItemFound() { var list = new[] { "Hello", "world" }; - Assert.Contains(list, item => item.StartsWith("w", StringComparison.InvariantCulture)); + Assert.Contains(list, item => item.StartsWith("w")); } [Fact] @@ -451,7 +407,7 @@ public static void ItemNotFound() { var list = new[] { "Hello", "world" }; - var ex = Record.Exception(() => Assert.Contains(list, item => item.StartsWith("q", StringComparison.InvariantCulture))); + var ex = Record.Exception(() => Assert.Contains(list, item => item.StartsWith("q"))); Assert.IsType(ex); Assert.Equal( @@ -468,7 +424,7 @@ public class Distinct public static void GuardClauses() { Assert.Throws("collection", () => Assert.Distinct(default(IEnumerable)!)); - Assert.Throws("comparer", () => Assert.Distinct(Array.Empty(), null!)); + Assert.Throws("comparer", () => Assert.Distinct(new object[0], null!)); } [Fact] @@ -603,8 +559,7 @@ public static void SetsAreTreatedSpecially() ); } -#if NET8_0_OR_GREATER - +#if NET5_0_OR_GREATER [Fact] public static void ReadOnlySetsAreTreatedSpecially() { @@ -621,8 +576,7 @@ public static void ReadOnlySetsAreTreatedSpecially() ex.Message ); } - -#endif // NET8_0_OR_GREATER +#endif } public class DoesNotContain_Comparer @@ -630,10 +584,10 @@ public class DoesNotContain_Comparer [Fact] public static void GuardClauses() { - var comparer = new MyComparer(); + var comparer = Substitute.For>(); Assert.Throws("collection", () => Assert.DoesNotContain(14, default(IEnumerable)!, comparer)); - Assert.Throws("comparer", () => Assert.DoesNotContain(14, [], null!)); + Assert.Throws("comparer", () => Assert.DoesNotContain(14, new int[0], null!)); } [Fact] @@ -654,11 +608,9 @@ public static void HashSetConstructorComparerIsIgnored() class MyComparer : IEqualityComparer { - public bool Equals(int x, int y) => - false; + public bool Equals(int x, int y) => false; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public int GetHashCode(int obj) => throw new NotImplementedException(); } } @@ -668,7 +620,7 @@ public class DoesNotContain_Predicate public static void GuardClauses() { Assert.Throws("collection", () => Assert.DoesNotContain(default(IEnumerable)!, item => true)); - Assert.Throws("filter", () => Assert.DoesNotContain([], (Predicate)null!)); + Assert.Throws("filter", () => Assert.DoesNotContain(new int[0], (Predicate)null!)); } [Fact] @@ -676,7 +628,7 @@ public static void ItemFound() { var list = new[] { "Hello", "world" }; - var ex = Record.Exception(() => Assert.DoesNotContain(list, item => item.StartsWith("w", StringComparison.InvariantCulture))); + var ex = Record.Exception(() => Assert.DoesNotContain(list, item => item.StartsWith("w"))); Assert.IsType(ex); Assert.Equal( @@ -692,7 +644,7 @@ public static void ItemNotFound() { var list = new[] { "Hello", "world" }; - Assert.DoesNotContain(list, item => item.StartsWith("q", StringComparison.InvariantCulture)); + Assert.DoesNotContain(list, item => item.StartsWith("q")); } } @@ -730,7 +682,7 @@ public static void NonEmptyCollection() [Fact] public static void CollectionEnumeratorDisposed() { - var enumerator = new SpyEnumerator([]); + var enumerator = new SpyEnumerator(Enumerable.Empty()); Assert.Empty(enumerator); @@ -791,8 +743,8 @@ public class Arrays [Fact] public static void Equal() { - string[] expected = ["@", "a", "ab", "b"]; - string[] actual = ["@", "a", "ab", "b"]; + string[] expected = { "@", "a", "ab", "b" }; + string[] actual = { "@", "a", "ab", "b" }; Assert.Equal(expected, actual); } @@ -800,8 +752,8 @@ public static void Equal() [Fact] public static void EmbeddedArrays_Equal() { - string[][] expected = [["@", "a"], ["ab", "b"]]; - string[][] actual = [["@", "a"], ["ab", "b"]]; + string[][] expected = { new[] { "@", "a" }, new[] { "ab", "b" } }; + string[][] actual = { new[] { "@", "a" }, new[] { "ab", "b" } }; Assert.Equal(expected, actual); } @@ -923,7 +875,7 @@ public void Truncation( [Fact] public void SameValueDifferentType() { - var ex = Record.Exception(() => Assert.Equal(new object[] { 1, 2, 3 }, [1, 2, 3L])); + var ex = Record.Exception(() => Assert.Equal(new object[] { 1, 2, 3 }, new object[] { 1, 2, 3L })); Assert.IsType(ex); Assert.Equal( @@ -958,16 +910,15 @@ public int GetHashCode(EnumerableItem obj) => throw new NotImplementedException(); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } + + public EnumerableItem(int value) => Value = value; - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } @@ -983,16 +934,15 @@ public void CollectionItemIsEnumerable() Assert.Equal(expected, actual, (x, y) => x.Value / 2 == y.Value / 2); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public EnumerableItem(int value) => Value = value; - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } @@ -1083,7 +1033,7 @@ public class CollectionsWithComparer public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 1, 2, 3, 4, 5 }; + var actual = new List(new int[] { 1, 2, 3, 4, 5 }); var ex = Record.Exception(() => Assert.Equal(expected, actual, new IntComparer(false))); @@ -1102,26 +1052,31 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 0, 0, 0, 0, 0 }; + var actual = new List(new int[] { 0, 0, 0, 0, 0 }); Assert.Equal(expected, actual, new IntComparer(true)); } - class IntComparer(bool answer) : IEqualityComparer + class IntComparer : IEqualityComparer { - public bool Equals(int x, int y) => - answer; + readonly bool answer; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public IntComparer(bool answer) + { + this.answer = answer; + } + + public bool Equals(int x, int y) => answer; + + public int GetHashCode(int obj) => throw new NotImplementedException(); } // https://github.com/xunit/xunit/issues/2795 [Fact] public void CollectionItemIsEnumerable() { - var actual = new List { new(0), new(2) }; - var expected = new List { new(1), new(3) }; + List actual = new List { new(0), new(2) }; + List expected = new List { new(1), new(3) }; Assert.Equal(expected, actual, new EnumerableItemComparer()); } @@ -1135,22 +1090,21 @@ public int GetHashCode(EnumerableItem obj) => throw new NotImplementedException(); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public EnumerableItem(int value) => Value = value; - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } [Fact] public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() { - var ex = Record.Exception(() => Assert.Equal([1, 2], [1, 3], new ThrowingComparer())); + var ex = Record.Exception(() => Assert.Equal(new[] { 1, 2 }, new[] { 1, 3 }, new ThrowingComparer())); Assert.IsType(ex); Assert.Equal( @@ -1174,11 +1128,13 @@ public int GetHashCode(int obj) => } } -#if !XUNIT_AOT // Embedded IEquatable cannot be done in Native AOT because of the reflection restrictions - public class CollectionsWithEquatable { +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal() { var expected = new[] { new EquatableObject { Char = 'a' } }; @@ -1212,24 +1168,16 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } -#endif // !XUNIT_AOT - public class CollectionsWithFunc { [Fact] public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 1, 2, 3, 4, 5 }; + var actual = new List(new int[] { 1, 2, 3, 4, 5 }); var ex = Record.Exception(() => Assert.Equal(expected, actual, (x, y) => false)); @@ -1248,7 +1196,7 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 0, 0, 0, 0, 0 }; + var actual = new List(new int[] { 0, 0, 0, 0, 0 }); Assert.Equal(expected, actual, (x, y) => true); } @@ -1257,22 +1205,21 @@ public static void AlwaysTrue() [Fact] public void CollectionItemIsEnumerable() { - var expected = new List { new(1), new(3) }; - var actual = new List { new(0), new(2) }; + List actual = new List { new(0), new(2) }; + List expected = new List { new(1), new(3) }; Assert.Equal(expected, actual, (x, y) => x.Value / 2 == y.Value / 2); } - public sealed class EnumerableItem(int value) : - IEnumerable + public sealed class EnumerableItem : IEnumerable { - public int Value { get; } = value; + public int Value { get; } - public IEnumerator GetEnumerator() => - Enumerable.Repeat("", Value).GetEnumerator(); + public EnumerableItem(int value) => Value = value; - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + public IEnumerator GetEnumerator() => Enumerable.Repeat("", Value).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } [Fact] @@ -1280,9 +1227,9 @@ public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() { var ex = Record.Exception(() => Assert.Equal( - [1, 2], - [1, 3], - static (e, a) => throw new DivideByZeroException() + new[] { 1, 2 }, + new[] { 1, 3 }, + (int e, int a) => throw new DivideByZeroException() ) ); @@ -1330,13 +1277,8 @@ public static void ExpectedLarger() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[a, 1], [b, 2], [c, 3]]" + Environment.NewLine + - "Actual: [[a, 1], [b, 2]]", -#else "Expected: [[\"a\"] = 1, [\"b\"] = 2, [\"c\"] = 3]" + Environment.NewLine + "Actual: [[\"a\"] = 1, [\"b\"] = 2]", -#endif ex.Message ); } @@ -1352,13 +1294,8 @@ public static void ActualLarger() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[a, 1], [b, 2]]" + Environment.NewLine + - "Actual: [[a, 1], [b, 2], [c, 3]]", -#else "Expected: [[\"a\"] = 1, [\"b\"] = 2]" + Environment.NewLine + "Actual: [[\"a\"] = 1, [\"b\"] = 2, [\"c\"] = 3]", -#endif ex.Message ); } @@ -1390,13 +1327,8 @@ public static void SomeKeysDiffer() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: [[a, 1], [be, 2], [c, 3], [d, 4], [e, 5], {ArgumentFormatter.Ellipsis}]" + Environment.NewLine + - $"Actual: [[a, 1], [ba, 2], [c, 3], [d, 4], [e, 5], {ArgumentFormatter.Ellipsis}]", -#else $"Expected: [[\"a\"] = 1, [\"be\"] = 2, [\"c\"] = 3, [\"d\"] = 4, [\"e\"] = 5, {ArgumentFormatter.Ellipsis}]" + Environment.NewLine + $"Actual: [[\"a\"] = 1, [\"ba\"] = 2, [\"c\"] = 3, [\"d\"] = 4, [\"e\"] = 5, {ArgumentFormatter.Ellipsis}]", -#endif ex.Message ); } @@ -1404,9 +1336,6 @@ public static void SomeKeysDiffer() [Fact] public static void WithCollectionValues_Equal() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization - // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new Dictionary> { @@ -1419,18 +1348,12 @@ public static void WithCollectionValues_Equal() ["ccAddresses"] = new string[] { "test2@example.com" }, }; -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization - Assert.Equal(expected, actual); } [Fact] public static void WithCollectionValues_NotEqual() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization - // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new Dictionary> { @@ -1443,28 +1366,22 @@ public static void WithCollectionValues_NotEqual() ["ccAddresses"] = new string[] { "test3@example.com" }, }; -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization - var ex = Record.Exception(() => Assert.Equal(expected, actual)); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[toAddresses, System.Collections.Generic.List`1[System.String]], [ccAddresses, System.Collections.Generic.List`1[System.String]]]" + Environment.NewLine + - "Actual: [[toAddresses, System.String[]], [ccAddresses, System.String[]]]", -#else "Expected: [[\"toAddresses\"] = [\"test1@example.com\"], [\"ccAddresses\"] = [\"test2@example.com\"]]" + Environment.NewLine + "Actual: [[\"toAddresses\"] = [\"test1@example.com\"], [\"ccAddresses\"] = [\"test3@example.com\"]]", -#endif ex.Message ); } -#if !XUNIT_AOT // Embedded IEquatable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableValues_Equal() { var expected = new Dictionary { { "Key1", new() { Char = 'a' } } }; @@ -1496,16 +1413,8 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } -#endif // !XUNIT_AOT - [Fact] public void ComplexEmbeddedValues_Equal() { @@ -1515,7 +1424,7 @@ public void ComplexEmbeddedValues_Equal() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value" } } @@ -1528,7 +1437,7 @@ public void ComplexEmbeddedValues_Equal() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value" } } @@ -1548,7 +1457,7 @@ public void ComplexEmbeddedValues_NotEqual() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value1" } } @@ -1561,7 +1470,7 @@ public void ComplexEmbeddedValues_NotEqual() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value2" } } @@ -1574,13 +1483,8 @@ public void ComplexEmbeddedValues_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[key, System.Collections.Generic.Dictionary`2[System.String,System.Object]]]" + Environment.NewLine + - "Actual: [[key, System.Collections.Generic.Dictionary`2[System.String,System.Object]]]", -#else "Expected: [[\"key\"] = [[\"key\"] = [[[\"key\"] = [\"value1\"]]]]]" + Environment.NewLine + "Actual: [[\"key\"] = [[\"key\"] = [[[\"key\"] = [\"value2\"]]]]]", -#endif ex.Message ); } @@ -1597,21 +1501,29 @@ public void Equal() Assert.Equal(expected, actual); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal_WithInternalComparer() { var comparer = new BitArrayComparer(); - var expected = new HashSet(comparer) { new([true, false]) }; - var actual = new HashSet(comparer) { new([true, false]) }; + var expected = new HashSet(comparer) { new BitArray(new[] { true, false }) }; + var actual = new HashSet(comparer) { new BitArray(new[] { true, false }) }; Assert.Equal(expected, actual); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal_WithExternalComparer() { - var expected = new HashSet { new([true, false]) }; - var actual = new HashSet { new([true, false]) }; + var expected = new HashSet { new BitArray(new[] { true, false }) }; + var actual = new HashSet { new BitArray(new[] { true, false }) }; Assert.Equal(expected, actual, new BitArrayComparer()); } @@ -1637,8 +1549,8 @@ public void NotEqual() public void NotEqual_WithInternalComparer() { var comparer = new BitArrayComparer(); - var expected = new HashSet(comparer) { new([true, false]) }; - var actual = new HashSet(comparer) { new([true, true]) }; + var expected = new HashSet(comparer) { new BitArray(new[] { true, false }) }; + var actual = new HashSet(comparer) { new BitArray(new[] { true, true }) }; var ex = Record.Exception(() => Assert.Equal(expected, actual)); @@ -1654,8 +1566,8 @@ public void NotEqual_WithInternalComparer() [Fact] public void NotEqual_WithExternalComparer() { - var expected = new HashSet { new([true, false]) }; - var actual = new HashSet { new([true, true]) }; + var expected = new HashSet { new BitArray(new[] { true, false }) }; + var actual = new HashSet { new BitArray(new[] { true, true }) }; var ex = Record.Exception(() => Assert.Equal(expected, actual, new BitArrayComparer())); @@ -1773,8 +1685,8 @@ public class Arrays [Fact] public static void Equal() { - string[] expected = ["@", "a", "ab", "b"]; - string[] actual = ["@", "a", "ab", "b"]; + string[] expected = { "@", "a", "ab", "b" }; + string[] actual = { "@", "a", "ab", "b" }; var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); Assert.IsType(ex); @@ -1789,8 +1701,8 @@ public static void Equal() [Fact] public static void EmbeddedArrays_Equal() { - string[][] expected = [["@", "a"], ["ab", "b"]]; - string[][] actual = [["@", "a"], ["ab", "b"]]; + string[][] expected = { new[] { "@", "a" }, new[] { "ab", "b" } }; + string[][] actual = { new[] { "@", "a" }, new[] { "ab", "b" } }; var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); Assert.IsType(ex); @@ -1805,8 +1717,8 @@ public static void EmbeddedArrays_Equal() [Fact] public static void NotEqual() { - IEnumerable expected = [1, 2, 3]; - IEnumerable actual = [1, 2, 4]; + IEnumerable expected = new[] { 1, 2, 3 }; + IEnumerable actual = new[] { 1, 2, 4 }; Assert.NotEqual(expected, actual); } @@ -1814,7 +1726,7 @@ public static void NotEqual() [Fact] public static void SameValueDifferentType() { - Assert.NotEqual([1, 2, 3], [1, 2, 3L]); + Assert.NotEqual(new object[] { 1, 2, 3 }, new object[] { 1, 2, 3L }); } } @@ -1841,7 +1753,7 @@ public static void Equal() public static void NotEqual() { var expected = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - var actual = new List { 1, 2, 3, 4, 0, 6, 7, 8, 9, 10 }; + var actual = new List(new[] { 1, 2, 3, 4, 0, 6, 7, 8, 9, 10 }); Assert.NotEqual(expected, actual); } @@ -1853,7 +1765,7 @@ public class CollectionsWithComparer public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 1, 2, 3, 4, 5 }; + var actual = new List(new int[] { 1, 2, 3, 4, 5 }); Assert.NotEqual(expected, actual, new IntComparer(false)); } @@ -1862,7 +1774,7 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 0, 0, 0, 0, 0 }; + var actual = new List(new int[] { 0, 0, 0, 0, 0 }); var ex = Record.Exception(() => Assert.NotEqual(expected, actual, new IntComparer(true))); @@ -1875,20 +1787,24 @@ public static void AlwaysTrue() ); } - class IntComparer(bool answer) : - IEqualityComparer + class IntComparer : IEqualityComparer { - public bool Equals(int x, int y) => - answer; + readonly bool answer; - public int GetHashCode(int obj) => - throw new NotImplementedException(); + public IntComparer(bool answer) + { + this.answer = answer; + } + + public bool Equals(int x, int y) => answer; + + public int GetHashCode(int obj) => throw new NotImplementedException(); } [Fact] public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() { - var ex = Record.Exception(() => Assert.NotEqual([1, 2], [1, 2], new ThrowingComparer())); + var ex = Record.Exception(() => Assert.NotEqual(new[] { 1, 2 }, new[] { 1, 2 }, new ThrowingComparer())); Assert.IsType(ex); Assert.Equal( @@ -1912,11 +1828,13 @@ public int GetHashCode(int obj) => } } -#if !XUNIT_AOT // Embedded IEquatable cannot be done in Native AOT because of the reflection restrictions - public class CollectionsWithEquatable { +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal() { var expected = new[] { new EquatableObject { Char = 'a' } }; @@ -1948,24 +1866,16 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } -#endif // !XUNIT_AOT - public class CollectionsWithFunc { [Fact] public static void AlwaysFalse() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 1, 2, 3, 4, 5 }; + var actual = new List(new int[] { 1, 2, 3, 4, 5 }); Assert.NotEqual(expected, actual, (x, y) => false); } @@ -1974,7 +1884,7 @@ public static void AlwaysFalse() public static void AlwaysTrue() { var expected = new[] { 1, 2, 3, 4, 5 }; - var actual = new List { 0, 0, 0, 0, 0 }; + var actual = new List(new int[] { 0, 0, 0, 0, 0 }); var ex = Record.Exception(() => Assert.NotEqual(expected, actual, (x, y) => true)); @@ -1992,9 +1902,9 @@ public void WithThrow_PrintsPointerWhereThrowOccurs_RecordsInnerException() { var ex = Record.Exception(() => Assert.NotEqual( - [1, 2], - [1, 2], - static (e, a) => throw new DivideByZeroException() + new[] { 1, 2 }, + new[] { 1, 2 }, + (int e, int a) => throw new DivideByZeroException() ) ); @@ -2024,13 +1934,8 @@ public static void InOrderDictionary() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[a, 1], [b, 2], [c, 3]]" + Environment.NewLine + - "Actual: [[a, 1], [b, 2], [c, 3]]", -#else "Expected: Not [[\"a\"] = 1, [\"b\"] = 2, [\"c\"] = 3]" + Environment.NewLine + "Actual: [[\"a\"] = 1, [\"b\"] = 2, [\"c\"] = 3]", -#endif ex.Message ); } @@ -2046,13 +1951,8 @@ public static void OutOfOrderDictionary() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[a, 1], [b, 2], [c, 3]]" + Environment.NewLine + - "Actual: [[b, 2], [c, 3], [a, 1]]", -#else "Expected: Not [[\"a\"] = 1, [\"b\"] = 2, [\"c\"] = 3]" + Environment.NewLine + "Actual: [[\"b\"] = 2, [\"c\"] = 3, [\"a\"] = 1]", -#endif ex.Message ); } @@ -2103,9 +2003,6 @@ public static void SomeKeysDiffer() [Fact] public static void WithCollectionValues_Equal() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization - // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new Dictionary> { @@ -2118,21 +2015,13 @@ public static void WithCollectionValues_Equal() ["ccAddresses"] = new string[] { "test2@example.com" }, }; -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization - var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[toAddresses, System.Collections.Generic.List`1[System.String]], [ccAddresses, System.Collections.Generic.List`1[System.String]]]" + Environment.NewLine + - "Actual: [[toAddresses, System.String[]], [ccAddresses, System.String[]]]", -#else "Expected: Not [[\"toAddresses\"] = [\"test1@example.com\"], [\"ccAddresses\"] = [\"test2@example.com\"]]" + Environment.NewLine + "Actual: [[\"toAddresses\"] = [\"test1@example.com\"], [\"ccAddresses\"] = [\"test2@example.com\"]]", -#endif ex.Message ); } @@ -2140,9 +2029,6 @@ public static void WithCollectionValues_Equal() [Fact] public static void WithCollectionValues_NotEqual() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization - // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new Dictionary> { @@ -2155,15 +2041,14 @@ public static void WithCollectionValues_NotEqual() ["ccAddresses"] = new string[] { "test3@example.com" }, }; -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization - Assert.NotEqual(expected, actual); } -#if !XUNIT_AOT // Embedded IEquatable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableValues_Equal() { var expected = new Dictionary { { "Key1", new() { Char = 'a' } } }; @@ -2195,16 +2080,8 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } -#endif // !XUNIT_AOT - [Fact] public void ComplexEmbeddedValues_Equal() { @@ -2214,7 +2091,7 @@ public void ComplexEmbeddedValues_Equal() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value" } } @@ -2227,7 +2104,7 @@ public void ComplexEmbeddedValues_Equal() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value" } } @@ -2240,13 +2117,8 @@ public void ComplexEmbeddedValues_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[key, System.Collections.Generic.Dictionary`2[System.String,System.Object]]]" + Environment.NewLine + - "Actual: [[key, System.Collections.Generic.Dictionary`2[System.String,System.Object]]]", -#else "Expected: Not [[\"key\"] = [[\"key\"] = [[[\"key\"] = [\"value\"]]]]]" + Environment.NewLine + "Actual: [[\"key\"] = [[\"key\"] = [[[\"key\"] = [\"value\"]]]]]", -#endif ex.Message ); } @@ -2260,7 +2132,7 @@ public void ComplexEmbeddedValues_NotEqual() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value1" } } @@ -2273,7 +2145,7 @@ public void ComplexEmbeddedValues_NotEqual() { ["key"] = new List>() { - new() + new Dictionary() { ["key"] = new List { "value2" } } @@ -2304,12 +2176,16 @@ public void Equal() ); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal_WithInternalComparer() { var comparer = new BitArrayComparer(); - var expected = new HashSet(comparer) { new([true, false]) }; - var actual = new HashSet(comparer) { new([true, false]) }; + var expected = new HashSet(comparer) { new BitArray(new[] { true, false }) }; + var actual = new HashSet(comparer) { new BitArray(new[] { true, false }) }; var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); @@ -2322,11 +2198,15 @@ public void Equal_WithInternalComparer() ); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal_WithExternalComparer() { - var expected = new HashSet { new([true, false]) }; - var actual = new HashSet { new([true, false]) }; + var expected = new HashSet { new BitArray(new[] { true, false }) }; + var actual = new HashSet { new BitArray(new[] { true, false }) }; var ex = Record.Exception(() => Assert.NotEqual(expected, actual, new BitArrayComparer())); @@ -2352,8 +2232,8 @@ public void NotEqual() public void NotEqual_WithInternalComparer() { var comparer = new BitArrayComparer(); - var expected = new HashSet(comparer) { new([true, false]) }; - var actual = new HashSet(comparer) { new([true, true]) }; + var expected = new HashSet(comparer) { new BitArray(new[] { true, false }) }; + var actual = new HashSet(comparer) { new BitArray(new[] { true, true }) }; Assert.NotEqual(expected, actual); } @@ -2361,8 +2241,8 @@ public void NotEqual_WithInternalComparer() [Fact] public void NotEqual_WithExternalComparer() { - var expected = new HashSet { new([true, false]) }; - var actual = new HashSet { new([true, true]) }; + var expected = new HashSet { new BitArray(new[] { true, false }) }; + var actual = new HashSet { new BitArray(new[] { true, true }) }; Assert.NotEqual(expected, actual, new BitArrayComparer()); } @@ -2627,7 +2507,7 @@ public static void SingleMatch() { var collection = new[] { "Hello", "World" }; - var result = Assert.Single(collection, item => item.StartsWith("H", StringComparison.InvariantCulture)); + var result = Assert.Single(collection, item => item.StartsWith("H")); Assert.Equal("Hello", result); } @@ -2709,10 +2589,14 @@ public static void StringAsCollection_NoMatch() } } - sealed class SpyEnumerator(IEnumerable enumerable) : - IEnumerable, IEnumerator + sealed class SpyEnumerator : IEnumerable, IEnumerator { - IEnumerator? innerEnumerator = enumerable.GetEnumerator(); + IEnumerator? innerEnumerator; + + public SpyEnumerator(IEnumerable enumerable) + { + innerEnumerator = enumerable.GetEnumerator(); + } public T Current => GuardNotNull("Tried to get Current on a disposed enumerator", innerEnumerator).Current; @@ -2720,14 +2604,11 @@ sealed class SpyEnumerator(IEnumerable enumerable) : object? IEnumerator.Current => GuardNotNull("Tried to get Current on a disposed enumerator", innerEnumerator).Current; - public bool IsDisposed => - innerEnumerator is null; + public bool IsDisposed => innerEnumerator is null; - public IEnumerator GetEnumerator() => - this; + public IEnumerator GetEnumerator() => this; - IEnumerator IEnumerable.GetEnumerator() => - this; + IEnumerator IEnumerable.GetEnumerator() => this; public bool MoveNext() => GuardNotNull("Tried to call MoveNext() on a disposed enumerator", innerEnumerator).MoveNext(); diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/CulturedFactDefaultAttribute.cs b/src/Microsoft.DotNet.XUnitAssert/tests/CulturedFactDefaultAttribute.cs deleted file mode 100644 index 87a4dfb5792..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/CulturedFactDefaultAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Xunit; - -public class CulturedFactDefaultAttribute( - [CallerFilePath] string? sourceFilePath = null, - [CallerLineNumber] int sourceLineNumber = -1) : - CulturedFactAttribute(["en-US", "fr-FR"], sourceFilePath, sourceLineNumber) -{ } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/CulturedTheoryDefaultAttribute.cs b/src/Microsoft.DotNet.XUnitAssert/tests/CulturedTheoryDefaultAttribute.cs deleted file mode 100644 index 5e09df6bf8c..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/CulturedTheoryDefaultAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Xunit; - -public class CulturedTheoryDefaultAttribute( - [CallerFilePath] string? sourceFilePath = null, - [CallerLineNumber] int sourceLineNumber = -1) : - CulturedTheoryAttribute(["en-US", "fr-FR"], sourceFilePath, sourceLineNumber) -{ } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/DictionaryAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/DictionaryAssertsTests.cs similarity index 92% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/DictionaryAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/DictionaryAssertsTests.cs index f5570f12662..8cf47228a92 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/DictionaryAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/DictionaryAssertsTests.cs @@ -1,8 +1,13 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using Xunit; using Xunit.Sdk; +#if XUNIT_IMMUTABLE_COLLECTIONS +using System.Collections.Immutable; +#endif + public class DictionaryAssertsTests { public class Contains @@ -19,8 +24,10 @@ public static void KeyInDictionary() Assert.Equal(42, Assert.Contains("FORTY-two", new ReadOnlyDictionary(dictionary))); Assert.Equal(42, Assert.Contains("FORTY-two", (IDictionary)dictionary)); Assert.Equal(42, Assert.Contains("FORTY-two", (IReadOnlyDictionary)dictionary)); +#if XUNIT_IMMUTABLE_COLLECTIONS Assert.Equal(42, Assert.Contains("FORTY-two", dictionary.ToImmutableDictionary(StringComparer.InvariantCultureIgnoreCase))); Assert.Equal(42, Assert.Contains("FORTY-two", (IImmutableDictionary)dictionary.ToImmutableDictionary(StringComparer.InvariantCultureIgnoreCase))); +#endif } [Fact] @@ -31,7 +38,7 @@ public static void KeyNotInDictionary() ["eleventeen"] = 110 }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -48,8 +55,10 @@ static void assertFailure(Action action) assertFailure(() => Assert.Contains("FORTY-two", new ReadOnlyDictionary(dictionary))); assertFailure(() => Assert.Contains("FORTY-two", (IDictionary)dictionary)); assertFailure(() => Assert.Contains("FORTY-two", (IReadOnlyDictionary)dictionary)); +#if XUNIT_IMMUTABLE_COLLECTIONS assertFailure(() => Assert.Contains("FORTY-two", dictionary.ToImmutableDictionary())); assertFailure(() => Assert.Contains("FORTY-two", (IImmutableDictionary)dictionary.ToImmutableDictionary())); +#endif } } @@ -67,8 +76,10 @@ public static void KeyNotInDictionary() Assert.DoesNotContain("FORTY-two", new ReadOnlyDictionary(dictionary)); Assert.DoesNotContain("FORTY-two", (IDictionary)dictionary); Assert.DoesNotContain("FORTY-two", (IReadOnlyDictionary)dictionary); +#if XUNIT_IMMUTABLE_COLLECTIONS Assert.DoesNotContain("FORTY-two", dictionary.ToImmutableDictionary()); Assert.DoesNotContain("FORTY-two", (IImmutableDictionary)dictionary.ToImmutableDictionary()); +#endif } [Fact] @@ -79,7 +90,7 @@ public static void KeyInDictionary() ["forty-two"] = 42 }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -96,8 +107,10 @@ static void assertFailure(Action action) assertFailure(() => Assert.DoesNotContain("FORTY-two", new ReadOnlyDictionary(dictionary))); assertFailure(() => Assert.DoesNotContain("FORTY-two", (IDictionary)dictionary)); assertFailure(() => Assert.DoesNotContain("FORTY-two", (IReadOnlyDictionary)dictionary)); +#if XUNIT_IMMUTABLE_COLLECTIONS assertFailure(() => Assert.DoesNotContain("FORTY-two", dictionary.ToImmutableDictionary(StringComparer.InvariantCultureIgnoreCase))); assertFailure(() => Assert.DoesNotContain("FORTY-two", (IImmutableDictionary)dictionary.ToImmutableDictionary(StringComparer.InvariantCultureIgnoreCase))); +#endif } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/EnumerableExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/EnumerableExtensions.cs similarity index 90% rename from src/Microsoft.DotNet.XUnitAssert/tests/Extensions/EnumerableExtensions.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/EnumerableExtensions.cs index b48735535cc..229b00aa475 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/EnumerableExtensions.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/EnumerableExtensions.cs @@ -1,12 +1,12 @@ +using System.Collections.Generic; + public static class EnumerableExtensions { #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable data) { foreach (var dataItem in data) yield return dataItem; } - #pragma warning restore CS1998 -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EqualityAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/EqualityAssertsTests.cs similarity index 79% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EqualityAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/EqualityAssertsTests.cs index 8f2b647c3ad..517bc7aba69 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EqualityAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/EqualityAssertsTests.cs @@ -1,9 +1,10 @@ -#pragma warning disable CA1512 // Use ArgumentOutOfRangeException throw helper - +using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Immutable; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Xunit; using Xunit.Sdk; @@ -11,44 +12,6 @@ public class EqualityAssertsTests { public class Equal { - public class ReferenceEquality - { - // https://github.com/xunit/xunit/issues/2271 - [Fact] - public void TwoIdenticalReferencesShouldBeEqual() - { - Field x = new Field(); - - Assert.Equal(x, x); - } - - sealed class Field : IReadOnlyList - { - Field IReadOnlyList.this[int index] - { - get - { - if (index != 0) - throw new ArgumentOutOfRangeException(nameof(index)); - - return this; - } - } - - int IReadOnlyCollection.Count => 1; - - IEnumerator IEnumerable.GetEnumerator() - { - yield return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - yield return this; - } - } - } - public class Intrinsics { [Fact] @@ -75,8 +38,8 @@ public void NotEqual() public void StringsPassViaObjectEqualAreNotFormattedOrTruncated() { var ex = Record.Exception( - () => Assert.Equal( - $"This is a long{Environment.NewLine}string with{Environment.NewLine}new lines" as object, + () => Assert.Equal( + $"This is a long{Environment.NewLine}string with{Environment.NewLine}new lines", $"This is a long{Environment.NewLine}string with embedded{Environment.NewLine}new lines" ) ); @@ -123,10 +86,14 @@ public void NotEqual() ); } - class Comparer(bool result) : - IEqualityComparer + class Comparer : IEqualityComparer { - readonly bool result = result; + readonly bool result; + + public Comparer(bool result) + { + this.result = result; + } public bool Equals(T? x, T? y) => result; @@ -159,7 +126,7 @@ public int GetHashCode(int obj) => [Fact] public void Enumerable_WithThrow_RecordsInnerException() { - var ex = Record.Exception(() => Assert.Equal([1, 2], [1, 3], new ThrowingEnumerableComparer())); + var ex = Record.Exception(() => Assert.Equal(new[] { 1, 2 }, new[] { 1, 3 }, new ThrowingEnumerableComparer())); Assert.IsType(ex); Assert.Equal( @@ -228,8 +195,8 @@ public void Enumerable_WithThrow_RecordsInnerException() { var ex = Record.Exception( () => Assert.Equal( - [1, 2], - [1, 3], + new[] { 1, 2 }, + new[] { 1, 3 }, (IEnumerable e, IEnumerable a) => throw new DivideByZeroException() ) ); @@ -268,13 +235,8 @@ public void NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: SpyComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyComparable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: SpyComparable { CompareCalled = True }" + Environment.NewLine + "Actual: SpyComparable { CompareCalled = False }", -#endif ex.Message ); } @@ -296,20 +258,15 @@ public void NonGeneric_SameType_NotEqual() var expected = new MultiComparable(1); var actual = new MultiComparable(2); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: MultiComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: MultiComparable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: MultiComparable { Value = 1 }" + Environment.NewLine + "Actual: MultiComparable { Value = 2 }", -#endif ex.Message ); } @@ -335,18 +292,14 @@ public void NonGeneric_DifferentType_NotEqual() var expected = new MultiComparable(1); var actual = 2; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: MultiComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + -#else "Expected: MultiComparable { Value = 1 }" + Environment.NewLine + -#endif "Actual: 2", ex.Message ); @@ -377,26 +330,19 @@ public void Generic_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: SpyComparable_Generic {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyComparable_Generic {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: SpyComparable_Generic { CompareCalled = True }" + Environment.NewLine + "Actual: SpyComparable_Generic { CompareCalled = False }", -#endif ex.Message ); } -#if !XUNIT_AOT // The default comparer can't go deep without reflection - [Fact] public void SubClass_SubClass_Equal() { var expected = new ComparableSubClassA(1); var actual = new ComparableSubClassB(1); - Assert.Equal(expected as object, actual); + Assert.Equal(expected, actual); } [Fact] @@ -405,7 +351,7 @@ public void SubClass_SubClass_NotEqual() var expected = new ComparableSubClassA(1); var actual = new ComparableSubClassB(2); - var ex = Record.Exception(() => Assert.Equal(expected as object, actual)); + var ex = Record.Exception(() => Assert.Equal(expected, actual)); Assert.IsType(ex); Assert.Equal( @@ -416,8 +362,6 @@ public void SubClass_SubClass_NotEqual() ); } -#endif // !XUNIT_AOT - [Fact] public void BaseClass_SubClass_Equal() { @@ -438,13 +382,8 @@ public void BaseClass_SubClass_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: ComparableBaseClass {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableSubClassA {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: ComparableBaseClass { Value = 1 }" + Environment.NewLine + "Actual: ComparableSubClassA { Value = 2 }", -#endif ex.Message ); } @@ -469,13 +408,8 @@ public void SubClass_BaseClass_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: ComparableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableBaseClass {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: ComparableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: ComparableBaseClass { Value = 2 }", -#endif ex.Message ); } @@ -497,20 +431,15 @@ public void Generic_ThrowsException_NotEqual() var expected = new ComparableThrower(1); var actual = new ComparableThrower(2); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: ComparableThrower {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableThrower {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: ComparableThrower { Value = 1 }" + Environment.NewLine + "Actual: ComparableThrower { Value = 2 }", -#endif ex.Message ); } @@ -520,9 +449,11 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, (object)actual)); } -#if !XUNIT_AOT // IComparable vs. IComparable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ImplicitImplementation_Equal() { object expected = new ImplicitIComparableExpected(1); @@ -548,7 +479,11 @@ public void DifferentTypes_ImplicitImplementation_NotEqual() ); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ExplicitImplementation_Equal() { object expected = new ExplicitIComparableActual(1); @@ -599,8 +534,6 @@ public void DifferentTypes_Throws_NotEqual() ex.Message ); } - -#endif // !XUNIT_AOT } public class NotComparable @@ -625,13 +558,8 @@ public void NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: NonComparableObject {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: NonComparableObject {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: NonComparableObject { }" + Environment.NewLine + "Actual: NonComparableObject { }", -#endif ex.Message ); } @@ -662,13 +590,8 @@ public void NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: SpyEquatable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyEquatable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: SpyEquatable { Equals__Called = True, Equals_Other = SpyEquatable { Equals__Called = False, Equals_Other = null } }" + Environment.NewLine + "Actual: SpyEquatable { Equals__Called = False, Equals_Other = null }", -#endif ex.Message ); } @@ -679,7 +602,7 @@ public void SubClass_SubClass_Equal() var expected = new EquatableSubClassA(1); var actual = new EquatableSubClassB(1); - Assert.Equal(expected as object, actual); + Assert.Equal(expected, actual); } [Fact] @@ -688,18 +611,13 @@ public void SubClass_SubClass_NotEqual() var expected = new EquatableSubClassA(1); var actual = new EquatableSubClassB(2); - var ex = Record.Exception(() => Assert.Equal(expected as object, actual)); + var ex = Record.Exception(() => Assert.Equal(expected, actual)); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableSubClassB {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: EquatableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: EquatableSubClassB { Value = 2 }", -#endif ex.Message ); } @@ -724,13 +642,8 @@ public void BaseClass_SubClass_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: EquatableBaseClass {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: EquatableBaseClass { Value = 1 }" + Environment.NewLine + "Actual: EquatableSubClassA { Value = 2 }", -#endif ex.Message ); } @@ -755,20 +668,17 @@ public void SubClass_BaseClass_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableBaseClass {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: EquatableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: EquatableBaseClass { Value = 2 }", -#endif ex.Message ); } -#if !XUNIT_AOT // Support for IEquatable vs. IEquatable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ImplicitImplementation_Equal() { object expected = new ImplicitIEquatableExpected(1); @@ -794,7 +704,11 @@ public void DifferentTypes_ImplicitImplementation_NotEqual() ); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ExplicitImplementation_Equal() { object expected = new ExplicitIEquatableExpected(1); @@ -819,17 +733,19 @@ public void DifferentTypes_ExplicitImplementation_NotEqual() ex.Message ); } - -#endif // !XUNIT_AOT } public class StructuralEquatable { +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal() { - var expected = new StructuralStringWrapper("a"); - var actual = new StructuralStringWrapper("a"); + var expected = new Tuple(new StringWrapper("a")); + var actual = new Tuple(new StringWrapper("a")); Assert.Equal(expected, actual); Assert.Equal(expected, (IStructuralEquatable)actual); @@ -839,23 +755,18 @@ public void Equal() [Fact] public void NotEqual() { - var expected = new StructuralStringWrapper("a"); - var actual = new StructuralStringWrapper("b"); + var expected = new Tuple(new StringWrapper("a")); + var actual = new Tuple(new StringWrapper("b")); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: StructuralStringWrapper {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: StructuralStringWrapper {{ {ArgumentFormatter.Ellipsis} }}", -#else - "Expected: StructuralStringWrapper { Value = \"a\" }" + Environment.NewLine + - "Actual: StructuralStringWrapper { Value = \"b\" }", -#endif + "Expected: Tuple (StringWrapper { Value = \"a\" })" + Environment.NewLine + + "Actual: Tuple (StringWrapper { Value = \"b\" })", ex.Message ); } @@ -882,7 +793,7 @@ public void ExpectedNull_ActualNonNull() var expected = new Tuple(null); var actual = new Tuple(new StringWrapper("a")); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -890,11 +801,7 @@ static void assertFailure(Action action) Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + "Expected: Tuple (null)" + Environment.NewLine + -#if XUNIT_AOT - $"Actual: Tuple (StringWrapper {{ {ArgumentFormatter.Ellipsis} }})", -#else "Actual: Tuple (StringWrapper { Value = \"a\" })", -#endif ex.Message ); } @@ -905,23 +812,19 @@ static void assertFailure(Action action) } [Fact] - public void ExpectedNonNull_ActualNull() + public void _ExpectedNonNull_ActualNull() { var expected = new Tuple(new StringWrapper("a")); var actual = new Tuple(null); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Tuple (StringWrapper {{ {ArgumentFormatter.Ellipsis} }})" + Environment.NewLine + -#else "Expected: Tuple (StringWrapper { Value = \"a\" })" + Environment.NewLine + -#endif "Actual: Tuple (null)", ex.Message ); @@ -948,28 +851,20 @@ public void IReadOnlyCollection_IEnumerable_Equal() [Fact] public void IReadOnlyCollection_IEnumerable_NotEqual() { - var expected = new string[] { @"C:\Program Files (x86)\Common Files\Extremely Long Path Name\VST2" }; - var actual = new ReadOnlyCollection([@"C:\Program Files (x86)\Common Files\Extremely Long Path Name\VST3"]); + var expected = new string[] { "foo", "bar" }; + var actual = new ReadOnlyCollection(new[] { "bar", "foo" }); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( -#if XUNIT_AOT "Assert.Equal() Failure: Collections differ" + Environment.NewLine + " ↓ (pos 0)" + Environment.NewLine + - "Expected: string[] [\"C:\\\\Program Files (x86)\\\\Common Files\\\\Extremely L\"···]" + Environment.NewLine + - "Actual: ReadOnlyCollection [\"C:\\\\Program Files (x86)\\\\Common Files\\\\Extremely L\"···]" + Environment.NewLine + + "Expected: string[] [\"foo\", \"bar\"]" + Environment.NewLine + + "Actual: ReadOnlyCollection [\"bar\", \"foo\"]" + Environment.NewLine + " ↑ (pos 0)", -#else - "Assert.Equal() Failure: Collections differ at index 0" + Environment.NewLine + - " ↓ (pos 64)" + Environment.NewLine + - "Expected: ···\"s (x86)\\\\Common Files\\\\Extremely Long Path Name\\\\VST2\"" + Environment.NewLine + - "Actual: ···\"s (x86)\\\\Common Files\\\\Extremely Long Path Name\\\\VST3\"" + Environment.NewLine + - " ↑ (pos 64)", -#endif ex.Message ); } @@ -1022,25 +917,17 @@ public void StringArray_ObjectArray_NotEqual() var expected = new string[] { "foo", "bar" }; var actual = new object[] { "foo", "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( -#if XUNIT_AOT "Assert.Equal() Failure: Collections differ" + Environment.NewLine + " ↓ (pos 1)" + Environment.NewLine + "Expected: string[] [\"foo\", \"bar\"]" + Environment.NewLine + "Actual: object[] [\"foo\", \"baz\"]" + Environment.NewLine + " ↑ (pos 1)", -#else - "Assert.Equal() Failure: Collections differ at index 1" + Environment.NewLine + - " ↓ (pos 2)" + Environment.NewLine + - "Expected: \"bar\"" + Environment.NewLine + - "Actual: \"baz\"" + Environment.NewLine + - " ↑ (pos 2)", -#endif ex.Message ); } @@ -1077,14 +964,12 @@ public void MultidimensionalArrays_NotEqual() ); } -#if !XUNIT_AOT // Array.CreateInstance is not available in Native AOT - [Fact] public void NonZeroBoundedArrays_Equal() { - var expected = Array.CreateInstance(typeof(int), [1], [1]); + var expected = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); expected.SetValue(42, 1); - var actual = Array.CreateInstance(typeof(int), [1], [1]); + var actual = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); actual.SetValue(42, 1); Assert.Equal(expected, actual); @@ -1093,9 +978,9 @@ public void NonZeroBoundedArrays_Equal() [Fact] public void NonZeroBoundedArrays_NotEqual() { - var expected = Array.CreateInstance(typeof(int), [1], [1]); + var expected = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); expected.SetValue(42, 1); - var actual = Array.CreateInstance(typeof(int), [1], [0]); + var actual = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 0 }); actual.SetValue(42, 0); var ex = Record.Exception(() => Assert.Equal(expected, (object)actual)); @@ -1109,15 +994,13 @@ public void NonZeroBoundedArrays_NotEqual() ); } -#endif // !XUNIT_AOT - [Fact] public void PrintPointersWithCompatibleComparers() { var expected = new[] { 1, 2, 3, 4, 5 }; var actual = new[] { 1, 2, 0, 4, 5 }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1215,10 +1098,9 @@ public void CollectionWithIEquatable_NotEqual() public sealed class EnumerableEquatable : IEnumerable, IEquatable> { - readonly List values = []; + List values = new(); - public void Add(T value) => - values.Add(value); + public void Add(T value) => values.Add(value); public bool Equals(EnumerableEquatable? other) { @@ -1228,17 +1110,9 @@ public bool Equals(EnumerableEquatable? other) return !values.Except(other.values).Any() && !other.values.Except(values).Any(); } - public override bool Equals(object? obj) => - Equals(obj as EnumerableEquatable); - - public IEnumerator GetEnumerator() => - values.GetEnumerator(); + public IEnumerator GetEnumerator() => values.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); - - public override int GetHashCode() => - values.GetHashCode(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } @@ -1261,20 +1135,15 @@ public void SameTypes_NotEqual() var expected = new Dictionary { ["foo"] = "bar" }; var actual = new Dictionary { ["foo"] = "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[foo, bar]]" + Environment.NewLine + - "Actual: [[foo, baz]]", -#else "Expected: [[\"foo\"] = \"bar\"]" + Environment.NewLine + "Actual: [[\"foo\"] = \"baz\"]", -#endif ex.Message ); } @@ -1300,20 +1169,15 @@ public void DifferentTypes_NotEqual() var expected = new Dictionary { ["foo"] = "bar" }; var actual = new ConcurrentDictionary { ["foo"] = "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Collections differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Dictionary [[foo, bar]]" + Environment.NewLine + - "Actual: ConcurrentDictionary [[foo, baz]]", -#else "Expected: Dictionary [[\"foo\"] = \"bar\"]" + Environment.NewLine + "Actual: ConcurrentDictionary [[\"foo\"] = \"baz\"]", -#endif ex.Message ); } @@ -1342,13 +1206,8 @@ public void NullValue_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Dictionaries differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [[two, ]]" + Environment.NewLine + - "Actual: [[two, 1]]", -#else "Expected: [[\"two\"] = null]" + Environment.NewLine + "Actual: [[\"two\"] = 1]", -#endif ex.Message ); } @@ -1450,15 +1309,11 @@ public void DifferentTypes_Equal() Assert.Equal(expected, actual); Assert.Equal(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.Equal(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection - [Fact] public void DifferentTypes_NotEqual() { @@ -1476,19 +1331,19 @@ public void DifferentTypes_NotEqual() ); } -#endif // !XUNIT_AOT - -#if !XUNIT_AOT // Comparer func overload is disabled in AOT via compiler - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void ComparerFunc_Throws() { var expected = new HashSet { "bar" }; var actual = new HashSet { "baz" }; -#pragma warning disable xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning disable xUnit2026 var ex = Record.Exception(() => Assert.Equal(expected, actual, (string l, string r) => true)); -#pragma warning restore xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning restore xUnit2026 Assert.IsType(ex); Assert.Equal( @@ -1496,8 +1351,6 @@ public void ComparerFunc_Throws() ex.Message ); } - -#endif // !XUNIT_AOT } public class Sets @@ -1510,11 +1363,9 @@ public void InOrder_Equal() Assert.Equal(expected, actual); Assert.Equal(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.Equal(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1523,7 +1374,7 @@ public void InOrder_NotEqual() var expected = new NonGenericSet { "bar", "foo" }; var actual = new NonGenericSet { "bar", "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1538,11 +1389,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, actual)); assertFailure(() => Assert.Equal(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.Equal(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1553,11 +1402,9 @@ public void OutOfOrder_Equal() Assert.Equal(expected, actual); Assert.Equal(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.Equal(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1566,7 +1413,7 @@ public void OutOfOrder_NotEqual() var expected = new NonGenericSet { "bar", "foo" }; var actual = new NonGenericSet { "foo", "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1581,11 +1428,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, actual)); assertFailure(() => Assert.Equal(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.Equal(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1594,7 +1439,7 @@ public void DifferentContents() var expected = new NonGenericSet { "bar" }; var actual = new NonGenericSet { "bar", "foo" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1609,11 +1454,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, actual)); assertFailure(() => Assert.Equal(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.Equal(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1624,11 +1467,9 @@ public void DifferentTypes_Equal() Assert.Equal(expected, actual); Assert.Equal(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.Equal(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1637,7 +1478,7 @@ public void DifferentTypes_NotEqual() var expected = new NonGenericSet { "bar" }; var actual = new HashSet { "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1652,11 +1493,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, actual)); assertFailure(() => Assert.Equal(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.Equal(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1667,11 +1506,9 @@ public void TwoGenericSubClass_Equal() Assert.Equal(expected, actual); Assert.Equal(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.Equal(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -1680,7 +1517,7 @@ public void TwoGenericSubClass_NotEqual() var expected = new TwoGenericSet { "foo", "bar" }; var actual = new TwoGenericSet { "foo", "baz" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -1695,24 +1532,24 @@ static void assertFailure(Action action) assertFailure(() => Assert.Equal(expected, actual)); assertFailure(() => Assert.Equal(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.Equal(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } -#if !XUNIT_AOT // Comparer func overload is disabled in AOT via compiler - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void ComparerFunc_Throws() { var expected = new NonGenericSet { "bar" }; var actual = new HashSet { "baz" }; -#pragma warning disable xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning disable xUnit2026 var ex = Record.Exception(() => Assert.Equal(expected, actual, (string l, string r) => true)); -#pragma warning restore xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning restore xUnit2026 Assert.IsType(ex); Assert.Equal( @@ -1720,56 +1557,16 @@ public void ComparerFunc_Throws() ex.Message ); } - -#endif - } - - // https://github.com/xunit/xunit/issues/3137 - public class ImmutableArrays - { - [Fact] - public void Equal() - { - var expected = new[] { 1, 2, 3 }.ToImmutableArray(); - var actual = new[] { 1, 2, 3 }.ToImmutableArray(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void NotEqual() - { - var expected = new[] { 1, 2, 3 }.ToImmutableArray(); - var actual = new[] { 1, 2, 4 }.ToImmutableArray(); - - var ex = Record.Exception(() => Assert.Equal(expected, actual)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Collections differ" + Environment.NewLine + - " ↓ (pos 2)" + Environment.NewLine + - "Expected: [1, 2, 3]" + Environment.NewLine + - "Actual: [1, 2, 4]" + Environment.NewLine + - " ↑ (pos 2)", - ex.Message - ); - } } public class KeyValuePair { -#if !XUNIT_AOT // AOT can't deep dive into collections in the same was a non-AOT - [Fact] public void CollectionKeys_Equal() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 var expected = new KeyValuePair, int>(new List { "Key1", "Key2" }, 42); var actual = new KeyValuePair, int>(new string[] { "Key1", "Key2" }, 42); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization Assert.Equal(expected, actual); } @@ -1777,13 +1574,9 @@ public void CollectionKeys_Equal() [Fact] public void CollectionKeys_NotEqual() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 var expected = new KeyValuePair, int>(new List { "Key1", "Key2" }, 42); var actual = new KeyValuePair, int>(new string[] { "Key1", "Key3" }, 42); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization var ex = Record.Exception(() => Assert.Equal(expected, actual)); @@ -1799,13 +1592,9 @@ public void CollectionKeys_NotEqual() [Fact] public void CollectionValues_Equal() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new KeyValuePair>("Key1", new List { "Value1a", "Value1b" }); var actual = new KeyValuePair>("Key1", new string[] { "Value1a", "Value1b" }); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization Assert.Equal(expected, actual); } @@ -1813,13 +1602,9 @@ public void CollectionValues_Equal() [Fact] public void CollectionValues_NotEqual() { -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization // Different concrete collection types in the value slot, per https://github.com/xunit/xunit/issues/2850 var expected = new KeyValuePair>("Key1", new List { "Value1a", "Value1b" }); var actual = new KeyValuePair>("Key1", new string[] { "Value1a", "Value2a" }); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization var ex = Record.Exception(() => Assert.Equal(expected, actual)); @@ -1832,9 +1617,11 @@ public void CollectionValues_NotEqual() ); } -#endif // !XUNIT_AOT - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableKeys_Equal() { var expected = new KeyValuePair(new() { Char = 'a' }, 42); @@ -1854,18 +1641,17 @@ public void EquatableKeys_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [EqualityAssertsTests+Equal+KeyValuePair+EquatableObject, 42]" + Environment.NewLine + - "Actual: [EqualityAssertsTests+Equal+KeyValuePair+EquatableObject, 42]", -#else "Expected: [EquatableObject { Char = 'a' }] = 42" + Environment.NewLine + "Actual: [EquatableObject { Char = 'b' }] = 42", -#endif ex.Message ); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableValues_Equal() { var expected = new KeyValuePair("Key1", new() { Char = 'a' }); @@ -1885,13 +1671,8 @@ public void EquatableValues_NotEqual() Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Values differ" + Environment.NewLine + -#if XUNIT_AOT - "Expected: [Key1, EqualityAssertsTests+Equal+KeyValuePair+EquatableObject]" + Environment.NewLine + - "Actual: [Key1, EqualityAssertsTests+Equal+KeyValuePair+EquatableObject]", -#else "Expected: [\"Key1\"] = EquatableObject { Char = 'a' }" + Environment.NewLine + "Actual: [\"Key1\"] = EquatableObject { Char = 'b' }", -#endif ex.Message ); } @@ -1902,12 +1683,6 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } @@ -1916,8 +1691,8 @@ public class DoubleEnumerationPrevention [Fact] public static void EnumeratesOnlyOnce_Equal() { - var expected = new RunOnceEnumerable([1, 2, 3, 4, 5]); - var actual = new RunOnceEnumerable([1, 2, 3, 4, 5]); + var expected = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); + var actual = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); Assert.Equal(expected, actual); } @@ -1925,8 +1700,8 @@ public static void EnumeratesOnlyOnce_Equal() [Fact] public static void EnumeratesOnlyOnce_NotEqual() { - var expected = new RunOnceEnumerable([1, 2, 3, 4, 5]); - var actual = new RunOnceEnumerable([1, 2, 3, 4, 5, 6]); + var expected = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); + var actual = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5, 6 }); var ex = Record.Exception(() => Assert.Equal(expected, actual)); @@ -1955,7 +1730,11 @@ public void Equal() Assert.Equal(expected, actual); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var expected = new DateTime(2023, 2, 11, 15, 4, 0); @@ -1986,7 +1765,11 @@ public void InRange() Assert.Equal(date2, date1, precision); // expected later than actual } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void OutOfRange() { var date1 = new DateTime(2023, 2, 11, 15, 4, 0); @@ -2033,7 +1816,11 @@ public void Equal() Assert.Equal(expected, actual); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var expected = new DateTimeOffset(2023, 2, 11, 15, 4, 0, TimeSpan.Zero); @@ -2062,7 +1849,11 @@ public void Equal() Assert.Equal(expected, actual); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var expected = new DateTimeOffset(2023, 2, 11, 15, 4, 0, TimeSpan.Zero); @@ -2093,7 +1884,11 @@ public void InRange() Assert.Equal(date2, date1, precision); // expected later than actual } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void OutOfRange() { var date1 = new DateTimeOffset(2023, 2, 11, 15, 4, 0, TimeSpan.Zero); @@ -2138,7 +1933,11 @@ public void InRange() Assert.Equal(date2, date1, precision); // expected later than actual } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void OutOfRange() { var date1 = new DateTimeOffset(2023, 2, 11, 15, 4, 0, TimeSpan.Zero); @@ -2179,7 +1978,11 @@ public void Equal() Assert.Equal(0.11111M, 0.11444M, 2); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11111M, 0.11444M, 3)); @@ -2204,7 +2007,11 @@ public void Equal() Assert.Equal(0.11111, 0.11444, 2); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11111, 0.11444, 3)); @@ -2227,7 +2034,11 @@ public void Equal() Assert.Equal(10.565, 10.566, 2, MidpointRounding.AwayFromZero); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11113, 0.11115, 4, MidpointRounding.ToEven)); @@ -2260,7 +2071,11 @@ public void Equal() Assert.Equal(10.566, 10.565, 0.01); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11113, 0.11115, 0.00001)); @@ -2280,7 +2095,11 @@ public void NaN_Equal() Assert.Equal(double.NaN, double.NaN, 1000.0); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NaN_NotEqual() { var ex = Record.Exception(() => Assert.Equal(20210102.2208, double.NaN, 20000000.0)); @@ -2300,7 +2119,11 @@ public void InfiniteTolerance_Equal() Assert.Equal(double.MinValue, double.MaxValue, double.PositiveInfinity); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void PositiveInfinity_NotEqual() { var ex = Record.Exception(() => Assert.Equal(double.PositiveInfinity, 77.7, 1.0)); @@ -2314,7 +2137,11 @@ public void PositiveInfinity_NotEqual() ); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NegativeInfinity_NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.0, double.NegativeInfinity, 1.0)); @@ -2340,7 +2167,11 @@ public void Equal() Assert.Equal(0.11111f, 0.11444f, 2); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11111f, 0.11444f, 3)); @@ -2363,7 +2194,11 @@ public void Equal() Assert.Equal(10.5655f, 10.5666f, 2, MidpointRounding.AwayFromZero); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.111133f, 0.111155f, 4, MidpointRounding.ToEven)); @@ -2396,7 +2231,11 @@ public void Equal() Assert.Equal(10.569f, 10.562f, 0.01f); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.11113f, 0.11115f, 0.00001f)); @@ -2416,7 +2255,11 @@ public void NaN_Equal() Assert.Equal(float.NaN, float.NaN, 1000.0f); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NaN_NotEqual() { var ex = Record.Exception(() => Assert.Equal(20210102.2208f, float.NaN, 20000000.0f)); @@ -2436,7 +2279,11 @@ public void InfiniteTolerance_Equal() Assert.Equal(float.MinValue, float.MaxValue, float.PositiveInfinity); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void PositiveInfinity_NotEqual() { var ex = Record.Exception(() => Assert.Equal(float.PositiveInfinity, 77.7f, 1.0f)); @@ -2450,7 +2297,11 @@ public void PositiveInfinity_NotEqual() ); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NegativeInfinity_NotEqual() { var ex = Record.Exception(() => Assert.Equal(0.0f, float.NegativeInfinity, 1.0f)); @@ -2519,10 +2370,14 @@ public void NotEqual() Assert.NotEqual(42, 42, new Comparer(false)); } - class Comparer(bool result) : - IEqualityComparer + class Comparer : IEqualityComparer { - readonly bool result = result; + readonly bool result; + + public Comparer(bool result) + { + this.result = result; + } public bool Equals(T? x, T? y) => result; @@ -2555,7 +2410,7 @@ public int GetHashCode(int obj) => [Fact] public void Enumerable_WithThrow_RecordsInnerException() { - var ex = Record.Exception(() => Assert.NotEqual([1, 2], [1, 2], new ThrowingEnumerableComparer())); + var ex = Record.Exception(() => Assert.NotEqual(new[] { 1, 2 }, new[] { 1, 2 }, new ThrowingEnumerableComparer())); Assert.IsType(ex); Assert.Equal( @@ -2647,8 +2502,8 @@ public void Enumerable_WithThrow_RecordsInnerException() { var ex = Record.Exception( () => Assert.NotEqual( - [1, 2], - [1, 2], + new[] { 1, 2 }, + new[] { 1, 2 }, (IEnumerable e, IEnumerable a) => throw new DivideByZeroException() ) ); @@ -2692,13 +2547,8 @@ public void Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not SpyComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyComparable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not SpyComparable { CompareCalled = True }" + Environment.NewLine + "Actual: SpyComparable { CompareCalled = False }", -#endif ex.Message ); } @@ -2719,20 +2569,15 @@ public void NonGeneric_SameType_Equal() var expected = new MultiComparable(1); var actual = new MultiComparable(1); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not MultiComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: MultiComparable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not MultiComparable { Value = 1 }" + Environment.NewLine + "Actual: MultiComparable { Value = 1 }", -#endif ex.Message ); } @@ -2759,18 +2604,14 @@ public void NonGeneric_DifferentType_Equal() var expected = new MultiComparable(1); var actual = 1; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not MultiComparable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + -#else "Expected: Not MultiComparable { Value = 1 }" + Environment.NewLine + -#endif "Actual: 1", ex.Message ); @@ -2801,13 +2642,8 @@ public void Generic_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not SpyComparable_Generic {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyComparable_Generic {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not SpyComparable_Generic { CompareCalled = True }" + Environment.NewLine + "Actual: SpyComparable_Generic { CompareCalled = False }", -#endif ex.Message ); } @@ -2833,13 +2669,8 @@ public void SubClass_SubClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not ComparableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableSubClassB {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not ComparableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: ComparableSubClassB { Value = 1 }", -#endif ex.Message ); } @@ -2864,13 +2695,8 @@ public void BaseClass_SubClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not ComparableBaseClass {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableSubClassA {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not ComparableBaseClass { Value = 1 }" + Environment.NewLine + "Actual: ComparableSubClassA { Value = 1 }", -#endif ex.Message ); } @@ -2895,13 +2721,8 @@ public void SubClass_BaseClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not ComparableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableBaseClass {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not ComparableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: ComparableBaseClass { Value = 1 }", -#endif ex.Message ); } @@ -2921,20 +2742,15 @@ public void Generic_ThrowsException_Equal() var expected = new ComparableThrower(1); var actual = new ComparableThrower(1); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not ComparableThrower {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: ComparableThrower {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not ComparableThrower { Value = 1 }" + Environment.NewLine + "Actual: ComparableThrower { Value = 1 }", -#endif ex.Message ); } @@ -2955,9 +2771,11 @@ public void Generic_ThrowsException_NotEqual() Assert.NotEqual(expected, (object)actual); } -#if !XUNIT_AOT // IComparable vs. IComparable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ImplicitImplementation_Equal() { object expected = new ImplicitIComparableExpected(1); @@ -2983,7 +2801,11 @@ public void DifferentTypes_ImplicitImplementation_NotEqual() Assert.NotEqual(expected, actual); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ExplicitImplementation_Equal() { object expected = new ExplicitIComparableActual(1); @@ -3034,8 +2856,6 @@ public void DifferentTypes_Throws_NotEqual() Assert.NotEqual(expected, actual); } - -#endif // !XUNIT_AOT } public class NotComparable @@ -3051,13 +2871,8 @@ public void Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not NonComparableObject {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: NonComparableObject {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not NonComparableObject { }" + Environment.NewLine + "Actual: NonComparableObject { }", -#endif ex.Message ); } @@ -3085,13 +2900,8 @@ public void Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not SpyEquatable {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: SpyEquatable {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not SpyEquatable { Equals__Called = True, Equals_Other = SpyEquatable { Equals__Called = False, Equals_Other = null } }" + Environment.NewLine + "Actual: SpyEquatable { Equals__Called = False, Equals_Other = null }", -#endif ex.Message ); } @@ -3119,13 +2929,8 @@ public void SubClass_SubClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableSubClassB {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not EquatableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: EquatableSubClassB { Value = 1 }", -#endif ex.Message ); } @@ -3150,13 +2955,8 @@ public void BaseClass_SubClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not EquatableBaseClass {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not EquatableBaseClass { Value = 1 }" + Environment.NewLine + "Actual: EquatableSubClassA { Value = 1 }", -#endif ex.Message ); } @@ -3181,13 +2981,8 @@ public void SubClass_BaseClass_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not EquatableSubClassA {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: EquatableBaseClass {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not EquatableSubClassA { Value = 1 }" + Environment.NewLine + "Actual: EquatableBaseClass { Value = 1 }", -#endif ex.Message ); } @@ -3201,9 +2996,11 @@ public void SubClass_BaseClass_NotEqual() Assert.NotEqual(expected, actual); } -#if !XUNIT_AOT // Support for IEquatable vs. IEquatable cannot be done in Native AOT because of the reflection restrictions - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ImplicitImplementation_Equal() { object expected = new ImplicitIEquatableExpected(1); @@ -3229,7 +3026,11 @@ public void DifferentTypes_ImplicitImplementation_NotEqual() Assert.NotEqual(expected, actual); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void DifferentTypes_ExplicitImplementation_Equal() { object expected = new ExplicitIEquatableExpected(1); @@ -3254,32 +3055,29 @@ public void DifferentTypes_ExplicitImplementation_NotEqual() Assert.NotEqual(expected, actual); } - -#endif // !XUNIT_AOT } public class StructuralEquatable { +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void Equal() { - var expected = new StructuralStringWrapper("a"); - var actual = new StructuralStringWrapper("a"); + var expected = new Tuple(new StringWrapper("a")); + var actual = new Tuple(new StringWrapper("a")); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not StructuralStringWrapper {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: StructuralStringWrapper {{ {ArgumentFormatter.Ellipsis} }}", -#else - "Expected: Not StructuralStringWrapper { Value = \"a\" }" + Environment.NewLine + - "Actual: StructuralStringWrapper { Value = \"a\" }", -#endif + "Expected: Not Tuple (StringWrapper { Value = \"a\" })" + Environment.NewLine + + "Actual: Tuple (StringWrapper { Value = \"a\" })", ex.Message ); } @@ -3292,8 +3090,8 @@ static void assertFailure(Action action) [Fact] public void NotEqual() { - var expected = new StructuralStringWrapper("a"); - var actual = new StructuralStringWrapper("b"); + var expected = new Tuple(new StringWrapper("a")); + var actual = new Tuple(new StringWrapper("b")); Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (IStructuralEquatable)actual); @@ -3306,7 +3104,7 @@ public void ExpectedNull_ActualNull() var expected = new Tuple(null); var actual = new Tuple(null); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3355,7 +3153,7 @@ public void IReadOnlyCollection_IEnumerable_Equal() var expected = new string[] { "foo", "bar" }; var actual = new ReadOnlyCollection(expected); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3376,7 +3174,7 @@ static void assertFailure(Action action) public void IReadOnlyCollection_IEnumerable_NotEqual() { var expected = new string[] { "foo", "bar" }; - var actual = new ReadOnlyCollection(["bar", "foo"]); + var actual = new ReadOnlyCollection(new[] { "bar", "foo" }); Assert.NotEqual(expected, (IReadOnlyCollection)actual); Assert.NotEqual(expected, (object)actual); @@ -3414,7 +3212,7 @@ public void StringArray_ObjectArray_Equal() var expected = new string[] { "foo", "bar" }; var actual = new object[] { "foo", "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3452,7 +3250,7 @@ public void MultidimensionalArrays_Equal() Assert.IsType(ex); // TODO: Would be better to have formatting that preserves the ranks instead of // flattening, which happens because multi-dimensional arrays enumerate flatly - Assert.Equal( + Assert.Equal( "Assert.NotEqual() Failure: Collections are equal" + Environment.NewLine + "Expected: Not [1, 2]" + Environment.NewLine + "Actual: [1, 2]", @@ -3469,14 +3267,12 @@ public void MultidimensionalArrays_NotEqual() Assert.NotEqual(expected, actual); } -#if !XUNIT_AOT // Array.CreateInstance is not available in Native AOT - [Fact] public void NonZeroBoundedArrays_Equal() { - var expected = Array.CreateInstance(typeof(int), [1], [1]); + var expected = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); expected.SetValue(42, 1); - var actual = Array.CreateInstance(typeof(int), [1], [1]); + var actual = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); actual.SetValue(42, 1); var ex = Record.Exception(() => Assert.NotEqual(expected, (object)actual)); @@ -3494,16 +3290,14 @@ public void NonZeroBoundedArrays_Equal() [Fact] public void NonZeroBoundedArrays_NotEqual() { - var expected = Array.CreateInstance(typeof(int), [1], [1]); + var expected = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }); expected.SetValue(42, 1); - var actual = Array.CreateInstance(typeof(int), [1], [0]); + var actual = Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 0 }); actual.SetValue(42, 0); Assert.NotEqual(expected, actual); } -#endif // !XUNIT_AOT - [Fact] public void CollectionWithIEquatable_Equal() { @@ -3533,7 +3327,7 @@ public void CollectionWithIEquatable_NotEqual() public sealed class EnumerableEquatable : IEnumerable, IEquatable> { - readonly List values = []; + List values = new(); public void Add(T value) => values.Add(value); @@ -3545,17 +3339,9 @@ public bool Equals(EnumerableEquatable? other) return !values.Except(other.values).Any() && !other.values.Except(values).Any(); } - public override bool Equals(object? obj) => - Equals(obj as EnumerableEquatable); - - public IEnumerator GetEnumerator() => - values.GetEnumerator(); + public IEnumerator GetEnumerator() => values.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); - - public override int GetHashCode() => - values.GetHashCode(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } @@ -3567,20 +3353,15 @@ public void SameTypes_Equal() var expected = new Dictionary { ["foo"] = "bar" }; var actual = new Dictionary { ["foo"] = "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[foo, bar]]" + Environment.NewLine + - "Actual: [[foo, bar]]", -#else "Expected: Not [[\"foo\"] = \"bar\"]" + Environment.NewLine + "Actual: [[\"foo\"] = \"bar\"]", -#endif ex.Message ); } @@ -3607,20 +3388,15 @@ public void DifferentTypes_Equal() var expected = new Dictionary { ["foo"] = "bar" }; var actual = new ConcurrentDictionary(expected); - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Collections are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not Dictionary [[foo, bar]]" + Environment.NewLine + - "Actual: ConcurrentDictionary [[foo, bar]]", -#else "Expected: Not Dictionary [[\"foo\"] = \"bar\"]" + Environment.NewLine + "Actual: ConcurrentDictionary [[\"foo\"] = \"bar\"]", -#endif ex.Message ); } @@ -3650,13 +3426,8 @@ public void NullValue_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Dictionaries are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [[two, ]]" + Environment.NewLine + - "Actual: [[two, ]]", -#else "Expected: Not [[\"two\"] = null]" + Environment.NewLine + "Actual: [[\"two\"] = null]", -#endif ex.Message ); } @@ -3731,7 +3502,7 @@ public void DifferentTypes_Equal() var expected = new HashSet { "bar", "foo" }; var actual = new SortedSet { "foo", "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3744,13 +3515,10 @@ static void assertFailure(Action action) ); } - assertFailure(() => Assert.NotEqual(expected, actual)); assertFailure(() => Assert.NotEqual(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.NotEqual(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3762,17 +3530,19 @@ public void DifferentTypes_NotEqual() Assert.NotEqual(expected, actual); } -#if !XUNIT_AOT // Comparer func overload is disabled in AOT via compiler - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void ComparerFunc_Throws() { var expected = new HashSet { "bar" }; var actual = new HashSet { "baz" }; -#pragma warning disable xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning disable xUnit2026 var ex = Record.Exception(() => Assert.NotEqual(expected, actual, (string l, string r) => false)); -#pragma warning restore xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning restore xUnit2026 Assert.IsType(ex); Assert.Equal( @@ -3780,8 +3550,6 @@ public void ComparerFunc_Throws() ex.Message ); } - -#endif // !XUNIT_AOT } public class Sets @@ -3792,7 +3560,7 @@ public void InOrder_Equal() var expected = new NonGenericSet { "bar", "foo" }; var actual = new NonGenericSet { "bar", "foo" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3807,11 +3575,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.NotEqual(expected, actual)); assertFailure(() => Assert.NotEqual(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.NotEqual(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3822,11 +3588,9 @@ public void InOrder_NotEqual() Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.NotEqual(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3835,7 +3599,7 @@ public void OutOfOrder_Equal() var expected = new NonGenericSet { "bar", "foo" }; var actual = new NonGenericSet { "foo", "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3850,11 +3614,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.NotEqual(expected, actual)); assertFailure(() => Assert.NotEqual(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.NotEqual(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3865,11 +3627,9 @@ public void OutOfOrder_NotEqual() Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.NotEqual(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3880,11 +3640,9 @@ public void DifferentContents() Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.NotEqual(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3893,7 +3651,7 @@ public void DifferentTypes_Equal() var expected = new NonGenericSet { "bar" }; var actual = new HashSet { "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3908,11 +3666,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.NotEqual(expected, actual)); assertFailure(() => Assert.NotEqual(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.NotEqual(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3923,11 +3679,9 @@ public void DifferentTypes_NotEqual() Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.NotEqual(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3936,7 +3690,7 @@ public void TwoGenericSubClass_Equal() var expected = new TwoGenericSet { "foo", "bar" }; var actual = new TwoGenericSet { "foo", "bar" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -3951,11 +3705,9 @@ static void assertFailure(Action action) assertFailure(() => Assert.NotEqual(expected, actual)); assertFailure(() => Assert.NotEqual(expected, (ISet)actual)); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 assertFailure(() => Assert.NotEqual(expected, (object)actual)); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } [Fact] @@ -3966,24 +3718,24 @@ public void TwoGenericSubClass_NotEqual() Assert.NotEqual(expected, actual); Assert.NotEqual(expected, (ISet)actual); -#if !XUNIT_AOT // AOT can't detect sets dynamically so it just treats it like a linear collection -#pragma warning disable xUnit2027 // Comparison of sets to linear containers have undefined results +#pragma warning disable xUnit2027 Assert.NotEqual(expected, (object)actual); -#pragma warning restore xUnit2027 // Comparison of sets to linear containers have undefined results -#endif +#pragma warning restore xUnit2027 } -#if !XUNIT_AOT // Comparer func overload is disabled in AOT via compiler - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void ComparerFunc_Throws() { var expected = new NonGenericSet { "bar" }; var actual = new HashSet { "baz" }; -#pragma warning disable xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning disable xUnit2026 var ex = Record.Exception(() => Assert.NotEqual(expected, actual, (string l, string r) => false)); -#pragma warning restore xUnit2026 // Comparison of sets must be done with IEqualityComparer +#pragma warning restore xUnit2026 Assert.IsType(ex); Assert.Equal( @@ -3991,38 +3743,6 @@ public void ComparerFunc_Throws() ex.Message ); } - -#endif // !XUNIT_AOT - } - - // https://github.com/xunit/xunit/issues/3137 - public class ImmutableArrays - { - [Fact] - public void Equal() - { - var expected = new[] { 1, 2, 3 }.ToImmutableArray(); - var actual = new[] { 1, 2, 3 }.ToImmutableArray(); - - var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.NotEqual() Failure: Collections are equal" + Environment.NewLine + - "Expected: Not [1, 2, 3]" + Environment.NewLine + - "Actual: [1, 2, 3]", - ex.Message - ); - } - - [Fact] - public void NotEqual() - { - var expected = new[] { 1, 2, 3 }.ToImmutableArray(); - var actual = new[] { 1, 2, 4 }.ToImmutableArray(); - - Assert.NotEqual(expected, actual); - } } public class Strings @@ -4069,31 +3789,20 @@ public void Truncation() public class KeyValuePair { -#if !XUNIT_AOT // AOT can't deep dive into collections in the same was a non-AOT - [Fact] public void CollectionKeys_Equal() { // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization var expected = new KeyValuePair, int>(new List { "Key1", "Key2" }, 42); var actual = new KeyValuePair, int>(new string[] { "Key1", "Key2" }, 42); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [System.Collections.Generic.List`1[System.String], 42]" + Environment.NewLine + - "Actual: [System.String[], 42]", -#else "Expected: Not [[\"Key1\", \"Key2\"]] = 42" + Environment.NewLine + "Actual: [[\"Key1\", \"Key2\"]] = 42", -#endif ex.Message ); } @@ -4102,12 +3811,8 @@ public void CollectionKeys_Equal() public void CollectionKeys_NotEqual() { // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization var expected = new KeyValuePair, int>(new List { "Key1", "Key2" }, 42); var actual = new KeyValuePair, int>(new string[] { "Key1", "Key3" }, 42); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization Assert.NotEqual(expected, actual); } @@ -4116,25 +3821,16 @@ public void CollectionKeys_NotEqual() public void CollectionValues_Equal() { // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization var expected = new KeyValuePair>("Key1", new List { "Value1a", "Value1b" }); var actual = new KeyValuePair>("Key1", new string[] { "Value1a", "Value1b" }); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [Key1, System.Collections.Generic.List`1[System.String]]" + Environment.NewLine + - "Actual: [Key1, System.String[]]", -#else "Expected: Not [\"Key1\"] = [\"Value1a\", \"Value1b\"]" + Environment.NewLine + "Actual: [\"Key1\"] = [\"Value1a\", \"Value1b\"]", -#endif ex.Message ); } @@ -4143,19 +3839,17 @@ public void CollectionValues_Equal() public void CollectionValues_NotEqual() { // Different concrete collection types in the key slot, per https://github.com/xunit/xunit/issues/2850 -#pragma warning disable IDE0028 // Simplify collection initialization -#pragma warning disable IDE0300 // Simplify collection initialization var expected = new KeyValuePair>("Key1", new List { "Value1a", "Value1b" }); var actual = new KeyValuePair>("Key1", new string[] { "Value1a", "Value2a" }); -#pragma warning restore IDE0300 // Simplify collection initialization -#pragma warning restore IDE0028 // Simplify collection initialization Assert.NotEqual(expected, actual); } -#endif // !XUNIT_AOT - +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableKeys_Equal() { var expected = new KeyValuePair(new() { Char = 'a' }, 42); @@ -4166,13 +3860,8 @@ public void EquatableKeys_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [EqualityAssertsTests+NotEqual+KeyValuePair+EquatableObject, 42]" + Environment.NewLine + - "Actual: [EqualityAssertsTests+NotEqual+KeyValuePair+EquatableObject, 42]", -#else "Expected: Not [EquatableObject { Char = 'a' }] = 42" + Environment.NewLine + "Actual: [EquatableObject { Char = 'a' }] = 42", -#endif ex.Message ); } @@ -4186,7 +3875,11 @@ public void EquatableKeys_NotEqual() Assert.NotEqual(expected, actual); } +#if XUNIT_AOT + [Fact(Skip = "Not supported with AOT")] +#else [Fact] +#endif public void EquatableValues_Equal() { var expected = new KeyValuePair("Key1", new() { Char = 'a' }); @@ -4197,13 +3890,8 @@ public void EquatableValues_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - "Expected: Not [Key1, EqualityAssertsTests+NotEqual+KeyValuePair+EquatableObject]" + Environment.NewLine + - "Actual: [Key1, EqualityAssertsTests+NotEqual+KeyValuePair+EquatableObject]", -#else "Expected: Not [\"Key1\"] = EquatableObject { Char = 'a' }" + Environment.NewLine + "Actual: [\"Key1\"] = EquatableObject { Char = 'a' }", -#endif ex.Message ); } @@ -4223,12 +3911,6 @@ public class EquatableObject : IEquatable public bool Equals(EquatableObject? other) => other != null && other.Char == Char; - - public override bool Equals(object? obj) => - Equals(obj as EquatableObject); - - public override int GetHashCode() => - Char.GetHashCode(); } } @@ -4237,8 +3919,8 @@ public class DoubleEnumerationPrevention [Fact] public static void EnumeratesOnlyOnce_Equal() { - var expected = new RunOnceEnumerable([1, 2, 3, 4, 5]); - var actual = new RunOnceEnumerable([1, 2, 3, 4, 5]); + var expected = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); + var actual = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); var ex = Record.Exception(() => Assert.NotEqual(expected, actual)); @@ -4254,8 +3936,8 @@ public static void EnumeratesOnlyOnce_Equal() [Fact] public static void EnumeratesOnlyOnce_NotEqual() { - var expected = new RunOnceEnumerable([1, 2, 3, 4, 5]); - var actual = new RunOnceEnumerable([1, 2, 3, 4, 5, 6]); + var expected = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5 }); + var actual = new RunOnceEnumerable(new[] { 1, 2, 3, 4, 5, 6 }); Assert.NotEqual(expected, actual); } @@ -4264,7 +3946,11 @@ public static void EnumeratesOnlyOnce_NotEqual() public class NotEqual_Decimal { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(0.11111M, 0.11444M, 2)); @@ -4289,7 +3975,11 @@ public class NotEqual_Double { public class WithPrecision { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(0.11111, 0.11444, 2)); @@ -4312,7 +4002,11 @@ public void NotEqual() public class WithMidPointRounding { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(10.565, 10.566, 2, MidpointRounding.AwayFromZero)); @@ -4345,7 +4039,11 @@ public void GuardClause() Assert.Equal("tolerance", argEx.ParamName); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(10.566, 10.565, 0.01)); @@ -4365,7 +4063,11 @@ public void NotEqual() Assert.NotEqual(0.11113, 0.11115, 0.00001); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NaN_Equal() { var ex = Record.Exception(() => Assert.NotEqual(double.NaN, double.NaN, 1000.0)); @@ -4385,7 +4087,11 @@ public void NaN_NotEqual() Assert.NotEqual(20210102.2208, double.NaN, 20000000.0); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void InfiniteTolerance_Equal() { var ex = Record.Exception(() => Assert.NotEqual(double.MinValue, double.MaxValue, double.PositiveInfinity)); @@ -4417,7 +4123,11 @@ public class NotEqual_Float { public class WithPrecision { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(0.11111f, 0.11444f, 2)); @@ -4440,7 +4150,11 @@ public void NotEqual() public class WithMidPointRounding { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(10.5655f, 10.5666f, 2, MidpointRounding.AwayFromZero)); @@ -4473,7 +4187,11 @@ public void GuardClause() Assert.Equal("tolerance", argEx.ParamName); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void Equal() { var ex = Record.Exception(() => Assert.NotEqual(10.569f, 10.562f, 0.01f)); @@ -4493,7 +4211,11 @@ public void NotEqual() Assert.NotEqual(0.11113f, 0.11115f, 0.00001f); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void NaN_Equal() { var ex = Record.Exception(() => Assert.NotEqual(float.NaN, float.NaN, 1000.0f)); @@ -4513,7 +4235,11 @@ public void NaN_NotEqual() Assert.NotEqual(20210102.2208f, float.NaN, 20000000.0f); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void InfiniteTolerance_Equal() { var ex = Record.Exception(() => Assert.NotEqual(float.MinValue, float.MaxValue, float.PositiveInfinity)); @@ -4577,13 +4303,8 @@ public static void DifferentTypes_Equal() Assert.IsType(ex); Assert.Equal( "Assert.NotStrictEqual() Failure: Values are equal" + Environment.NewLine + -#if XUNIT_AOT - $"Expected: Not DerivedClass {{ {ArgumentFormatter.Ellipsis} }}" + Environment.NewLine + - $"Actual: BaseClass {{ {ArgumentFormatter.Ellipsis} }}", -#else "Expected: Not DerivedClass { }" + Environment.NewLine + "Actual: BaseClass { }", -#endif ex.Message ); } @@ -4618,9 +4339,7 @@ public static void NotEqual_Strings() [Fact] public static void NotEqual_Classes() { -#pragma warning disable xUnit2006 // TODO: Fix this analyzer in the face of non-generic StrictEqual var ex = Record.Exception(() => Assert.StrictEqual(new EnumerableClass("ploeh"), new EnumerableClass("fnaah"))); -#pragma warning restore xUnit2006 Assert.IsType(ex); Assert.Equal( @@ -4634,9 +4353,7 @@ public static void NotEqual_Classes() [Fact] public static void DifferentTypes_Equal() { -#pragma warning disable xUnit2006 // TODO: Fix this analyzer in the face of non-generic StrictEqual Assert.StrictEqual(new DerivedClass(), new BaseClass()); -#pragma warning restore xUnit2006 } } @@ -4650,21 +4367,28 @@ public override bool Equals(object? obj) => public override int GetHashCode() => 0; } - class EnumerableClass(string _, params BaseClass[] bars) : - IEnumerable + class EnumerableClass : IEnumerable { - readonly string _ = _; - readonly IEnumerable bars = bars; + private readonly IEnumerable bars; + + public EnumerableClass(string _, params BaseClass[] bars) + { + this.bars = bars; + } public IEnumerator GetEnumerator() => bars.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - class MultiComparable(int value) : - IComparable + class MultiComparable : IComparable { - public int Value { get; } = value; + public int Value { get; } + + public MultiComparable(int value) + { + Value = value; + } public int CompareTo(object? obj) { @@ -4677,151 +4401,175 @@ public int CompareTo(object? obj) } } - class ComparableBaseClass(int value) : - IComparable + class ComparableBaseClass : IComparable { - public int Value { get; } = value; + public int Value { get; } + + public ComparableBaseClass(int value) + { + Value = value; + } public int CompareTo(ComparableBaseClass? other) => Value.CompareTo(other!.Value); } - class ComparableSubClassA(int value) : - ComparableBaseClass(value) - { } + class ComparableSubClassA : ComparableBaseClass + { + public ComparableSubClassA(int value) : base(value) + { } + } - class ComparableSubClassB(int value) : - ComparableBaseClass(value) - { } + class ComparableSubClassB : ComparableBaseClass + { + public ComparableSubClassB(int value) : base(value) + { } + } - class ComparableThrower(int value) : - IComparable + class ComparableThrower : IComparable { - public int Value { get; } = value; + public int Value { get; } - public int CompareTo(ComparableThrower? other) => + public ComparableThrower(int value) + { + Value = value; + } + + public int CompareTo(ComparableThrower? other) + { throw new InvalidOperationException(); + } - public override bool Equals(object? obj) => - Value == ((ComparableThrower?)obj)!.Value; + public override bool Equals(object? obj) => Value == ((ComparableThrower?)obj)!.Value; - public override int GetHashCode() => - Value; + public override int GetHashCode() => Value; } - class EquatableBaseClass(int value) : - IEquatable + class EquatableBaseClass : IEquatable { - public int Value { get; } = value; - - public bool Equals(EquatableBaseClass? other) => - Value == other!.Value; + public int Value { get; } - public override bool Equals(object? obj) => - Equals(obj as EquatableBaseClass); + public EquatableBaseClass(int value) + { + Value = value; + } - public override int GetHashCode() => - Value.GetHashCode(); + public bool Equals(EquatableBaseClass? other) => Value == other!.Value; } - class EquatableSubClassA(int value) : - EquatableBaseClass(value) - { } - - class EquatableSubClassB(int value) : - EquatableBaseClass(value) - { } - -#pragma warning disable CA1067 // Override Object.Equals(object) when implementing IEquatable - - class StringWrapper(string value) : - IEquatable + class EquatableSubClassA : EquatableBaseClass { - public string Value { get; } = value; - - bool IEquatable.Equals(StringWrapper? other) => - Value == other!.Value; + public EquatableSubClassA(int value) : base(value) { } } -#pragma warning restore CA1067 // Override Object.Equals(object) when implementing IEquatable + class EquatableSubClassB : EquatableBaseClass + { + public EquatableSubClassB(int value) : base(value) { } + } - class StructuralStringWrapper(string value) : - IStructuralEquatable + class StringWrapper : IEquatable { - public string Value { get; } = value; + public string Value { get; } - public bool Equals( - object? other, - IEqualityComparer comparer) + public StringWrapper(string value) { - if (other is not StructuralStringWrapper otherWrapper) - return false; - - return comparer.Equals(Value, otherWrapper.Value); + Value = value; } - public int GetHashCode(IEqualityComparer comparer) => - Value.GetHashCode(); + bool IEquatable.Equals(StringWrapper? other) => Value == other!.Value; } class NonGenericSet : HashSet { } class TwoGenericSet : HashSet { } - class ImplicitIEquatableExpected(int value) : - IEquatable + class ImplicitIEquatableExpected : IEquatable { - public int Value { get; } = value; + public int Value { get; } + + public ImplicitIEquatableExpected(int value) + { + Value = value; + } public bool Equals(IntWrapper? other) => Value == other!.Value; } - class ExplicitIEquatableExpected(int value) : - IEquatable + class ExplicitIEquatableExpected : IEquatable { - public int Value { get; } = value; + public int Value { get; } + + public ExplicitIEquatableExpected(int value) + { + Value = value; + } bool IEquatable.Equals(IntWrapper? other) => Value == other!.Value; } - class ImplicitIComparableExpected(int value) : - IComparable + class ImplicitIComparableExpected : IComparable { - public int Value { get; } = value; + public int Value { get; } + + public ImplicitIComparableExpected(int value) + { + Value = value; + } public int CompareTo(IntWrapper? other) => Value.CompareTo(other!.Value); } - class ExplicitIComparableActual(int value) : - IComparable + class ExplicitIComparableActual : IComparable { - public int Value { get; } = value; + public int Value { get; } + + public ExplicitIComparableActual(int value) + { + Value = value; + } int IComparable.CompareTo(IntWrapper? other) => Value.CompareTo(other!.Value); } - class IComparableActualThrower(int value) : - IComparable + class IComparableActualThrower : IComparable { - public int Value { get; } = value; + public int Value { get; } + + public IComparableActualThrower(int value) + { + Value = value; + } - public int CompareTo(IntWrapper? other) => + public int CompareTo(IntWrapper? other) + { throw new NotSupportedException(); + } public override bool Equals(object? obj) => Value == ((IntWrapper?)obj)!.Value; public override int GetHashCode() => Value; } - class IntWrapper(int value) + class IntWrapper { - public int Value { get; } = value; + public int Value { get; } + + public IntWrapper(int value) + { + Value = value; + } } - class SpyComparable(int result) : - IComparable + class SpyComparable : IComparable { + readonly int result; + public bool CompareCalled; + public SpyComparable(int result) + { + this.result = result; + } + public int CompareTo(object? obj) { CompareCalled = true; @@ -4829,11 +4577,17 @@ public int CompareTo(object? obj) } } - class SpyComparable_Generic(int result = 0) : - IComparable + class SpyComparable_Generic : IComparable { + int result; + public bool CompareCalled; + public SpyComparable_Generic(int result = 0) + { + this.result = result; + } + public int CompareTo(SpyComparable_Generic? other) { CompareCalled = true; @@ -4841,12 +4595,17 @@ public int CompareTo(SpyComparable_Generic? other) } } - class SpyEquatable(bool result = true) : - IEquatable + class SpyEquatable : IEquatable { + bool result; public bool Equals__Called; public SpyEquatable? Equals_Other; + public SpyEquatable(bool result = true) + { + this.result = result; + } + public bool Equals(SpyEquatable? other) { Equals__Called = true; @@ -4854,29 +4613,32 @@ public bool Equals(SpyEquatable? other) return result; } - - public override bool Equals(object? obj) => - Equals(obj as SpyEquatable); - - public override int GetHashCode() => - 42; } - class NonComparableObject(bool result = true) + class NonComparableObject { - public override bool Equals(object? obj) => - result; + bool result; + + public NonComparableObject(bool result = true) + { + this.result = result; + } - public override int GetHashCode() => - 42; + public override bool Equals(object? obj) => result; + + public override int GetHashCode() => 42; } - sealed class RunOnceEnumerable(IEnumerable source) : - IEnumerable + sealed class RunOnceEnumerable : IEnumerable { private bool _called; - public IEnumerable Source { get; } = source; + public RunOnceEnumerable(IEnumerable source) + { + Source = source; + } + + public IEnumerable Source { get; } public IEnumerator GetEnumerator() { diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EquivalenceAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/EquivalenceAssertsTests.cs similarity index 78% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EquivalenceAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/EquivalenceAssertsTests.cs index 7ae448ab15c..19ebb47d505 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EquivalenceAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/EquivalenceAssertsTests.cs @@ -1,14 +1,14 @@ -#if !XUNIT_AOT // Assert.Equivalent is not available in Native AOT - using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; using Xunit; -using Xunit.Internal; using Xunit.Sdk; +#if XUNIT_IMMUTABLE_COLLECTIONS +using System.Collections.Immutable; +#endif + public class EquivalenceAssertsTests { public class NullValues @@ -75,7 +75,7 @@ public void SameValueFromDifferentIntrinsicTypes_Success() Assert.Equivalent(12, 12L); } - // https://github.com/xunit/xunit/issues/2913 + // https://github.com/xunit/xunit/issues/2913 [Fact] public void Decimals_Success() { @@ -111,46 +111,6 @@ public void IntrinsicPlusNonIntrinsic_Failure() ex.Message ); } - - public class Guids - { - // https://github.com/xunit/xunit/issues/2974 - [Fact] - public void SameType_Success() - { - Assert.Equivalent(new Guid("b727762b-a1c1-49a0-b045-59ba97b17b61"), new Guid("b727762b-a1c1-49a0-b045-59ba97b17b61")); - } - - // https://github.com/xunit/xunit/issues/2974 - [Fact] - public void SameType_Failure() - { - var ex = Record.Exception(() => Assert.Equivalent(new Guid("b727762b-a1c1-49a0-b045-59ba97b17b61"), new Guid("963ff9f5-cb83-480e-85ea-7e8950a01f00"))); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure" + Environment.NewLine + - "Expected: b727762b-a1c1-49a0-b045-59ba97b17b61" + Environment.NewLine + - "Actual: 963ff9f5-cb83-480e-85ea-7e8950a01f00", - ex.Message - ); - } - - // https://github.com/xunit/xunit/issues/2974 - [Fact] - public void IntrinsicPlusNonIntrinsic_Failure() - { - var ex = Record.Exception(() => Assert.Equivalent(new Guid("b727762b-a1c1-49a0-b045-59ba97b17b61"), new object())); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure" + Environment.NewLine + - "Expected: b727762b-a1c1-49a0-b045-59ba97b17b61" + Environment.NewLine + - "Actual: Object { }", - ex.Message - ); - } - } } public class NullableValueTypes @@ -646,7 +606,7 @@ public void Failure_EmbeddedArray() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [9, 4, 1]", ex.Message @@ -709,7 +669,7 @@ public void Failure_EmbeddedArray_ValueNotFoundInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [9, 4, 1]", ex.Message @@ -726,7 +686,7 @@ public void Failure_EmbeddedArray_ExtraValueInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine + "Expected: [1, 9, 4]" + Environment.NewLine + "Actual: [6, 12] left over from [6, 9, 4, 1, 12]", ex.Message @@ -734,6 +694,8 @@ public void Failure_EmbeddedArray_ExtraValueInActual() } } +#if XUNIT_IMMUTABLE_COLLECTIONS + public class ImmutableArrayOfValueTypes_NotStrict { [Fact] @@ -775,7 +737,7 @@ public void Failure_EmbeddedArray() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [9, 4, 1]", ex.Message @@ -838,7 +800,7 @@ public void Failure_EmbeddedArray_ValueNotFoundInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [9, 4, 1]", ex.Message @@ -855,7 +817,7 @@ public void Failure_EmbeddedArray_ExtraValueInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine + "Expected: [1, 9, 4]" + Environment.NewLine + "Actual: [6, 12] left over from [6, 9, 4, 1, 12]", ex.Message @@ -863,6 +825,8 @@ public void Failure_EmbeddedArray_ExtraValueInActual() } } +#endif + public class ArrayOfObjects_NotStrict { [Fact] @@ -910,7 +874,7 @@ public void Failure_EmbeddedArray() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: { Foo = \"Biff\" }" + Environment.NewLine + "In: [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -982,7 +946,7 @@ public void Failure_EmbeddedArray_ValueNotFoundInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: { Foo = \"Biff\" }" + Environment.NewLine + "In: [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -999,7 +963,7 @@ public void Failure_EmbeddedArray_ExtraValueInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine + "Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine + "Actual: [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -1054,7 +1018,7 @@ public void Failure_EmbeddedArray() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: { Foo = \"Biff\" }" + Environment.NewLine + "In: [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -1126,7 +1090,7 @@ public void Failure_EmbeddedArray_ValueNotFoundInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: { Foo = \"Biff\" }" + Environment.NewLine + "In: [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -1143,7 +1107,7 @@ public void Failure_EmbeddedArray_ExtraValueInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine + "Expected: [{ Foo = \"Bar\" }]" + Environment.NewLine + "Actual: [{ Foo = \"Baz\" }] left over from [{ Foo = \"Baz\" }, { Foo = \"Bar\" }]", ex.Message @@ -1165,6 +1129,7 @@ public void ListIsEquivalentToArray() Assert.Equivalent(new List { 1, 2, 3 }, new[] { 1, 2, 3 }); } +#if XUNIT_IMMUTABLE_COLLECTIONS [Fact] public void ArrayIsEquivalentToImmutableArray() { @@ -1182,6 +1147,7 @@ public void ImmutableListIsEquivalentToImmutableSortedSet() { Assert.Equivalent(new[] { 1, 2, 3 }.ToImmutableList(), new[] { 1, 2, 3 }.ToImmutableSortedSet()); } +#endif } public class Dictionaries_NotStrict @@ -1198,8 +1164,8 @@ public void Success() [Fact] public void SuccessWithArrayValues() { - var expected = new Dictionary { ["Foo"] = [42] }; - var actual = new Dictionary { ["Foo"] = [42], ["Bar"] = [2112] }; + var expected = new Dictionary { ["Foo"] = new[] { 42 } }; + var actual = new Dictionary { ["Foo"] = new[] { 42 }, ["Bar"] = new[] { 2112 } }; Assert.Equivalent(expected, actual, strict: false); } @@ -1207,8 +1173,8 @@ public void SuccessWithArrayValues() [Fact] public void SuccessWithListValues() { - var expected = new Dictionary> { ["Foo"] = [42] }; - var actual = new Dictionary> { ["Foo"] = [42], ["Bar"] = [2112] }; + var expected = new Dictionary> { ["Foo"] = new List { 42 } }; + var actual = new Dictionary> { ["Foo"] = new List { 42 }, ["Bar"] = new List { 2112 } }; Assert.Equivalent(expected, actual, strict: false); } @@ -1242,8 +1208,8 @@ public void Failure() [Fact] public void FailureWithArrayValues() { - var expected = new Dictionary { ["Foo"] = [16] }; - var actual = new Dictionary { ["Foo"] = [42], ["Bar"] = [2112] }; + var expected = new Dictionary { ["Foo"] = new[] { 16 } }; + var actual = new Dictionary { ["Foo"] = new[] { 42 }, ["Bar"] = new[] { 2112 } }; var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false)); @@ -1259,8 +1225,8 @@ public void FailureWithArrayValues() [Fact] public void FailureWithListValues() { - var expected = new Dictionary> { ["Foo"] = [16] }; - var actual = new Dictionary> { ["Foo"] = [42], ["Bar"] = [2112] }; + var expected = new Dictionary> { ["Foo"] = new List { 16 } }; + var actual = new Dictionary> { ["Foo"] = new List { 42 }, ["Bar"] = new List { 2112 } }; var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false)); @@ -1283,7 +1249,7 @@ public void Failure_EmbeddedDictionary() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: [\"Foo\"] = 16" + Environment.NewLine + "In: [[\"Foo\"] = 42, [\"Bar\"] = 2112]", ex.Message @@ -1355,7 +1321,7 @@ public void Failure_EmbeddedDictionary_ValueNotFoundInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'x'" + Environment.NewLine + "Expected: [\"Foo\"] = 16" + Environment.NewLine + "In: [[\"Foo\"] = 42, [\"Bar\"] = 2112]", ex.Message @@ -1372,7 +1338,7 @@ public void Failure_EmbeddedDictionary_ExtraValueInActual() Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'x[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Extra values found in member 'x'" + Environment.NewLine + "Expected: [[\"Bar\"] = 2112, [\"Foo\"] = 42]" + Environment.NewLine + "Actual: [[\"Biff\"] = 2600] left over from [[\"Foo\"] = 42, [\"Biff\"] = 2600, [\"Bar\"] = 2112]", ex.Message @@ -1385,8 +1351,8 @@ public class KeyValuePairs_NotStrict [Fact] public void Success() { - var expected = new KeyValuePair(42, [1, 4]); - var actual = new KeyValuePair(42, [9, 4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 4 }); + var actual = new KeyValuePair(42, new[] { 9, 4, 1 }); Assert.Equivalent(expected, actual, strict: false); } @@ -1394,8 +1360,8 @@ public void Success() [Fact] public void Failure_Key() { - var expected = new KeyValuePair(42, [1, 4]); - var actual = new KeyValuePair(41, [9, 4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 4 }); + var actual = new KeyValuePair(41, new[] { 9, 4, 1 }); var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false)); @@ -1411,14 +1377,14 @@ public void Failure_Key() [Fact] public void Failure_Value() { - var expected = new KeyValuePair(42, [1, 6]); - var actual = new KeyValuePair(42, [9, 4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 6 }); + var actual = new KeyValuePair(42, new[] { 9, 4, 1 }); var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: false)); Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'Value[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'Value'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [9, 4, 1]", ex.Message @@ -1431,8 +1397,8 @@ public class KeyValuePairs_Strict [Fact] public void Success() { - var expected = new KeyValuePair(42, [1, 4]); - var actual = new KeyValuePair(42, [4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 4 }); + var actual = new KeyValuePair(42, new[] { 4, 1 }); Assert.Equivalent(expected, actual, strict: true); } @@ -1440,8 +1406,8 @@ public void Success() [Fact] public void Failure_Key() { - var expected = new KeyValuePair(42, [1, 4]); - var actual = new KeyValuePair(41, [4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 4 }); + var actual = new KeyValuePair(41, new[] { 4, 1 }); var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true)); @@ -1457,14 +1423,14 @@ public void Failure_Key() [Fact] public void Failure_Value() { - var expected = new KeyValuePair(42, [1, 6]); - var actual = new KeyValuePair(42, [4, 1]); + var expected = new KeyValuePair(42, new[] { 1, 6 }); + var actual = new KeyValuePair(42, new[] { 4, 1 }); var ex = Record.Exception(() => Assert.Equivalent(expected, actual, strict: true)); Assert.IsType(ex); Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found in member 'Value[]'" + Environment.NewLine + + "Assert.Equivalent() Failure: Collection value not found in member 'Value'" + Environment.NewLine + "Expected: 6" + Environment.NewLine + "In: [4, 1]", ex.Message @@ -1472,53 +1438,6 @@ public void Failure_Value() } } - // https://github.com/xunit/xunit/issues/3028 - public class Groupings - { - [Fact] - public static void Success() - { - var expected = Enumerable.Range(1, 4).ToLookup(i => (i % 2) == 0); - var actual = Enumerable.Range(1, 4).GroupBy(i => (i % 2) == 0); - - Assert.Equivalent(expected, actual); - } - - [Fact] - public static void Failure_KeysDontMatch() - { - var expected = Enumerable.Range(1, 4).ToLookup(i => true); - var actual = Enumerable.Range(1, 4).GroupBy(i => false); - - var ex = Record.Exception(() => Assert.Equivalent(expected, actual)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure: Collection value not found" + Environment.NewLine + - "Expected: [True] = [1, 2, 3, 4]" + Environment.NewLine + - "In: [[False] = [1, 2, 3, 4]]", - ex.Message - ); - } - - [Fact] - public static void Failure_KeysMatch_ValuesDoNot() - { - var expected = Enumerable.Range(1, 4).ToLookup(i => (i % 2) == 0); - var actual = Enumerable.Range(1, 4).GroupBy(i => (i % 2) == 1); - - var ex = Record.Exception(() => Assert.Equivalent(expected, actual)); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure: Grouping key [False] has mismatched values" + Environment.NewLine + - "Expected: [1, 3]" + Environment.NewLine + - "Actual: [2, 4]", - ex.Message - ); - } - } - public class SpecialCases { // DateTime @@ -1694,119 +1613,18 @@ public void FileInfoToDirectoryInfo_Failure_Embedded() ); } - // Uri - - public static TheoryData UriData = - [ - new Uri("https://xunit.net/"), - new Uri("a/b#c", UriKind.RelativeOrAbsolute), - ]; - - [Theory] - [MemberData(nameof(UriData))] - public void Uri_Success(Uri uri) - { - Assert.Equivalent(uri, new Uri(uri.OriginalString, UriKind.RelativeOrAbsolute)); - } - - [Theory] - [MemberData(nameof(UriData))] - public void Uri_Failure(Uri uri) - { - var ex = Record.Exception(() => Assert.Equivalent(uri, new Uri("hello/world", UriKind.RelativeOrAbsolute))); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure" + Environment.NewLine + - "Expected: " + uri.OriginalString + Environment.NewLine + - "Actual: hello/world", - ex.Message - ); - } - // Ensuring we use reference equality for the circular reference hash sets - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Issue2939(bool strict) - { - var expected = new Uri("http://example.com"); - var actual = new Uri("http://example.com"); - - Assert.Equivalent(expected, actual, strict); - } - - // Lazy - - [Fact] - public void LazyValueEquivalentToValue() - { - var expected = "Hello"; - var actual = new Lazy(() => "Hello"); - - Assert.Equivalent(expected, actual); - } + // [Theory] + // [InlineData(true)] + // [InlineData(false)] + // public void Issue2939(bool strict) + // { + // var expected = new Uri("http://example.com"); + // var actual = new Uri("http://example.com"); - [Fact] - public void ValueEquivalentToLazyValue() - { - var expected = new Lazy(() => "Hello"); - var actual = "Hello"; - - Assert.Equivalent(expected, actual); - } - - [Fact] - public void UnretrievedLazyValueEquivalentToRetrievedLazyValue() - { - var expected = new Lazy(() => "Hello"); - var actual = new Lazy(() => "Hello"); - _ = expected.Value; - - Assert.Equivalent(expected, actual); - } - } - - public class Obsolete - { - [Fact] - public void SkipsObsoleteProperties() - { - var value1 = new ClassWithObsoleteProperty { Value = 42 }; - var value2 = new ClassWithObsoleteProperty { Value = 42 }; - - Assert.Equivalent(value1, value2); - } - - class ClassWithObsoleteProperty - { - public int Value { get; set; } - - [Obsolete("This property is obsolete")] - public int ObsoleteProperty => throw new NotImplementedException(); - } - - [Fact] - public void SkipsObsoletePropertyGetters() - { - var value1 = new ClassWithObsoletePropertyGetter { Value = 42, ObsoleteProperty = 2112 }; - var value2 = new ClassWithObsoletePropertyGetter { Value = 42, ObsoleteProperty = 2600 }; - - Assert.Equivalent(value1, value2); - } - - class ClassWithObsoletePropertyGetter - { - public int Value { get; set; } - - public int ObsoleteProperty - { - [Obsolete("This property is obsolete")] - get { throw new NotImplementedException(); } - set { } - } - } + // Assert.Equivalent(expected, actual, strict); + // } } public class CircularReferences @@ -1848,16 +1666,14 @@ public void PreventArbitrarilyLargeDepthObjectTree() Assert.IsType(ex); Assert.Equal( - $"Assert.Equivalent() Failure: Exceeded the maximum depth {EnvironmentVariables.Defaults.AssertEquivalentMaxDepth} with '{string.Join(".", Enumerable.Repeat("Parent", EnvironmentVariables.Defaults.AssertEquivalentMaxDepth))}'; check for infinite recursion or circular references", + "Assert.Equivalent() Failure: Exceeded the maximum depth 50 with 'Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent'; check for infinite recursion or circular references", ex.Message ); } class InfiniteRecursionClass { -#pragma warning disable CA1822 // Mark members as static public InfiniteRecursionClass Parent => new(); -#pragma warning restore CA1822 // Mark members as static } } @@ -1964,172 +1780,7 @@ class Person } } - public class WithExclusions - { - public class ByExpression - { - [Fact] - public void Shallow() - { - Assert.EquivalentWithExclusions( - new ShallowClass { Value1 = 42, Value2 = "Hello" }, - new ShallowClass { Value1 = 42, Value2 = "World" }, - s => s.Value2 - ); - } - - [Fact] - public void MixedShallowAndDeep() - { - Assert.EquivalentWithExclusions( - new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello" } }, - new DeepClass { Value3 = 42.24m, Shallow = new ShallowClass { Value1 = 42, Value2 = "World" } }, - d => d.Value3, - d => d.Shallow!.Value2 - ); - } - - // https://github.com/xunit/xunit/issues/3338 - // https://github.com/xunit/xunit/issues/3347 - [Fact] - public void PartialDeepComparisonBug() - { - var ex = Record.Exception(() => - Assert.EquivalentWithExclusions( - new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 4, Value2 = "Hello" }, Other = new ShallowClass { Value1 = 10, Value2 = "world" } }, - new DeepClass { Value3 = 42.24m, Shallow = new ShallowClass { Value1 = 2, Value2 = "Hello" }, Other = new ShallowClass { Value1 = 15, Value2 = "world" } }, - d => d.Value3, - d => d.Shallow!.Value2, - d => d.Other!.Value1 - ) - ); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value1'" + Environment.NewLine + - "Expected: 4" + Environment.NewLine + - "Actual: 2", - ex.Message - ); - } - } - - public class ByString - { - [Fact] - public void Shallow() - { - Assert.EquivalentWithExclusions( - new ShallowClass { Value1 = 42, Value2 = "Hello" }, - new ShallowClass { Value1 = 42, Value2 = "World" }, - "Value2" - ); - } - - [Fact] - public void MixedShallowAndDeep() - { - Assert.EquivalentWithExclusions( - new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 42, Value2 = "Hello" } }, - new DeepClass { Value3 = 42.24m, Shallow = new ShallowClass { Value1 = 42, Value2 = "World" } }, - "Value3", - "Shallow.Value2" - ); - } - - // https://github.com/xunit/xunit/issues/3338 - // https://github.com/xunit/xunit/issues/3347 - [Fact] - public void PartialDeepComparisonBug() - { - var ex = Record.Exception(() => - Assert.EquivalentWithExclusions( - new DeepClass { Value3 = 21.12m, Shallow = new ShallowClass { Value1 = 4, Value2 = "Hello" }, Other = new ShallowClass { Value1 = 10, Value2 = "world" } }, - new DeepClass { Value3 = 42.24m, Shallow = new ShallowClass { Value1 = 2, Value2 = "Hello" }, Other = new ShallowClass { Value1 = 15, Value2 = "world" } }, - "Value3", - "Shallow.Value2", - "Other.Value1" - ) - ); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure: Mismatched value on member 'Shallow.Value1'" + Environment.NewLine + - "Expected: 4" + Environment.NewLine + - "Actual: 2", - ex.Message - ); - } - - // https://github.com/xunit/xunit/issues/3394 - [Fact] - public void Collections() - { - var expected = new DeepRecord() - { - Value1 = 35, - Shallow = [ - new() { Value2 = 42, Value3 = 10 }, - ], - }; - var actual = new DeepRecord() - { - Value1 = 35, - Shallow = [ - new() { Value2 = 42, Value3 = 15 }, - new() { Value2 = 50, Value3 = 3 }, - ], - }; - - Assert.EquivalentWithExclusions(expected, actual, strict: false, "Shallow[].Value3"); - } - - // https://github.com/xunit/xunit/issues/3394 - [Fact] - public void Collections_Strict() - { - var expected = new DeepRecord() - { - Value1 = 35, - Shallow = [ - new() { Value2 = 42, Value3 = 10 }, - ], - }; - var actual = new DeepRecord() - { - Value1 = 35, - Shallow = [ - new() { Value2 = 42, Value3 = 15 }, - new() { Value2 = 50, Value3 = 3 }, - ], - }; - - var ex = Record.Exception(() => Assert.EquivalentWithExclusions(expected, actual, strict: true, "Shallow[].Value3")); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equivalent() Failure: Extra values found in member 'Shallow[]'" + Environment.NewLine + - "Expected: [ShallowRecord { Value2 = 42, Value3 = 10 }]" + Environment.NewLine + - "Actual: [ShallowRecord { Value2 = 50, Value3 = 3 }] left over from [ShallowRecord { Value2 = 42, Value3 = 15 }, ShallowRecord { Value2 = 50, Value3 = 3 }]", - ex.Message - ); - } - - class DeepRecord - { - public required int Value1 { get; set; } - public required List Shallow { get; set; } - } - - class ShallowRecord - { - public required int Value2 { get; set; } - public required int Value3 { get; set; } - } - } - } - -#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER // https://github.com/xunit/xunit/issues/3088 public sealed class ByRefLikeParameters @@ -2176,46 +1827,6 @@ public sealed class RefParameterClass2(string value) #endif - public sealed class ClassWithNewOverrides - { - [Fact] - public void ExpectedOverridden() - { - var expected = new DerivedClass { ID = "123" }; - var actual = new BaseClass { ID = "123" }; - - Assert.Equivalent(expected, actual); - } - - [Fact] - public void ActualOverridden() - { - var expected = new BaseClass { ID = "123" }; - var actual = new DerivedClass { ID = "123" }; - - Assert.Equivalent(expected, actual); - } - - [Fact] - public void BothPropertiesOverridden() - { - var expected = new DerivedClass { ID = "123" }; - var actual = new DerivedClass { ID = "123" }; - - Assert.Equivalent(expected, actual); - } - - public class BaseClass - { - public object? ID { get; set; } - } - - public class DerivedClass : BaseClass - { - public new string? ID { get; set; } - } - } - class ShallowClass { public static int StaticValue { get; set; } @@ -2230,33 +1841,39 @@ class ShallowClass2 public string? Value2; } - class PrivateMembersClass(int value1, string value2) + class PrivateMembersClass { - readonly int Value1 = value1; -#pragma warning disable IDE0051 - string Value2 { get; } = value2; -#pragma warning restore IDE0051 + public PrivateMembersClass(int value1, string value2) + { + Value1 = value1; + Value2 = value2; + } + + private readonly int Value1; + private string Value2 { get; } } class DeepClass { public decimal Value3; + public ShallowClass? Shallow { get; set; } - public ShallowClass? Other { get; set; } } class DeepClass2 { public decimal Value3 { get; set; } public ShallowClass? Shallow; -#pragma warning disable CA1822 // Mark members as static - public ShallowClass? Other => null; -#pragma warning restore CA1822 // Mark members as static } - readonly struct DeepStruct(ShallowClass shallow) + struct DeepStruct { - public ShallowClass Shallow { get; } = shallow; + public DeepStruct(ShallowClass shallow) + { + Shallow = shallow; + } + + public ShallowClass Shallow { get; } } class SelfReferential @@ -2280,5 +1897,3 @@ class ClassWithIndexer public string this[int idx] => idx.ToString(); } } - -#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EventAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/EventAssertsTests.cs similarity index 72% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EventAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/EventAssertsTests.cs index 5b49591dae7..42400028504 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/EventAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/EventAssertsTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Threading.Tasks; using Xunit; using Xunit.Sdk; @@ -377,7 +379,7 @@ public static async Task ExactTypeRaised() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -394,7 +396,7 @@ public static async Task DerivedTypeRaised() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -458,7 +460,7 @@ public static async Task ExactTypeRaised() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -475,7 +477,7 @@ public static async Task ExactTypeRaised_NonGeneric() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -492,7 +494,7 @@ public static async Task DerivedTypeRaised() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -509,7 +511,7 @@ public static async Task DerivedTypeRaised_NonGeneric() var evt = await Assert.RaisesAnyAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -568,7 +570,7 @@ public static async Task ExactTypeRaised() var evt = await Assert.RaisesAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -586,7 +588,7 @@ public static async Task DerivedTypeRaised() () => Assert.RaisesAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ) ); @@ -633,7 +635,7 @@ public static async Task ExactTypeRaised() var evt = await Assert.RaisesAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ); Assert.NotNull(evt); @@ -651,7 +653,7 @@ public static async Task DerivedTypeRaised() () => Assert.RaisesAsync( h => obj.Completed += h, h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) + () => Task.Run(() => obj.RaiseWithArgs(eventObj)) ) ); @@ -665,239 +667,6 @@ public static async Task DerivedTypeRaised() } } - public class NotRaisedAny_Action - { - [Fact] - public static void NoEventRaised() - { - var obj = new RaisingClass_ActionOfT(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => { } - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static void EventRaised() - { - var obj = new RaisingClass_ActionOfT(); - var eventObj = new object(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => obj.RaiseWithArgs(eventObj) - ) - ); - - Assert.IsType(ex); - Assert.Equal( - $"Assert.NotRaisedAny() Failure: An unexpected event was raised{Environment.NewLine}Unexpected: typeof(object){Environment.NewLine}Actual: An event was raised", - ex.Message - ); - } - - } - - public class NotRaisedAny_EventHandler - { - [Fact] - public static void NoEventRaised() - { - var obj = new RaisingClass_EventHandlerOfT(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => { } - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static void EventRaised() - { - var obj = new RaisingClass_EventHandlerOfT(); - var eventObj = new object(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => obj.RaiseWithArgs(eventObj) - ) - ); - - Assert.IsType(ex); - Assert.Equal( - $"Assert.NotRaisedAny() Failure: An unexpected event was raised{Environment.NewLine}Unexpected: typeof(object){Environment.NewLine}Actual: An event was raised", - ex.Message - ); - } - } - - public class NotRaisedAnyAsync_Action - { - [Fact] - public static async Task NoEventRaised() - { - var obj = new RaisingClass_ActionOfT(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.FromResult(0) - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static async Task EventRaised() - { - var obj = new RaisingClass_ActionOfT(); - var eventObj = new object(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) - ) - ); - - Assert.IsType(ex); - Assert.Equal( - $"Assert.NotRaisedAny() Failure: An unexpected event was raised{Environment.NewLine}Unexpected: typeof(object){Environment.NewLine}Actual: An event was raised", - ex.Message - ); - } - } - - public class NotRaisedAnyAsync_EventHandler - { - [Fact] - public static async Task NoEventRaised() - { - var obj = new RaisingClass_EventHandlerOfT(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.FromResult(0) - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static async Task EventRaised() - { - var obj = new RaisingClass_EventHandlerOfT(); - var eventObj = new object(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.Run(() => obj.RaiseWithArgs(eventObj), TestContext.Current.CancellationToken) - ) - ); - - Assert.IsType(ex); - Assert.Equal( - $"Assert.NotRaisedAny() Failure: An unexpected event was raised{Environment.NewLine}Unexpected: typeof(object){Environment.NewLine}Actual: An event was raised", - ex.Message - ); - } - } - - public class NotRaisedAny_NoArgs - { - [Fact] - public static void NoEventRaised() - { - var obj = new RaisingClass_Action(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => { } - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static void EventRaised() - { - var obj = new RaisingClass_Action(); - - var ex = Record.Exception( - () => Assert.NotRaisedAny( - h => obj.Completed += h, - h => obj.Completed -= h, - () => obj.Raise() - ) - ); - - Assert.IsType(ex); - Assert.Equal("Assert.NotRaisedAny() Failure: An unexpected event was raised", ex.Message); - } - } - - public class NotRaisedAnyAsync_NoArgs - { - [Fact] - public static async Task NoEventRaised() - { - var obj = new RaisingClass_Action(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.CompletedTask - ) - ); - - Assert.Null(ex); - } - - [Fact] - public static async Task EventRaised() - { - var obj = new RaisingClass_Action(); - - var ex = await Record.ExceptionAsync( - () => Assert.NotRaisedAnyAsync( - h => obj.Completed += h, - h => obj.Completed -= h, - () => Task.Run(() => obj.Raise(), TestContext.Current.CancellationToken) - ) - ); - - Assert.IsType(ex); - Assert.Equal("Assert.NotRaisedAny() Failure: An unexpected event was raised", ex.Message); - } - } - class RaisingClass_Action { public void Raise() diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/ExceptionAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/ExceptionAssertsTests.cs new file mode 100644 index 00000000000..8a3a3df7cb3 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/ExceptionAssertsTests.cs @@ -0,0 +1,840 @@ +using System; +using System.Threading.Tasks; +using Xunit; +using Xunit.Sdk; + +public class ExceptionAssertsTests +{ +#pragma warning disable xUnit2015 // Do not use typeof expression to check the exception type + public class Throws_NonGeneric + { + public class WithAction + { + [Fact] + public static void GuardClauses() + { + void testCode() { } + + Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode)); + Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Action)!)); + } + + [Fact] + public static void NoExceptionThrown() + { + void testCode() { } + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + void testCode() => throw new ArgumentException(); + + Assert.Throws(typeof(ArgumentException), testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + + public class WithFunc + { + [Fact] + public static void GuardClauses() + { + object testCode() => 42; + + Assert.Throws("exceptionType", () => Assert.Throws(null!, testCode)); + Assert.Throws("testCode", () => Assert.Throws(typeof(Exception), default(Func)!)); + } + + [Fact] + public static void ProtectsAgainstAccidentalTask() + { + static object testCode() => Task.FromResult(42); + + var ex = Record.Exception(() => Assert.Throws(typeof(Exception), testCode)); + + Assert.IsType(ex); + Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); + } + + [Fact] + public static void NoExceptionThrown() + { + object testCode() => 42; + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + object testCode() => throw new ArgumentException(); + + Assert.Throws(typeof(ArgumentException), testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(typeof(ArgumentException), testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + } +#pragma warning restore xUnit2015 // Do not use typeof expression to check the exception type + + public class Throws_Generic + { + public class WithAction + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.Throws(default(Action)!)); + } + + [Fact] + public static void NoExceptionThrown() + { + void testCode() { } + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + void testCode() => throw new ArgumentException(); + + Assert.Throws(testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + + public class WithFunc + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.Throws(default(Func)!)); + } + + [Fact] + public static void ProtectsAgainstAccidentalTask() + { + static object testCode() => Task.FromResult(42); + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal("You must call Assert.ThrowsAsync when testing async code", ex.Message); + } + + [Fact] + public static void NoExceptionThrown() + { + object testCode() => 42; + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + object testCode() => throw new ArgumentException(); + + Assert.Throws(testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + } + + public class Throws_Generic_ArgumentException + { + public class WithAction + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.Throws(default(Action)!)); + } + + [Fact] + public static void NoExceptionThrown() + { + void testCode() { } + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + void testCode() => throw new ArgumentException("Hello world", "paramName"); + + Assert.Throws("paramName", testCode); + } + + [Fact] + public static void IncorrectParameterName() + { + void testCode() => throw new ArgumentException("Hello world", "paramName1"); + + var ex = Record.Exception(() => Assert.Throws("paramName2", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + + "Exception: typeof(System.ArgumentException)" + Environment.NewLine + + "Expected: \"paramName2\"" + Environment.NewLine + + "Actual: \"paramName1\"", + ex.Message + ); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + + public class WithFunc + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.Throws(default(Func)!)); + } + + [Fact] + public static void NoExceptionThrown() + { + object testCode() => 42; + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + object testCode() => throw new ArgumentException("Hello world", "paramName"); + + Assert.Throws("paramName", testCode); + } + + [Fact] + public static void IncorrectParameterName() + { + object testCode() => throw new ArgumentException("Hello world", "paramName1"); + + var ex = Record.Exception(() => Assert.Throws("paramName2", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + + "Exception: typeof(System.ArgumentException)" + Environment.NewLine + + "Expected: \"paramName2\"" + Environment.NewLine + + "Actual: \"paramName1\"", + ex.Message + ); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.Throws("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + } + + public class ThrowsAny + { + public class WithAction + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.ThrowsAny(default(Action)!)); + } + + [Fact] + public static void NoExceptionThrown() + { + void testCode() { } + + var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + void testCode() => throw new ArgumentException(); + + Assert.ThrowsAny(testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + void testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + void testCode() => throw new ArgumentNullException(); + + Assert.ThrowsAny(testCode); + } + } + + public class WithFunc + { + [Fact] + public static void GuardClause() + { + Assert.Throws("testCode", () => Assert.ThrowsAny(default(Func)!)); + } + + [Fact] + public static void ProtectsAgainstAccidentalTask() + { + static object testCode() => Task.FromResult(42); + + var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); + + Assert.IsType(ex); + Assert.Equal("You must call Assert.ThrowsAnyAsync when testing async code", ex.Message); + } + + [Fact] + public static void NoExceptionThrown() + { + object testCode() => 42; + + var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static void CorrectExceptionThrown() + { + object testCode() => throw new ArgumentException(); + + Assert.ThrowsAny(testCode); + } + + [Fact] + public static void IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + object testCode() => throw thrown; + + var ex = Record.Exception(() => Assert.ThrowsAny(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static void DerivedExceptionThrown() + { + object testCode() => throw new ArgumentNullException(); + + Assert.ThrowsAny(testCode); + } + } + } + + public class ThrowsAnyAsync + { + [Fact] + public static async Task GuardClause() + { + await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAnyAsync(default(Func)!)); + } + + [Fact] + public static async Task NoExceptionThrown() + { + Task testCode() => Task.FromResult(42); + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static async Task CorrectExceptionThrown() + { + Task testCode() => throw new ArgumentException(); + + await Assert.ThrowsAnyAsync(testCode); + } + + [Fact] + public static async Task IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + Task testCode() => throw thrown; + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAnyAsync(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.ThrowsAny() Failure: Exception type was not compatible" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static async Task DerivedExceptionThrown() + { + Task testCode() => throw new ArgumentNullException(); + + await Assert.ThrowsAnyAsync(testCode); + } + } + + public class ThrowsAsync + { + [Fact] + public static async Task GuardClause() + { + await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAsync(default(Func)!)); + } + + [Fact] + public static async Task NoExceptionThrown() + { + Task testCode() => Task.FromResult(42); + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static async Task CorrectExceptionThrown() + { + Task testCode() => throw new ArgumentException(); + + await Assert.ThrowsAsync(testCode); + } + + [Fact] + public static async Task IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + Task testCode() => throw thrown; + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static async Task DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + Task testCode() => throw thrown; + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync(testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } + + public class ThrowsAsync_ArgumentException + { + [Fact] + public static async Task GuardClause() + { + await Assert.ThrowsAsync("testCode", () => Assert.ThrowsAsync("paramName", default(Func)!)); + } + + [Fact] + public static async Task NoExceptionThrown() + { + Task testCode() => Task.FromResult(42); + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: No exception was thrown" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)", + ex.Message + ); + Assert.Null(ex.InnerException); + } + + [Fact] + public static async Task CorrectExceptionThrown() + { + Task testCode() => throw new ArgumentException("Hello world", "paramName"); + + await Assert.ThrowsAsync("paramName", testCode); + } + + [Fact] + public static async Task IncorrectParameterName() + { + Task testCode() => throw new ArgumentException("Hello world", "paramName1"); + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName2", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Incorrect parameter name" + Environment.NewLine + + "Exception: typeof(System.ArgumentException)" + Environment.NewLine + + "Expected: \"paramName2\"" + Environment.NewLine + + "Actual: \"paramName1\"", + ex.Message + ); + } + + [Fact] + public static async Task IncorrectExceptionThrown() + { + var thrown = new DivideByZeroException(); + Task testCode() => throw thrown; + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.DivideByZeroException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + + [Fact] + public static async Task DerivedExceptionThrown() + { + var thrown = new ArgumentNullException(); + Task testCode() => throw thrown; + + var ex = await Record.ExceptionAsync(() => Assert.ThrowsAsync("paramName", testCode)); + + Assert.IsType(ex); + Assert.Equal( + "Assert.Throws() Failure: Exception type was not an exact match" + Environment.NewLine + + "Expected: typeof(System.ArgumentException)" + Environment.NewLine + + "Actual: typeof(System.ArgumentNullException)", + ex.Message + ); + Assert.Same(thrown, ex.InnerException); + } + } +} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/MemoryExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/MemoryExtensions.cs deleted file mode 100644 index 617c812135c..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/MemoryExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -internal static class MemoryExtensions -{ - public static Memory Memoryify(this T[]? values) => - new(values); - - public static Memory Memoryify(this string? value) => - new((value ?? string.Empty).ToCharArray()); -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SetExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SetExtensions.cs deleted file mode 100644 index 51c13647f84..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SetExtensions.cs +++ /dev/null @@ -1,7 +0,0 @@ -internal static class SetExtensions -{ - public static SortedSet ToSortedSet( - this ISet set, - IComparer? comparer = null) => - new(set, comparer); -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SpanExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SpanExtensions.cs deleted file mode 100644 index 662a52a6864..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Extensions/SpanExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -internal static class SpanExtensions -{ - public static Span Spanify(this T[]? values) => - new(values); - - public static Span Spanify(this string? value) => - new((value ?? string.Empty).ToCharArray()); -} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/FailAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/FailAssertsTests.cs similarity index 100% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/FailAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/FailAssertsTests.cs diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/GlobalUsings.cs b/src/Microsoft.DotNet.XUnitAssert/tests/GlobalUsings.cs deleted file mode 100644 index 9e69ce73274..00000000000 --- a/src/Microsoft.DotNet.XUnitAssert/tests/GlobalUsings.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using System.Diagnostics.CodeAnalysis; -global using Xunit.Internal; diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/IdentityAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/IdentityAssertsTests.cs similarity index 98% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/IdentityAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/IdentityAssertsTests.cs index 7b19c4731f1..5bf4882dd57 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/IdentityAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/IdentityAssertsTests.cs @@ -1,3 +1,4 @@ +using System; using Xunit; using Xunit.Sdk; diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MemoryAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/MemoryAssertsTests.cs similarity index 93% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MemoryAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/MemoryAssertsTests.cs index 58abb6dcfbe..1472e265625 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/MemoryAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/MemoryAssertsTests.cs @@ -1,3 +1,8 @@ +#if XUNIT_SPAN + +using System; +using System.Collections.Generic; +using System.Linq; using Xunit; using Xunit.Sdk; @@ -92,7 +97,7 @@ public void VeryLongStrings() { var ex = Record.Exception( () => Assert.Contains( - "We are looking for something that is actually very long as well".Memoryify(), + "We are looking for something very long as well".Memoryify(), "This is a relatively long string so that we can see the truncation in action".Memoryify() ) ); @@ -100,8 +105,8 @@ public void VeryLongStrings() Assert.IsType(ex); Assert.Equal( "Assert.Contains() Failure: Sub-string not found" + Environment.NewLine + - "String: \"This is a relatively long string so that we can se\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + - "Not found: \"We are looking for something that is actually very\"" + ArgumentFormatter.Ellipsis, + "String: \"This is a relatively long string so that \"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "Not found: \"We are looking for something very long as\"" + ArgumentFormatter.Ellipsis, ex.Message ); } @@ -293,7 +298,7 @@ public void VeryLongString_FoundAtFront() Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + " ↓ (pos 7)" + Environment.NewLine + - "String: \"Hello, world from a very long string that will end\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "String: \"Hello, world from a very long string that\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -307,8 +312,8 @@ public void VeryLongString_FoundInMiddle() Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - "String: " + ArgumentFormatter.Ellipsis + "\" string that has 'Hello, world' placed in the midd\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + " ↓ (pos 50)" + Environment.NewLine + + "String: " + ArgumentFormatter.Ellipsis + "\"ng that has 'Hello, world' placed in the \"" + ArgumentFormatter.Ellipsis + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -322,8 +327,8 @@ public void VeryLongString_FoundAtEnd() Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 89)" + Environment.NewLine + - "String: " + ArgumentFormatter.Ellipsis + "\"om the front truncated, just to say 'Hello, world'\"" + Environment.NewLine + + " ↓ (pos 89)" + Environment.NewLine + + "String: " + ArgumentFormatter.Ellipsis + "\"ont truncated, just to say 'Hello, world'\"" + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -425,7 +430,7 @@ public void Success() [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -447,7 +452,7 @@ static void assertFailure(Action action) [Fact] public void CaseSensitiveByDefault() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -478,7 +483,7 @@ public void CanSpecifyComparisonType() [Fact] public void NullStringIsEmpty() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -501,15 +506,15 @@ public void Truncation() var expected = "This is a long string that we're looking for at the end"; var actual = "This is the long string that we expected to find this ending inside"; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.EndsWith() Failure: String end does not match" + Environment.NewLine + - "String: " + ArgumentFormatter.Ellipsis + "\"string that we expected to find this ending inside\"" + Environment.NewLine + - "Expected end: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: " + ArgumentFormatter.Ellipsis + "\"at we expected to find this ending inside\"" + Environment.NewLine + + "Expected end: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } @@ -649,17 +654,17 @@ void assertFailure(Action action) [Fact] public void Truncation() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 21)" + Environment.NewLine + - "Expected: \"Why hello there world, you're a long string with s\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + - "Actual: \"Why hello there world! You're a long string!\"" + Environment.NewLine + - " ↑ (pos 21)", + " ↓ (pos 21)" + Environment.NewLine + + "Expected: " + ArgumentFormatter.Ellipsis + "\"hy hello there world, you're a long strin\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "Actual: " + ArgumentFormatter.Ellipsis + "\"hy hello there world! You're a long strin\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + " ↑ (pos 21)", ex.Message ); } @@ -726,7 +731,7 @@ public void Success( [Fact] public void Failure_MidCollection() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -750,7 +755,7 @@ static void assertFailure(Action action) [Fact] public void Failure_BeyondEnd() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -806,7 +811,7 @@ public void Success( [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -842,7 +847,7 @@ public void Success() [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -864,7 +869,7 @@ static void assertFailure(Action action) [Fact] public void CaseSensitiveByDefault() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -895,7 +900,7 @@ public void CanSpecifyComparisonType() [Fact] public void NullStringIsEmpty() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -918,15 +923,15 @@ public void Truncation() var expected = "This is a long string that we're looking for at the start"; var actual = "This is the long string that we expected to find this starting inside"; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.StartsWith() Failure: String start does not match" + Environment.NewLine + - "String: \"This is the long string that we expected to find t\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + - "Expected start: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: \"This is the long string that we expected \"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "Expected start: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } @@ -938,3 +943,5 @@ static void assertFailure(Action action) } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/MemoryExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/MemoryExtensions.cs new file mode 100644 index 00000000000..775eae45944 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/MemoryExtensions.cs @@ -0,0 +1,18 @@ +#if XUNIT_SPAN + +using System; + +internal static class MemoryExtensions +{ + public static Memory Memoryify(this T[]? values) + { + return new Memory(values); + } + + public static Memory Memoryify(this string? value) + { + return new Memory((value ?? string.Empty).ToCharArray()); + } +} + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Microsoft.DotNet.XUnitAssert.Tests.csproj b/src/Microsoft.DotNet.XUnitAssert/tests/Microsoft.DotNet.XUnitAssert.Tests.csproj index bd726a272e7..407bc9ff7cf 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Microsoft.DotNet.XUnitAssert.Tests.csproj +++ b/src/Microsoft.DotNet.XUnitAssert/tests/Microsoft.DotNet.XUnitAssert.Tests.csproj @@ -1,29 +1,21 @@ - $(BundledNETCoreAppTargetFramework) - Exe + $(NetToolCurrent) enable - enable - true + $(NoWarn);xUnit2000;xUnit2003;xUnit2005;xUnit2007;xUnit2011;xUnit2015;xUnit2017 $(NoWarn);IDE0073 $(DefineConstants);XUNIT_NULLABLE;XUNIT_SPAN;XUNIT_IMMUTABLE_COLLECTIONS;XUNIT_AOT true - - - true - - true - - - - - + + + + diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/MultipleAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/MultipleAssertsTests.cs new file mode 100644 index 00000000000..cec0db439c0 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/MultipleAssertsTests.cs @@ -0,0 +1,75 @@ +using Xunit; +using Xunit.Sdk; + +public class MultipleAssertsTests +{ + [Fact] + public void NoActions_DoesNotThrow() + { + Assert.Multiple(); + } + + [Fact] + public void SingleAssert_Success_DoesNotThrow() + { + Assert.Multiple( + () => Assert.True(true) + ); + } + + [Fact] + public void SingleAssert_Fails_ThrowsFailingAssert() + { + var ex = Record.Exception(() => + Assert.Multiple( + () => Assert.True(false) + ) + ); + + Assert.IsType(ex); + } + + [Fact] + public void MultipleAssert_Success_DoesNotThrow() + { + Assert.Multiple( + () => Assert.True(true), + () => Assert.False(false) + ); + } + + [Fact] + public void MultipleAssert_SingleFailure_ThrowsFailingAssert() + { + var ex = Record.Exception(() => + Assert.Multiple( + () => Assert.True(true), + () => Assert.False(true) + ) + ); + + Assert.IsType(ex); + } + + [Fact] + public void MultipleAssert_MultipleFailures_ThrowsMultipleException() + { + var ex = Record.Exception(() => + Assert.Multiple( + () => Assert.True(false), + () => Assert.False(true) + ) + ); + + var multiEx = Assert.IsType(ex); + Assert.Equal( + "Assert.Multiple() Failure: Multiple failures were encountered", + ex.Message + ); + Assert.Collection( + multiEx.InnerExceptions, + innerEx => Assert.IsType(innerEx), + innerEx => Assert.IsType(innerEx) + ); + } +} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/NullAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/NullAssertsTests.cs similarity index 60% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/NullAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/NullAssertsTests.cs index 4f3d60b59b3..1c096286425 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/NullAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/NullAssertsTests.cs @@ -1,5 +1,4 @@ -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type - +using System; using Xunit; using Xunit.Sdk; @@ -24,17 +23,6 @@ public void Success_NullableStruct() Assert.Equal(42, result); } - -#if XUNIT_POINTERS - [Theory] - [InlineData(42)] - [InlineData("Hello, world")] - public unsafe void Success_Pointer(T data) - { - Assert.NotNull(&data); - } -#endif - [Fact] public void Failure_Reference() { @@ -54,18 +42,6 @@ public void Failure_NullableStruct() Assert.IsType(ex); Assert.Equal("Assert.NotNull() Failure: Value of type 'Nullable' does not have a value", ex.Message); } - - -#if XUNIT_POINTERS - [Fact] - public unsafe void Failure_Pointer() - { - var ex = Record.Exception(() => Assert.NotNull((object*)null)); - - Assert.IsType(ex); - Assert.Equal("Assert.NotNull() Failure: Value of type 'object*' is null", ex.Message); - } -#endif } public class Null @@ -84,15 +60,6 @@ public void Success_NullableStruct() Assert.Null(x); } - -#if XUNIT_POINTERS - [Fact] - public unsafe void Success_Pointer() - { - Assert.Null((object*)null); - } -#endif - [Fact] public void Failure_Reference() { @@ -102,11 +69,7 @@ public void Failure_Reference() Assert.Equal( "Assert.Null() Failure: Value is not null" + Environment.NewLine + "Expected: null" + Environment.NewLine + -#if XUNIT_AOT - $"Actual: Object {{ {ArgumentFormatter.Ellipsis} }}", -#else "Actual: Object { }", -#endif ex.Message ); } @@ -126,21 +89,5 @@ public void Failure_NullableStruct() ex.Message ); } - - -#if XUNIT_POINTERS - [Theory] - [InlineData(42)] - [InlineData("Hello, world")] - public unsafe void Failure_Pointer(T data) - { - var ptr = &data; - - var ex = Record.Exception(() => Assert.Null(ptr)); - - Assert.IsType(ex); - Assert.Equal($"Assert.Null() Failure: Value of type '{ArgumentFormatter.FormatTypeName(typeof(T))}*' is not null", ex.Message); - } -#endif } } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/PropertyAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/PropertyAssertsTests.cs similarity index 93% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/PropertyAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/PropertyAssertsTests.cs index e8504f148d7..0396f8732bf 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/PropertyAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/PropertyAssertsTests.cs @@ -1,4 +1,7 @@ +using System; using System.ComponentModel; +using System.Threading.Tasks; +using NSubstitute; using Xunit; using Xunit.Sdk; @@ -10,7 +13,7 @@ public class PropertyChanged public void GuardClauses() { Assert.Throws("object", () => Assert.PropertyChanged(null!, "propertyName", delegate { })); - Assert.Throws("testCode", () => Assert.PropertyChanged(new NotifiedClass(), "propertyName", (Action)null!)); + Assert.Throws("testCode", () => Assert.PropertyChanged(Substitute.For(), "propertyName", (Action)null!)); } [Fact] @@ -63,7 +66,7 @@ public class PropertyChangedAsync public async Task GuardClauses() { await Assert.ThrowsAsync("object", () => Assert.PropertyChangedAsync(null!, "propertyName", () => Task.FromResult(0))); - await Assert.ThrowsAsync("testCode", () => Assert.PropertyChangedAsync(new NotifiedClass(), "propertyName", default(Func)!)); + await Assert.ThrowsAsync("testCode", () => Assert.PropertyChangedAsync(Substitute.For(), "propertyName", default(Func)!)); } [Fact] diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/RangeAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/RangeAssertsTests.cs similarity index 88% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/RangeAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/RangeAssertsTests.cs index 07ddb12139c..2c758f27531 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/RangeAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/RangeAssertsTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using Xunit; using Xunit.Sdk; @@ -5,7 +7,11 @@ public class RangeAssertsTests { public class InRange { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void DoubleNotWithinRange() { var ex = Record.Exception(() => Assert.InRange(1.50, .75, 1.25)); @@ -88,7 +94,11 @@ public void DoubleValueWithinRange() Assert.InRange(400.0, .75, 1.25, new DoubleComparer(-1)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public void DoubleValueNotWithinRange() { var ex = Record.Exception(() => Assert.InRange(1.0, .75, 1.25, new DoubleComparer(1))); @@ -119,8 +129,8 @@ public void DoubleWithinRange() Assert.IsType(ex); Assert.Equal( "Assert.NotInRange() Failure: Value in range" + Environment.NewLine + - "Range: (0.75 - 1.25)" + Environment.NewLine + - "Actual: 1", + $"Range: ({0.75:G17} - {1.25:G17})" + Environment.NewLine + + $"Actual: {1:G17}", ex.Message ); } @@ -176,8 +186,8 @@ public void DoubleValueWithinRange() Assert.IsType(ex); Assert.Equal( "Assert.NotInRange() Failure: Value in range" + Environment.NewLine + - "Range: (0.75 - 1.25)" + Environment.NewLine + - "Actual: 400", + $"Range: ({0.75:G17} - {1.25:G17})" + Environment.NewLine + + $"Actual: {400:G17}", ex.Message ); } @@ -189,12 +199,15 @@ public void DoubleValueNotWithinRange() } } - class DoubleComparer(int returnValue) : - IComparer + class DoubleComparer : IComparer { - public int Compare( - double x, - double y) => - returnValue; + readonly int returnValue; + + public DoubleComparer(int returnValue) + { + this.returnValue = returnValue; + } + + public int Compare(double x, double y) => returnValue; } } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/ArgumentFormatterTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Sdk/ArgumentFormatterTests.cs similarity index 65% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/ArgumentFormatterTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/Sdk/ArgumentFormatterTests.cs index 617a91d71a3..ad58a0a3a00 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/ArgumentFormatterTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/Sdk/ArgumentFormatterTests.cs @@ -1,4 +1,8 @@ +using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Sdk; @@ -6,7 +10,11 @@ public class ArgumentFormatterTests { public class SimpleValues { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void NullValue() { Assert.Equal("null", ArgumentFormatter.Format(null)); @@ -43,13 +51,13 @@ public static void NullValue() // For more information, see the following links: // - http://stackoverflow.com/q/36104766/4077294 // - http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/ - public static IEnumerable> StringValue_TestData = - [ - new("\uD800", @"""\xd800"""), - new("\uDC00", @"""\xdc00"""), - new("\uDC00\uD800", @"""\xdc00\xd800"""), - new("\uFFFD", "\"\uFFFD\""), - ]; + public static IEnumerable StringValue_TestData() + { + yield return new object[] { "\uD800", @"""\xd800""" }; + yield return new object[] { "\uDC00", @"""\xdc00""" }; + yield return new object[] { "\uDC00\uD800", @"""\xdc00\xd800""" }; + yield return new object[] { "\uFFFD", "\"\uFFFD\"" }; + } [Theory] [InlineData("Hello, world!", "\"Hello, world!\"")] @@ -75,13 +83,13 @@ public static void StringValue(string value, string expected) Assert.Equal(expected.Replace("$$ELLIPSIS$$", ArgumentFormatter.Ellipsis), ArgumentFormatter.Format(value)); } - public static IEnumerable> CharValue_TestData = - [ - new('\uD800', "0xd800"), - new('\uDC00', "0xdc00"), - new('\uFFFD', "'\uFFFD'"), - new('\uFFFE', "0xfffe"), - ]; + public static IEnumerable CharValue_TestData() + { + yield return new object[] { '\uD800', "0xd800" }; + yield return new object[] { '\uDC00', "0xdc00" }; + yield return new object[] { '\uFFFD', "'\uFFFD'" }; + yield return new object[] { '\uFFFE', "0xfffe" }; + } [Theory] @@ -116,7 +124,11 @@ public static void CharacterValue(char value, string expected) Assert.Equal(expected, ArgumentFormatter.Format(value)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void FloatValue() { var floatPI = (float)Math.PI; @@ -124,19 +136,31 @@ public static void FloatValue() Assert.Equal(floatPI.ToString("G9"), ArgumentFormatter.Format(floatPI)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void DoubleValue() { Assert.Equal(Math.PI.ToString("G17"), ArgumentFormatter.Format(Math.PI)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void DecimalValue() { Assert.Equal(123.45M.ToString(), ArgumentFormatter.Format(123.45M)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void DateTimeValue() { var now = DateTime.UtcNow; @@ -144,7 +168,11 @@ public static void DateTimeValue() Assert.Equal(now.ToString("o"), ArgumentFormatter.Format(now)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void DateTimeOffsetValue() { var now = DateTimeOffset.UtcNow; @@ -155,7 +183,7 @@ public static void DateTimeOffsetValue() [Fact] public static async Task TaskValue() { - var task = Task.Run(() => { }, TestContext.Current.CancellationToken); + var task = Task.Run(() => { }); await task; Assert.Equal("Task { Status = RanToCompletion }", ArgumentFormatter.Format(task)); @@ -170,29 +198,27 @@ public static void TaskGenericValue() Assert.Equal("Task { Status = Faulted }", ArgumentFormatter.Format(taskCompletionSource.Task)); } - public static IEnumerable> TypeValueData = - [ - new(typeof(string), "typeof(string)"), - new(typeof(int[]), "typeof(int[])"), -#if !XUNIT_AOT // MakeArrayType is not available in Native AOT - new(typeof(int).MakeArrayType(1), "typeof(int[*])"), - new(typeof(int).MakeArrayType(2), "typeof(int[,])"), - new(typeof(int).MakeArrayType(3), "typeof(int[,,])"), -#endif - new(typeof(DateTime[,]), "typeof(System.DateTime[,])"), - new(typeof(decimal[][,]), "typeof(decimal[][,])"), - new(typeof(IEnumerable<>), "typeof(System.Collections.Generic.IEnumerable<>)"), - new(typeof(IEnumerable), "typeof(System.Collections.Generic.IEnumerable)"), - new(typeof(IDictionary<,>), "typeof(System.Collections.Generic.IDictionary<,>)"), - new(typeof(IDictionary), "typeof(System.Collections.Generic.IDictionary)"), - new(typeof(IDictionary), "typeof(System.Collections.Generic.IDictionary)"), - new(typeof(bool?), "typeof(bool?)"), - new(typeof(bool?[]), "typeof(bool?[])"), - new(typeof(nint), "typeof(nint)"), - new(typeof(IntPtr), "typeof(nint)"), - new(typeof(nuint), "typeof(nuint)"), - new(typeof(UIntPtr), "typeof(nuint)"), - ]; + public static TheoryData TypeValueData = new() + { + { typeof(string), "typeof(string)" }, + { typeof(int[]), "typeof(int[])" }, + { typeof(int).MakeArrayType(1), "typeof(int[*])" }, + { typeof(int).MakeArrayType(2), "typeof(int[,])" }, + { typeof(int).MakeArrayType(3), "typeof(int[,,])" }, + { typeof(DateTime[,]), "typeof(System.DateTime[,])" }, + { typeof(decimal[][,]), "typeof(decimal[][,])" }, + { typeof(IEnumerable<>), "typeof(System.Collections.Generic.IEnumerable<>)" }, + { typeof(IEnumerable), "typeof(System.Collections.Generic.IEnumerable)" }, + { typeof(IDictionary<,>), "typeof(System.Collections.Generic.IDictionary<,>)" }, + { typeof(IDictionary), "typeof(System.Collections.Generic.IDictionary)" }, + { typeof(IDictionary), "typeof(System.Collections.Generic.IDictionary)" }, + { typeof(bool?), "typeof(bool?)" }, + { typeof(bool?[]), "typeof(bool?[])" }, + { typeof(nint), "typeof(nint)" }, + { typeof(IntPtr), "typeof(nint)" }, + { typeof(nuint), "typeof(nuint)" }, + { typeof(UIntPtr), "typeof(nuint)" }, + }; [Theory] [MemberData(nameof(TypeValueData), DisableDiscoveryEnumeration = true)] @@ -210,7 +236,11 @@ public enum NonFlagsEnum Value1 = 1 } - [CulturedTheoryDefault] +#if XUNIT_AOT + [Theory] +#else + [CulturedTheory] +#endif #pragma warning disable xUnit1010 // The value is not convertible to the method parameter type [InlineData(0, "Value0")] [InlineData(1, "Value1")] @@ -231,7 +261,11 @@ public enum FlagsEnum Value2 = 2, } - [CulturedTheoryDefault] +#if XUNIT_AOT + [Theory] +#else + [CulturedTheory] +#endif #pragma warning disable xUnit1010 // The value is not convertible to the method parameter type [InlineData(0, "Nothing")] [InlineData(1, "Value1")] @@ -249,10 +283,14 @@ public static void Flags(FlagsEnum enumValue, string expected) public class KeyValuePair { - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void KeyValuePairValue() { - var kvp = new KeyValuePair>(42, [21.12M, "2600"]); + var kvp = new KeyValuePair>(42, new() { 21.12M, "2600" }); var expected = $"[42] = [{21.12M}, \"2600\"]"; Assert.Equal(expected, ArgumentFormatter.Format(kvp)); @@ -261,18 +299,18 @@ public static void KeyValuePairValue() public class Enumerables { -#pragma warning disable xUnit1047 // Avoid using TheoryDataRow arguments that might not be serializable - // Both tracked and untracked should be the same - public static TheoryData> Collections = - [ - new([1, 2.3M, "Hello, world!"]), - new(CollectionTracker.Wrap([1, 2.3M, "Hello, world!"])), - ]; - -#pragma warning restore xUnit1047 + public static TheoryData Collections = new() + { + new object[] { 1, 2.3M, "Hello, world!" }, + new object[] { 1, 2.3M, "Hello, world!" }.AsTracker(), + }; - [CulturedTheoryDefault] +#if XUNIT_AOT + [Theory] +#else + [CulturedTheory] +#endif [MemberData(nameof(Collections), DisableDiscoveryEnumeration = true)] public static void EnumerableValue(IEnumerable collection) { @@ -281,7 +319,11 @@ public static void EnumerableValue(IEnumerable collection) Assert.Equal(expected, ArgumentFormatter.Format(collection)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void DictionaryValue() { var value = new Dictionary> @@ -289,104 +331,62 @@ public static void DictionaryValue() { 42, new() { 21.12M, "2600" } }, { "123", new() { } }, }; -#if XUNIT_AOT - var expected = "[[42, System.Collections.Generic.List`1[System.Object]], [123, System.Collections.Generic.List`1[System.Object]]]"; -#else var expected = $"[[42] = [{21.12M}, \"2600\"], [\"123\"] = []]"; -#endif Assert.Equal(expected, ArgumentFormatter.Format(value)); } -#pragma warning disable xUnit1047 // Avoid using TheoryDataRow arguments that might not be serializable - - public static TheoryData> LongCollections = - [ - new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - new(CollectionTracker.Wrap([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), - ]; - -#pragma warning restore xUnit1047 + public static TheoryData LongCollections = new() + { + new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, + new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsTracker(), + }; - [CulturedTheoryDefault] +#if XUNIT_AOT + [Theory] +#else + [CulturedTheory] +#endif [MemberData(nameof(LongCollections), DisableDiscoveryEnumeration = true)] public static void OnlyFirstFewValuesOfEnumerableAreRendered(IEnumerable collection) { Assert.Equal($"[0, 1, 2, 3, 4, {ArgumentFormatter.Ellipsis}]", ArgumentFormatter.Format(collection)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void EnumerablesAreRenderedWithMaximumDepthToPreventInfiniteRecursion() { var looping = new object[2]; looping[0] = 42; looping[1] = looping; - Assert.Equal($"[42, [42, [42, [{ArgumentFormatter.Ellipsis}]]]]", ArgumentFormatter.Format(looping)); - } - - [Fact] - public static void GroupingIsRenderedAsCollectionsOfKeysLinkedToCollectionsOfValues() - { -#if XUNIT_AOT - var expected = $"[{ArgumentFormatter.Ellipsis}]"; -#else - var expected = "[True] = [0, 2, 4, 6, 8]"; -#endif - - var grouping = Enumerable.Range(0, 10).GroupBy(i => i % 2 == 0).FirstOrDefault(g => g.Key == true); - - Assert.Equal(expected, ArgumentFormatter.Format(grouping)); - } - - [Fact] - public static void GroupingsAreRenderedAsCollectionsOfKeysLinkedToCollectionsOfValues() - { -#if XUNIT_AOT - var expected = $"[{ArgumentFormatter.Ellipsis}]"; -#else - var expected = "[[True] = [0, 2, 4, 6, 8], [False] = [1, 3, 5, 7, 9]]"; -#endif - - var grouping = Enumerable.Range(0, 10).GroupBy(i => i % 2 == 0); - - Assert.Equal(expected, ArgumentFormatter.Format(grouping)); + Assert.Equal($"[42, [42, [{ArgumentFormatter.Ellipsis}]]]", ArgumentFormatter.Format(looping)); } } public class ComplexTypes { - [CulturedFactDefault] - public static void Empty() - { #if XUNIT_AOT - var expected = $"Object {{ {ArgumentFormatter.Ellipsis} }}"; + [Fact] #else - var expected = "Object { }"; + [CulturedFact] #endif - - var result = ArgumentFormatter.Format(new object()); - - Assert.Equal(expected, result); - } - -#if !XUNIT_AOT // Native AOT cannot render properties and fields of object - - [CulturedFactDefault] public static void ReturnsValuesInAlphabeticalOrder() { var expected = $"MyComplexType {{ MyPublicField = 42, MyPublicProperty = {21.12M} }}"; - var result = ArgumentFormatter.Format(new MyComplexType()); - - Assert.Equal(expected, result); + Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexType())); } public class MyComplexType { -#pragma warning disable CS0414 - readonly string MyPrivateField = "Hello, world"; -#pragma warning restore CS0414 +#pragma warning disable 414 + private string MyPrivateField = "Hello, world"; +#pragma warning restore 414 public static int MyPublicStaticField = 2112; @@ -400,14 +400,16 @@ public MyComplexType() } } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void ComplexTypeInsideComplexType() { var expected = $"MyComplexTypeWrapper {{ c = 'A', s = \"Hello, world!\", t = MyComplexType {{ MyPublicField = 42, MyPublicProperty = {21.12M} }} }}"; - var result = ArgumentFormatter.Format(new MyComplexTypeWrapper()); - - Assert.Equal(expected, result); + Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexTypeWrapper())); } public class MyComplexTypeWrapper @@ -417,12 +419,24 @@ public class MyComplexTypeWrapper public string s = "Hello, world!"; } - [CulturedFactDefault] - public static void WithThrowingPropertyGetter() +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif + public static void Empty() { - var result = ArgumentFormatter.Format(new ThrowingGetter()); + Assert.Equal("Object { }", ArgumentFormatter.Format(new object())); + } - Assert.Equal("ThrowingGetter { MyThrowingProperty = (throws NotImplementedException) }", result); +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif + public static void WithThrowingPropertyGetter() + { + Assert.Equal("ThrowingGetter { MyThrowingProperty = (throws NotImplementedException) }", ArgumentFormatter.Format(new ThrowingGetter())); } public class ThrowingGetter @@ -430,14 +444,16 @@ public class ThrowingGetter public string MyThrowingProperty { get { throw new NotImplementedException(); } } } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void LimitsOutputToFirstFewValues() { - var expected = $@"Big {{ MyField1 = 42, MyField2 = ""Hello, world!"", MyProp1 = {21.12}, MyProp2 = typeof({typeof(Big).FullName}), MyProp3 = 2014-04-17T07:45:23.0000000+00:00, {ArgumentFormatter.Ellipsis} }}"; + var expected = $@"Big {{ MyField1 = 42, MyField2 = ""Hello, world!"", MyProp1 = {21.12}, MyProp2 = typeof(ArgumentFormatterTests+ComplexTypes+Big), MyProp3 = 2014-04-17T07:45:23.0000000+00:00, {ArgumentFormatter.Ellipsis} }}"; - var result = ArgumentFormatter.Format(new Big()); - - Assert.Equal(expected, result); + Assert.Equal(expected, ArgumentFormatter.Format(new Big())); } public class Big @@ -463,14 +479,14 @@ public Big() } } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void TypesAreRenderedWithMaximumDepthToPreventInfiniteRecursion() { - var expected = $"Looping {{ Me = Looping {{ Me = Looping {{ {ArgumentFormatter.Ellipsis} }} }} }}"; - - var result = ArgumentFormatter.Format(new Looping()); - - Assert.Equal(expected, result); + Assert.Equal($"Looping {{ Me = Looping {{ Me = Looping {{ {ArgumentFormatter.Ellipsis} }} }} }}", ArgumentFormatter.Format(new Looping())); } public class Looping @@ -480,40 +496,10 @@ public class Looping public Looping() => Me = this; } - [CulturedFactDefault] - public static void RecursionViaCollectionsIsPreventedWithMaximumDepth() - { - var expected = $"LoopingChild {{ Parent = LoopingParent {{ ProjectsById = [{ArgumentFormatter.Ellipsis}] }} }}"; - - var result = ArgumentFormatter.Format(new LoopingChild(new LoopingParent())); - - Assert.Equal(expected, result); - } - - public class LoopingParent - { - public Dictionary ProjectsById { get; } = []; - } - - public class LoopingChild - { - public LoopingParent Parent; - - public LoopingChild(LoopingParent parent) - { - parent.ProjectsById[Guid.NewGuid().ToString()] = this; - Parent = parent; - } - } - -#endif // !XUNIT_AOT - [Fact] public static void WhenCustomTypeImplementsToString_UsesToString() { - var result = ArgumentFormatter.Format(new TypeWithToString()); - - Assert.Equal("This is what you should show", result); + Assert.Equal("This is what you should show", ArgumentFormatter.Format(new TypeWithToString())); } public class TypeWithToString diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/CollectionTrackerTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Sdk/CollectionTrackerTests.cs similarity index 82% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/CollectionTrackerTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/Sdk/CollectionTrackerTests.cs index 9cd6e544cef..261277e3844 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/Sdk/CollectionTrackerTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/Sdk/CollectionTrackerTests.cs @@ -1,6 +1,11 @@ +using System.Collections.Generic; using Xunit; using Xunit.Sdk; +#if XUNIT_SPAN +using System; +#endif + public class CollectionTrackerTests { public class FormatIndexedMismatch_IEnumerable @@ -10,7 +15,7 @@ public static void ExceededDepth() { var tracker = new[] { 42, 2112 }.AsTracker(); - var result = tracker.FormatIndexedMismatch(2600, out var pointerIndent, ArgumentFormatter.MaxEnumerableLength + 1); + var result = tracker.FormatIndexedMismatch(2600, out var pointerIndent, ArgumentFormatter.MAX_DEPTH); Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", result); // - ^ @@ -66,6 +71,7 @@ public static void LargeCollection_End() } } +#if XUNIT_SPAN public class FormatIndexedMismatch_Span { [Fact] @@ -73,7 +79,7 @@ public static void ExceededDepth() { var span = new[] { 42, 2112 }.AsSpan(); - var result = CollectionTracker.FormatIndexedMismatch(span, 2600, out var pointerIndent, ArgumentFormatter.MaxEnumerableLength + 1); + var result = CollectionTracker.FormatIndexedMismatch(span, 2600, out var pointerIndent, ArgumentFormatter.MAX_DEPTH); Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", result); // - ^ @@ -128,13 +134,14 @@ public static void LargeCollection_End() Assert.Equal(18, pointerIndent); } } +#endif public class FormatStart_IEnumerable_Tracked { [Fact] public static void Empty() { - var tracker = Array.Empty().AsTracker(); + var tracker = new object[0].AsTracker(); Assert.Equal("[]", tracker.FormatStart()); } @@ -142,12 +149,16 @@ public static void Empty() [Fact] public static void ExceededDepth() { - var tracker = Array.Empty().AsTracker(); + var tracker = new object[0].AsTracker(); - Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", tracker.FormatStart(ArgumentFormatter.MaxEnumerableLength + 1)); + Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", tracker.FormatStart(ArgumentFormatter.MAX_DEPTH)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Short() { var tracker = new object[] { 1, 2.3M, "Hello, world!" }.AsTracker(); @@ -155,10 +166,14 @@ public static void Short() Assert.Equal($"[1, {2.3M}, \"Hello, world!\"]", tracker.FormatStart()); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Long() { - var tracker = new object[] { 1, 2.3M, "Hello, world!", 42, 2112, new() }.AsTracker(); + var tracker = new object[] { 1, 2.3M, "Hello, world!", 42, 2112, new object() }.AsTracker(); Assert.Equal($"[1, {2.3M}, \"Hello, world!\", 42, 2112, {ArgumentFormatter.Ellipsis}]", tracker.FormatStart()); } @@ -169,7 +184,7 @@ public class FormatStart_IEnumerable_Untracked [Fact] public static void Empty() { - IEnumerable collection = []; + IEnumerable collection = new object[0]; Assert.Equal("[]", CollectionTracker.FormatStart(collection)); } @@ -177,34 +192,43 @@ public static void Empty() [Fact] public static void ExceededDepth() { - IEnumerable collection = []; + IEnumerable collection = new object[0]; - Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(collection, ArgumentFormatter.MaxEnumerableLength + 1)); + Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(collection, ArgumentFormatter.MAX_DEPTH)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Short() { - IEnumerable collection = [1, 2.3M, "Hello, world!"]; + IEnumerable collection = new object[] { 1, 2.3M, "Hello, world!" }; Assert.Equal($"[1, {2.3M}, \"Hello, world!\"]", CollectionTracker.FormatStart(collection)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Long() { - IEnumerable collection = [1, 2.3M, "Hello, world!", 42, 2112, new object()]; + IEnumerable collection = new object[] { 1, 2.3M, "Hello, world!", 42, 2112, new object() }; Assert.Equal($"[1, {2.3M}, \"Hello, world!\", 42, 2112, {ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(collection)); } } +#if XUNIT_SPAN public class FormatStart_Span { [Fact] public static void Empty() { - var span = Array.Empty().AsSpan(); + var span = new object[0].AsSpan(); Assert.Equal("[]", CollectionTracker.FormatStart(span)); } @@ -212,12 +236,16 @@ public static void Empty() [Fact] public static void ExceededDepth() { - var span = Array.Empty().AsSpan(); + var span = new object[0].AsSpan(); - Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(span, ArgumentFormatter.MaxEnumerableLength + 1)); + Assert.Equal($"[{ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(span, ArgumentFormatter.MAX_DEPTH)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Short() { var span = new object[] { 1, 2.3M, "Hello, world!" }.AsSpan(); @@ -225,12 +253,17 @@ public static void Short() Assert.Equal($"[1, {2.3M}, \"Hello, world!\"]", CollectionTracker.FormatStart(span)); } - [CulturedFactDefault] +#if XUNIT_AOT + [Fact] +#else + [CulturedFact] +#endif public static void Long() { - var span = new object[] { 1, 2.3M, "Hello, world!", 42, 2112, new() }.AsSpan(); + var span = new object[] { 1, 2.3M, "Hello, world!", 42, 2112, new object() }.AsSpan(); Assert.Equal($"[1, {2.3M}, \"Hello, world!\", 42, 2112, {ArgumentFormatter.Ellipsis}]", CollectionTracker.FormatStart(span)); } } +#endif } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SetAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/SetAssertsTests.cs similarity index 95% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SetAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/SetAssertsTests.cs index 2c61adefe63..18cdc672fd1 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SetAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/SetAssertsTests.cs @@ -1,7 +1,12 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Generic; using Xunit; using Xunit.Sdk; +#if XUNIT_IMMUTABLE_COLLECTIONS +using System.Collections.Immutable; +#endif + public class SetAssertsTests { public class Contains @@ -14,11 +19,13 @@ public static void ValueInSet() Assert.Contains("FORTY-two", set); Assert.Contains("FORTY-two", (ISet)set); Assert.Contains("FORTY-two", set.ToSortedSet(StringComparer.OrdinalIgnoreCase)); -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER Assert.Contains("FORTY-two", (IReadOnlySet)set); #endif +#if XUNIT_IMMUTABLE_COLLECTIONS Assert.Contains("FORTY-two", set.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)); Assert.Contains("FORTY-two", set.ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase)); +#endif } [Fact] @@ -26,7 +33,7 @@ public static void ValueNotInSet() { var set = new HashSet() { "eleventeen" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -42,11 +49,13 @@ static void assertFailure(Action action) assertFailure(() => Assert.Contains("FORTY-two", set)); assertFailure(() => Assert.Contains("FORTY-two", (ISet)set)); assertFailure(() => Assert.Contains("FORTY-two", set.ToSortedSet())); -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER assertFailure(() => Assert.Contains("FORTY-two", (IReadOnlySet)set)); #endif +#if XUNIT_IMMUTABLE_COLLECTIONS assertFailure(() => Assert.Contains("FORTY-two", set.ToImmutableHashSet())); assertFailure(() => Assert.Contains("FORTY-two", set.ToImmutableSortedSet())); +#endif } } @@ -60,11 +69,13 @@ public static void ValueNotInSet() Assert.DoesNotContain("FORTY-two", set); Assert.DoesNotContain("FORTY-two", (ISet)set); Assert.DoesNotContain("FORTY-two", set.ToSortedSet()); -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER Assert.DoesNotContain("FORTY-two", (IReadOnlySet)set); #endif +#if XUNIT_IMMUTABLE_COLLECTIONS Assert.DoesNotContain("FORTY-two", set.ToImmutableHashSet()); Assert.DoesNotContain("FORTY-two", set.ToImmutableSortedSet()); +#endif } [Fact] @@ -72,7 +83,7 @@ public static void ValueInSet() { var set = new HashSet(StringComparer.OrdinalIgnoreCase) { "forty-two" }; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -88,11 +99,13 @@ static void assertFailure(Action action) assertFailure(() => Assert.DoesNotContain("FORTY-two", set)); assertFailure(() => Assert.DoesNotContain("FORTY-two", (ISet)set)); assertFailure(() => Assert.DoesNotContain("FORTY-two", set.ToSortedSet(StringComparer.OrdinalIgnoreCase))); -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER assertFailure(() => Assert.DoesNotContain("FORTY-two", (IReadOnlySet)set)); #endif +#if XUNIT_IMMUTABLE_COLLECTIONS assertFailure(() => Assert.DoesNotContain("FORTY-two", set.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase))); assertFailure(() => Assert.DoesNotContain("FORTY-two", set.ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase))); +#endif } } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/SetExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/SetExtensions.cs new file mode 100644 index 00000000000..879c07a6e28 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/SetExtensions.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; + +internal static class SetExtensions +{ + public static SortedSet ToSortedSet(this ISet set, IComparer? comparer = null) => + new SortedSet(set, comparer); +} diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/SkipAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/SkipAssertsTests.cs new file mode 100644 index 00000000000..335b961f718 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/SkipAssertsTests.cs @@ -0,0 +1,115 @@ +#if XUNIT_SKIP + +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.v3; + +public class SkipAssertsTests +{ + public class Skip : AcceptanceTestV3 + { + [Fact] + public void GuardClause() + { + Assert.Throws("reason", () => Assert.Skip(null!)); + } + + [Fact] + public async Task AcceptanceTest() + { + var results = await RunAsync(typeof(ClassUnderTest)); + + var skipResult = Assert.Single(results.OfType<_TestSkipped>()); + Assert.Equal("This test was skipped", skipResult.Reason); + } + + class ClassUnderTest + { + [Fact] + public void Unconditional() + { + Assert.Skip("This test was skipped"); + } + } + } + + public class SkipUnless : AcceptanceTestV3 + { + [Fact] + public void GuardClause() + { + Assert.Throws("reason", () => Assert.SkipUnless(true, null!)); + } + + [Fact] + public async Task AcceptanceTest() + { + var results = await RunAsync(typeof(ClassUnderTest)); + + var skipResult = Assert.Single(results.OfType<_TestSkipped>()); + var skipMethodStarting = Assert.Single(results.OfType<_TestMethodStarting>().Where(s => s.TestMethodUniqueID == skipResult.TestMethodUniqueID)); + Assert.Equal("Skipped", skipMethodStarting.TestMethod); + Assert.Equal("This test was skipped", skipResult.Reason); + var passResult = Assert.Single(results.OfType<_TestPassed>()); + var passMethodStarting = results.OfType<_TestMethodStarting>().Where(ts => ts.TestMethodUniqueID == passResult.TestMethodUniqueID).Single(); + Assert.Equal("Passed", passMethodStarting.TestMethod); + } + + class ClassUnderTest + { + [Fact] + public void Skipped() + { + Assert.SkipUnless(false, "This test was skipped"); + } + + [Fact] + public void Passed() + { + Assert.SkipUnless(true, "This test is not skipped"); + } + } + } + + public class SkipWhen : AcceptanceTestV3 + { + [Fact] + public void GuardClause() + { + Assert.Throws("reason", () => Assert.SkipWhen(true, null!)); + } + + [Fact] + public async Task AcceptanceTest() + { + var results = await RunAsync(typeof(ClassUnderTest)); + + var skipResult = Assert.Single(results.OfType<_TestSkipped>()); + var skipMethodStarting = Assert.Single(results.OfType<_TestMethodStarting>().Where(s => s.TestMethodUniqueID == skipResult.TestMethodUniqueID)); + Assert.Equal("Skipped", skipMethodStarting.TestMethod); + Assert.Equal("This test was skipped", skipResult.Reason); + var passResult = Assert.Single(results.OfType<_TestPassed>()); + var passMethodStarting = results.OfType<_TestMethodStarting>().Where(ts => ts.TestMethodUniqueID == passResult.TestMethodUniqueID).Single(); + Assert.Equal("Passed", passMethodStarting.TestMethod); + } + + class ClassUnderTest + { + [Fact] + public void Skipped() + { + Assert.SkipWhen(true, "This test was skipped"); + } + + [Fact] + public void Passed() + { + Assert.SkipWhen(false, "This test is not skipped"); + } + } + } +} + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SpanAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/SpanAssertsTests.cs similarity index 93% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SpanAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/SpanAssertsTests.cs index 8808af0265d..c58e2540a67 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/SpanAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/SpanAssertsTests.cs @@ -1,3 +1,8 @@ +#if XUNIT_SPAN + +using System; +using System.Collections.Generic; +using System.Linq; using Xunit; using Xunit.Sdk; @@ -100,8 +105,8 @@ public void VeryLongStrings() Assert.IsType(ex); Assert.Equal( "Assert.Contains() Failure: Sub-string not found" + Environment.NewLine + - $"String: \"This is a relatively long string so that we can se\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + - "Not found: \"We are looking for something very long as well\"", + $"String: \"This is a relatively long string so that \"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"Not found: \"We are looking for something very long as\"{ArgumentFormatter.Ellipsis}", ex.Message ); } @@ -293,7 +298,7 @@ public void VeryLongString_FoundAtFront() Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + " ↓ (pos 7)" + Environment.NewLine + - $"String: \"Hello, world from a very long string that will end\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"String: \"Hello, world from a very long string that\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -307,8 +312,8 @@ public void VeryLongString_FoundInMiddle() Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - $"String: {ArgumentFormatter.Ellipsis}\" string that has 'Hello, world' placed in the midd\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + " ↓ (pos 50)" + Environment.NewLine + + $"String: {ArgumentFormatter.Ellipsis}\"ng that has 'Hello, world' placed in the \"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -322,8 +327,8 @@ public void VeryLongString_FoundAtEnd() Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 89)" + Environment.NewLine + - $"String: {ArgumentFormatter.Ellipsis}\"om the front truncated, just to say 'Hello, world'\"" + Environment.NewLine + + " ↓ (pos 89)" + Environment.NewLine + + $"String: {ArgumentFormatter.Ellipsis}\"ont truncated, just to say 'Hello, world'\"" + Environment.NewLine + "Found: \"world\"", ex.Message ); @@ -425,7 +430,7 @@ public void Success() [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -447,7 +452,7 @@ static void assertFailure(Action action) [Fact] public void CaseSensitiveByDefault() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -478,7 +483,7 @@ public void CanSpecifyComparisonType() [Fact] public void NullStringIsEmpty() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -501,15 +506,15 @@ public void Truncation() var expected = "This is a long string that we're looking for at the end"; var actual = "This is the long string that we expected to find this ending inside"; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.EndsWith() Failure: String end does not match" + Environment.NewLine + - "String: " + ArgumentFormatter.Ellipsis + "\"string that we expected to find this ending inside\"" + Environment.NewLine + - "Expected end: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: " + ArgumentFormatter.Ellipsis + "\"at we expected to find this ending inside\"" + Environment.NewLine + + "Expected end: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } @@ -649,17 +654,17 @@ void assertFailure(Action action) [Fact] public void Truncation() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 21)" + Environment.NewLine + - $"Expected: \"Why hello there world, you're a long string with s\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + - "Actual: \"Why hello there world! You're a long string!\"" + Environment.NewLine + - " ↑ (pos 21)", + " ↓ (pos 21)" + Environment.NewLine + + $"Expected: {ArgumentFormatter.Ellipsis}\"hy hello there world, you're a long strin\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"Actual: {ArgumentFormatter.Ellipsis}\"hy hello there world! You're a long strin\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + " ↑ (pos 21)", ex.Message ); } @@ -689,14 +694,6 @@ static void assertFailure(Action action) ) ); } - - // https://github.com/xunit/xunit/discussions/3021 - [Fact] - public void ArrayOverload() - { - string[] str = ["hello"]; - Assert.Equal(["hello"], str); - } } public class Ints @@ -734,7 +731,7 @@ public void Success( [Fact] public void Failure_MidCollection() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -758,7 +755,7 @@ static void assertFailure(Action action) [Fact] public void Failure_BeyondEnd() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -814,7 +811,7 @@ public void Success( [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -850,7 +847,7 @@ public void Success() [Fact] public void Failure() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -872,7 +869,7 @@ static void assertFailure(Action action) [Fact] public void CaseSensitiveByDefault() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -903,7 +900,7 @@ public void CanSpecifyComparisonType() [Fact] public void NullStringIsEmpty() { - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); @@ -926,15 +923,15 @@ public void Truncation() var expected = "This is a long string that we're looking for at the start"; var actual = "This is the long string that we expected to find this starting inside"; - static void assertFailure(Action action) + void assertFailure(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.StartsWith() Failure: String start does not match" + Environment.NewLine + - "String: \"This is the long string that we expected to find t\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + - "Expected start: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: \"This is the long string that we expected \"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "Expected start: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } @@ -946,3 +943,5 @@ static void assertFailure(Action action) } } } + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/SpanExtensions.cs b/src/Microsoft.DotNet.XUnitAssert/tests/SpanExtensions.cs new file mode 100644 index 00000000000..d88c72c4522 --- /dev/null +++ b/src/Microsoft.DotNet.XUnitAssert/tests/SpanExtensions.cs @@ -0,0 +1,18 @@ +#if XUNIT_SPAN + +using System; + +internal static class SpanExtensions +{ + public static Span Spanify(this T[]? values) + { + return new Span(values); + } + + public static Span Spanify(this string? value) + { + return new Span((value ?? string.Empty).ToCharArray()); + } +} + +#endif diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/StringAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/StringAssertsTests.cs similarity index 72% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/StringAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/StringAssertsTests.cs index 7de6447a3c3..6f106822484 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/StringAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/StringAssertsTests.cs @@ -1,5 +1,4 @@ -#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. - +using System; using System.Text.RegularExpressions; using Xunit; using Xunit.Sdk; @@ -12,6 +11,7 @@ public class Contains public void CanSearchForSubstrings() { Assert.Contains("wor", "Hello, world!"); +#if XUNIT_SPAN Assert.Contains("wor".Memoryify(), "Hello, world!".Memoryify()); Assert.Contains("wor".AsMemory(), "Hello, world!".Memoryify()); Assert.Contains("wor".Memoryify(), "Hello, world!".AsMemory()); @@ -20,12 +20,13 @@ public void CanSearchForSubstrings() Assert.Contains("wor".AsSpan(), "Hello, world!".Spanify()); Assert.Contains("wor".Spanify(), "Hello, world!".AsSpan()); Assert.Contains("wor".AsSpan(), "Hello, world!".AsSpan()); +#endif } [Fact] public void SubstringContainsIsCaseSensitiveByDefault() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -39,6 +40,7 @@ static void verify(Action action) } verify(() => Assert.Contains("WORLD", "Hello, world!")); +#if XUNIT_SPAN verify(() => Assert.Contains("WORLD".Memoryify(), "Hello, world!".Memoryify())); verify(() => Assert.Contains("WORLD".AsMemory(), "Hello, world!".Memoryify())); verify(() => Assert.Contains("WORLD".Memoryify(), "Hello, world!".AsMemory())); @@ -47,12 +49,13 @@ static void verify(Action action) verify(() => Assert.Contains("WORLD".AsSpan(), "Hello, world!".Spanify())); verify(() => Assert.Contains("WORLD".Spanify(), "Hello, world!".AsSpan())); verify(() => Assert.Contains("WORLD".AsSpan(), "Hello, world!".AsSpan())); +#endif } [Fact] public void SubstringNotFound() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -66,6 +69,7 @@ static void verify(Action action) } verify(() => Assert.Contains("hey", "Hello, world!")); +#if XUNIT_SPAN verify(() => Assert.Contains("hey".Memoryify(), "Hello, world!".Memoryify())); verify(() => Assert.Contains("hey".AsMemory(), "Hello, world!".Memoryify())); verify(() => Assert.Contains("hey".Memoryify(), "Hello, world!".AsMemory())); @@ -74,6 +78,7 @@ static void verify(Action action) verify(() => Assert.Contains("hey".AsSpan(), "Hello, world!".Spanify())); verify(() => Assert.Contains("hey".Spanify(), "Hello, world!".AsSpan())); verify(() => Assert.Contains("hey".AsSpan(), "Hello, world!".AsSpan())); +#endif } [Fact] @@ -93,23 +98,24 @@ public void NullActualStringThrows() [Fact] public void VeryLongStrings() { - var expected = "We are looking for something that is actually very long as well"; + var expected = "We are looking for something very long as well"; var actual = "This is a relatively long string so that we can see the truncation in action"; - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Contains() Failure: Sub-string not found" + Environment.NewLine + - $"String: \"This is a relatively long string so that we can se\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + - $"Not found: \"We are looking for something that is actually very\"{ArgumentFormatter.Ellipsis}", + $"String: \"This is a relatively long string so that \"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"Not found: \"We are looking for something very long as\"{ArgumentFormatter.Ellipsis}", ex.Message ); } verify(() => Assert.Contains(expected, actual)); +#if XUNIT_SPAN verify(() => Assert.Contains(expected.Memoryify(), actual.Memoryify())); verify(() => Assert.Contains(expected.AsMemory(), actual.Memoryify())); verify(() => Assert.Contains(expected.Memoryify(), actual.AsMemory())); @@ -118,12 +124,14 @@ static void verify(Action action) verify(() => Assert.Contains(expected.AsSpan(), actual.Spanify())); verify(() => Assert.Contains(expected.Spanify(), actual.AsSpan())); verify(() => Assert.Contains(expected.AsSpan(), actual.AsSpan())); +#endif } [Fact] public void CanSearchForSubstringsCaseInsensitive() { Assert.Contains("WORLD", "Hello, world!", StringComparison.OrdinalIgnoreCase); +#if XUNIT_SPAN Assert.Contains("WORLD".Memoryify(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.Contains("WORLD".AsMemory(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.Contains("WORLD".Memoryify(), "Hello, world!".AsMemory(), StringComparison.OrdinalIgnoreCase); @@ -132,6 +140,7 @@ public void CanSearchForSubstringsCaseInsensitive() Assert.Contains("WORLD".AsSpan(), "Hello, world!".Spanify(), StringComparison.OrdinalIgnoreCase); Assert.Contains("WORLD".Spanify(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); Assert.Contains("WORLD".AsSpan(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); +#endif } } @@ -141,6 +150,7 @@ public class DoesNotContain public void CanSearchForSubstrings() { Assert.DoesNotContain("hey", "Hello, world!"); +#if XUNIT_SPAN Assert.DoesNotContain("hey".Memoryify(), "Hello, world!".Memoryify()); Assert.DoesNotContain("hey".AsMemory(), "Hello, world!".Memoryify()); Assert.DoesNotContain("hey".Memoryify(), "Hello, world!".AsMemory()); @@ -149,12 +159,14 @@ public void CanSearchForSubstrings() Assert.DoesNotContain("hey".AsSpan(), "Hello, world!".Spanify()); Assert.DoesNotContain("hey".Spanify(), "Hello, world!".AsSpan()); Assert.DoesNotContain("hey".AsSpan(), "Hello, world!".AsSpan()); +#endif } [Fact] public void SubstringDoesNotContainIsCaseSensitiveByDefault() { Assert.DoesNotContain("WORLD", "Hello, world!"); +#if XUNIT_SPAN Assert.DoesNotContain("WORLD".Memoryify(), "Hello, world!".Memoryify()); Assert.DoesNotContain("WORLD".AsMemory(), "Hello, world!".Memoryify()); Assert.DoesNotContain("WORLD".Memoryify(), "Hello, world!".AsMemory()); @@ -163,12 +175,13 @@ public void SubstringDoesNotContainIsCaseSensitiveByDefault() Assert.DoesNotContain("WORLD".AsSpan(), "Hello, world!".Spanify()); Assert.DoesNotContain("WORLD".Spanify(), "Hello, world!".AsSpan()); Assert.DoesNotContain("WORLD".AsSpan(), "Hello, world!".AsSpan()); +#endif } [Fact] public void SubstringFound() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -183,6 +196,7 @@ static void verify(Action action) } verify(() => Assert.DoesNotContain("world", "Hello, world!")); +#if XUNIT_SPAN verify(() => Assert.DoesNotContain("world".Memoryify(), "Hello, world!".Memoryify())); verify(() => Assert.DoesNotContain("world".AsMemory(), "Hello, world!".Memoryify())); verify(() => Assert.DoesNotContain("world".Memoryify(), "Hello, world!".AsMemory())); @@ -191,6 +205,7 @@ static void verify(Action action) verify(() => Assert.DoesNotContain("world".AsSpan(), "Hello, world!".Spanify())); verify(() => Assert.DoesNotContain("world".Spanify(), "Hello, world!".AsSpan())); verify(() => Assert.DoesNotContain("world".AsSpan(), "Hello, world!".AsSpan())); +#endif } [Fact] @@ -202,7 +217,7 @@ public void NullActualStringDoesNotThrow() [Fact] public void VeryLongString_FoundAtFront() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -210,13 +225,14 @@ static void verify(Action action) Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + " ↓ (pos 7)" + Environment.NewLine + - $"String: \"Hello, world from a very long string that will end\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"String: \"Hello, world from a very long string that\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + "Found: \"world\"", ex.Message ); } verify(() => Assert.DoesNotContain("world", "Hello, world from a very long string that will end up being truncated")); +#if XUNIT_SPAN verify(() => Assert.DoesNotContain("world".Memoryify(), "Hello, world from a very long string that will end up being truncated".Memoryify())); verify(() => Assert.DoesNotContain("world".AsMemory(), "Hello, world from a very long string that will end up being truncated".Memoryify())); verify(() => Assert.DoesNotContain("world".Memoryify(), "Hello, world from a very long string that will end up being truncated".AsMemory())); @@ -225,26 +241,28 @@ static void verify(Action action) verify(() => Assert.DoesNotContain("world".AsSpan(), "Hello, world from a very long string that will end up being truncated".Spanify())); verify(() => Assert.DoesNotContain("world".Spanify(), "Hello, world from a very long string that will end up being truncated".AsSpan())); verify(() => Assert.DoesNotContain("world".AsSpan(), "Hello, world from a very long string that will end up being truncated".AsSpan())); +#endif } [Fact] public void VeryLongString_FoundInMiddle() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - $"String: {ArgumentFormatter.Ellipsis}\" string that has 'Hello, world' placed in the midd\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + " ↓ (pos 50)" + Environment.NewLine + + $"String: {ArgumentFormatter.Ellipsis}\"ng that has 'Hello, world' placed in the \"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + "Found: \"world\"", ex.Message ); } verify(() => Assert.DoesNotContain("world", "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction")); +#if XUNIT_SPAN verify(() => Assert.DoesNotContain("world".Memoryify(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".Memoryify())); verify(() => Assert.DoesNotContain("world".AsMemory(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".Memoryify())); verify(() => Assert.DoesNotContain("world".Memoryify(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".AsMemory())); @@ -253,26 +271,28 @@ static void verify(Action action) verify(() => Assert.DoesNotContain("world".AsSpan(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".Spanify())); verify(() => Assert.DoesNotContain("world".Spanify(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".AsSpan())); verify(() => Assert.DoesNotContain("world".AsSpan(), "This is a relatively long string that has 'Hello, world' placed in the middle so that we can dual trunaction".AsSpan())); +#endif } [Fact] public void VeryLongString_FoundAtEnd() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.DoesNotContain() Failure: Sub-string found" + Environment.NewLine + - " ↓ (pos 89)" + Environment.NewLine + - $"String: {ArgumentFormatter.Ellipsis}\"om the front truncated, just to say 'Hello, world'\"" + Environment.NewLine + + " ↓ (pos 89)" + Environment.NewLine + + $"String: {ArgumentFormatter.Ellipsis}\"ont truncated, just to say 'Hello, world'\"" + Environment.NewLine + "Found: \"world\"", ex.Message ); } verify(() => Assert.DoesNotContain("world", "This is a relatively long string that will from the front truncated, just to say 'Hello, world'")); +#if XUNIT_SPAN verify(() => Assert.DoesNotContain("world".Memoryify(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".Memoryify())); verify(() => Assert.DoesNotContain("world".AsMemory(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".Memoryify())); verify(() => Assert.DoesNotContain("world".Memoryify(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".AsMemory())); @@ -281,12 +301,13 @@ static void verify(Action action) verify(() => Assert.DoesNotContain("world".AsSpan(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".Spanify())); verify(() => Assert.DoesNotContain("world".Spanify(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".AsSpan())); verify(() => Assert.DoesNotContain("world".AsSpan(), "This is a relatively long string that will from the front truncated, just to say 'Hello, world'".AsSpan())); +#endif } [Fact] public void CanSearchForSubstringsCaseInsensitive() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -301,6 +322,7 @@ static void verify(Action action) } verify(() => Assert.DoesNotContain("WORLD", "Hello, world!", StringComparison.OrdinalIgnoreCase)); +#if XUNIT_SPAN verify(() => Assert.DoesNotContain("WORLD".Memoryify(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase)); verify(() => Assert.DoesNotContain("WORLD".AsMemory(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase)); verify(() => Assert.DoesNotContain("WORLD".Memoryify(), "Hello, world!".AsMemory(), StringComparison.OrdinalIgnoreCase)); @@ -309,6 +331,7 @@ static void verify(Action action) verify(() => Assert.DoesNotContain("WORLD".AsSpan(), "Hello, world!".Spanify(), StringComparison.OrdinalIgnoreCase)); verify(() => Assert.DoesNotContain("WORLD".Spanify(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase)); verify(() => Assert.DoesNotContain("WORLD".AsSpan(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase)); +#endif } } @@ -377,7 +400,7 @@ public class Empty [Fact] public static void GuardClause() { - Assert.Throws("value", () => Assert.Empty(default!)); + Assert.Throws("value", () => Assert.Empty(default(string)!)); } [Fact] @@ -406,6 +429,7 @@ public class EndsWith public void Success() { Assert.EndsWith("world!", "Hello, world!"); +#if XUNIT_SPAN Assert.EndsWith("world!".Memoryify(), "Hello, world!".Memoryify()); Assert.EndsWith("world!".AsMemory(), "Hello, world!".Memoryify()); Assert.EndsWith("world!".Memoryify(), "Hello, world!".AsMemory()); @@ -414,12 +438,13 @@ public void Success() Assert.EndsWith("world!".AsSpan(), "Hello, world!".Spanify()); Assert.EndsWith("world!".Spanify(), "Hello, world!".AsSpan()); Assert.EndsWith("world!".AsSpan(), "Hello, world!".AsSpan()); +#endif } [Fact] public void Failure() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -433,6 +458,7 @@ static void verify(Action action) } verify(() => Assert.EndsWith("hey", "Hello, world!")); +#if XUNIT_SPAN verify(() => Assert.EndsWith("hey".Memoryify(), "Hello, world!".Memoryify())); verify(() => Assert.EndsWith("hey".AsMemory(), "Hello, world!".Memoryify())); verify(() => Assert.EndsWith("hey".Memoryify(), "Hello, world!".AsMemory())); @@ -441,12 +467,13 @@ static void verify(Action action) verify(() => Assert.EndsWith("hey".AsSpan(), "Hello, world!".Spanify())); verify(() => Assert.EndsWith("hey".Spanify(), "Hello, world!".AsSpan())); verify(() => Assert.EndsWith("hey".AsSpan(), "Hello, world!".AsSpan())); +#endif } [Fact] public void CaseSensitiveByDefault() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -460,6 +487,7 @@ static void verify(Action action) } verify(() => Assert.EndsWith("WORLD!", "world!")); +#if XUNIT_SPAN verify(() => Assert.EndsWith("WORLD!".Memoryify(), "world!".Memoryify())); verify(() => Assert.EndsWith("WORLD!".AsMemory(), "world!".Memoryify())); verify(() => Assert.EndsWith("WORLD!".Memoryify(), "world!".AsMemory())); @@ -468,12 +496,14 @@ static void verify(Action action) verify(() => Assert.EndsWith("WORLD!".AsSpan(), "world!".Spanify())); verify(() => Assert.EndsWith("WORLD!".Spanify(), "world!".AsSpan())); verify(() => Assert.EndsWith("WORLD!".AsSpan(), "world!".AsSpan())); +#endif } [Fact] public void CanSpecifyComparisonType() { Assert.EndsWith("WORLD!", "Hello, world!", StringComparison.OrdinalIgnoreCase); +#if XUNIT_SPAN Assert.EndsWith("WORLD!".Memoryify(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.EndsWith("WORLD!".AsMemory(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.EndsWith("WORLD!".Memoryify(), "Hello, world!".AsMemory(), StringComparison.OrdinalIgnoreCase); @@ -482,6 +512,7 @@ public void CanSpecifyComparisonType() Assert.EndsWith("WORLD!".AsSpan(), "Hello, world!".Spanify(), StringComparison.OrdinalIgnoreCase); Assert.EndsWith("WORLD!".Spanify(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); Assert.EndsWith("WORLD!".AsSpan(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); +#endif } [Fact] @@ -504,20 +535,21 @@ public void Truncation() var expected = "This is a long string that we're looking for at the end"; var actual = "This is the long string that we expected to find this ending inside"; - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.EndsWith() Failure: String end does not match" + Environment.NewLine + - "String: " + ArgumentFormatter.Ellipsis + "\"string that we expected to find this ending inside\"" + Environment.NewLine + - "Expected end: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: " + ArgumentFormatter.Ellipsis + "\"at we expected to find this ending inside\"" + Environment.NewLine + + "Expected end: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } verify(() => Assert.EndsWith(expected, actual)); +#if XUNIT_SPAN verify(() => Assert.EndsWith(expected.Memoryify(), actual.Memoryify())); verify(() => Assert.EndsWith(expected.AsMemory(), actual.Memoryify())); verify(() => Assert.EndsWith(expected.Memoryify(), actual.AsMemory())); @@ -526,6 +558,7 @@ static void verify(Action action) verify(() => Assert.EndsWith(expected.AsSpan(), actual.Spanify())); verify(() => Assert.EndsWith(expected.Spanify(), actual.AsSpan())); verify(() => Assert.EndsWith(expected.AsSpan(), actual.AsSpan())); +#endif } } @@ -548,6 +581,7 @@ public class Equal [InlineData(" ", "\t", false, false, true, false)] [InlineData(" \t", "\t ", false, false, true, false)] [InlineData(" ", "\t", false, false, true, false)] +#if XUNIT_SPAN [InlineData(" ", " \u180E", false, false, true, false)] [InlineData(" \u180E", "\u180E ", false, false, true, false)] [InlineData(" ", "\u180E", false, false, true, false)] @@ -563,6 +597,7 @@ public class Equal [InlineData("\u2007\u2008\u1680\t\u0009\u3000 ", " ", false, false, true, false)] [InlineData("\u1680", "\t", false, false, true, false)] [InlineData("\u1680", " ", false, false, true, false)] +#endif // All whitespace differences [InlineData("", " ", false, false, false, true)] [InlineData("", " ", false, false, true, true)] @@ -579,6 +614,7 @@ public void Success( // Run them in both directions, as the values should be interchangeable when they're equal Assert.Equal(value1, value2, ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); Assert.Equal(value2, value1, ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); +#if XUNIT_SPAN Assert.Equal(value1.Memoryify(), value2.Memoryify(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); Assert.Equal(value2.Memoryify(), value1.Memoryify(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); Assert.Equal(value1.AsMemory(), value2.Memoryify(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); @@ -595,6 +631,7 @@ public void Success( Assert.Equal(value2.Spanify(), value1.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); Assert.Equal(value1.AsSpan(), value2.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); Assert.Equal(value2.AsSpan(), value1.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace); +#endif } [Theory] @@ -646,6 +683,7 @@ void verify(Action action) } verify(() => Assert.Equal(expected, actual, ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace)); +#if XUNIT_SPAN if (expected is not null && actual is not null) { verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace)); @@ -657,6 +695,7 @@ void verify(Action action) verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace)); verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences, ignoreAllWhiteSpace)); } +#endif } [Fact] @@ -665,242 +704,23 @@ public void Truncation() var expected = "Why hello there world, you're a long string with some truncation!"; var actual = "Why hello there world! You're a long string!"; - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 21)" + Environment.NewLine + - $"Expected: \"Why hello there world, you're a long string with s\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + - "Actual: \"Why hello there world! You're a long string!\"" + Environment.NewLine + - " ↑ (pos 21)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify())); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify())); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory())); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory())); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify())); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify())); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan())); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan())); - } - - [Fact] - public void Overrun_LongerExpected() - { - var expected = "012345678901234567890123456789012345678901234567890123456789"; - var actual = "01234567890123456789012345678901234567890123456789"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789\"", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify())); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify())); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory())); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory())); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify())); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify())); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan())); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan())); - } - - [Fact] - public void Overrun_LongerExpected_IgnoreWhitespace() - { - var expected = " 012345678901234567890123456789012345678901234567890123456789"; - var actual = " 01234567890123456789012345678901234567890123456789"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 55)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789\"", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual, ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - } - - [Fact] - public void Overrun_LongerActual() - { - var expected = "01234567890123456789012345678901234567890123456789"; - var actual = "012345678901234567890123456789012345678901234567890123456789"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - " ↑ (pos 50)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify())); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify())); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory())); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory())); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify())); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify())); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan())); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan())); - } - - [Fact] - public void Overrun_LongerActual_IgnoreWhitespace() - { - var expected = " 01234567890123456789012345678901234567890123456789"; - var actual = " 012345678901234567890123456789012345678901234567890123456789"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"23456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - " ↑ (pos 55)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual, ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - } - - [Fact] - public void MismatchedAtEnd_LongerExpected() - { - var expected = "012345678901234567890123456789012345678901234567890123456789"; - var actual = "01234567890123456789012345678901234567890123456789_1234"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789_1234\"" + Environment.NewLine + - " ↑ (pos 50)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify())); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify())); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory())); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory())); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify())); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify())); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan())); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan())); - } - - [Fact] - public void MismatchedAtEnd_LongerExpected_IgnoreWhitespace() - { - var expected = " 012345678901234567890123456789012345678901234567890123456789"; - var actual = " 01234567890123456789012345678901234567890123456789_1234"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 55)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789_1234\"" + Environment.NewLine + - " ↑ (pos 53)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual, ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - } - - [Fact] - public void MismatchedAtEnd_LongerActual() - { - var expected = "01234567890123456789012345678901234567890123456789_1234"; - var actual = "012345678901234567890123456789012345678901234567890123456789"; - - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 50)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"0123456789012345678901234567890123456789_1234\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"01234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - " ↑ (pos 50)", + " ↓ (pos 21)" + Environment.NewLine + + $"Expected: {ArgumentFormatter.Ellipsis}\"hy hello there world, you're a long strin\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + $"Actual: {ArgumentFormatter.Ellipsis}\"hy hello there world! You're a long strin\"{ArgumentFormatter.Ellipsis}" + Environment.NewLine + + " ↑ (pos 21)", ex.Message ); } verify(() => Assert.Equal(expected, actual)); +#if XUNIT_SPAN verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify())); verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify())); verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory())); @@ -909,38 +729,7 @@ static void verify(Action action) verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify())); verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan())); verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan())); - } - - [Fact] - public void MismatchedAtEnd_LongerActual_IgnoreWhitespace() - { - var expected = " 01234567890123456789012345678901234567890123456789_1234"; - var actual = " 012345678901234567890123456789012345678901234567890123456789"; - - static void verify(Action action) - { - var ex = Record.Exception(action); - - Assert.IsType(ex); - Assert.Equal( - "Assert.Equal() Failure: Strings differ" + Environment.NewLine + - " ↓ (pos 53)" + Environment.NewLine + - $"Expected: {ArgumentFormatter.Ellipsis}\"23456789012345678901234567890123456789_1234\"" + Environment.NewLine + - $"Actual: {ArgumentFormatter.Ellipsis}\"234567890123456789012345678901234567890123456789\"" + Environment.NewLine + - " ↑ (pos 55)", - ex.Message - ); - } - - verify(() => Assert.Equal(expected, actual, ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.Memoryify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Memoryify(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsMemory(), actual.AsMemory(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.Spanify(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.Spanify(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); - verify(() => Assert.Equal(expected.AsSpan(), actual.AsSpan(), ignoreWhiteSpaceDifferences: true)); +#endif } } @@ -1042,6 +831,7 @@ public class StartsWith public void Success() { Assert.StartsWith("Hello", "Hello, world!"); +#if XUNIT_SPAN Assert.StartsWith("Hello".Memoryify(), "Hello, world!".Memoryify()); Assert.StartsWith("Hello".AsMemory(), "Hello, world!".Memoryify()); Assert.StartsWith("Hello".Memoryify(), "Hello, world!".AsMemory()); @@ -1050,12 +840,13 @@ public void Success() Assert.StartsWith("Hello".AsSpan(), "Hello, world!".Spanify()); Assert.StartsWith("Hello".Spanify(), "Hello, world!".AsSpan()); Assert.StartsWith("Hello".AsSpan(), "Hello, world!".AsSpan()); +#endif } [Fact] public void Failure() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -1069,6 +860,7 @@ static void verify(Action action) } verify(() => Assert.StartsWith("hey", "Hello, world!")); +#if XUNIT_SPAN verify(() => Assert.StartsWith("hey".Memoryify(), "Hello, world!".Memoryify())); verify(() => Assert.StartsWith("hey".AsMemory(), "Hello, world!".Memoryify())); verify(() => Assert.StartsWith("hey".Memoryify(), "Hello, world!".AsMemory())); @@ -1077,12 +869,13 @@ static void verify(Action action) verify(() => Assert.StartsWith("hey".AsSpan(), "Hello, world!".Spanify())); verify(() => Assert.StartsWith("hey".Spanify(), "Hello, world!".AsSpan())); verify(() => Assert.StartsWith("hey".AsSpan(), "Hello, world!".AsSpan())); +#endif } [Fact] public void CaseSensitiveByDefault() { - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); @@ -1096,6 +889,7 @@ static void verify(Action action) } verify(() => Assert.StartsWith("WORLD!", "world!")); +#if XUNIT_SPAN verify(() => Assert.StartsWith("WORLD!".Memoryify(), "world!".Memoryify())); verify(() => Assert.StartsWith("WORLD!".AsMemory(), "world!".Memoryify())); verify(() => Assert.StartsWith("WORLD!".Memoryify(), "world!".AsMemory())); @@ -1104,12 +898,14 @@ static void verify(Action action) verify(() => Assert.StartsWith("WORLD!".AsSpan(), "world!".Spanify())); verify(() => Assert.StartsWith("WORLD!".Spanify(), "world!".AsSpan())); verify(() => Assert.StartsWith("WORLD!".AsSpan(), "world!".AsSpan())); +#endif } [Fact] public void CanSpecifyComparisonType() { Assert.StartsWith("HELLO", "Hello, world!", StringComparison.OrdinalIgnoreCase); +#if XUNIT_SPAN Assert.StartsWith("HELLO".Memoryify(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.StartsWith("HELLO".AsMemory(), "Hello, world!".Memoryify(), StringComparison.OrdinalIgnoreCase); Assert.StartsWith("HELLO".Memoryify(), "Hello, world!".AsMemory(), StringComparison.OrdinalIgnoreCase); @@ -1118,12 +914,13 @@ public void CanSpecifyComparisonType() Assert.StartsWith("HELLO".AsSpan(), "Hello, world!".Spanify(), StringComparison.OrdinalIgnoreCase); Assert.StartsWith("HELLO".Spanify(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); Assert.StartsWith("HELLO".AsSpan(), "Hello, world!".AsSpan(), StringComparison.OrdinalIgnoreCase); +#endif } [Fact] public void NullStrings() { - var ex = Record.Exception(() => Assert.StartsWith(default(string), default)); + var ex = Record.Exception(() => Assert.StartsWith(default(string), default(string))); Assert.IsType(ex); Assert.Equal( @@ -1140,20 +937,21 @@ public void Truncation() var expected = "This is a long string that we're looking for at the start"; var actual = "This is the long string that we expected to find this starting inside"; - static void verify(Action action) + void verify(Action action) { var ex = Record.Exception(action); Assert.IsType(ex); Assert.Equal( "Assert.StartsWith() Failure: String start does not match" + Environment.NewLine + - "String: \"This is the long string that we expected to find t\"" + ArgumentFormatter.Ellipsis + Environment.NewLine + - "Expected start: \"This is a long string that we're looking for at th\"" + ArgumentFormatter.Ellipsis, + "String: \"This is the long string that we expected \"" + ArgumentFormatter.Ellipsis + Environment.NewLine + + "Expected start: \"This is a long string that we're looking \"" + ArgumentFormatter.Ellipsis, ex.Message ); } verify(() => Assert.StartsWith(expected, actual)); +#if XUNIT_SPAN verify(() => Assert.StartsWith(expected.Memoryify(), actual.Memoryify())); verify(() => Assert.StartsWith(expected.AsMemory(), actual.Memoryify())); verify(() => Assert.StartsWith(expected.Memoryify(), actual.AsMemory())); @@ -1162,6 +960,7 @@ static void verify(Action action) verify(() => Assert.StartsWith(expected.AsSpan(), actual.Spanify())); verify(() => Assert.StartsWith(expected.Spanify(), actual.AsSpan())); verify(() => Assert.StartsWith(expected.AsSpan(), actual.AsSpan())); +#endif } } } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/TypeAssertsTests.cs b/src/Microsoft.DotNet.XUnitAssert/tests/TypeAssertsTests.cs similarity index 65% rename from src/Microsoft.DotNet.XUnitAssert/tests/Asserts/TypeAssertsTests.cs rename to src/Microsoft.DotNet.XUnitAssert/tests/TypeAssertsTests.cs index 199db30ac5a..c993818fbcf 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Asserts/TypeAssertsTests.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/TypeAssertsTests.cs @@ -1,16 +1,17 @@ +using System; using Xunit; using Xunit.Sdk; #if NETFRAMEWORK +using System.Collections.Generic; +using System.IO; using System.Reflection; +using System.Threading.Tasks; using System.Xml; #endif public class TypeAssertsTests { - -#pragma warning disable xUnit2032 // Type assertions based on 'assignable from' are confusingly named - public class IsAssignableFrom_Generic { [Fact] @@ -80,7 +81,6 @@ public void IncompatibleType() } #pragma warning disable xUnit2007 // Do not use typeof expression to check the type - public class IsAssignableFrom_NonGeneric { [Fact] @@ -148,7 +148,6 @@ public void IncompatibleType() ); } } - #pragma warning restore xUnit2007 // Do not use typeof expression to check the type public class IsNotAssignableFrom_Generic @@ -277,8 +276,6 @@ public void IncompatibleType() } } -#pragma warning restore xUnit2032 // Type assertions based on 'assignable from' are confusingly named - public class IsNotType_Generic { [Fact] @@ -310,73 +307,7 @@ public void NullObject() } } - public class IsNotType_Generic_InexactMatch - { - [Fact] - public void NullObject() - { - Assert.IsNotType(null, exactMatch: false); - } - - [Fact] - public void SameType() - { - var ex = new InvalidCastException(); - - var result = Record.Exception(() => Assert.IsNotType(ex, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.InvalidCastException)" + Environment.NewLine + - "Actual: typeof(System.InvalidCastException)", - result.Message - ); - } - - [Fact] - public void BaseType() - { - var ex = new InvalidCastException(); - - var result = Record.Exception(() => Assert.IsNotType(ex, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.Exception)" + Environment.NewLine + - "Actual: typeof(System.InvalidCastException)", - result.Message - ); - } - - [Fact] - public void Interface() - { - var ex = new DisposableClass(); - -#pragma warning disable xUnit2018 // TODO: Temporary until xUnit2018 is updated for the new signatures - var result = Record.Exception(() => Assert.IsNotType(ex, exactMatch: false)); -#pragma warning restore xUnit2018 - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.IDisposable)" + Environment.NewLine + - "Actual: typeof(TypeAssertsTests+DisposableClass)", - result.Message - ); - } - - [Fact] - public void IncompatibleType() - { - Assert.IsNotType(new InvalidOperationException(), exactMatch: false); - } - } - #pragma warning disable xUnit2007 // Do not use typeof expression to check the type - public class IsNotType_NonGeneric { [Fact] @@ -407,70 +338,6 @@ public void NullObject() Assert.IsNotType(typeof(object), null); } } - - public class IsNotType_NonGeneric_InexactMatch - { - [Fact] - public void NullObject() - { - Assert.IsNotType(typeof(object), null, exactMatch: false); - } - - [Fact] - public void SameType() - { - var ex = new InvalidCastException(); - - var result = Record.Exception(() => Assert.IsNotType(typeof(InvalidCastException), ex, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.InvalidCastException)" + Environment.NewLine + - "Actual: typeof(System.InvalidCastException)", - result.Message - ); - } - - [Fact] - public void BaseType() - { - var ex = new InvalidCastException(); - - var result = Record.Exception(() => Assert.IsNotType(typeof(Exception), ex, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.Exception)" + Environment.NewLine + - "Actual: typeof(System.InvalidCastException)", - result.Message - ); - } - - [Fact] - public void Interface() - { - var ex = new DisposableClass(); - - var result = Record.Exception(() => Assert.IsNotType(typeof(IDisposable), ex, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsNotType() Failure: Value is a compatible type" + Environment.NewLine + - "Expected: typeof(System.IDisposable)" + Environment.NewLine + - "Actual: typeof(TypeAssertsTests+DisposableClass)", - result.Message - ); - } - - [Fact] - public void IncompatibleType() - { - Assert.IsNotType(typeof(InvalidCastException), new InvalidOperationException(), exactMatch: false); - } - } - #pragma warning restore xUnit2007 // Do not use typeof expression to check the type public class IsType_Generic : TypeAssertsTests @@ -544,78 +411,7 @@ public void NullObject() } } - public class IsType_Generic_InexactMatch - { - [Fact] - public void NullObject() - { - var result = Record.Exception(() => Assert.IsType(null, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsType() Failure: Value is null" + Environment.NewLine + - "Expected: typeof(object)" + Environment.NewLine + - "Actual: null", - result.Message - ); - } - - [Fact] - public void SameType() - { - var ex = new InvalidCastException(); - - Assert.IsType(ex, exactMatch: false); - } - - [Fact] - public void BaseType() - { - var ex = new InvalidCastException(); - - Assert.IsType(ex, exactMatch: false); - } - - [Fact] - public void Interface() - { - var ex = new DisposableClass(); - -#pragma warning disable xUnit2018 // TODO: Temporary until xUnit2018 is updated for the new signatures - Assert.IsType(ex, exactMatch: false); -#pragma warning restore xUnit2018 - } - - [Fact] - public void ReturnsCastObject() - { - var ex = new InvalidCastException(); - - var result = Assert.IsType(ex, exactMatch: false); - - Assert.Same(ex, result); - } - - [Fact] - public void IncompatibleType() - { - var result = - Record.Exception( - () => Assert.IsType(new InvalidOperationException(), exactMatch: false) - ); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsType() Failure: Value is an incompatible type" + Environment.NewLine + - "Expected: typeof(System.InvalidCastException)" + Environment.NewLine + - "Actual: typeof(System.InvalidOperationException)", - result.Message - ); - } - } - #pragma warning disable xUnit2007 // Do not use typeof expression to check the type - public class IsType_NonGeneric : TypeAssertsTests { [Fact] @@ -641,7 +437,6 @@ public void UnmatchedTypeThrows() } #if NETFRAMEWORK - [Fact] public async Task UnmatchedTypesWithIdenticalNamesShowAssemblies() { @@ -661,7 +456,6 @@ public async Task UnmatchedTypesWithIdenticalNamesShowAssemblies() result.Message ); } - #endif [Fact] @@ -678,75 +472,6 @@ public void NullObjectThrows() ); } } - - public class IsType_NonGeneric_InexactMatch - { - [Fact] - public void NullObject() - { - var result = Record.Exception(() => Assert.IsType(typeof(object), null, exactMatch: false)); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsType() Failure: Value is null" + Environment.NewLine + - "Expected: typeof(object)" + Environment.NewLine + - "Actual: null", - result.Message - ); - } - - [Fact] - public void SameType() - { - var ex = new InvalidCastException(); - - Assert.IsType(typeof(InvalidCastException), ex, exactMatch: false); - } - - [Fact] - public void BaseType() - { - var ex = new InvalidCastException(); - - Assert.IsType(typeof(Exception), ex, exactMatch: false); - } - - [Fact] - public void Interface() - { - var ex = new DisposableClass(); - - Assert.IsType(typeof(IDisposable), ex, exactMatch: false); - } - - [Fact] - public void ReturnsCastObject() - { - var ex = new InvalidCastException(); - - var result = Assert.IsType(ex, exactMatch: false); - - Assert.Same(ex, result); - } - - [Fact] - public void IncompatibleType() - { - var result = - Record.Exception( - () => Assert.IsType(typeof(InvalidCastException), new InvalidOperationException(), exactMatch: false) - ); - - Assert.IsType(result); - Assert.Equal( - "Assert.IsType() Failure: Value is an incompatible type" + Environment.NewLine + - "Expected: typeof(System.InvalidCastException)" + Environment.NewLine + - "Actual: typeof(System.InvalidOperationException)", - result.Message - ); - } - } - #pragma warning restore xUnit2007 // Do not use typeof expression to check the type class DisposableClass : IDisposable @@ -756,7 +481,6 @@ public void Dispose() } #if NETFRAMEWORK - class CSharpDynamicAssembly : CSharpAcceptanceTestAssembly { public CSharpDynamicAssembly() : @@ -773,6 +497,5 @@ public static async Task Create(string code) return assembly; } } - #endif } diff --git a/src/Microsoft.DotNet.XUnitAssert/tests/Utility/ReadOnlySet.cs b/src/Microsoft.DotNet.XUnitAssert/tests/Utility/ReadOnlySet.cs index a6987d3fdf1..45f31b8db2c 100644 --- a/src/Microsoft.DotNet.XUnitAssert/tests/Utility/ReadOnlySet.cs +++ b/src/Microsoft.DotNet.XUnitAssert/tests/Utility/ReadOnlySet.cs @@ -1,44 +1,28 @@ -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER using System.Collections; using System.Collections.Generic; -public class ReadOnlySet( - IEqualityComparer comparer, - params T[] items) : - IReadOnlySet +public class ReadOnlySet : IReadOnlySet { - readonly HashSet hashSet = new(items, comparer); - - public int Count => - hashSet.Count; - - public bool Contains(T item) => - hashSet.Contains(item); - - public IEnumerator GetEnumerator() => - hashSet.GetEnumerator(); - - public bool IsProperSubsetOf(IEnumerable other) => - hashSet.IsProperSubsetOf(other); - - public bool IsProperSupersetOf(IEnumerable other) => - hashSet.IsProperSupersetOf(other); - - public bool IsSubsetOf(IEnumerable other) => - hashSet.IsSubsetOf(other); - - public bool IsSupersetOf(IEnumerable other) => - hashSet.IsSupersetOf(other); - - public bool Overlaps(IEnumerable other) => - hashSet.Overlaps(other); - - public bool SetEquals(IEnumerable other) => - hashSet.SetEquals(other); - - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + readonly HashSet hashSet; + + public ReadOnlySet( + IEqualityComparer comparer, + params T[] items) => + hashSet = new HashSet(items, comparer); + + public int Count => hashSet.Count; + + public bool Contains(T item) => hashSet.Contains(item); + public IEnumerator GetEnumerator() => hashSet.GetEnumerator(); + public bool IsProperSubsetOf(IEnumerable other) => hashSet.IsProperSubsetOf(other); + public bool IsProperSupersetOf(IEnumerable other) => hashSet.IsProperSupersetOf(other); + public bool IsSubsetOf(IEnumerable other) => hashSet.IsSubsetOf(other); + public bool IsSupersetOf(IEnumerable other) => hashSet.IsSupersetOf(other); + public bool Overlaps(IEnumerable other) => hashSet.Overlaps(other); + public bool SetEquals(IEnumerable other) => hashSet.SetEquals(other); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } -#endif +#endif \ No newline at end of file diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj index 6609d1c6f64..68bdb062ee1 100644 --- a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Exe xunit.console true diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs index 1fe0d1b62ee..975dc6adada 100644 --- a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs @@ -56,13 +56,23 @@ internal static string[] GetDefaultProbeDirectories(Platform osPlatform, IEnviro return new string[] { packageDirectory }; } - string basePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string basePath; + if (osPlatform == Platform.Windows) + { + basePath = environment.GetEnvironmentVariable("USERPROFILE"); + } + else + { + basePath = environment.GetEnvironmentVariable("HOME"); + } + if (string.IsNullOrEmpty(basePath)) { return new string[] { string.Empty }; } return new string[] { Path.Combine(basePath, ".nuget", "packages") }; + } public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies) diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs index 819a1491dee..56015598f02 100644 --- a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs @@ -44,7 +44,12 @@ static List GetDefaultProbeDirectories(Platform osPlatform, IMessageSink results.Add(packageDirectory); else { - string basePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string basePath; + if (osPlatform == Platform.Windows) + basePath = Environment.GetEnvironmentVariable("USERPROFILE"); + else + basePath = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(basePath)) results.Add(Path.Combine(basePath, ".nuget", "packages")); } diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalFactAttribute.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalFactAttribute.cs index 42ca62c5659..a036ab78989 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalFactAttribute.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalFactAttribute.cs @@ -1,6 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + +// Not adding the support for xunit.v3. +// Still keeping the logic inside compatible with xunit.v3 in case we decided to add it. +// For cases that used to do [ConditionalFact] in xunit.v2, they can now call Assert.Skip instead of throwing SkipTestException +// In this case, [Fact] will just work because Assert.Skip is natively supported in xunit.v3 +// TODO: Evaluate whether or not we want to still expose this attribute in xunit.v3 for usages of CalleeType and ConditionMemberNames? +#if !USES_XUNIT_3 + using System; using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.XUnitExtensions; @@ -8,7 +16,9 @@ namespace Xunit { -#if !USES_XUNIT_3 +#if USES_XUNIT_3 + [XunitTestCaseDiscoverer(typeof(ConditionalFactDiscoverer))] +#else [XunitTestCaseDiscoverer("Microsoft.DotNet.XUnitExtensions.ConditionalFactDiscoverer", "Microsoft.DotNet.XUnitExtensions")] #endif [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] @@ -25,19 +35,12 @@ public ConditionalFactAttribute( { CalleeType = calleeType; ConditionMemberNames = conditionMemberNames; -#if USES_XUNIT_3 - string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); - if (skipReason != null) - Skip = skipReason; -#endif } -#if USES_XUNIT_3 - [Obsolete("Use the overload that takes a Type parameter: ConditionalFact(typeof(MyClass), nameof(MyCondition)).")] -#endif public ConditionalFactAttribute(params string[] conditionMemberNames) { ConditionMemberNames = conditionMemberNames; } } } +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalTheoryAttribute.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalTheoryAttribute.cs index a8b16ef3024..38d8d359d2b 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalTheoryAttribute.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/ConditionalTheoryAttribute.cs @@ -1,6 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// Not adding the support for xunit.v3. +// Still keeping the logic inside compatible with xunit.v3 in case we decided to add it. +// For cases that used to do [ConditionalTheory] in xunit.v2, they can now call Assert.Skip instead of throwing SkipTestException +// In this case, [Fact] will just work because Assert.Skip is natively supported in xunit.v3 +// TODO: Evaluate whether or not we want to still expose this attribute in xunit.v3 for usages of CalleeType and ConditionMemberNames? +#if !USES_XUNIT_3 + using System; using System.Diagnostics.CodeAnalysis; using Microsoft.DotNet.XUnitExtensions; @@ -8,14 +15,16 @@ namespace Xunit { -#if !USES_XUNIT_3 +#if USES_XUNIT_3 + [XunitTestCaseDiscoverer(typeof(ConditionalTheoryDiscoverer))] +#else [XunitTestCaseDiscoverer("Microsoft.DotNet.XUnitExtensions.ConditionalTheoryDiscoverer", "Microsoft.DotNet.XUnitExtensions")] #endif [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class ConditionalTheoryAttribute : TheoryAttribute { [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] - public Type CalleeType { get; private set; } + public Type CalleeType { get; private set; } public string[] ConditionMemberNames { get; private set; } public ConditionalTheoryAttribute( @@ -25,19 +34,12 @@ public ConditionalTheoryAttribute( { CalleeType = calleeType; ConditionMemberNames = conditionMemberNames; -#if USES_XUNIT_3 - string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); - if (skipReason != null) - Skip = skipReason; -#endif } -#if USES_XUNIT_3 - [Obsolete("Use the overload that takes a Type parameter: ConditionalTheory(typeof(MyClass), nameof(MyCondition)).")] -#endif public ConditionalTheoryAttribute(params string[] conditionMemberNames) { ConditionMemberNames = conditionMemberNames; } } } +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/SkipOnCoreClrAttribute.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/SkipOnCoreClrAttribute.cs index 19d16508350..22c432f7fd5 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/SkipOnCoreClrAttribute.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Attributes/SkipOnCoreClrAttribute.cs @@ -26,7 +26,6 @@ public class SkipOnCoreClrAttribute : Attribute, ITraitAttribute private static readonly Lazy s_isReleaseRuntime = new Lazy(() => CoreClrConfigurationDetection.IsReleaseRuntime); private static readonly Lazy s_isDebugRuntime = new Lazy(() => CoreClrConfigurationDetection.IsDebugRuntime); private static readonly Lazy s_isStressTest = new Lazy(() => CoreClrConfigurationDetection.IsStressTest); - private static readonly Lazy s_isCoreClrInterpreter = new Lazy(() => CoreClrConfigurationDetection.IsCoreClrInterpreter); private readonly TestPlatforms _testPlatforms = TestPlatforms.Any; private readonly RuntimeTestModes _testMode = RuntimeTestModes.Any; @@ -59,8 +58,7 @@ private static bool StressModeApplies(RuntimeTestModes stressMode) => (stressMode.HasFlag(RuntimeTestModes.TailcallStress) && s_isTailCallStress.Value) || (stressMode.HasFlag(RuntimeTestModes.JitStressRegs) && s_isJitStressRegs.Value) || (stressMode.HasFlag(RuntimeTestModes.JitStress) && s_isJitStress.Value) || - (stressMode.HasFlag(RuntimeTestModes.JitMinOpts) && s_isJitMinOpts.Value) || - (stressMode.HasFlag(RuntimeTestModes.InterpreterActive) && s_isCoreClrInterpreter.Value); + (stressMode.HasFlag(RuntimeTestModes.JitMinOpts) && s_isJitMinOpts.Value); #endif internal SkipOnCoreClrAttribute() { } diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/CoreClrConfigurationDetection.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/CoreClrConfigurationDetection.cs index 22fa1999717..027c1495e73 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/CoreClrConfigurationDetection.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/CoreClrConfigurationDetection.cs @@ -22,20 +22,8 @@ public static class CoreClrConfigurationDetection public static bool IsTieredCompilation => string.Equals(GetEnvironmentVariableValue("TieredCompilation", "1"), "1", StringComparison.InvariantCulture); public static bool IsHeapVerify => string.Equals(GetEnvironmentVariableValue("HeapVerify"), "1", StringComparison.InvariantCulture); - public static bool IsCoreClrInterpreter - { - get - { - if (!string.IsNullOrWhiteSpace(GetEnvironmentVariableValue("Interpreter", ""))) - return true; - if (int.TryParse(GetEnvironmentVariableValue("InterpMode", "0"), out int mode) && (mode > 0)) - return true; - return false; - } - } - public static bool IsGCStress => !string.Equals(GetEnvironmentVariableValue("GCStress"), "0", StringComparison.InvariantCulture); - + public static bool IsAnyJitStress => IsJitStress || IsJitStressRegs || IsJitMinOpts || IsTailCallStress; public static bool IsAnyJitOptimizationStress => IsAnyJitStress || IsTieredCompilation; diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/DiscovererHelpers.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/DiscovererHelpers.cs index fdfe4f862ae..a5a4b495b75 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/DiscovererHelpers.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/DiscovererHelpers.cs @@ -22,7 +22,6 @@ public static bool TestPlatformApplies(TestPlatforms platforms) => (platforms.HasFlag(TestPlatforms.FreeBSD) && RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD"))) || (platforms.HasFlag(TestPlatforms.Linux) && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) || (platforms.HasFlag(TestPlatforms.NetBSD) && RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD"))) || - (platforms.HasFlag(TestPlatforms.OpenBSD) && RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD"))) || (platforms.HasFlag(TestPlatforms.OSX) && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) || (platforms.HasFlag(TestPlatforms.illumos) && RuntimeInformation.IsOSPlatform(OSPlatform.Create("ILLUMOS"))) || (platforms.HasFlag(TestPlatforms.Solaris) && RuntimeInformation.IsOSPlatform(OSPlatform.Create("SOLARIS"))) || diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalFactDiscoverer.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalFactDiscoverer.cs index 9457897694f..60380f2a26c 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalFactDiscoverer.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalFactDiscoverer.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. +// Not adding the support for xunit.v3. +// This is used by ConditionalFact and ConditionalTheory which we no longer support in xunit.v3. +// Still keeping the logic inside supporting xunit.v3 in case we decided to add it. #if !USES_XUNIT_3 using System; @@ -9,27 +12,52 @@ using Xunit; using Xunit.Internal; + +#if !USES_XUNIT_3 using Xunit.Abstractions; +#endif using Xunit.Sdk; namespace Microsoft.DotNet.XUnitExtensions { public class ConditionalFactDiscoverer : FactDiscoverer { +#if !USES_XUNIT_3 public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } +#endif +#if USES_XUNIT_3 + protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, IFactAttribute factAttribute) +#else protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) +#endif { +#if USES_XUNIT_3 + var conditionalFactAttribute = (ConditionalFactAttribute)factAttribute; + object[] constructorArgs = conditionalFactAttribute.CalleeType is null + ? [conditionalFactAttribute.ConditionMemberNames] + : [conditionalFactAttribute.CalleeType, conditionalFactAttribute.ConditionMemberNames]; + + if (ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, testMethod, constructorArgs, out string skipReason, out ExecutionErrorTestCase errorTestCase)) +#else if (ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, DiagnosticMessageSink, testMethod, factAttribute.GetConstructorArguments().ToArray(), out string skipReason, out ExecutionErrorTestCase errorTestCase)) +#endif { +#if USES_XUNIT_3 + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, factAttribute); + + return skipReason != null + ? (IXunitTestCase)new SkippedTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout) + : new SkippedFactTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout); // Test case skippable at runtime. +#else return skipReason != null ? (IXunitTestCase) new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) : new SkippedFactTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); // Test case skippable at runtime. +#endif } return errorTestCase; } } } - -#endif // !USES_XUNIT_3 +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTestDiscoverer.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTestDiscoverer.cs index 3b64c01e7e9..e9e206f1251 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTestDiscoverer.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTestDiscoverer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; #if !USES_XUNIT_3 @@ -17,52 +16,15 @@ namespace Microsoft.DotNet.XUnitExtensions // [ConditionalFact] and [ConditionalTheory] internal static class ConditionalTestDiscoverer { -#if USES_XUNIT_3 - /// - /// Evaluates skip conditions given an explicit callee type and condition member names. - /// Used by attribute constructors in xunit v3 where discoverers are not needed. - /// - internal static string EvaluateSkipConditions( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type calleeType, - string[] conditionMemberNames) - { - if (calleeType == null || conditionMemberNames == null || conditionMemberNames.Length == 0) - return null; - - List falseConditions = new(conditionMemberNames.Length); - foreach (string entry in conditionMemberNames) - { - if (string.IsNullOrWhiteSpace(entry)) - continue; - - Func conditionFunc = LookupConditionalMember(calleeType, entry); - if (conditionFunc == null) - throw new ConditionalDiscovererException(GetFailedLookupString(entry, calleeType)); - - try - { - if (!conditionFunc()) - falseConditions.Add(entry); - } - catch (Exception exc) - { - falseConditions.Add($"{entry} ({exc.GetType().Name})"); - } - } - - return falseConditions.Count > 0 - ? string.Format("Condition(s) not met: \"{0}\"", string.Join("\", \"", falseConditions)) - : null; - } -#endif - -#if !USES_XUNIT_3 // This helper method evaluates the given condition member names for a given set of test cases. // If any condition member evaluates to 'false', the test cases are marked to be skipped. // The skip reason is the collection of all the condition members that evaluated to 'false'. internal static string EvaluateSkipConditions( +#if USES_XUNIT_3 + IXunitTestMethod testMethod, +#else ITestMethod testMethod, +#endif object[] conditionArguments) { Type calleeType = null; @@ -70,7 +32,11 @@ internal static string EvaluateSkipConditions( if (CheckInputToSkipExecution(conditionArguments, ref calleeType, ref conditionMemberNames, testMethod)) return null; +#if USES_XUNIT_3 + MethodInfo testMethodInfo = testMethod.Method; +#else MethodInfo testMethodInfo = testMethod.Method.ToRuntimeMethod(); +#endif Type testMethodDeclaringType = testMethodInfo.DeclaringType; List falseConditions = new List(conditionMemberNames.Count()); @@ -98,11 +64,15 @@ internal static string EvaluateSkipConditions( if (symbols.Length == 2) { conditionMemberName = symbols[1]; +#if USES_XUNIT_3 + declaringType = testMethod.TestClass.Class.Assembly.ExportedTypes.Where(t => t.Name.Contains(symbols[0])).FirstOrDefault(); +#else ITypeInfo type = testMethod.TestClass.Class.Assembly.GetTypes(false).Where(t => t.Name.Contains(symbols[0])).FirstOrDefault(); if (type != null) { declaringType = type.ToRuntimeType(); } +#endif } } @@ -137,7 +107,11 @@ internal static string EvaluateSkipConditions( return null; } +#if USES_XUNIT_3 + internal static bool TryEvaluateSkipConditions(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, object[] conditionArguments, out string skipReason, out ExecutionErrorTestCase errorTestCase) +#else internal static bool TryEvaluateSkipConditions(ITestFrameworkDiscoveryOptions discoveryOptions, IMessageSink diagnosticMessageSink, ITestMethod testMethod, object[] conditionArguments, out string skipReason, out ExecutionErrorTestCase errorTestCase) +#endif { skipReason = null; errorTestCase = null; @@ -148,16 +122,20 @@ internal static bool TryEvaluateSkipConditions(ITestFrameworkDiscoveryOptions di } catch (ConditionalDiscovererException e) { +#if USES_XUNIT_3 + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, new Xunit.FactAttribute()); + errorTestCase = new ExecutionErrorTestCase(testMethod, details.TestCaseDisplayName, details.UniqueID, details.SourceFilePath, details.SourceLineNumber, e.Message); +#else errorTestCase = new ExecutionErrorTestCase( diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, e.Message); +#endif return false; } } -#endif // !USES_XUNIT_3 internal static string GetFailedLookupString(string name, Type type) { @@ -189,13 +167,7 @@ internal static Func LookupConditionalMember(Type t, string name) return LookupConditionalMember(ti.BaseType, name); } - internal static bool CheckInputToSkipExecution(object[] conditionArguments, ref Type calleeType, ref string[] conditionMemberNames, -#if !USES_XUNIT_3 - ITestMethod -#else - object -#endif - testMethod = null) + internal static bool CheckInputToSkipExecution(object[] conditionArguments, ref Type calleeType, ref string[] conditionMemberNames, ITestMethod testMethod = null) { // A null or empty list of conditionArguments is treated as "no conditions". // and the test cases will be executed. diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTheoryDiscoverer.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTheoryDiscoverer.cs index 9767480fb7e..e12e6032519 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTheoryDiscoverer.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/ConditionalTheoryDiscoverer.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. +// Not adding the support for xunit.v3. +// This is used by ConditionalFact and ConditionalTheory which we no longer support in xunit.v3. +// Still keeping the logic inside supporting xunit.v3 in case we decided to add it. #if !USES_XUNIT_3 using System; @@ -11,41 +14,97 @@ using Xunit; using Xunit.Internal; +#if !USES_XUNIT_3 using Xunit.Abstractions; +#endif using Xunit.Sdk; namespace Microsoft.DotNet.XUnitExtensions { public class ConditionalTheoryDiscoverer : TheoryDiscoverer { +#if USES_XUNIT_3 + private readonly Dictionary _conditionCache = new(); +#else private readonly Dictionary _conditionCache = new(); +#endif +#if !USES_XUNIT_3 public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } +#endif + +#if USES_XUNIT_3 + protected override ValueTask> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, ITheoryAttribute theoryAttribute) +#else protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) +#endif { +#if USES_XUNIT_3 + var conditionalTheoryAttribute = (ConditionalTheoryAttribute)theoryAttribute; + object[] constructorArgs = conditionalTheoryAttribute.CalleeType is null + ? [conditionalTheoryAttribute.ConditionMemberNames] + : [conditionalTheoryAttribute.CalleeType, conditionalTheoryAttribute.ConditionMemberNames]; + + if (ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, testMethod, constructorArgs, out string skipReason, out ExecutionErrorTestCase errorTestCase)) +#else if (ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, DiagnosticMessageSink, testMethod, theoryAttribute.GetConstructorArguments().ToArray(), out string skipReason, out ExecutionErrorTestCase errorTestCase)) +#endif { +#if USES_XUNIT_3 + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, theoryAttribute); + + var testCases = skipReason != null + ? new[] { new SkippedTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout) } + : new IXunitTestCase[] { new SkippedFactTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout) }; // Theory skippable at runtime. + + return new ValueTask>(Task.FromResult>(testCases)); +#else return skipReason != null ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) } : new IXunitTestCase[] { new SkippedTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; // Theory skippable at runtime. +#endif } +#if USES_XUNIT_3 + return new ValueTask>(Task.FromResult>(new IXunitTestCase[] { errorTestCase })); +#else return new IXunitTestCase[] { errorTestCase }; +#endif } +#if USES_XUNIT_3 + protected override ValueTask> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, IXunitTestMethod testMethod, ITheoryAttribute theoryAttribute, ITheoryDataRow dataRow, object[] testMethodArguments) +#else protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) +#endif { var methodInfo = testMethod.Method; List skippedTestCase = new List(); +#if USES_XUNIT_3 + var details = TestIntrospectionHelper.GetTestCaseDetails(discoveryOptions, testMethod, theoryAttribute); +#endif if (!_conditionCache.TryGetValue(methodInfo, out string skipReason)) { +#if USES_XUNIT_3 + var conditionalTheoryAttribute = (ConditionalTheoryAttribute)theoryAttribute; + object[] constructorArgs = conditionalTheoryAttribute.CalleeType is null + ? [conditionalTheoryAttribute.ConditionMemberNames] + : [conditionalTheoryAttribute.CalleeType, conditionalTheoryAttribute.ConditionMemberNames]; + + if (!ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, testMethod, constructorArgs, out skipReason, out ExecutionErrorTestCase errorTestCase)) +#else if (!ConditionalTestDiscoverer.TryEvaluateSkipConditions(discoveryOptions, DiagnosticMessageSink, testMethod, theoryAttribute.GetConstructorArguments().ToArray(), out skipReason, out ExecutionErrorTestCase errorTestCase)) +#endif { +#if USES_XUNIT_3 + return new ValueTask>(Task.FromResult>(new IXunitTestCase[] { errorTestCase })); +#else return new IXunitTestCase[] { errorTestCase }; +#endif } _conditionCache.Add(methodInfo, skipReason); @@ -53,7 +112,11 @@ protected override IEnumerable CreateTestCasesForDataRow(ITestFr if (skipReason != null) { // If this is the first time we evalute the condition we return a SkippedTestCase to avoid printing a skip for every inline-data. +#if USES_XUNIT_3 + skippedTestCase.Add(new SkippedTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout)); +#else skippedTestCase.Add(new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod)); +#endif } } @@ -61,11 +124,18 @@ protected override IEnumerable CreateTestCasesForDataRow(ITestFr (IReadOnlyCollection)skippedTestCase : new[] { +#if USES_XUNIT_3 + new SkippedFactTestCase(details.ResolvedTestMethod, details.TestCaseDisplayName, details.UniqueID, details.Explicit, details.SkipExceptions, details.SkipReason, details.SkipType, details.SkipUnless, details.SkipWhen, testMethod.Traits.ToReadWrite(StringComparer.OrdinalIgnoreCase), timeout: details.Timeout) +#else new SkippedFactTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow) +#endif }; // Test case skippable at runtime. +#if USES_XUNIT_3 + return new ValueTask>(Task.FromResult>(result)); +#else return result; +#endif } } } - -#endif // !USES_XUNIT_3 +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/SkipOnCoreClrDiscoverer.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/SkipOnCoreClrDiscoverer.cs index 63dce43b298..6476b08071d 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/SkipOnCoreClrDiscoverer.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/Discoverers/SkipOnCoreClrDiscoverer.cs @@ -25,7 +25,6 @@ public class SkipOnCoreClrDiscoverer : ITraitDiscoverer private static readonly Lazy s_isReleaseRuntime = new Lazy(() => CoreClrConfigurationDetection.IsReleaseRuntime); private static readonly Lazy s_isDebugRuntime = new Lazy(() => CoreClrConfigurationDetection.IsDebugRuntime); private static readonly Lazy s_isStressTest = new Lazy(() => CoreClrConfigurationDetection.IsStressTest); - private static readonly Lazy s_isCoreClrInterpreter = new Lazy(() => CoreClrConfigurationDetection.IsCoreClrInterpreter); public IEnumerable> GetTraits(IAttributeInfo traitAttribute) { @@ -73,8 +72,7 @@ private static bool StressModeApplies(RuntimeTestModes stressMode) => (stressMode.HasFlag(RuntimeTestModes.TailcallStress) && s_isTailCallStress.Value) || (stressMode.HasFlag(RuntimeTestModes.JitStressRegs) && s_isJitStressRegs.Value) || (stressMode.HasFlag(RuntimeTestModes.JitStress) && s_isJitStress.Value) || - (stressMode.HasFlag(RuntimeTestModes.JitMinOpts) && s_isJitMinOpts.Value) || - (stressMode.HasFlag(RuntimeTestModes.InterpreterActive) && s_isCoreClrInterpreter.Value); + (stressMode.HasFlag(RuntimeTestModes.JitMinOpts) && s_isJitMinOpts.Value); } } #endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/RuntimeTestModes.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/RuntimeTestModes.cs index 4fed720a782..24e25026a28 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/RuntimeTestModes.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/RuntimeTestModes.cs @@ -44,7 +44,5 @@ public enum RuntimeTestModes AnyJitOptimizationStress = AnyJitStress | TieredCompilation, // Disable when any JIT non-full optimization stress mode is exercised. HeapVerify = 1 << 9, // DOTNET_HeapVerify (or COMPlus_HeapVerify) is set. - - InterpreterActive = 1 << 10, // DOTNET_Interpreter != "" or DOTNET_InterpMode != 0 } } diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedFactTestCase.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedFactTestCase.cs index 65af69705fa..785f36cde4e 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedFactTestCase.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedFactTestCase.cs @@ -1,6 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// Not adding the support for xunit.v3. +// This is used by ConditionalFact and ConditionalTheory which we no longer support in xunit.v3. +// Still keeping the logic inside supporting xunit.v3 in case we decided to add it. +#if !USES_XUNIT_3 + using System; using System.Collections.Generic; using System.Linq; @@ -74,3 +79,4 @@ public override async Task RunAsync(IMessageSink diagnosticMessageSi } } } +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedTestMessageBus.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedTestMessageBus.cs index 328f5835ee3..1339daf8a8f 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedTestMessageBus.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/SkippedTestMessageBus.cs @@ -1,6 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +// Not adding the support for xunit.v3. +// This is used by ConditionalFact and ConditionalTheory which we no longer support in xunit.v3. +// Still keeping the logic inside supporting xunit.v3 in case we decided to add it. +#if !USES_XUNIT_3 + using System; using System.Linq; #if !USES_XUNIT_3 @@ -67,3 +72,4 @@ public bool QueueMessage(IMessageSinkMessage message) } } } +#endif diff --git a/src/Microsoft.DotNet.XUnitExtensions.Shared/TestPlatforms.cs b/src/Microsoft.DotNet.XUnitExtensions.Shared/TestPlatforms.cs index ba10b87b08d..57f2b6ed0f8 100644 --- a/src/Microsoft.DotNet.XUnitExtensions.Shared/TestPlatforms.cs +++ b/src/Microsoft.DotNet.XUnitExtensions.Shared/TestPlatforms.cs @@ -8,25 +8,22 @@ namespace Xunit [Flags] public enum TestPlatforms { - Windows = 1 << 0, - Linux = 1 << 1, - OSX = 1 << 2, - FreeBSD = 1 << 3, - NetBSD = 1 << 4, - illumos = 1 << 5, - Solaris = 1 << 6, - iOS = 1 << 7, - tvOS = 1 << 8, - Android = 1 << 9, - Browser = 1 << 10, - MacCatalyst = 1 << 11, - LinuxBionic = 1 << 12, - Wasi = 1 << 13, - Haiku = 1 << 14, - OpenBSD = 1 << 15, - - AnyApple = OSX | iOS | tvOS | MacCatalyst, - AnyUnix = AnyApple | Linux | FreeBSD | NetBSD | OpenBSD | illumos | Solaris | Android | Browser | LinuxBionic | Wasi | Haiku, + Windows = 1, + Linux = 2, + OSX = 4, + FreeBSD = 8, + NetBSD = 16, + illumos= 32, + Solaris = 64, + iOS = 128, + tvOS = 256, + Android = 512, + Browser = 1024, + MacCatalyst = 2048, + LinuxBionic = 4096, + Wasi = 8192, + Haiku = 16384, + AnyUnix = FreeBSD | Linux | NetBSD | OSX | illumos | Solaris | iOS | tvOS | MacCatalyst | Android | Browser | LinuxBionic | Wasi | Haiku, Any = ~0 } } diff --git a/src/Microsoft.DotNet.XUnitExtensions/tests/Microsoft.DotNet.XUnitExtensions.Tests.csproj b/src/Microsoft.DotNet.XUnitExtensions/tests/Microsoft.DotNet.XUnitExtensions.Tests.csproj index 3a6913322a6..41d1983b2d6 100644 --- a/src/Microsoft.DotNet.XUnitExtensions/tests/Microsoft.DotNet.XUnitExtensions.Tests.csproj +++ b/src/Microsoft.DotNet.XUnitExtensions/tests/Microsoft.DotNet.XUnitExtensions.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework);$(NetFrameworkCurrent) + $(NetToolCurrent);$(NetFrameworkToolCurrent) diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/AlphabeticalOrderer.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/AlphabeticalOrderer.cs deleted file mode 100644 index a3f42e09912..00000000000 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/AlphabeticalOrderer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using Xunit.Sdk; -using Xunit.v3; - -namespace Microsoft.DotNet.XUnitExtensions.Tests -{ - /// - /// Orders test cases alphabetically so that we can test 'failing' test cases through - /// side-effects. - /// - public class AlphabeticalOrderer : ITestCaseOrderer - { - public IReadOnlyCollection OrderTestCases(IReadOnlyCollection testCases) - where TTestCase : notnull, ITestCase - { - return testCases.OrderBy(tc => tc.TestCaseDisplayName).ToList(); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/ConditionalAttributeTests.cs b/src/Microsoft.DotNet.XUnitV3Extensions/tests/ConditionalAttributeTests.cs deleted file mode 100644 index 3d8b9b0e741..00000000000 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/ConditionalAttributeTests.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace Microsoft.DotNet.XUnitExtensions.Tests -{ - [TestCaseOrderer(typeof(AlphabeticalOrderer))] - public class ConditionalAttributeTests - { - // The tests under this class validate that ConditionalFact and ConditionalTheory - // tests are discovered and executed correctly under xunit v3. - // This test class is test-order dependent so do not rename the tests. - - private static bool s_conditionalFactTrueExecuted; - private static bool s_conditionalFactFalseExecuted; - private static int s_conditionalTheoryTrueCount; - private static int s_conditionalTheoryFalseCount; - private static readonly List s_conditionalTheoryTrueArgs = new(); - - public static bool AlwaysTrue => true; - public static bool AlwaysFalse => false; - - [ConditionalFact(typeof(ConditionalAttributeTests), nameof(AlwaysTrue))] - public void ConditionalAttributeTrue() - { - s_conditionalFactTrueExecuted = true; - } - - [ConditionalFact(typeof(ConditionalAttributeTests), nameof(AlwaysFalse))] - public void ConditionalAttributeFalse() - { - s_conditionalFactFalseExecuted = true; - } - - [ConditionalTheory(typeof(ConditionalAttributeTests), nameof(AlwaysTrue))] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void ConditionalTheoryTrue(int value) - { - // Verify the argument was actually passed through (the bug being tested). - Assert.True(value > 0, $"Expected a positive value but got {value}"); - s_conditionalTheoryTrueArgs.Add(value); - s_conditionalTheoryTrueCount++; - } - - [ConditionalTheory(typeof(ConditionalAttributeTests), nameof(AlwaysFalse))] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] -#pragma warning disable xUnit1026 // Theory methods should use all of their parameters - public void ConditionalTheoryFalse(int value) -#pragma warning restore xUnit1026 - { - s_conditionalTheoryFalseCount++; - } - - [ConditionalTheory(typeof(ConditionalAttributeTests), nameof(AlwaysTrue))] - [InlineData("hello")] - [InlineData("world")] - public void ConditionalTheoryTrueStringArgs(string text) - { - // Verify string arguments are passed through correctly. - Assert.False(string.IsNullOrEmpty(text), "Expected a non-empty string argument"); - } - - [ConditionalTheory(typeof(ConditionalAttributeTests), nameof(AlwaysTrue))] - [InlineData(10, "ten")] - [InlineData(20, "twenty")] - public void ConditionalTheoryTrueMultipleArgs(int number, string name) - { - // Verify multiple arguments are passed through correctly. - Assert.True(number > 0); - Assert.False(string.IsNullOrEmpty(name)); - } - - [Fact] - public void ValidateConditionalFactTrue() - { - Assert.True(s_conditionalFactTrueExecuted); - } - - [Fact] - public void ValidateConditionalFactFalse() - { - Assert.False(s_conditionalFactFalseExecuted); - } - - [Fact] - public void ValidateConditionalTheoryTrue() - { - Assert.Equal(3, s_conditionalTheoryTrueCount); - } - - [Fact] - public void ValidateConditionalTheoryTrueReceivedArgs() - { - // This is the key test: if testMethodArguments were dropped, - // the data row values would not reach the test method. - Assert.Equal(new[] { 1, 2, 3 }, s_conditionalTheoryTrueArgs.OrderBy(x => x).ToArray()); - } - - [Fact] - public void ValidateConditionalTheoryFalse() - { - Assert.Equal(0, s_conditionalTheoryFalseCount); - } - } -} diff --git a/src/Microsoft.DotNet.XUnitV3Extensions/tests/Microsoft.DotNet.XUnitV3Extensions.Tests.csproj b/src/Microsoft.DotNet.XUnitV3Extensions/tests/Microsoft.DotNet.XUnitV3Extensions.Tests.csproj deleted file mode 100644 index 4f3eb908f30..00000000000 --- a/src/Microsoft.DotNet.XUnitV3Extensions/tests/Microsoft.DotNet.XUnitV3Extensions.Tests.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - $(BundledNETCoreAppTargetFramework) - XUnitV3 - Exe - - - - - - - diff --git a/src/Microsoft.DotNet.XliffTasks.Tests/Microsoft.DotNet.XliffTasks.Tests.csproj b/src/Microsoft.DotNet.XliffTasks.Tests/Microsoft.DotNet.XliffTasks.Tests.csproj index d1c45953035..0204cba0f3f 100644 --- a/src/Microsoft.DotNet.XliffTasks.Tests/Microsoft.DotNet.XliffTasks.Tests.csproj +++ b/src/Microsoft.DotNet.XliffTasks.Tests/Microsoft.DotNet.XliffTasks.Tests.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) MicrosoftAspNetCore diff --git a/src/Microsoft.DotNet.XliffTasks/Microsoft.DotNet.XliffTasks.csproj b/src/Microsoft.DotNet.XliffTasks/Microsoft.DotNet.XliffTasks.csproj index ee12cd5b4ee..165a4a7d5fd 100644 --- a/src/Microsoft.DotNet.XliffTasks/Microsoft.DotNet.XliffTasks.csproj +++ b/src/Microsoft.DotNet.XliffTasks/Microsoft.DotNet.XliffTasks.csproj @@ -1,7 +1,7 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolMinimum);$(NetFrameworkToolCurrent) true true XliffTasks @@ -18,6 +18,7 @@ + diff --git a/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.targets b/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.targets index 8049e5c5ec5..b0d7174e1ec 100644 --- a/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.targets +++ b/src/Microsoft.DotNet.XliffTasks/build/Microsoft.DotNet.XliffTasks.targets @@ -2,17 +2,19 @@ - $(MSBuildThisFileDirectory)..\tools\net\ + $(MSBuildThisFileDirectory)..\tools\net\ + $(MSBuildThisFileDirectory)..\tools\netframework\ $(XliffTasksDirectory)Microsoft.DotNet.XliffTasks.dll + AssemblyTaskFactory - - - - - - - + + + + + + + false + true true - - - - + @@ -40,6 +38,43 @@ CopyToOutputDirectory="PreserveNewest" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - /// Looks up a localized string similar to Verification error: {0}. - /// - internal static string DetailVerificationError { - get { - return ResourceManager.GetString("DetailVerificationError", resourceCulture); - } - } - /// /// Looks up a localized string similar to Timestamp: {0:MM/dd/yy H:mm:ss} ({1}). /// diff --git a/src/SignCheck/Microsoft.SignCheck/SignCheckResources.resx b/src/SignCheck/Microsoft.SignCheck/SignCheckResources.resx index ced83318221..97eec695bd2 100644 --- a/src/SignCheck/Microsoft.SignCheck/SignCheckResources.resx +++ b/src/SignCheck/Microsoft.SignCheck/SignCheckResources.resx @@ -168,9 +168,6 @@ Skipped (unsupported file type) - - Verification error: {0} - Timestamp: {0:MM/dd/yy H:mm:ss} ({1}) diff --git a/src/SignCheck/Microsoft.SignCheck/Utils.cs b/src/SignCheck/Microsoft.SignCheck/Utils.cs index fb5e306cc88..a93733604c4 100644 --- a/src/SignCheck/Microsoft.SignCheck/Utils.cs +++ b/src/SignCheck/Microsoft.SignCheck/Utils.cs @@ -9,14 +9,17 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +#if NET using System.Formats.Tar; +#endif namespace Microsoft.SignCheck { public static class Utils { +#if NET private static readonly HttpClient s_client = new(new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(10) }); - +#endif /// /// Generate a hash for a string value using a given hash algorithm. /// @@ -188,6 +191,7 @@ public static (int exitCode, string output, string error) RunBashCommand(string } } +#if NET /// /// Download the Microsoft and Azure Linux public keys and import them into the keyring. /// @@ -235,6 +239,7 @@ public static TarEntry TryGetNextTarEntry(this TarReader reader) return null; } } +#endif /// /// Parses a code signing timestamp string into a DateTime object. diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/ArchiveVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/ArchiveVerifier.cs index a1d760457a0..bb64a96d20a 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/ArchiveVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/ArchiveVerifier.cs @@ -129,17 +129,8 @@ protected void VerifyContent(SignatureVerificationResult svr) // and we need to ensure they are extracted before we verify the MSIs. foreach (string fullName in archiveMap.Keys) { - SignatureVerificationResult result; - try - { - result = VerifyFile(archiveMap[fullName], svr.VirtualPath, - Path.Combine(svr.VirtualPath, fullName), fullName); - } - catch (Exception e) when (e is not PlatformNotSupportedException) - { - result = SignatureVerificationResult.ErrorResult( - archiveMap[fullName], svr.VirtualPath, Path.Combine(svr.VirtualPath, fullName), e); - } + SignatureVerificationResult result = VerifyFile(archiveMap[fullName], svr.VirtualPath, + Path.Combine(svr.VirtualPath, fullName), fullName); // Tag the full path into the result detail result.AddDetail(DetailKeys.File, SignCheckResources.DetailFullName, fullName); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs index 3fef8aaed06..05a94a8ca89 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs @@ -22,6 +22,103 @@ public static bool IsSigned(string path, SignatureVerificationResult svr, ISecur public static IEnumerable GetTimestamps(string path, ISecurityInfoProvider securityInfoProvider) => string.IsNullOrEmpty(path) ? Enumerable.Empty() : GetTimestampsInternal(path, securityInfoProvider); +#if NETFRAMEWORK + private static bool IsSignedInternal(string path, SignatureVerificationResult svr, ISecurityInfoProvider securityInfoProvider) + { + WinTrustFileInfo fileInfo = new WinTrustFileInfo() + { + cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustFileInfo)), + pcwszFilePath = Path.GetFullPath(path), + hFile = IntPtr.Zero, + pgKnownSubject = IntPtr.Zero + }; + + WinTrustData data = new WinTrustData() + { + cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustData)), + dwProvFlags = 0, + dwStateAction = Convert.ToUInt32(StateAction.WTD_STATEACTION_IGNORE), + dwUIChoice = Convert.ToUInt32(UIChoice.WTD_UI_NONE), + dwUIContext = 0, + dwUnionChoice = Convert.ToUInt32(UnionChoice.WTD_CHOICE_FILE), + fdwRevocationChecks = Convert.ToUInt32(RevocationChecks.WTD_REVOKE_NONE), + hWVTStateData = IntPtr.Zero, + pFile = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WinTrustFileInfo))), + pPolicyCallbackData = IntPtr.Zero, + pSIPClientData = IntPtr.Zero, + pwszURLReference = IntPtr.Zero + }; + + // Potential memory leak. Need to investigate + Marshal.StructureToPtr(fileInfo, data.pFile, false); + + IntPtr pGuid = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))); + IntPtr pData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WinTrustData))); + Marshal.StructureToPtr(data, pData, true); + Marshal.StructureToPtr(WinTrust.WINTRUST_ACTION_GENERIC_VERIFY_V2, pGuid, true); + + uint hrresult = WinTrust.WinVerifyTrust(IntPtr.Zero, pGuid, pData); + + Marshal.FreeHGlobal(pGuid); + Marshal.FreeHGlobal(pData); + + // Log non-zero HRESULTs + if (hrresult != 0) + { + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + svr.AddDetail(DetailKeys.Error, String.Format(SignCheckResources.ErrorHResult, hrresult, errorMessage)); + } + + return hrresult == 0; + } + + private static IEnumerable GetTimestampsInternal(string path, ISecurityInfoProvider securityInfoProvider) + { + int msgAndCertEncodingType; + int msgContentType; + int formatType; + + // NULL indicates that information is unneeded + IntPtr certStore = IntPtr.Zero; + IntPtr msg = IntPtr.Zero; + IntPtr context = IntPtr.Zero; + + if (!WinCrypt.CryptQueryObject( + WinCrypt.CERT_QUERY_OBJECT_FILE, + Marshal.StringToHGlobalUni(path), + WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED | WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED | WinCrypt.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + WinCrypt.CERT_QUERY_FORMAT_FLAG_ALL, + 0, + out msgAndCertEncodingType, + out msgContentType, + out formatType, + ref certStore, + ref msg, + ref context)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + int cbData = 0; + + // Passing in NULL to pvData retrieves the size of the encoded message + if (!WinCrypt.CryptMsgGetParam(msg, WinCrypt.CMSG_ENCODED_MESSAGE, 0, IntPtr.Zero, ref cbData)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + byte[] vData = new byte[cbData]; + if (!WinCrypt.CryptMsgGetParam(msg, WinCrypt.CMSG_ENCODED_MESSAGE, 0, vData, ref cbData)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + var signedCms = new SignedCms(); + signedCms.Decode(vData); + + return ExtractTimestamps(signedCms); + } +#else private static bool IsSignedInternal(string path, SignatureVerificationResult svr, ISecurityInfoProvider securityInfoProvider) { if (securityInfoProvider == null) @@ -84,6 +181,7 @@ private static SignerInfo GetPrimarySignerInfo(SignerInfoCollection signerInfos) return signerInfos[0]; } +#endif private static IEnumerable ExtractTimestamps(SignedCms signedCms) { diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs index 62d8a0a7712..83bc68bdf90 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs @@ -6,12 +6,12 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Security.Cryptography; using System.Security.Cryptography.Pkcs; -using Microsoft.SignCheck.Interop; using Microsoft.SignCheck.Logging; +#if NET using System.Reflection.PortableExecutable; +#endif namespace Microsoft.SignCheck.Verification { @@ -56,17 +56,6 @@ protected SignatureVerificationResult VerifyAuthentiCode(string path, string par svr.IsAuthentiCodeSigned = AuthentiCode.IsSigned(path, svr, _securityInfoProvider); svr.IsSigned = svr.IsAuthentiCodeSigned; - // WinVerifyTrust validates the file hash against the Authenticode signature, - // catching post-signing modifications that CMS-only validation would miss. - if (svr.IsAuthentiCodeSigned && OperatingSystem.IsWindows()) - { - if (!VerifyFileIntegrity(path, svr)) - { - svr.IsAuthentiCodeSigned = false; - svr.IsSigned = false; - } - } - // TODO: Should only check if there is a signature, even if it's invalid if (VerifyAuthenticodeTimestamps) { @@ -95,87 +84,13 @@ protected SignatureVerificationResult VerifyAuthentiCode(string path, string par return svr; } - - /// - /// Uses WinVerifyTrust to verify the file's Authenticode signature integrity, - /// ensuring the file content has not been modified after signing. - /// Uses WTD_HASH_ONLY_FLAG so only the file digest is verified, not trust policy - /// (untrusted roots, expired certs, etc. are handled separately by CMS validation). - /// - [SupportedOSPlatform("windows")] - private bool VerifyFileIntegrity(string path, SignatureVerificationResult svr) - { - var fileInfo = new WinTrustFileInfo - { - cbStruct = (uint)Marshal.SizeOf(), - pcwszFilePath = path, - hFile = IntPtr.Zero, - pgKnownSubject = IntPtr.Zero - }; - - IntPtr pFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf()); - IntPtr pGuid = Marshal.AllocHGlobal(Marshal.SizeOf()); - try - { - Marshal.StructureToPtr(fileInfo, pFileInfo, false); - - var trustData = new WinTrustData - { - cbStruct = (uint)Marshal.SizeOf(), - pPolicyCallbackData = IntPtr.Zero, - pSIPClientData = IntPtr.Zero, - dwUIChoice = (uint)UIChoice.WTD_UI_NONE, - fdwRevocationChecks = (uint)RevocationChecks.WTD_REVOKE_NONE, - dwUnionChoice = (uint)UnionChoice.WTD_CHOICE_FILE, - pFile = pFileInfo, - dwStateAction = (uint)StateAction.WTD_STATEACTION_VERIFY, - hWVTStateData = IntPtr.Zero, - pwszURLReference = IntPtr.Zero, - dwProvFlags = (uint)(Provider.WTD_HASH_ONLY_FLAG), - dwUIContext = 0 - }; - - IntPtr pTrustData = Marshal.AllocHGlobal(Marshal.SizeOf()); - try - { - Marshal.StructureToPtr(trustData, pTrustData, false); - - Guid actionId = WinTrust.WINTRUST_ACTION_GENERIC_VERIFY_V2; - Marshal.StructureToPtr(actionId, pGuid, false); - - uint result = WinTrust.WinVerifyTrust(IntPtr.Zero, pGuid, pTrustData); - - if (result != 0) - { - svr.AddDetail(DetailKeys.Error, "WinVerifyTrust failed: 0x{0:X8}. The file may have been modified after signing.", result); - return false; - } - - return true; - } - finally - { - // Read back the state handle that WinVerifyTrust wrote into the unmanaged buffer - trustData = Marshal.PtrToStructure(pTrustData); - trustData.dwStateAction = (uint)StateAction.WTD_STATEACTION_CLOSE; - Marshal.StructureToPtr(trustData, pTrustData, false); - WinTrust.WinVerifyTrust(IntPtr.Zero, pGuid, pTrustData); - Marshal.FreeHGlobal(pTrustData); - } - } - finally - { - Marshal.DestroyStructure(pFileInfo); - Marshal.FreeHGlobal(pFileInfo); - Marshal.FreeHGlobal(pGuid); - } - } } public class AuthentiCodeSecurityInfoProvider : ISecurityInfoProvider { public SignedCms ReadSecurityInfo(string path) { +#if NET using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) using (PEReader peReader = new PEReader(fs)) { @@ -208,8 +123,10 @@ public SignedCms ReadSecurityInfo(string path) return signedCms; } } - return null; +#else + throw new NotSupportedException("Not supported on .NET Framework"); +#endif } } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/CabSecurityInfoProvider.cs b/src/SignCheck/Microsoft.SignCheck/Verification/CabSecurityInfoProvider.cs deleted file mode 100644 index 7cd5b2a691a..00000000000 --- a/src/SignCheck/Microsoft.SignCheck/Verification/CabSecurityInfoProvider.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Security.Cryptography.Pkcs; - -namespace Microsoft.SignCheck.Verification -{ - /// - /// Reads digital signature information from Cabinet (.cab) files. - /// Signed CAB files store their PKCS#7 Authenticode signature at an offset - /// specified in the per-cabinet reserved area of the cabinet header. - /// MSU files are also CAB-format files and use the same signing mechanism. - /// - public class CabSecurityInfoProvider : ISecurityInfoProvider - { - // Cabinet file signature: 'MSCF' (0x4D, 0x53, 0x43, 0x46) - // Read as little-endian uint32: 0x4643534D - private const uint CabinetSignature = 0x4643534D; - - // Cabinet header flag indicating reserved fields are present - private const ushort CfhdrReservePresent = 0x0004; - - // Minimum header size: 36 bytes standard header + 4 bytes reserve fields - private const int MinHeaderSize = 40; - - public SignedCms ReadSecurityInfo(string path) - { - try - { - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new BinaryReader(fs)) - { - if (fs.Length < MinHeaderSize) - { - return null; - } - - // CFHEADER fields - uint signature = reader.ReadUInt32(); // offset 0: signature - if (signature != CabinetSignature) - { - return null; - } - - reader.ReadUInt32(); // offset 4: reserved1 - reader.ReadUInt32(); // offset 8: cbCabinet - reader.ReadUInt32(); // offset 12: reserved2 - reader.ReadUInt32(); // offset 16: coffFiles - reader.ReadUInt32(); // offset 20: reserved3 - reader.ReadByte(); // offset 24: versionMinor - reader.ReadByte(); // offset 25: versionMajor - reader.ReadUInt16(); // offset 26: cFolders - reader.ReadUInt16(); // offset 28: cFiles - ushort flags = reader.ReadUInt16(); // offset 30: flags - reader.ReadUInt16(); // offset 32: setID - reader.ReadUInt16(); // offset 34: iCabinet - - if ((flags & CfhdrReservePresent) == 0) - { - // No reserved area — file is not signed - return null; - } - - // CFRESERVE fields - ushort cbCFHeader = reader.ReadUInt16(); // offset 36: per-cabinet reserved size - reader.ReadByte(); // offset 38: cbCFFolder - reader.ReadByte(); // offset 39: cbCFData - - // The per-cabinet reserved area for signed CABs contains: - // uint32 signatureOffset - file offset to the PKCS#7 signature - // uint32 signatureSize - size of the PKCS#7 signature - if (cbCFHeader < 8) - { - return null; - } - - uint signatureOffset = reader.ReadUInt32(); - uint signatureSize = reader.ReadUInt32(); - - if (signatureOffset == 0 || signatureSize == 0) - { - return null; - } - - if (signatureOffset + signatureSize > (uint)fs.Length) - { - return null; - } - - // Read the PKCS#7 signature data - fs.Position = signatureOffset; - byte[] signatureBytes = new byte[signatureSize]; - fs.ReadExactly(signatureBytes); - - var signedCms = new SignedCms(); - signedCms.Decode(signatureBytes); - return signedCms; - } - } - catch (Exception) - { - return null; - } - } - } -} diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/CabVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/CabVerifier.cs index 7ac138433f9..8c8ec1cb1ff 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/CabVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/CabVerifier.cs @@ -7,7 +7,7 @@ namespace Microsoft.SignCheck.Verification { public class CabVerifier : AuthentiCodeVerifier { - public CabVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".cab", new CabSecurityInfoProvider()) + public CabVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, ".cab") { } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs index 6444c37e96e..8059917efac 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs @@ -5,7 +5,9 @@ using System.IO; using System.Linq; using Microsoft.SignCheck.Logging; +#if NETFRAMEWORK using Microsoft.Tools.WindowsInstallerXml; +#endif namespace Microsoft.SignCheck.Verification { @@ -24,6 +26,7 @@ public override SignatureVerificationResult VerifySignature(string path, string if (VerifyRecursive) { +#if NETFRAMEWORK if (PEHeader.ImageSectionHeaders.Select(s => s.SectionName).Contains(".wixburn")) { Log.WriteMessage(LogVerbosity.Diagnostic, SignCheckResources.DiagSectionHeader, ".wixburn"); @@ -54,6 +57,9 @@ public override SignatureVerificationResult VerifySignature(string path, string unbinder.DeleteTempFiles(); } } +#else + Log.WriteMessage(LogVerbosity.Normal, $"Unable to verify contents of '{svr.FullPath}' on .NET Core."); +#endif } // TODO: Check for SFXCAB, IronMan, etc. @@ -64,9 +70,11 @@ public override SignatureVerificationResult VerifySignature(string path, string /// /// Event handler for WiX Burn to extract a bundle. /// +#if NETFRAMEWORK private void UnbinderEventHandler(object sender, MessageEventArgs e) { Log.WriteMessage(LogVerbosity.Detailed, String.Format("{0}|{1}|{2}|{3}", e.Id, e.Level, e.ResourceName, e.SourceLineNumbers)); } +#endif } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/LZMAUtils.cs b/src/SignCheck/Microsoft.SignCheck/Verification/LZMAUtils.cs index 2ede504db04..ca64cb8afff 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/LZMAUtils.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/LZMAUtils.cs @@ -40,9 +40,25 @@ public static void Decompress(string sourceFile, string destinationFile) } } +#if NET private static void ReadExactly(FileStream stream, byte[] buffer, int offset, int count) { stream.ReadExactly(buffer, offset, count); } +#else + private static void ReadExactly(FileStream stream, byte[] buffer, int offset, int count) + { + while (count > 0) + { + int read = stream.Read(buffer, offset, count); + if (read <= 0) + { + throw new EndOfStreamException(); + } + offset += read; + count -= read; + } + } +#endif } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/MsiVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/MsiVerifier.cs index e64f68647ea..13275148d6f 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/MsiVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/MsiVerifier.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.Versioning; using Microsoft.Deployment.WindowsInstaller; using Microsoft.Deployment.WindowsInstaller.Package; using Microsoft.SignCheck.Interop; @@ -12,10 +11,9 @@ namespace Microsoft.SignCheck.Verification { - [SupportedOSPlatform("windows")] public class MsiVerifier : AuthentiCodeVerifier { - public MsiVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msi", new OleStorageSecurityInfoProvider()) + public MsiVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msi") { } @@ -62,7 +60,6 @@ public override SignatureVerificationResult VerifySignature(string path, string catch (Exception e) { Log.WriteError(e.Message); - svr.AddDetail(DetailKeys.Error, SignCheckResources.DetailVerificationError, e.Message); } } @@ -95,7 +92,6 @@ public override SignatureVerificationResult VerifySignature(string path, string catch (Exception e) { Log.WriteError(e.Message); - svr.AddDetail(DetailKeys.Error, SignCheckResources.DetailVerificationError, e.Message); } DeleteDirectory(svr.TempPath); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/MspVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/MspVerifier.cs index 64a3582de61..37e2088b662 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/MspVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/MspVerifier.cs @@ -2,17 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using System.Runtime.Versioning; using Microsoft.SignCheck.Interop; using Microsoft.SignCheck.Logging; namespace Microsoft.SignCheck.Verification { - [SupportedOSPlatform("windows")] public class MspVerifier : AuthentiCodeVerifier { - public MspVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msp", new OleStorageSecurityInfoProvider()) + public MspVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msp") { } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/MsuVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/MsuVerifier.cs index dab920bc5c8..5a7e2baa6f6 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/MsuVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/MsuVerifier.cs @@ -14,7 +14,7 @@ namespace Microsoft.SignCheck.Verification { public class MsuVerifier : AuthentiCodeVerifier { - public MsuVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msu", new CabSecurityInfoProvider()) + public MsuVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".msu") { } @@ -23,9 +23,9 @@ public override SignatureVerificationResult VerifySignature(string path, string { SignatureVerificationResult svr = base.VerifySignature(path, parent, virtualPath); - if (VerifyRecursive && OperatingSystem.IsWindows()) + if (VerifyRecursive) { - // MSU is just a CAB file really. Recursive extraction uses WiX CabInfo which is Windows-only. + // MSU is just a CAB file really Log.WriteMessage(LogVerbosity.Diagnostic, SignCheckResources.DiagExtractingFileContents, svr.TempPath); CabInfo cabInfo = new CabInfo(path); cabInfo.Unpack(svr.TempPath); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/OleStorageSecurityInfoProvider.cs b/src/SignCheck/Microsoft.SignCheck/Verification/OleStorageSecurityInfoProvider.cs deleted file mode 100644 index 6310932359c..00000000000 --- a/src/SignCheck/Microsoft.SignCheck/Verification/OleStorageSecurityInfoProvider.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security.Cryptography.Pkcs; -using Microsoft.SignCheck.Interop; -using Microsoft.VisualStudio.OLE.Interop; - -#pragma warning disable CA1416 // Validate platform compatibility - -namespace Microsoft.SignCheck.Verification -{ - /// - /// Reads digital signature information from OLE Compound Document files (MSI, MSP). - /// These files store their Authenticode signature in a stream named "\u0005DigitalSignature". - /// - [SupportedOSPlatform("windows")] - public class OleStorageSecurityInfoProvider : ISecurityInfoProvider - { - // Use \u0005 (not \x05) because \x greedily consumes hex digits, - // turning \x05D into U+005D (']') instead of U+0005 followed by 'D'. - private const string DigitalSignatureStreamName = "\u0005DigitalSignature"; - - public SignedCms ReadSecurityInfo(string path) - { - Guid iidStorage = typeof(IStorage).GUID; - int hr = Ole32.StgOpenStorageEx(path, STGM.STGM_READ | STGM.STGM_SHARE_EXCLUSIVE, - Ole32.STGFMT_STORAGE, 0, IntPtr.Zero, IntPtr.Zero, ref iidStorage, out object storageObj); - IStorage storage = storageObj as IStorage; - - if (hr != StructuredStorage.S_OK || storage == null) - { - return null; - } - - try - { - Microsoft.VisualStudio.OLE.Interop.IStream stream; - storage.OpenStream(DigitalSignatureStreamName, IntPtr.Zero, STGM.STGM_READ | STGM.STGM_SHARE_EXCLUSIVE, 0, out stream); - - if (stream == null) - { - return null; - } - - try - { - using (var memoryStream = new MemoryStream()) - { - byte[] buffer = new byte[4096]; - while (true) - { - stream.Read(buffer, (uint)buffer.Length, out uint bytesRead); - if (bytesRead == 0) - { - break; - } - memoryStream.Write(buffer, 0, (int)bytesRead); - } - - byte[] signatureBytes = memoryStream.ToArray(); - if (signatureBytes.Length == 0) - { - return null; - } - - var signedCms = new SignedCms(); - signedCms.Decode(signatureBytes); - return signedCms; - } - } - finally - { - Marshal.ReleaseComObject(stream); - } - } - catch (COMException) - { - // Stream doesn't exist - file is not signed - return null; - } - finally - { - if (storage != null) - { - Marshal.ReleaseComObject(storage); - } - } - } - } -} diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/PowerShellScriptVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/PowerShellScriptVerifier.cs deleted file mode 100644 index bf1017bb78a..00000000000 --- a/src/SignCheck/Microsoft.SignCheck/Verification/PowerShellScriptVerifier.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. - -using System; -using System.IO; -using System.Security.Cryptography.Pkcs; -using System.Text.RegularExpressions; -using Microsoft.SignCheck.Logging; - -namespace Microsoft.SignCheck.Verification -{ - public class PowerShellScriptVerifier : AuthentiCodeVerifier - { - public PowerShellScriptVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) - : base(log, exclusions, options, fileExtension, new PowerShellSecurityInfoProvider()) { } - - public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) - => base.VerifySignature(path, parent, virtualPath); - - public class PowerShellSecurityInfoProvider : ISecurityInfoProvider - { - public SignedCms ReadSecurityInfo(string path) - { - string content = File.ReadAllText(path); - string pattern = @"(?<=# SIG # Begin signature block\s)([\s\S]*?)(?=# SIG # End signature block)"; - Match match = Regex.Match(content, pattern); - - if (match.Success) - { - string signatureBlock = Regex.Replace(match.Groups[1].Value, @"^# ", "", RegexOptions.Multiline); - byte[] signatureBytes = Convert.FromBase64String(signatureBlock); - - SignedCms signedCms = new SignedCms(); - signedCms.Decode(signatureBytes); - - return signedCms; - } - - return null; - } - } - } -} diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs index c8b67d3d9e0..1fbec04b00d 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs @@ -86,21 +86,18 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer Log = log; Options = options; - if (OperatingSystem.IsWindows()) - { - AddFileVerifier(new MsiVerifier(log, exclusions, options)); - AddFileVerifier(new MspVerifier(log, exclusions, options)); - } - - AddFileVerifier(new CabVerifier(log, exclusions, options)); +#if NETFRAMEWORK + AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psd1")); + AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psm1")); + AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1")); + AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1xml")); + AddFileVerifier(new CabVerifier(log, exclusions, options, ".cab")); AddFileVerifier(new JarVerifier(log, exclusions, options)); + AddFileVerifier(new MsiVerifier(log, exclusions, options)); + AddFileVerifier(new MspVerifier(log, exclusions, options)); AddFileVerifier(new MsuVerifier(log, exclusions, options)); - - AddFileVerifier(new PowerShellScriptVerifier(log, exclusions, options, ".psd1")); - AddFileVerifier(new PowerShellScriptVerifier(log, exclusions, options, ".psm1")); - AddFileVerifier(new PowerShellScriptVerifier(log, exclusions, options, ".ps1")); - AddFileVerifier(new PowerShellScriptVerifier(log, exclusions, options, ".ps1xml")); - + AddFileVerifier(new VsixVerifier(log, exclusions, options)); +#else AddFileVerifier(new DebVerifier(log, exclusions, options)); AddFileVerifier(new MachOVerifier(log, exclusions, options, ".dylib")); AddFileVerifier(new MachOVerifier(log, exclusions, options, ".macho")); @@ -112,6 +109,7 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer AddFileVerifier(new TarVerifier(log, exclusions, options, ".tgz")); AddFileVerifier(new TarVerifier(log, exclusions, options, ".gz")); AddFileVerifier(new RpmVerifier(log, exclusions, options)); +#endif AddFileVerifier(new ExeVerifier(log, exclusions, options, ".exe")); AddFileVerifier(new JavaScriptVerifier(log, exclusions, options)); AddFileVerifier(new LzmaVerifier(log, exclusions, options)); @@ -132,15 +130,7 @@ public IEnumerable VerifyFiles(IEnumerable { FileVerifier fileVerifier = GetFileVerifier(file); SignatureVerificationResult result; - - try - { - result = fileVerifier.VerifySignature(file, parent: null, virtualPath: Path.GetFileName(file)); - } - catch (Exception e) - { - result = SignatureVerificationResult.ErrorResult(file, parent: null, virtualPath: Path.GetFileName(file), e); - } + result = fileVerifier.VerifySignature(file, parent: null, virtualPath: Path.GetFileName(file)); if ((Options & SignatureVerificationOptions.GenerateExclusion) == SignatureVerificationOptions.GenerateExclusion) { @@ -260,6 +250,11 @@ public static FileVerifier GetFileVerifierByHeader(string path) // NUPKGs use .zip format, but should have a .nuspec files inside fileVerifier = GetFileVerifierByExtension(".nupkg"); } + else if (zipArchive.Entries.Any(z => String.Equals(Path.GetExtension(z.FullName), "vsixmanifest", StringComparison.OrdinalIgnoreCase))) + { + // If it's an SDK based VSIX there should be a vsixmanifest file + fileVerifier = GetFileVerifierByExtension(".vsix"); + } else if (zipArchive.Entries.Any(z => String.Equals(z.FullName, "META-INF/MANIFEST.MF", StringComparison.OrdinalIgnoreCase))) { // Zip file with META-INF/MANIFEST.MF file is likely a JAR @@ -281,7 +276,7 @@ public static FileVerifier GetFileVerifierByHeader(string path) fileVerifier = GetFileVerifierByExtension(".macho"); } } - +#if NETFRAMEWORK reader.BaseStream.Seek(0, SeekOrigin.Begin); if (stream.Length > 2) { @@ -300,6 +295,7 @@ public static FileVerifier GetFileVerifierByHeader(string path) } } } +#endif } return fileVerifier; diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationResult.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationResult.cs index 0dbd1fa8409..177d19bd5d6 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationResult.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationResult.cs @@ -308,23 +308,6 @@ public static SignatureVerificationResult UnsupportedFileTypeResult(string path, return signatureVerificationResult; } - /// - /// Creates a SignatureVerificationResult for a file that failed verification due to an unexpected error. - /// - /// The path to the file that caused the error. - /// The parent container of the file, or null for top-level files. - /// The virtual path of the file. - /// The exception that occurred during verification. - public static SignatureVerificationResult ErrorResult(string path, string parent, string virtualPath, Exception exception) - { - var signatureVerificationResult = new SignatureVerificationResult(path, parent, virtualPath); - - signatureVerificationResult.AddDetail(DetailKeys.Error, - String.Format(SignCheckResources.DetailVerificationError, exception.ToString())); - - return signatureVerificationResult; - } - /// /// Creates a SignatureVerificationResult for an excluded file type or file extension. /// diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/VsixVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/VsixVerifier.cs new file mode 100644 index 00000000000..4b9b11c9400 --- /dev/null +++ b/src/SignCheck/Microsoft.SignCheck/Verification/VsixVerifier.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Xml; +using Microsoft.SignCheck.Interop; +using Microsoft.SignCheck.Logging; + +namespace Microsoft.SignCheck.Verification +{ + public class VsixVerifier : ZipVerifier + { + public VsixVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, fileExtension: ".vsix") + { + + } + + public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) + => VerifySupportedFileType(path, parent, virtualPath); + + private bool TryGetTimestamp(PackageDigitalSignature packageSignature, out Timestamp timestamp) + { + bool isValidTimestampSignature = false; + + if (packageSignature == null) + { + throw new ArgumentNullException(nameof(packageSignature)); + } + + timestamp = new Timestamp() + { + SignedOn = DateTime.MaxValue + }; + + XmlNamespaceManager namespaceManager = new XmlNamespaceManager(new NameTable()); + namespaceManager.AddNamespace("ds", "http://schemas.openxmlformats.org/package/2006/digital-signature"); + + // Obtain timestamp from Signature Xml if there is one. + XmlElement element = packageSignature.Signature.GetXml(); + XmlNode encodedTimeNode = element.SelectNodes("//ds:TimeStamp/ds:EncodedTime", namespaceManager).OfType().FirstOrDefault(); + + // If timestamp found, verify it. + if (encodedTimeNode != null && encodedTimeNode.InnerText != null) + { + byte[] binaryTimestamp = null; + + try + { + binaryTimestamp = Convert.FromBase64String(encodedTimeNode.InnerText); + } + catch (FormatException) + { + return false; + } + + IntPtr TSContextPtr = IntPtr.Zero; + IntPtr TSSignerPtr = IntPtr.Zero; + IntPtr StoreHandle = IntPtr.Zero; + + // Ensure timestamp corresponds to package signature + isValidTimestampSignature = WinCrypt.CryptVerifyTimeStampSignature(binaryTimestamp, + (uint)binaryTimestamp.Length, + packageSignature.SignatureValue, + (uint)packageSignature.SignatureValue.Length, + IntPtr.Zero, + out TSContextPtr, + out TSSignerPtr, + out StoreHandle); + + if (isValidTimestampSignature) + { + var timestampContext = (CRYPT_TIMESTAMP_CONTEXT)Marshal.PtrToStructure(TSContextPtr, typeof(CRYPT_TIMESTAMP_CONTEXT)); + var timestampInfo = (CRYPT_TIMESTAMP_INFO)Marshal.PtrToStructure(timestampContext.pTimeStamp, typeof(CRYPT_TIMESTAMP_INFO)); + + unchecked + { + uint low = (uint)timestampInfo.ftTime.dwLowDateTime; + long ftTimestamp = (((long)timestampInfo.ftTime.dwHighDateTime) << 32) | low; + + timestamp.SignedOn = DateTime.FromFileTime(ftTimestamp); + } + + // Get the algorithm name based on the OID. + timestamp.SignatureAlgorithm = Oid.FromOidValue(timestampInfo.HashAlgorithm.pszObjId, OidGroup.HashAlgorithm).FriendlyName; + + X509Certificate2 certificate = new X509Certificate2(packageSignature.Signer); + timestamp.EffectiveDate = certificate.NotBefore; + timestamp.ExpiryDate = certificate.NotAfter; + } + + if (IntPtr.Zero != TSContextPtr) + { + WinCrypt.CryptMemFree(TSContextPtr); + } + if (IntPtr.Zero != TSSignerPtr) + { + WinCrypt.CertFreeCertificateContext(TSSignerPtr); + } + if (IntPtr.Zero != StoreHandle) + { + WinCrypt.CertCloseStore(StoreHandle, 0); + } + } + + return isValidTimestampSignature; + } + + protected override bool IsSigned(string path, SignatureVerificationResult result) + { + PackageDigitalSignature packageSignature = null; + + using (var vsixStream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + var vsixPackage = Package.Open(vsixStream); + var signatureManager = new PackageDigitalSignatureManager(vsixPackage); + + if (!signatureManager.IsSigned) + { + return false; + } + + if (signatureManager.Signatures.Count() != 1) + { + return false; + } + + if (signatureManager.Signatures[0].SignedParts.Count != vsixPackage.GetParts().Count() - 1) + { + return false; + } + + packageSignature = signatureManager.Signatures[0]; + + // Retrieve the timestamp + Timestamp timestamp; + if (!TryGetTimestamp(packageSignature, out timestamp)) + { + // Timestamp is either invalid or not present + result.AddDetail(DetailKeys.Error, SignCheckResources.ErrorInvalidOrMissingTimestamp); + return false; + } + + // Update the result with the timestamp detail + result.AddDetail(DetailKeys.Signature, String.Format(SignCheckResources.DetailTimestamp, timestamp.SignedOn, timestamp.SignatureAlgorithm)); + + // Verify the certificate chain + X509Certificate2 certificate = new X509Certificate2(packageSignature.Signer); + + X509Chain certChain = new X509Chain(); + certChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; + certChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; + + // If the certificate has expired, but the VSIX was signed prior to expiration + // we can ignore invalid time policies. + bool certExpired = DateTime.Now > certificate.NotAfter; + + if (timestamp.IsValid && certExpired) + { + certChain.ChainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreNotTimeValid; + } + + if (!certChain.Build(certificate)) + { + result.AddDetail(DetailKeys.Error, SignCheckResources.DetailErrorFailedToBuildCertChain); + return false; + } + + result.AddDetail(DetailKeys.Misc, SignCheckResources.DetailCertChainValid); + } + + return true; + } + } +} diff --git a/src/SignCheck/README.md b/src/SignCheck/README.md index c3256f413db..8efe1ca824b 100644 --- a/src/SignCheck/README.md +++ b/src/SignCheck/README.md @@ -35,11 +35,12 @@ Arcade defaults to using the signing task via script invocation for signing vali The CLI tool is maintained for legacy purposes and is only recommended for repositories that already use it. Refrane from using this; new repositories should use the Signing Task instead. +- **Supported Frameworks**: .NET Framework only - **Invocation**: - - `dnx Microsoft.DotNet.SignCheck` + - `Microsoft.DotNet.SignCheck.exe` - **CLI Options**: ``` -Microsoft.DotNet.SignCheck [options] +Microsoft.DotNet.SignCheck.exe [options] Options: @@ -80,46 +81,46 @@ Options: #### Detected via File Extensions -| File Extension | Platforms | -|----------------|----------------------------| -| .a | macOS | -| .app | macOS | -| .cab | Windows, macOS, Linux | -| .deb | Linux | -| .dll | Windows, macOS, Linux | -| .dylib | macOS | -| .exe | Windows, macOS, Linux | -| .gz | macOS, Linux | -| .jar | Windows, macOS, Linux | -| .js | Windows, macOS, Linux | -| .lzma | Windows, macOS, Linux | -| .macho | macOS | -| .msi | Windows | -| .msp | Windows | -| .msu | Windows, macOS, Linux * | -| .nupkg | Windows, macOS, Linux | -| .pkg | macOS | -| .ps1 | Windows, macOS, Linux | -| .ps1xml | Windows, macOS, Linux | -| .psd1 | Windows, macOS, Linux | -| .psm1 | Windows, macOS, Linux | -| .rpm | Linux | -| .so | macOS | -| .tar | macOS, Linux | -| .tgz | macOS, Linux | -| .xml | Windows, macOS, Linux | -| .zip | Windows, macOS, Linux | - -\* Signature verification is cross-platform. Recursive content extraction is Windows-only. +| File Extension | Platforms | .NET Product | +|----------------|----------------------------|----------------------| +| .a | macOS | .NET Core | +| .app | macOS | .NET Core | +| .cab | Windows | .NET Framework | +| .deb | Linux | .NET Core | +| .dll | Windows, macOS, Linux | .NET Framework, Core | +| .dylib | macOS | .NET Core | +| .exe | Windows, macOS, Linux | .NET Framework, Core | +| .gz | macOS, Linux | .NET Core | +| .jar | Windows | .NET Framework | +| .js | Windows, macOS, Linux | .NET Framework, Core | +| .lzma | Windows, macOS, Linux | .NET Framework, Core | +| .macho | macOS | .NET Core | +| .msi | Windows | .NET Framework | +| .msp | Windows | .NET Framework | +| .msu | Windows | .NET Framework | +| .nupkg | Windows, macOS, Linux | .NET Framework, Core | +| .pkg | macOS | .NET Core | +| .ps1 | Windows | .NET Framework | +| .ps1xml | Windows | .NET Framework | +| .psd1 | Windows | .NET Framework | +| .psm1 | Windows | .NET Framework | +| .rpm | Linux | .NET Core | +| .so | macOS | .NET Core | +| .tar | macOS, Linux | .NET Core | +| .tgz | macOS, Linux | .NET Core | +| .vsix | Windows | .NET Framework | +| .xml | Windows, macOS, Linux | .NET Framework, Core | +| .zip | Windows, macOS, Linux | .NET Framework, Core | #### Detected via File Headers -| File Type | Platforms | -|----------------------------|----------------------------| -| Cab Files | Windows, macOS, Linux | -| EXE Files | Windows | -| Jar Files | Windows, macOS, Linux | -| Mach-O Files | macOS | -| NuGet Packages | Windows, macOS, Linux | -| PE Files | Windows | -| Zip Files | Windows, macOS, Linux | +| File Type | Platforms | .NET Product | +|----------------------------|----------------------------|----------------------| +| Cab Files | Windows | .NET Framework | +| EXE Files | Windows | .NET Framework | +| Jar Files | Windows | .NET Framework | +| Mach-O Files | macOS | .NET Core | +| NuGet Packages | Windows, macOS, Linux | .NET Framework, Core | +| PE Files | Windows | .NET Framework | +| VSIX Files | Windows | .NET Framework | +| Zip Files | Windows, macOS, Linux | .NET Framework, Core | diff --git a/src/SignCheck/SignCheck/App.config b/src/SignCheck/SignCheck/App.config new file mode 100644 index 00000000000..731f6de6c29 --- /dev/null +++ b/src/SignCheck/SignCheck/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj b/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj index 294c0def325..d0628c78798 100644 --- a/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj +++ b/src/SignCheck/SignCheck/Microsoft.DotNet.SignCheck.csproj @@ -3,21 +3,31 @@ - $(BundledNETCoreAppTargetFramework) + $(NetFrameworkToolCurrent) Exe + true false true + + true Build artifact signing validation tool Arcade Signing Validation Tool SignCheck - true - signcheck - true - + + + + + + + + + diff --git a/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj b/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj index 0342bd8deb0..ede49db7e8c 100644 --- a/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj +++ b/src/SignCheck/SignCheckTask/Microsoft.DotNet.SignCheckTask.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent);$(NetFrameworkToolCurrent) true false true @@ -21,6 +21,11 @@ + + + + + - $(MSBuildThisFileDirectory)..\lib\net\Microsoft.DotNet.SignCheckTask.dll + $(MSBuildThisFileDirectory)..\lib\net\Microsoft.DotNet.SignCheckTask.dll + $(MSBuildThisFileDirectory)..\lib\netframework\Microsoft.DotNet.SignCheckTask.dll - + diff --git a/src/SignCheck/SignCheckTask/src/SignCheckTask.cs b/src/SignCheck/SignCheckTask/src/SignCheckTask.cs index 84df0910bdc..63cdd484c7a 100644 --- a/src/SignCheck/SignCheckTask/src/SignCheckTask.cs +++ b/src/SignCheck/SignCheckTask/src/SignCheckTask.cs @@ -3,7 +3,11 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +#if NET472 +using AppDomainIsolatedTask = Microsoft.Build.Utilities.AppDomainIsolatedTask; +#else using BuildTask = Microsoft.Build.Utilities.Task; +#endif using Microsoft.SignCheck.Logging; using System; using System.Collections.Generic; @@ -12,8 +16,16 @@ namespace SignCheckTask { +#if NETFRAMEWORK + [LoadInSeparateAppDomain] + [RunInSTA] + public class SignCheckTask : AppDomainIsolatedTask + { + static SignCheckTask() => Microsoft.DotNet.AssemblyResolution.Initialize(); +#else public class SignCheckTask : BuildTask { +#endif public bool EnableJarSignatureVerification { get; @@ -92,6 +104,9 @@ public string ArtifactFolder public override bool Execute() { +#if NETFRAMEWORK + Microsoft.DotNet.AssemblyResolution.Log = Log; +#endif try { bool succeeded = ExecuteImpl(); @@ -99,6 +114,9 @@ public override bool Execute() } finally { +#if NETFRAMEWORK + Microsoft.DotNet.AssemblyResolution.Log = null; +#endif } } diff --git a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/Microsoft.DotNet.VersionTools.Cli.Tests.csproj b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/Microsoft.DotNet.VersionTools.Cli.Tests.csproj index a97ccda0d6d..ac8f8cfe175 100644 --- a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/Microsoft.DotNet.VersionTools.Cli.Tests.csproj +++ b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/Microsoft.DotNet.VersionTools.Cli.Tests.csproj @@ -1,12 +1,12 @@ - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) - + diff --git a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/VersionTrimmingOperationTests.cs b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/VersionTrimmingOperationTests.cs index ea825b30c20..6237bd5fc2c 100644 --- a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/VersionTrimmingOperationTests.cs +++ b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli.Tests/VersionTrimmingOperationTests.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. -using AwesomeAssertions; +using FluentAssertions; using Microsoft.Arcade.Common; using Moq; using NuGet.Versioning; diff --git a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli/Microsoft.DotNet.VersionTools.Cli.csproj b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli/Microsoft.DotNet.VersionTools.Cli.csproj index 56086dac4df..e02e2d9a40e 100644 --- a/src/VersionTools/Microsoft.DotNet.VersionTools.Cli/Microsoft.DotNet.VersionTools.Cli.csproj +++ b/src/VersionTools/Microsoft.DotNet.VersionTools.Cli/Microsoft.DotNet.VersionTools.Cli.csproj @@ -1,7 +1,7 @@  - $(BundledNETCoreAppTargetFramework) + $(NetToolCurrent) Exe true true diff --git a/src/VersionTools/Microsoft.DotNet.VersionTools.Tasks.Tests/Microsoft.DotNet.VersionTools.Tasks.Tests.csproj b/src/VersionTools/Microsoft.DotNet.VersionTools.Tasks.Tests/Microsoft.DotNet.VersionTools.Tasks.Tests.csproj new file mode 100644 index 00000000000..deac5bd978a --- /dev/null +++ b/src/VersionTools/Microsoft.DotNet.VersionTools.Tasks.Tests/Microsoft.DotNet.VersionTools.Tasks.Tests.csproj @@ -0,0 +1,18 @@ + + + + $(NetToolCurrent) + + + + + + + + + + + + + + diff --git a/tests/UnitTests.proj b/tests/UnitTests.proj index 75d73434482..7dcbd830e9e 100644 --- a/tests/UnitTests.proj +++ b/tests/UnitTests.proj @@ -1,23 +1,19 @@ - - - $(BundledNETCoreAppTargetFramework) - $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/Microsoft.DotNet.Helix.Sdk.dll - TaskHostFactory + $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/$(NetToolCurrent)/publish/Microsoft.DotNet.Helix.Sdk.dll + $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/$(NetFrameworkToolCurrent)/publish/Microsoft.DotNet.Helix.Sdk.dll test/product/ - Arcade Job Name true sdk - $(AGENT_JOBNAME) run on + $(AGENT_JOBNAME) 300 @@ -39,7 +35,7 @@ - + @@ -47,14 +43,8 @@ - - - - - - - - + + @@ -69,14 +59,9 @@ - - $(TestRunNamePrefix)AlmaLinux.9.Amd64 - - - - $(TestRunNamePrefix)Debian.13.Amd64 - - + + + @@ -86,14 +71,9 @@ - - $(TestRunNamePrefix)AlmaLinux.9.Amd64.Open - - - - $(TestRunNamePrefix)Debian.13.Amd64.Open - - + + + @@ -141,5 +121,4 @@ $(MSBuildThisFileDirectory)\AspNetVersionCheck - diff --git a/tests/XHarness.Tests.Common.props b/tests/XHarness.Tests.Common.props index 4e36863a3c4..2c46db86366 100644 --- a/tests/XHarness.Tests.Common.props +++ b/tests/XHarness.Tests.Common.props @@ -1,5 +1,4 @@ - @@ -8,8 +7,8 @@ --> - $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/Microsoft.DotNet.Helix.Sdk.dll - TaskHostFactory + $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/$(NetToolCurrent)/publish/Microsoft.DotNet.Helix.Sdk.dll + $(MSBuildThisFileDirectory)../artifacts/bin/Microsoft.DotNet.Helix.Sdk/$(Configuration)/$(NetFrameworkToolCurrent)/publish/Microsoft.DotNet.Helix.Sdk.dll