diff --git a/.azuredevops/dependabot.yml b/.azuredevops/dependabot.yml new file mode 100644 index 0000000000..a47d2fdb43 --- /dev/null +++ b/.azuredevops/dependabot.yml @@ -0,0 +1,5 @@ +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/.editorconfig b/.editorconfig index 39f9dc14c9..bf8aa865dd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -101,8 +101,8 @@ dotnet_style_prefer_simplified_boolean_expressions = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion # IDE0074: Use compound assignment -dotnet_style_prefer_compound_assignment = true:warning # not default, default is true:suggestion, increased severity to ensure it is used -dotnet_diagnostic.IDE0074.severity = warning # not default, set in accordance to previous setting +dotnet_style_prefer_compound_assignment = true:suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues +dotnet_diagnostic.IDE0074.severity = suggestion # not default, set in accordance to previous setting # IDE0032: Use auto property dotnet_style_prefer_auto_properties = true:suggestion # not default, default is true:suggestion, increased severity to ensure it is used @@ -120,18 +120,18 @@ dotnet_diagnostic.IDE0060.severity = warning dotnet_remove_unnecessary_suppression_exclusions = none # IDE0090: Use 'new(...)' -dotnet_diagnostic.IDE0090.severity = warning # not default, increased severity to go with simpler declarations +dotnet_diagnostic.IDE0090.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # IDE0005: Remove unnecessary import -dotnet_diagnostic.IDE0005.severity = warning # not default, increased severity to ensure it is used +dotnet_diagnostic.IDE0005.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # 'using' directive preferences # Keep this in sync with the related C# rule: csharp_using_directive_placement -dotnet_diagnostic.IDE0065.severity = warning +dotnet_diagnostic.IDE0065.severity = suggestion # Use simple using statements # Keep this in sync with th related C# rule: csharp_prefer_simple_using_statement -dotnet_diagnostic.IDE0063.severity = warning +dotnet_diagnostic.IDE0063.severity = suggestion # CA2208: Instantiate argument exceptions correctly dotnet_diagnostic.CA2208.severity = warning # not default, increased severity to ensure it is always applied @@ -141,7 +141,7 @@ dotnet_diagnostic.CA2241.severity = warning # not default, increased severity to # IDE0053: Use expression body for lambda expressions # Keep this in sync with csharp_style_expression_bodied_lambdas -dotnet_diagnostic.IDE0053.severity = warning # not default, increased severity to ensure it is applied +dotnet_diagnostic.IDE0053.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # CA2016: Forward the 'CancellationToken' parameter to methods dotnet_diagnostic.CA2016.severity = warning # not default, increased severity to ensure it is applied @@ -172,19 +172,19 @@ dotnet_diagnostic.CA2215.severity=warning # not default, increased severity to e # IDE0019: Use pattern matching # Keep this in sync with csharp_style_pattern_matching_over_as_with_null_check -dotnet_diagnostic.IDE0019.severity = warning # not default, increased severity to ensure it is applied +dotnet_diagnostic.IDE0019.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # IDE0020: # Keep this in sync with csharp_style_pattern_matching_over_is_with_cast_check -dotnet_diagnostic.IDE0020.severity = warning # not default, increased severity to ensure it is applied +dotnet_diagnostic.IDE0020.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # IDE0078: Use pattern matching # Keep this in sync with csharp_style_prefer_pattern_matching -dotnet_diagnostic.IDE0078.severity = warning # not default, increased severity to ensure it is applied +dotnet_diagnostic.IDE0078.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # IDE0083: Use pattern matching (not operator) # Keep this in sync with csharp_style_prefer_not_pattern -dotnet_diagnostic.IDE0083.severity = warning # not default, increased severity to ensure it is applied +dotnet_diagnostic.IDE0083.severity = suggestion # not default, reduced from warning to suggestion to avoid breaking builds on style issues # CA1836: Prefer IsEmpty over Count dotnet_diagnostic.CA1836.severity = warning # not default, increased severity to ensure it is applied @@ -239,6 +239,9 @@ dotnet_diagnostic.CA1050.severity = warning # not default, increased severity to # CA1061: Do not hide base class methods dotnet_diagnostic.CA1061.severity = warning # not default, increased severity to ensure it is applied +# CA1067: Override Object.Equals when implementing IEquatable +dotnet_diagnostic.CA1067.severity = warning # not default, increased severity to ensure it is applied + # CA1069: Enums should not have duplicate values dotnet_diagnostic.CA1069.severity = warning # not default, increased severity to ensure it is applied @@ -278,7 +281,7 @@ csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_indexers = true:silent # Keep this in sync with IDE0053 -csharp_style_expression_bodied_lambdas = true:warning # not default, increased severity to ensure it is applied +csharp_style_expression_bodied_lambdas = true:suggestion # not default, reduced from warning to suggestion csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_operators = false:silent @@ -286,14 +289,14 @@ csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences # Keep this in sync with IDE0019 -csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion # Keep this in sync with IDE0020 and IDE0038 -csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion # Keep this in sync with IDE0083 -csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_not_pattern = true:suggestion # Keep this in sync with IDE0078 -csharp_style_prefer_pattern_matching = true:warning -csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion @@ -305,7 +308,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter # Code-block preferences csharp_prefer_braces = true:silent # Keep this in sync with the related .NET rule: IDE0063 -csharp_prefer_simple_using_statement = true:warning # not default, default is true:suggestion, increased severity to ensure it is used +csharp_prefer_simple_using_statement = true:suggestion # not default, reduced from warning to suggestion # Expression-level preferences csharp_prefer_simple_default_expression = true:suggestion @@ -320,7 +323,7 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:sil # 'using' directive preferences # Keep this in sync with the related .NET rule: IDE0065 -csharp_using_directive_placement = outside_namespace:warning +csharp_using_directive_placement = outside_namespace:suggestion # IDE0190: Null check can be simplified # Keep this in sync with the related .NET rule: IDE0190 @@ -329,7 +332,7 @@ csharp_style_prefer_parameter_null_checking = false # not default, disabled as n #### .NET Formatting Rules #### # IDE0055: Fix formatting - Set the severity of all .NET and C# formatting rules (https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules) -dotnet_diagnostic.IDE0055.severity = warning # ensure all formatting rules are enforced on build +dotnet_diagnostic.IDE0055.severity = suggestion # reduced from warning to suggestion to avoid breaking builds on formatting issues # IDE0057: Use range operator dotnet_diagnostic.IDE0057.severity = none # Range operator is not supported in some TFMs. @@ -393,7 +396,7 @@ dotnet_diagnostic.IDE0052.severity = silent # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = warning -dotnet_diagnostic.IDE0073.severity = warning +dotnet_diagnostic.IDE0073.severity = suggestion # Naming rules diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..5da0bc8573 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,94 @@ +This is a .NET based repository that contains the VSTest test platform. Please follow these guidelines when contributing: + +## Code Standards + +You MUST follow all code-formatting and naming conventions defined in [`.editorconfig`](../.editorconfig). + +In addition to the rules enforced by `.editorconfig`, you SHOULD: + +- Favor style and conventions that are consistent with the existing codebase. +- Prefer file-scoped namespace declarations and single-line using directives. +- Ensure that the final return statement of a method is on its own line. +- Use pattern matching and switch expressions wherever possible. +- Use `nameof` instead of string literals when referring to member names. +- Always use `is null` or `is not null` instead of `== null` or `!= null`. +- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null. +- Prefer `?.` if applicable (e.g. `scope?.Dispose()`). +- Use `ObjectDisposedException.ThrowIf` where applicable. +- Respect StyleCop.Analyzers rules, in particular: + - SA1028: Code must not contain trailing whitespace + - SA1316: Tuple element names should use correct casing + - SA1518: File is required to end with a single newline character + +You MUST minimize adding public API surface area but any newly added public API MUST be declared in the related `PublicAPI.Unshipped.txt` file. + +## Working with Git Worktrees + +This repository uses git worktrees to work on multiple things at the same time. Worktrees live in `../vstest-tree/` relative to the main clone at `c:\p\vstest`. + +The following global git aliases are configured: + +```shell +git config --global alias.wta '!f() { mkdir -p "$(git rev-parse --show-toplevel)/../vstest-tree" 2>/dev/null; git worktree add -b "$1" "../vstest-tree/$1"; }; f' +git config --global alias.wtr '!f() { git worktree remove "../vstest-tree/$1"; }; f' +git config --global alias.wtl '!f() { git worktree list; }; f' +``` + +Usage: + +```shell +git wta my-feature-branch # creates c:\p\vstest-tree\my-feature-branch +git wtl # list all active worktrees +git wtr my-feature-branch # remove worktree when done +``` + +The upstream remote `upstream` points to `https://github.com/microsoft/vstest`. To sync with upstream: + +```shell +git fetch upstream +git checkout main +git merge upstream/main +``` + +## Localization Guidelines + +Anytime you add a new localization resource, you MUST: +- Add a corresponding entry in the localization resource file. +- Add an entry in all `*.xlf` files related to the modified `.resx` file. +- Do not modify existing entries in '*.xlf' files unless you are also modifying the corresponding `.resx` file. + +## Unattended Work Instructions + +When working autonomously on issues (e.g. from a milestone), follow this workflow: + +### Before Starting + +- **Assign the issue** to the user you are working on behalf of before starting work. +- **Skip issues with active PRs** — if an issue already has a PR with recent activity, don't duplicate the work. + +### Implement + +- Create a feature branch from `main` (never commit to `main` directly). +- Implement the change and write tests where applicable. +- Build locally to validate the change compiles. Debug configuration is fine for local builds. + +### Create PR + +- Check `git remote -v` to identify which remote is the fork (has the user's name in the URL, e.g. `github.com//vstest`) and which is the upstream repo (`github.com/microsoft/vstest`). +- Push the branch to the fork remote. +- Create the PR against `microsoft/vstest` (the upstream repo) — **never PR to the fork**. +- Do not create draft PRs — undrafting forces a re-build. + +### Monitor PRs + +- CI pipeline skips builds for doc-only changes (e.g. `.md` files) — these PRs go green in under a minute. +- For code changes, check PR status within 15–20 minutes — the Windows build and tests finish first, macOS/ubuntu take longer. +- Check **both** CI status (pass/fail) **and** mergeable state — PR checks can show green even when there are merge conflicts. Always verify with `gh pr view --json mergeable`. +- When a build fails, take hints from the automated PR review comments but reason about them — the reviewer is automated and may be wrong. +- If a build fails, investigate the failure, push a fix to the same branch, and wait for the rebuild. +- If a PR becomes CONFLICTING, rebase the branch onto `main` and force-push using `--force-with-lease` (e.g. `git push --force-with-lease`). +- When a PR is fully green (all checks pass, no conflicts), mark it as ready to merge. + +### Troubleshooting + +- If a build fails with warnings treated as errors (e.g. IDE0005 unnecessary using), and you cannot reproduce locally, try building with `-c Release` to match CI: `./build.cmd -c Release`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..a1ef8b6a84 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "dotnet-sdk" + directory: "/" + schedule: + interval: "weekly" + day: "wednesday" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" diff --git a/.github/policies/BreakingChangeManagement.yml b/.github/policies/BreakingChangeManagement.yml new file mode 100644 index 0000000000..1b32be5c25 --- /dev/null +++ b/.github/policies/BreakingChangeManagement.yml @@ -0,0 +1,43 @@ +id: +name: GitOps.BreakingChangeManagement +description: GitOps.BreakingChangeManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - if: + - payloadType: Issues + - labelAdded: + label: breaking-change + then: + - addReply: + reply: >- + Refer to the [.NET SDK breaking change guidelines](https://github.com/dotnet/sdk/blob/main/documentation/project-docs/breaking-change-guidelines.md#required-process-for-all-net-sdk-breaking-changes) + description: Add breaking change doc instructions to issue + - if: + - payloadType: Pull_Request + - labelAdded: + label: breaking-change + then: + - addLabel: + label: needs-breaking-change-doc-created + - addReply: + reply: >- + Added `needs-breaking-change-doc-created` label because this PR has the `breaking-change` label. + + + When you commit this breaking change: + + + 1. [ ] Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the [breaking change documentation template](https://aka.ms/dotnet/docs/new-breaking-change-issue), then remove this `needs-breaking-change-doc-created` label. + + 2. [ ] Ask a committer to mail the `.NET SDK Breaking Change Notification` email list. + + + You can refer to the [.NET SDK breaking change guidelines](https://github.com/dotnet/sdk/blob/main/documentation/project-docs/breaking-change-guidelines.md) + description: Add breaking change instructions to PR. +onFailure: +onSuccess: diff --git a/.github/policies/LabelManagement.IssueClosed.yml b/.github/policies/LabelManagement.IssueClosed.yml new file mode 100644 index 0000000000..e4865dc4f3 --- /dev/null +++ b/.github/policies/LabelManagement.IssueClosed.yml @@ -0,0 +1,36 @@ +id: +name: LabelManagement.IssueClosed +description: Handlers when an issue gets closed +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Remove labels when an issue is closed + if: + - payloadType: Issues + - isAction: + action: Closed + then: + - removeLabel: + label: 'Needs: Triage :mag:' + - removeLabel: + label: 'Needs: Attention :wave:' + - removeLabel: + label: 'Needs: Author Feedback' + - removeLabel: + label: Help-Wanted + - description: Remove labels when a pull request is closed + if: + - payloadType: Pull_Request + - isAction: + action: Closed + then: + - removeLabel: + label: 'Needs: Attention :wave:' + - removeLabel: + label: 'Needs: Author Feedback' +onFailure: +onSuccess: diff --git a/.github/policies/LabelManagement.IssueOpened.yml b/.github/policies/LabelManagement.IssueOpened.yml new file mode 100644 index 0000000000..02b4d0bf22 --- /dev/null +++ b/.github/policies/LabelManagement.IssueOpened.yml @@ -0,0 +1,27 @@ +id: +name: LabelManagement.IssueOpened +description: Handlers when an issue is first opened +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Add needs triage to new issues + if: + - payloadType: Issues + - isAction: + action: Opened + - and: + - not: + activitySenderHasPermission: + permission: Admin + - not: + activitySenderHasPermission: + permission: Write + then: + - addLabel: + label: 'Needs: Triage :mag:' +onFailure: +onSuccess: diff --git a/.github/policies/LabelManagement.IssueUpdated.yml b/.github/policies/LabelManagement.IssueUpdated.yml new file mode 100644 index 0000000000..fee7f3e06e --- /dev/null +++ b/.github/policies/LabelManagement.IssueUpdated.yml @@ -0,0 +1,144 @@ +id: +name: LabelManagement.IssueUpdated +description: Handlers when an issue is updated but not closed +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: >- + Remove "State: No Recent Activity" when a pull request or issue is updated + if: + - or: + - payloadType: Pull_Request + - payloadType: Pull_Request_Review + - payloadType: Pull_Request_Review_Comment + - payloadType: Issue_Comment + - payloadType: Issues + - not: + isAction: + action: Closed + - hasLabel: + label: "State: No Recent Activity" + then: + - removeLabel: + label: "State: No Recent Activity" + # The policy service should not trigger itself here, or else the label would be removed immediately after being added + triggerOnOwnActions: False + - description: Clean email replies on every comment + if: + - payloadType: Issue_Comment + then: + - cleanEmailReply + - description: Remove "Help-Wanted" label when an issue goes into PR + if: + - payloadType: Issues + - labelAdded: + label: In-PR + - hasLabel: + label: Help-Wanted + then: + - removeLabel: + label: Help-Wanted + - description: >- + If an author responds to an issue which needs author feedback + * Remove the "Needs: Author Feedback" Label + * Add the "Needs: Attention :wave:" Label + if: + - or: + - payloadType: Pull_Request_Review + - payloadType: Pull_Request_Review_Comment + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - hasLabel: + label: "Needs: Author Feedback" + - not: + isAction: + action: Synchronize + then: + - removeLabel: + label: "Needs: Author Feedback" + - addLabel: + label: "Needs: Attention :wave:" + - description: >- + If team members respond to an issue which needs attention + * Remove the "Needs: Attention :wave:" Label + if: + - or: + - payloadType: Pull_Request_Review + - payloadType: Pull_Request_Review_Comment + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - hasLabel: + label: "Needs: Attention :wave:" + - not: + isAction: + action: Synchronize + - or: + - activitySenderHasAssociation: + association: Member + - activitySenderHasAssociation: + association: Owner + - activitySenderHasAssociation: + association: Collaborator + then: + - removeLabel: + label: "Needs: Attention :wave:" + - description: >- + If team members respond to an issue which needs triage + * Remove the "Needs: Triage :mag:" Label + if: + - or: + - payloadType: Pull_Request_Review + - payloadType: Pull_Request_Review_Comment + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - hasLabel: + label: "Needs: Triage :mag:" + - not: + isAction: + action: Synchronize + - or: + - activitySenderHasAssociation: + association: Member + - activitySenderHasAssociation: + association: Owner + - activitySenderHasAssociation: + association: Collaborator + then: + - removeLabel: + label: "Needs: Triage :mag:" + - description: >- + When changes are requested on a pull request + * Disable automerge + * Assign to the author + * Label with "Needs: Author Feedback" + if: + - payloadType: Pull_Request_Review + - isAction: + action: Submitted + - isReviewState: + reviewState: Changes_requested + then: + - disableAutoMerge + - assignTo: + author: True + - addLabel: + label: "Needs: Author Feedback" + - description: Sync labels from issues on all pull request events + if: + - payloadType: Pull_Request + then: + - labelSync: + pattern: "Area:" + - labelSync: + pattern: "Type:" + - inPrLabel: + label: In-PR +onFailure: +onSuccess: diff --git a/.github/policies/PullRequestIssueManagement b/.github/policies/PullRequestIssueManagement new file mode 100644 index 0000000000..b8f34dbf72 --- /dev/null +++ b/.github/policies/PullRequestIssueManagement @@ -0,0 +1,28 @@ +id: +name: GitOps.PullRequestIssueManagement +description: GitOps.PullRequestIssueManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + + eventResponderTasks: + + - description: Auto-approve maestro PRs + triggerOnOwnActions: false + if: + - payloadType: Pull_Request + - isPullRequest + - isActivitySender: + user: dotnet-maestro[bot] + issueAuthor: False + - isAction: + action: Opened + then: + - approvePullRequest: + comment: Auto-approve + +onFailure: +onSuccess: diff --git a/.github/policies/ScheduledSearch.AutoClose.yml b/.github/policies/ScheduledSearch.AutoClose.yml new file mode 100644 index 0000000000..df645b7c6e --- /dev/null +++ b/.github/policies/ScheduledSearch.AutoClose.yml @@ -0,0 +1,102 @@ +id: ScheduledSearch.AutoClose +name: GitOps.PullRequestIssueManagement +description: Housekeeping of issues that should be closed +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: >- + Search for PR where - + * Pull Request is Open + * Pull request has the label "State: No Recent Activity" + * Pull request has the label "Needs: Author Feedback" + * Has not had activity in the last 7 days + + Then - + * Close the PR + frequencies: + - hourly: + hour: 6 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: "State: No Recent Activity" + - hasLabel: + label: "Needs: Author Feedback" + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: >- + Search for Issues where - + * Issue is Open + * Issue has the label "State: No Recent Activity" + * Issue has the label "Needs: Author Feedback" + * Has not had activity in the last 7 days + + Then - + * Close the Issue + frequencies: + - hourly: + hour: 6 + filters: + - isIssue + - isOpen + - hasLabel: + label: "State: No Recent Activity" + - hasLabel: + label: "Needs: Author Feedback" + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: >- + Search for Issues where - + * Issue is Open + * Issue has the label "State: Won't Fix" + * Has not had activity in the last 1 day + + Then - + * Close the Issue + frequencies: + - hourly: + hour: 6 + filters: + - isIssue + - isOpen + - hasLabel: + label: "State: Won't Fix" + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as won't fix and has not had any activity for **1 day**. It will be closed for housekeeping purposes. + - closeIssue + - description: >- + Search for Issues where - + * Issue is Open + * Issue has the label "Resolution: Duplicate" + * Has not had activity in the last 1 day + + Then - + * Close the Issue + frequencies: + - hourly: + hour: 6 + filters: + - isIssue + - isOpen + - hasLabel: + label: "Resolution: Duplicate" + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. + - closeIssue +onFailure: +onSuccess: \ No newline at end of file diff --git a/.github/policies/ScheduledSearch.MarkNoRecentActivity.yml b/.github/policies/ScheduledSearch.MarkNoRecentActivity.yml new file mode 100644 index 0000000000..f4fd2da1a8 --- /dev/null +++ b/.github/policies/ScheduledSearch.MarkNoRecentActivity.yml @@ -0,0 +1,72 @@ +id: ScheduledSearch.MarkNoRecentActivity +name: GitOps.PullRequestIssueManagement +description: Mark issues and pull requests with no recent activity +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: >- + Search for PR where - + * Pull Request is Open + * Pull request does not have the label "State: No Recent Activity" + * Pull request has the label "Needs: Author Feedback" + * Has not had activity in the last 7 days + + Then - + * Add "State: No Recent Activity" label + * Warn user about pending closure + frequencies: + - hourly: + hour: 6 + filters: + - isPullRequest + - isOpen + - isNotLabeledWith: + label: "State: No Recent Activity" + - hasLabel: + label: "Needs: Author Feedback" + - noActivitySince: + days: 7 + actions: + - addLabel: + label: "State: No Recent Activity" + - addReply: + reply: >- + Hello @${issueAuthor}, + + This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. + - description: >- + Search for Issues where - + * Issue is Open + * Issue has the label "Needs: Author Feedback" + * Issue does not have the label "State: No Recent Activity" + * Has not had activity in the last 7 days + + Then - + * Add "State: No Recent Activity" label + * Warn user about pending closure + frequencies: + - hourly: + hour: 6 + filters: + - isIssue + - isOpen + - hasLabel: + label: "Needs: Author Feedback" + - isNotLabeledWith: + label: "State: No Recent Activity" + - noActivitySince: + days: 7 + actions: + - addLabel: + label: "State: No Recent Activity" + - addReply: + reply: >- + Hello @${issueAuthor}, + + This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. +onFailure: +onSuccess: \ No newline at end of file diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml deleted file mode 100644 index b3c77f150f..0000000000 --- a/.github/policies/resourceManagement.yml +++ /dev/null @@ -1,112 +0,0 @@ -id: -name: GitOps.PullRequestIssueManagement -description: GitOps.PullRequestIssueManagement primitive -owner: -resource: repository -disabled: false -where: -configuration: - resourceManagementConfiguration: - scheduledSearches: - - description: - frequencies: - - hourly: - hour: 6 - filters: - - isIssue - - isOpen - - hasLabel: - label: needs-author-feedback - - noActivitySince: - days: 10 - - isNotLabeledWith: - label: status-no-recent-activity - actions: - - addLabel: - label: status-no-recent-activity - - addReply: - reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **10 days**. - - description: - frequencies: - - hourly: - hour: 6 - filters: - - isIssue - - isOpen - - hasLabel: - label: duplicate - - noActivitySince: - days: 1 - actions: - - addReply: - reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. - - closeIssue - eventResponderTasks: - - if: - - payloadType: Issue_Comment - - isAction: - action: Created - - isActivitySender: - issueAuthor: True - - hasLabel: - label: needs-author-feedback - - isOpen - then: - - addLabel: - label: needs-attention - - removeLabel: - label: needs-author-feedback - description: - - if: - - payloadType: Issues - - not: - isAction: - action: Closed - - hasLabel: - label: status-no-recent-activity - then: - - removeLabel: - label: status-no-recent-activity - description: - - if: - - payloadType: Issue_Comment - - hasLabel: - label: status-no-recent-activity - then: - - removeLabel: - label: status-no-recent-activity - description: - - if: - - payloadType: Pull_Request - - hasLabel: - label: auto-merge - then: - - enableAutoMerge: - mergeMethod: Squash - description: - - if: - - payloadType: Pull_Request - - labelRemoved: - label: auto-merge - then: - - disableAutoMerge - description: - - if: - - payloadType: Pull_Request - - titleContains: - pattern: .+Update dependencies from dotnet/arcade .+ - isRegex: True - - isActivitySender: - user: dotnet-maestro - issueAuthor: False - - isAction: - action: Opened - then: - - addLabel: - label: auto-merge - - approvePullRequest: - comment: Arcade update PR auto-approved. - description: - triggerOnOwnActions: true -onFailure: -onSuccess: diff --git a/.github/skills/creating-skills/SKILL.md b/.github/skills/creating-skills/SKILL.md new file mode 100644 index 0000000000..2ed42681bc --- /dev/null +++ b/.github/skills/creating-skills/SKILL.md @@ -0,0 +1,166 @@ +--- +name: creating-skills +description: Create custom agent capabilities when discovering novel tools, receiving task-agnostic tips from reviewers, or after researching specialized workflows not covered in existing instructions. Teaches structure, YAML optimization for LLM discoverability, and token efficiency. +--- + +# Creating GitHub Copilot Agent Skills + +This skill teaches you how to create effective GitHub Copilot Agent Skills for this repository. + +## Pre-Check: Avoid Duplication + +**STOP** and check before creating a new skill: + +1. **Does it exist already?** + - List all skills: `ls -la .github/skills/` + - Read existing skill frontmatter and content + - If semantically similar skill exists, STOP + +2. **Should an existing skill be expanded?** + - If frontmatter semantically matches your use case → Update existing skill's description + - Add keywords to improve discoverability rather than creating duplicate + +3. **Should existing skill content change?** + - If frontmatter matches but content incomplete → Add section to existing skill + - Enhance with additional examples, procedures, or troubleshooting + - Update frontmatter only if significantly broadening scope + +**Only create new skill if:** +- No semantic overlap with existing skills +- Addresses distinct problem domain +- Has unique triggering conditions + +## Skill Structure + +### Directory Placement + +Skills should be placed in `.github/skills/` directory: +- **Project skills** (repository-specific): `.github/skills/skill-name/` + +Each skill must have its own subdirectory with a lowercase, hyphenated name that matches the `name` field in the frontmatter. + +### File Requirements + +Every skill directory must contain a `SKILL.md` file (case-sensitive) with: + +1. **YAML Frontmatter** (required): + +2. **Markdown Body** with clear instructions, examples, procedures, guidelines, and references + +### Additional Resources + +Skills can include: +- Scripts (e.g., `.sh`, `.fsx`, `.ps1`) +- Example files +- Templates +- Reference documentation + +## YAML Frontmatter Best Practices + +The frontmatter is critical for skill discoverability and token efficiency: + +### Required Fields + +- **name** (string): Unique identifier, lowercase with hyphens + - Must match the directory name + - Should be descriptive but concise + - Example: `hypothesis-driven-debugging`, `github-actions-failure-debugging` + +- **description** (string): When and why to use this skill + - Should be 1-2 sentences + - Include trigger keywords that help the AI recognize when to load the skill + - Example: "Guide for debugging failing GitHub Actions workflows. Use this when asked to debug failing GitHub Actions workflows." + - **SEO-like optimization for LLMs**: Include key terms that would appear in user requests + +### Optional Fields + +- **license** (string): License for the skill (e.g., MIT, Apache-2.0) + +### Description Guidelines + +The description is crucial for skill discoverability. Think of it like SEO for LLMs: + +✅ **Good descriptions** (specific, actionable, keyword-rich): +- "Guide for debugging failing GitHub Actions workflows. Use this when asked to debug failing GitHub Actions workflows." +- "Systematic approach to investigating F# compiler performance issues using traces, dumps, and benchmarks." +- "Step-by-step process for analyzing test failures using hypothesis-driven debugging." + +❌ **Poor descriptions** (vague, generic): +- "Helps with debugging" +- "Tool for testing" +- "Useful utility" + +### Token Efficiency + +Skills should be concise to avoid wasting context tokens: +- Keep instructions focused and relevant +- Use bullet points and numbered lists +- Avoid redundant information +- Reference external resources rather than duplicating content +- The agent will only load skills when relevant, so clear descriptions help prevent unnecessary loading + +## Skill Content Best Practices + +### Structure + +1. **Title and Overview**: Brief introduction +2. **When to Use**: Clear triggering conditions +3. **Prerequisites**: Required tools, setup, or knowledge +4. **Step-by-Step Instructions**: Numbered procedures +5. **Examples**: Concrete use cases +6. **Troubleshooting**: Common issues +7. **References**: Links to related documentation + +### Writing Style + +- Use imperative mood ("Run the test", not "You should run the test") +- Be specific and actionable +- Include command examples with expected output +- Use code blocks with language identifiers +- Highlight warnings and critical information +- Reference tools and APIs that the agent has access to + +### Examples + +Always include concrete examples: +- Command invocations with flags and arguments +- Expected output and how to interpret it +- Common variations and edge cases +- Links to real-world usage in the repository + +## Testing Your Skill + +After creating a skill: + +1. Verify the file structure: + ```bash + ls -la .github/skills/your-skill-name/ + # Should show SKILL.md and any additional resources + ``` + +2. Validate YAML frontmatter: + - Ensure proper YAML syntax + - Required fields are present + - Name matches directory name + +3. Test skill invocation: + - Ask Copilot a question that should trigger the skill + - Verify the skill is loaded (check response for skill-specific guidance) + - Ensure instructions are clear and actionable + +4. Iterate based on usage: + - Monitor how often the skill is used + - Refine description for better discoverability + - Update instructions based on feedback + +## Examples from This Repository + +See existing skills in `.github/skills/` for reference: +- `hypothesis-driven-debugging`: Systematic failure investigation +- Additional skills may be added over time + +## References + +- [GitHub Copilot Agent Skills Documentation](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills) +- [Agent Skills Open Standard](https://github.com/agentskills/agentskills) +- [Community Skills Collection](https://github.com/github/awesome-copilot) \ No newline at end of file diff --git a/.github/skills/trx-analysis/SKILL.md b/.github/skills/trx-analysis/SKILL.md new file mode 100644 index 0000000000..efd9149b6c --- /dev/null +++ b/.github/skills/trx-analysis/SKILL.md @@ -0,0 +1,187 @@ +--- +name: trx-analysis +description: Parse and analyze Visual Studio TRX test result files. Use when asked about slow tests, test durations, test frequency, flaky tests, failure analysis, or test execution patterns from TRX files. +--- + +# TRX Test Results Analysis + +Parse `.trx` files (Visual Studio Test Results XML) to answer questions about test performance, frequency, failures, and patterns. + +## TRX File Format + +TRX files use XML namespace `http://microsoft.com/schemas/VisualStudio/TeamTest/2010`. Key elements: + +- `TestRun.Results.UnitTestResult` — individual test executions with `testName`, `duration` (HH:mm:ss.fffffff), `outcome` (Passed/Failed/NotExecuted) +- `TestRun.TestDefinitions.UnitTest` — test metadata including class and method info +- `TestRun.ResultSummary` — aggregate pass/fail/skip counts + +## Loading a TRX File + +```powershell +[xml]$trx = Get-Content "path/to/file.trx" +$results = $trx.TestRun.Results.UnitTestResult +``` + +## Common Queries + +### Top N slowest tests + +```powershell +$results | ForEach-Object { + [PSCustomObject]@{ + Test = $_.testName + Seconds = [TimeSpan]::Parse($_.duration).TotalSeconds + Outcome = $_.outcome + } +} | Sort-Object Seconds -Descending | Select-Object -First 25 | + Format-Table @{L='Sec';E={'{0,6:N1}' -f $_.Seconds}}, Outcome, Test -AutoSize +``` + +### Slowest test from each distinct class (top N) + +```powershell +$results | ForEach-Object { + $parts = $_.testName -split '\.' + [PSCustomObject]@{ + Test = $_.testName + ClassName = ($parts[0..($parts.Length-2)] -join '.') + Seconds = [TimeSpan]::Parse($_.duration).TotalSeconds + } +} | Sort-Object Seconds -Descending | + Group-Object ClassName | ForEach-Object { $_.Group | Select-Object -First 1 } | + Sort-Object Seconds -Descending | Select-Object -First 10 | + Format-Table @{L='Sec';E={'{0,6:N1}' -f $_.Seconds}}, ClassName, Test -AutoSize +``` + +### Most-executed tests (parameterization frequency) + +Extract the base method name before parameterization and count runs: + +```powershell +$results | ForEach-Object { + $name = $_.testName + if ($name -match '^(\S+?)[\s(]') { $base = $Matches[1] } else { $base = $name } + [PSCustomObject]@{ Base = $base; Seconds = [TimeSpan]::Parse($_.duration).TotalSeconds } +} | Group-Object Base | ForEach-Object { + [PSCustomObject]@{ + Runs = $_.Count + TotalSec = ($_.Group | Measure-Object Seconds -Sum).Sum + Test = $_.Name + } +} | Sort-Object TotalSec -Descending | Select-Object -First 20 | + Format-Table @{L='Runs';E={$_.Runs}}, @{L='TotalSec';E={'{0,7:N1}' -f $_.TotalSec}}, Test -AutoSize +``` + +### Failed tests + +```powershell +$results | Where-Object { $_.outcome -eq 'Failed' } | ForEach-Object { + [PSCustomObject]@{ + Test = $_.testName + Seconds = [TimeSpan]::Parse($_.duration).TotalSeconds + Error = $_.Output.ErrorInfo.Message + } +} | Format-Table -Wrap +``` + +### Summary statistics + +```powershell +$summary = $trx.TestRun.ResultSummary.Counters +[PSCustomObject]@{ + Total = $summary.total + Passed = $summary.passed + Failed = $summary.failed + Skipped = $summary.notExecuted + Duration = $trx.TestRun.Times.finish +} | Format-List +``` + +## Cross-File Duplicate Analysis + +Compare two TRX files to find tests that appear in both and ran (were not skipped) in both. Useful for identifying redundant CI work across different configurations (e.g., net9.0 x64 vs net48 x86). + +### Load and find duplicates that ran in both files + +```powershell +[xml]$trx1 = Get-Content "path/to/file1.trx" +[xml]$trx2 = Get-Content "path/to/file2.trx" + +$r1 = $trx1.TestRun.Results.UnitTestResult +$r2 = $trx2.TestRun.Results.UnitTestResult + +# Build lookup: testName -> (outcome, duration) keeping best outcome per name +function Get-TestLookup($results) { + $lookup = @{} + foreach ($r in $results) { + $name = $r.testName + $outcome = $r.outcome + $dur = [TimeSpan]::Parse($r.duration) + if (-not $lookup.ContainsKey($name) -or ($lookup[$name].Outcome -eq 'NotExecuted' -and $outcome -ne 'NotExecuted')) { + $lookup[$name] = [PSCustomObject]@{ Outcome = $outcome; Duration = $dur } + } + } + $lookup +} + +$t1 = Get-TestLookup $r1 +$t2 = Get-TestLookup $r2 + +$skipped = @('NotExecuted','Pending','Disconnected','Warning','InProgress','Inconclusive') +$common = $t1.Keys | Where-Object { $t2.ContainsKey($_) -and $t1[$_].Outcome -notin $skipped -and $t2[$_].Outcome -notin $skipped } +``` + +### Separate non-parametrized vs parametrized duplicates + +Parametrized tests contain `(` in their name (e.g., `RunAllTests (Row: 0, Runner = net10.0, ...)`). The base method name is everything before the first `(`. + +```powershell +$nonParam = $common | Where-Object { $_ -notmatch '\(' } +$param = $common | Where-Object { $_ -match '\(' } +``` + +### Non-parametrized duplicates ordered by duration + +```powershell +$nonParam | ForEach-Object { + $d1 = $t1[$_].Duration; $d2 = $t2[$_].Duration + [PSCustomObject]@{ + Test = $_ + File1Sec = $d1.TotalSeconds + File2Sec = $d2.TotalSeconds + TotalSec = $d1.TotalSeconds + $d2.TotalSeconds + } +} | Sort-Object TotalSec -Descending | + Format-Table @{L='File1';E={'{0,6:N1}' -f $_.File1Sec}}, + @{L='File2';E={'{0,6:N1}' -f $_.File2Sec}}, + @{L='Total';E={'{0,6:N1}' -f $_.TotalSec}}, Test -AutoSize +``` + +### Parametrized duplicates squashed by base method + +Tests with `(Row: ...)` or other parameterization are instances of the same test. Squash them into one row per base method, showing variant count, max single-instance duration, and total duration across all instances in both files. + +```powershell +$param | ForEach-Object { + if ($_ -match '^(.+?)\s*\(') { $base = $Matches[1] } else { $base = $_ } + $d1 = $t1[$_].Duration; $d2 = $t2[$_].Duration + [PSCustomObject]@{ Base = $base; D1 = $d1.TotalSeconds; D2 = $d2.TotalSeconds; Max = [Math]::Max($d1.TotalSeconds, $d2.TotalSeconds) } +} | Group-Object Base | ForEach-Object { + [PSCustomObject]@{ + Test = $_.Name + Variants = $_.Count + OneInstance = ($_.Group | Measure-Object Max -Maximum).Maximum + AllInstances = ($_.Group | Measure-Object { $_.D1 + $_.D2 } -Sum).Sum + } +} | Sort-Object AllInstances -Descending | + Format-Table @{L='Variants';E={$_.Variants}}, + @{L='1 Instance';E={'{0,7:N1}s' -f $_.OneInstance}}, + @{L='All Instances';E={'{0,7:N1}s' -f $_.AllInstances}}, Test -AutoSize +``` + +## Tips + +- Parameterized tests appear as separate `UnitTestResult` entries. Use regex `'^(\S+?)[\s(]'` to extract the base method name. +- Sort by **TotalSec** (runs × avg duration) to find tests that consume the most CI time overall, even if each individual run is fast. +- When comparing files, filter out `NotExecuted` tests — many parameterized tests are skipped in one configuration but not the other, so raw name overlap overstates true duplication. +- TRX files from CI are typically found in `TestResults/` or as pipeline artifacts. diff --git a/.github/skills/validate-skills/SKILL.md b/.github/skills/validate-skills/SKILL.md new file mode 100644 index 0000000000..07a5a9ff09 --- /dev/null +++ b/.github/skills/validate-skills/SKILL.md @@ -0,0 +1,98 @@ +--- +name: validate-skills +description: Validate that commands documented in skill files actually work. Use when creating, updating, or reviewing skills to ensure all documented commands exit with code 0. +--- + +# Validating Skills + +Verify every executable command in a skill runs successfully on the current OS. + +## When to Use + +- After creating or updating a skill that contains executable commands +- During skill review to catch stale or broken instructions +- When switching OS (e.g. Windows → Linux) to confirm cross-platform commands + +## Procedure + +### 1. Detect Current OS + +Determine which platform commands to extract: + +```powershell +# PowerShell (Windows) +$os = "Windows" +``` + +```bash +# Bash (Linux / macOS) +OS=$(uname -s) # "Linux" or "Darwin" +``` + +### 2. Extract Commands + +Parse the target skill's `SKILL.md` and list every shell command for the detected OS: + +- Many skills document commands in tables with **Windows** and **Linux / macOS** columns. Pick the column matching your OS. +- If a command contains comments like `# Windows` or `# Linux / macOS`, only run the one for your OS. +- **Placeholder substitution:** Replace obvious placeholders (e.g. ``, ``) with real values from the repo. If no sensible value exists, skip the command. +- **Adapt cross-platform commands:** Commands like `ls -la` should be adapted to PowerShell equivalents (`Get-ChildItem`) on Windows when no native Windows command is documented. + +### 3. Track Results + +Use the SQL tool to create a tracking table: + +```sql +CREATE TABLE skill_commands ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + skill TEXT NOT NULL, + command TEXT NOT NULL, + expected_exit INTEGER DEFAULT 0, + actual_exit INTEGER, + status TEXT DEFAULT 'pending', + notes TEXT +); +``` + +### 4. Run Each Command + +For every extracted command: + +1. Run it from the repo root +2. Record the exit code +3. Classify the result: + - **Exit 0 → PASS** + - **Non-zero + environment issue (missing SDK, no internet) → ENV_ISSUE** + - **Non-zero + command/docs wrong → ERROR** + +### 5. Safety Rules + +> **CRITICAL:** Never run unfiltered integration/acceptance tests. They take hours. +> - `test.sh --integrationTest` or `test.cmd -Integration` **MUST** include a `--filter` or `-p` flag. +> - `test.sh -p smoke` is acceptable (scoped to smoke tests), but expect it to be slow. + +### 6. Report + +After all commands finish, print a summary: + +``` +=== Skill Validation Report === +Skill: +OS: +Commands tested: N +PASS: X +ENV_ISSUE: Y (list with reasons) +ERROR: Z (list failed commands with exit codes) +``` + +### 7. Fix or Flag + +- **ERROR (documentation bug):** Update the skill's `SKILL.md` to fix the command. +- **ENV_ISSUE:** Add a troubleshooting note to the skill if the environment prerequisite is not already documented. +- **PASS:** No action needed. + +## Ordering Tips + +- Run restore/build before tests (tests depend on build output) +- Run the cheapest commands first to fail fast +- Batch independent test commands in parallel when possible diff --git a/.github/skills/vstest-build-test/SKILL.md b/.github/skills/vstest-build-test/SKILL.md new file mode 100644 index 0000000000..3c061b2c28 --- /dev/null +++ b/.github/skills/vstest-build-test/SKILL.md @@ -0,0 +1,145 @@ +--- +name: vstest-build-test +description: Build, test, and validate changes in the vstest repository. Use when building vstest projects, running unit tests, smoke tests, or acceptance tests, or when deploying locally built vstest.console for manual testing. +--- + +# Building and Testing vstest + +## Pre-Build: Environment Setup + +Before building, verify the `.dotnet` toolchain matches the current OS. The repo bootstraps its own .NET SDK into `.dotnet/`. + +### Detect OS vs .dotnet Mismatch + +Run this check **before every first build in a session**: + +```bash +# Determine current OS +OS=$(uname -s) # "Linux", "Darwin" (macOS), or contains "MINGW"/"MSYS" (Windows/Git Bash) + +if [ -d ".dotnet" ]; then + if [ "$OS" = "Linux" ] || [ "$OS" = "Darwin" ]; then + # On Linux/macOS the dotnet binary must be an ELF/Mach-O executable, not .exe + if [ -f ".dotnet/dotnet.exe" ] && [ ! -f ".dotnet/dotnet" ]; then + echo "MISMATCH: .dotnet contains Windows binaries but OS is $OS" + rm -rf .dotnet .packages artifacts + echo "Cleaned .dotnet, .packages, and artifacts for fresh bootstrap" + fi + else + # On Windows the dotnet binary should be dotnet.exe + if [ -f ".dotnet/dotnet" ] && [ ! -f ".dotnet/dotnet.exe" ]; then + echo "MISMATCH: .dotnet contains Linux/macOS binaries but OS is Windows" + rm -rf .dotnet .packages artifacts + echo "Cleaned .dotnet, .packages, and artifacts for fresh bootstrap" + fi + fi +fi +``` + +After cleanup (or if `.dotnet` doesn't exist), the build script automatically downloads the correct SDK version from `global.json`. + +## Build + +### Platform Commands + +| Action | Windows | Linux / macOS | +|---|---|---| +| Restore + Build | `./build.cmd` | `./build.sh` | +| Restore only | `./restore.cmd` | `./restore.sh` | +| Build + Pack | `./build.cmd -pack` | `./build.sh --pack` | +| Release config | `./build.cmd -c Release -pack` | `./build.sh -c Release --pack` | +| Single project | `./build.cmd -project ` | `./build.sh --projects ` | + +### Full Build (Recommended) + +For projects with many cross-project dependencies (e.g., HtmlLogger, TrxLogger, vstest.console): + +```bash +# Linux / macOS +./build.sh --pack + +# Windows +./build.cmd -pack +``` + +This produces NuGet packages under `artifacts/packages/Debug/Shipping/`. + +### Single Project Build + +For isolated projects with few dependencies: + +```bash +# Linux / macOS +./build.sh --projects + +# Windows +./build.cmd -project +``` + +> **Warning:** This does NOT work for projects like HtmlLogger that have many transitive dependencies. Use `--pack` / `-pack` instead. + +## Test + +### Unit Tests (Default) + +```bash +# Linux / macOS +./test.sh + +# Windows +./test.cmd +``` + +### Specific Test Assembly + +Use `-p` to filter by assembly name pattern: + +```bash +# Linux / macOS +./test.sh -p htmllogger # HTML logger tests +./test.sh -p trxlogger # TRX logger tests +./test.sh -p datacollector # Data collector tests +./test.sh -p smoke # Smoke tests + +# Windows (-p is ambiguous in PowerShell; use -projects) +./test.cmd -projects htmllogger +./test.cmd -projects smoke +``` + +### Specific Test by Name + +```bash +# Windows +./test.cmd -bl -c release /p:TestRunnerAdditionalArguments="'--filter TestName'" -Integration + +# Linux / macOS +./test.sh -bl -c release /p:TestRunnerAdditionalArguments="'--filter TestName'" --integrationTest +``` + +## Manual Validation with vstest.console + +After building with `--pack` / `-pack`, validate vstest.console changes by unzipping the built package: + +1. Locate the package: `artifacts/packages/Debug/Shipping/Microsoft.TestPlatform.-dev.nupkg` +2. Unzip it (`.nupkg` files are ZIP archives) +3. Run the local vstest.console against a test project + +### Alternative: Direct Artifact Paths + +- **xplat (netcoreapp):** `artifacts//netcoreapp1.0/vstest.console.dll` +- **Windows desktop:** `artifacts//net46/win7-x64/vstest.console.exe` + +## Test Categories + +| Category | Speed | What it tests | Filter | +|---|---|---|---| +| Unit tests | Fast | Individual units | `./test.sh` / `./test.cmd` (default) | +| Smoke tests | Slow | P0 end-to-end scenarios | `-p smoke` | +| Acceptance tests | Slowest | Extensive coverage | `--integrationTest` / `-Integration` flag | + +## Troubleshooting + +- **OS mismatch errors:** If you see SDK load failures, run the mismatch detection script above to clean and re-bootstrap. +- If build fails asking for .NET 4.6 targeting pack, install it from [Microsoft Downloads](https://www.microsoft.com/download/details.aspx?id=48136) +- Enable verbose diagnostics: see `docs/diagnose.md` +- For debugging, add `Debugger.Launch` at process entry points (testhost.exe, vstest.console.exe) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000..c249893da0 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,27 @@ +name: "Copilot Setup Steps" + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Build the repository + run: | + chmod +x build.sh eng/build.sh eng/common/build.sh eng/common/tools.sh + ./build.sh diff --git a/.github/workflows/enable-auto-merge.yml b/.github/workflows/enable-auto-merge.yml new file mode 100644 index 0000000000..69586339c1 --- /dev/null +++ b/.github/workflows/enable-auto-merge.yml @@ -0,0 +1,26 @@ +name: Enable auto merge +on: + pull_request_target: + types: [opened, ready_for_review] +permissions: + contents: write + pull-requests: write +jobs: + add_milestone: + runs-on: ubuntu-latest + if: ${{ github.repository == 'microsoft/vstest' && github.event.pull_request.user.login == 'dotnet-maestro[bot]' && (startsWith(github.event.pull_request.title, '[main] Source code updates from dotnet/') || startsWith(github.event.pull_request.title, '[main] Update dependencies from dotnet/') || startsWith(github.event.pull_request.title, '[main] Update dependencies from devdiv/')) }} + steps: + - name: Enable pull request auto-merge + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} + run: | + gh api graphql -f query=' + mutation($pull: ID!) { + enablePullRequestAutoMerge(input: {pullRequestId: $pull, mergeMethod: SQUASH}) { + pullRequest { + id + number + } + } + }' -f pull=$PULL_REQUEST_ID diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..ba563d65b1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,152 @@ +# Contribution Guide + +This article will help you build, test and try out local builds of the VS test +platform. + +## Prerequisites + +Clone the repository to a local directory. Rest of this article assumes +`C:\source\vstest` as the location where you cloned the repo. + +```shell +> cd C:\source +> git clone https://github.com/Microsoft/vstest.git +``` + +### Windows requirements + +You can use any editor paired with dotnet SDK to develop VSTest. + +If you're planning to use **Visual Studio** as development environment, install `VS 2026` with .NET Desktop development workload. + +### Linux / macOS requirements + +You can use any editor paired with dotnet SDK to develop VSTest. + +## Build + +On Windows, run `build.cmd` (or `.\build.cmd` in PowerShell); on Linux/macOS, run `./build.sh` to install the required .NET SDK into the `.dotnet` directory and to build and restore dependencies. After that you can use Visual Studio and build normally. + +### Building with Visual Studio + +Open `C:\source\vstest\TestPlatform.slnx` in VS. + +Use `Build Solution` to build the source code. + +Binaries for each assembly are produced in the +`artifacts/src//bin/Debug` directory. + +### Building with CLI + +To build the repository, run the following command: + +```shell +> cd C:\source\vstest +> build.cmd +``` + +Or `build.cmd -pack` to also produce nuget packages. + + +## Test + +There are following sets of tests that you would run locally to validate your changes: + +* Unit tests - run test.cmd +* Smoke tests - run test.cmd -smokeTest + +Additional tests that can be run locally, but typically you would run just the ones related to changes, and rely on PR build to validate the complete change: + +* Integration tests - run test.cmd -integrationTest +* Compatibility tests - run test.cmd -compatibilityTest +* Performance tests - run test.cmd -performanceTest + +> ⚠️Smoke, Integration, Compatibility and Performance tests do use the build packages that are produced by running `build.cmd -pack`, if you touch the production code (in src, e.g. in vstest.console) you should re-build before running these tests. +> If you however touched just the integration test code, or test assets (in test/TestAssets) you can do `./build.cmd` for faster build (without -pack), or run the tests from IDE directly. The test assets will automatically re-build, but since you did not re-pack, there is no reason to fully clean and restore the dev packages, making it much faster to startup the integration tests. + +### Running a specific test + +With integration tests you typically want to run all integration tests that affect a particular component, for example when changing blame data collector you want to run all tests for blame. This can be done by providing a parameter `-filter` to the test run, providing part of the test name, or providing a more complete filter using [MSTest filter syntax](https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=mstest) + +#### Windows + +On Windows, `test.cmd` wraps `eng/Build.ps1` which has a dedicated `-filter` parameter: + +```powershell +test.cmd -integrationTest -filter blame +test.cmd -integrationTest -filter "FullyQualifiedName~StackOverflow" +``` + +#### Linux / macOS + +On Linux/macOS, `test.sh` wraps arcade's `eng/common/build.sh` which does **not** have a dedicated `-filter` parameter. Instead, you pass the filter as an MSBuild property. This requires careful quoting to prevent bash from interpreting special characters: + +```bash +# Run a specific test by name (use single quotes to protect the value from shell expansion): +./test.sh --integrationTest --property:'TestRunnerAdditionalArguments=--filter "FullyQualifiedName~StackOverflow"' + +# Run tests matching a simple keyword: +./test.sh --integrationTest --property:'TestRunnerAdditionalArguments=--filter "blame"' + +# Combine with -tl:off to disable terminal logger and see raw output: +./test.sh --integrationTest --property:'TestRunnerAdditionalArguments=--filter "FullyQualifiedName~StackOverflow"' -tl:off +``` + +> ⚠️ The single quotes around `--property:'...'` are critical — without them, bash will interpret `&`, `|`, spaces, and quotes inside the filter expression. If your filter contains `&` (AND), wrap the whole `--property:` argument in single quotes. + +> ⚠️ On non-Windows platforms, tests marked with `TestCategory=Windows` or `TestCategory=Windows-Review` are automatically excluded. + +## Using the development packages + +The nuget packages produced by `./build.cmd -pack` are stored in `C:\source\vstest\artifacts\packages\\Shipping`, and the VSIX in `C:\source\vstest\artifacts\VSSetup\Debug\Insertion\Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix`. + +You can use those packages in your own test projects to test your changes, by importing them via a local nuget source defined in nuget.config. + +- Microsoft.TestPlatform - ships vstest.console.exe to nuget, and is used in CI runs +- Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix - ships vstest.console.exe into Visual Studio, is used by Test Explorer, and in CI runs from hosted AzDO images +- Microsoft.TestPlatform.CLI - ships vstest.console.dll to dotnet/sdk and is used by dotnet test +- Microsoft.NET.Test.Sdk - ships targets and brings testhost as dependency + +Before using these packages directly, consider writing an Integration test instead, integration tests already do lot of the work for you automatically, and you can debug there easily as well. + +## Debugging Integration Tests + +Integration tests (in `test/Microsoft.TestPlatform.Acceptance.IntegrationTests`) run vstest.console and testhost as separate processes. You can attach Visual Studio to those processes automatically by using the `DebugInfo` properties on the test data source attributes. + +### Using DebugInfo properties on test attributes + +The `NetCoreRunnerAttribute` and `NetFrameworkRunnerAttribute` attributes (and other data source attributes like `CompatibilityRowsBuilder`) expose the following boolean properties: + +| Property | What it debugs | Environment variable set | +|---|---|---| +| `DebugVSTestConsole = true` | vstest.console (the runner) | `VSTEST_RUNNER_DEBUG_ATTACHVS=1` | +| `DebugTestHost = true` | testhost (the test process) | `VSTEST_HOST_DEBUG_ATTACHVS=1` | +| `DebugDataCollector = true` | data collector process | `VSTEST_DATACOLLECTOR_DEBUG_ATTACHVS=1` | +| `DebugStopAtEntrypoint = true` | keeps entry point breakpoints | (when `false`, sets `VSTEST_DEBUG_NOBP=1`) | + +### Step-by-step: attaching VS to an integration test + +1. Open the solution in Visual Studio. +2. Find the integration test method you want to debug, for example: + + ```csharp + [TestMethod] + [NetCoreRunner(AcceptanceTestBase.NET9, DebugVSTestConsole = true)] + public void MyTest(RunnerInfo runnerInfo) + { + // ... + } + ``` + +3. Set breakpoints in the vstest source code corresponding to what you are debugging (e.g. inside `vstest.console`, `testhost`, or the `datacollector` project). +4. Run that specific test case from the Test Explorer in Visual Studio. +5. The test infrastructure automatically builds `AttachVS.exe` (from `src/AttachVS`) and sets `VSTEST_DEBUG_ATTACHVS_PATH` to point to it. When vstest.console starts, it will invoke `AttachVS.exe`, which attaches the running Visual Studio instance to the launched process. +6. Your breakpoints in the vstest source code will be hit. + +> **Note:** `DebugStopAtEntrypoint = false` (the default) sets `VSTEST_DEBUG_NOBP=1`, which skips the entry-point breakpoint to go directly to your breakpoints. Set `DebugStopAtEntrypoint = true` if you want to explore and are not sure where to put your breakpoint. + +> **Note:** `AttachVS` looks for a running Visual Studio instance. Make sure you are running the integration test from within Visual Studio (not from the CLI) for the automatic attach to work. If you do run from the command line it will try to find VS instance using the AttachVS heuristic (look for parent process that is VS, look for the instance of VS that was started first). + +## Debugging in general + +There are several other environment variables that allow you to wait for debugger in a specific component, see [docs/environment-variables.md](docs/environment-variables.md#debug-variables) diff --git a/Directory.Build.props b/Directory.Build.props index 778e8f488c..a5744bc1ee 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,23 +10,14 @@ true $(RepoRoot)src\package\ enable + + false - - 17.10.0 - - $(TPVersionPrefix)-dev 15.0.0 - + false @@ -52,21 +43,79 @@ net462 - - net9.0 - netcoreapp3.1 + net8.0 + net10.0 + --> $(NetMinimum) + + + net48 + + $(NetCoreAppStable) + + $(NetCoreAppMinimum) + $(NetSDKTargetFramework);$(NetPortableTargetFrameworks) + $(NetFrameworkRunnerTargetFramework);$(NetRunnerTargetFrameworks) + + + $(RunnerTargetFrameworks) + + netstandard2.0 + + + $(NetCoreAppMinimum);$(NetFrameworkMinimum) + + $(TestHostMinimumTargetFrameworks);net47;net471;net472;net48;net481 + + + $(NetFrameworkMinimum);$(NetCoreAppMinimum);netstandard2.0 $(CopyrightMicrosoft) MIT + https://github.com/microsoft/vstest false $(DefineConstants);DOTNET_BUILD_FROM_SOURCE @@ -75,8 +124,6 @@ embedded true - - true @@ -87,8 +134,12 @@ MSTest + + $(TestRunnerExternalArguments) $(TestRunnerAdditionalArguments) --filter "TestCategory!=Windows&TestCategory!=Windows-Review" + + false diff --git a/Directory.Build.targets b/Directory.Build.targets index 5aee4dd768..6d96423635 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -4,22 +4,30 @@ <_NetFrameworkHostedCompilersVersion Condition="'$(_NetFrameworkHostedCompilersVersion)' == ''">4.11.0-3.24280.3 - - - - - $(NetPrevious);$(NetCurrent) + + $(DefineConstants);IS_VSTEST_REPO + + $(MSBuildWarningsAsMessages);MSB3277 + + + + + + - + $(NetCurrent) @@ -61,7 +69,6 @@ true - + + + @@ -12,22 +16,22 @@ + + + + - - - - - + diff --git a/README.md b/README.md index 6229777d4e..8b83cd7240 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VSTest -The Visual Studio Test Platform is an open and extensible test platform that enables running tests, collect diagnostics data and report results. The Test Platform supports running tests written in various test frameworks, and using a pluggable adapter model. Based on user-choice, the desired test framework and its corresponding adapter can be acquired as a vsix or as NuGet package as the case may be. Adapters can be written in terms of a public API exposed by the Test Platform. +The Visual Studio Test Platform is an open and extensible test platform that runs tests, collects diagnostics data, and reports results. The Test Platform supports running tests written in various test frameworks, and using a pluggable adapter model. Based on user-choice, the desired test framework and its corresponding adapter can be acquired as a vsix or as NuGet package as the case may be. Adapters can be written in terms of a public API exposed by the Test Platform. The Test Platform currently ships as part Visual Studio 2019, and in the .NET Core Tools Preview 3. @@ -16,7 +16,7 @@ There are many ways to contribute to VSTest - [Submit issues](https://github.com/Microsoft/vstest/issues) and help verify fixes as they are checked in. - Review the [open PRs](https://github.com/Microsoft/vstest/pulls). -- [Contribute features and fixes](./docs/contribute.md). +- [Contribute features and fixes](./CONTRIBUTING.md). - Contribute to the documentation. NOTE: When adding a new public API, always add it directly to the `PublicAPI.Shipped.txt` file. This helps us ensure we are always considering potential breaking changes (even between successive commits of un-released version) and avoids the burden of the unshipped to shipped commit. @@ -52,6 +52,7 @@ NOTE: When adding a new public API, always add it directly to the `PublicAPI.Shi ### Other +- [Environment Variables](./docs/environment-variables.md) - [Roadmap](./docs/releases.md) - [Troubleshooting guide](./docs/troubleshooting.md) @@ -60,7 +61,6 @@ NOTE: When adding a new public API, always add it directly to the `PublicAPI.Shi VSTest can be built from within Visual Studio or from the CLI. - [Building with Visual Studio](./docs/contribute.md#building-with-visual-studio) -- [Building with CLI, CI, Editors](./docs/contribute.md#building-with-cli-ci-editors) ## Microsoft Open Source Code of Conduct diff --git a/SECURITY.md b/SECURITY.md index 869fdfe2b2..d8e8bb9ca1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,20 +1,18 @@ - + ## Security -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). +You should receive a response within 24 hours. If for some reason you do not, please follow up using the messaging functionality found at the bottom of the Activity tab on your vulnerability report on [https://msrc.microsoft.com/report/vulnerability](https://msrc.microsoft.com/report/vulnerability/) or via email as described in the instructions at the bottom of [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc) or on MSRC's [FAQ page for reporting an issue](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: @@ -28,7 +26,7 @@ Please include the requested information listed below (as much as you can provid This information will help us triage your report more quickly. -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. ## Preferred Languages @@ -36,6 +34,6 @@ We prefer all communications to be in English. ## Policy -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). diff --git a/TestPlatform.sln b/TestPlatform.sln deleted file mode 100644 index 6398540d95..0000000000 --- a/TestPlatform.sln +++ /dev/null @@ -1,570 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31519.33 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED0C35EB-7F31-4841-A24F-8EB708FFA959}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CoreUtilities", "src\Microsoft.TestPlatform.CoreUtilities\Microsoft.TestPlatform.CoreUtilities.csproj", "{50C00046-0DA3-4B5C-9F6F-7BE1145E156A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6}" - ProjectSection(SolutionItems) = preProject - test\.editorconfig = test\.editorconfig - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CoreUtilities.UnitTests", "test\Microsoft.TestPlatform.CoreUtilities.UnitTests\Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj", "{01409D95-A5F1-4EBE-94B1-909D5D2D0DC3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "datacollector", "src\datacollector\datacollector.csproj", "{2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Build", "src\Microsoft.TestPlatform.Build\Microsoft.TestPlatform.Build.csproj", "{6F5EC38C-4A11-40D3-827C-F607B90BEFF0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Loggers", "Loggers", "{5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Client", "src\Microsoft.TestPlatform.Client\Microsoft.TestPlatform.Client.csproj", "{E19B5128-3469-492E-82E1-725631C4A68C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Common", "src\Microsoft.TestPlatform.Common\Microsoft.TestPlatform.Common.csproj", "{68ADC720-316E-4895-9F8E-C3CCADD262BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CommunicationUtilities", "src\Microsoft.TestPlatform.CommunicationUtilities\Microsoft.TestPlatform.CommunicationUtilities.csproj", "{1621415E-7723-4F46-A589-4C4620C0751A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CrossPlatEngine", "src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.csproj", "{987898D9-724E-4324-BF91-77B1A6DBE8F1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.ObjectModel", "src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj", "{FD63F778-3938-45D2-900B-51EC770F70E5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Utilities", "src\Microsoft.TestPlatform.Utilities\Microsoft.TestPlatform.Utilities.csproj", "{61F7F446-9EF3-4768-B33A-4D75F60E1059}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.TrxLogger", "src\Microsoft.TestPlatform.Extensions.TrxLogger\Microsoft.TestPlatform.Extensions.TrxLogger.csproj", "{D5296435-3A3F-4B1A-81D1-434EC9E97DEF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.VsTestConsole.TranslationLayer", "src\Microsoft.TestPlatform.VsTestConsole.TranslationLayer\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj", "{790B8030-00C2-4121-B125-EDC4CE329BA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testhost", "src\testhost\testhost.csproj", "{27DFBD04-64B2-4F1B-82B2-006620CCA6F8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testhost.x86", "src\testhost.x86\testhost.x86.csproj", "{71CB42FF-E750-4A3B-9C3A-AC938853CC89}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vstest.console", "src\vstest.console\vstest.console.csproj", "{10B6ADE1-F808-4612-801D-4452F5B52242}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{46250E12-4CF1-4051-B4A7-80C8C06E0068}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Loggers", "Loggers", "{020E15EA-731F-4667-95AF-226671E0C3AE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Client.UnitTests", "test\Microsoft.TestPlatform.Client.UnitTests\Microsoft.TestPlatform.Client.UnitTests.csproj", "{0D59BA81-6279-4650-AEBB-4EA735C28A1A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Common.UnitTests", "test\Microsoft.TestPlatform.Common.UnitTests\Microsoft.TestPlatform.Common.UnitTests.csproj", "{DE730F17-7D5C-4D9D-B479-025024BF4F1D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CommunicationUtilities.UnitTests", "test\Microsoft.TestPlatform.CommunicationUtilities.UnitTests\Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj", "{E062FFD6-DEB1-4DB4-8B6E-ADBF04129545}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CrossPlatEngine.UnitTests", "test\Microsoft.TestPlatform.CrossPlatEngine.UnitTests\Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj", "{F582949D-8B92-47BD-9DD7-9F2BFCCC290C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.ObjectModel.UnitTests", "test\Microsoft.TestPlatform.ObjectModel.UnitTests\Microsoft.TestPlatform.ObjectModel.UnitTests.csproj", "{376A7588-50DF-46CD-955B-0309F491D830}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.TestUtilities", "test\Microsoft.TestPlatform.TestUtilities\Microsoft.TestPlatform.TestUtilities.csproj", "{5DF3CF65-3E11-4639-964D-7BEB4109DCF9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Utilities.UnitTests", "test\Microsoft.TestPlatform.Utilities.UnitTests\Microsoft.TestPlatform.Utilities.UnitTests.csproj", "{D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testhost.UnitTests", "test\testhost.UnitTests\testhost.UnitTests.csproj", "{9EFCEFB5-253E-4DE2-8A70-821D7B8189DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vstest.console.UnitTests", "test\vstest.console.UnitTests\vstest.console.UnitTests.csproj", "{3A8080FB-9C93-45B9-8EB5-828DDC31FDF0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests", "test\Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests\Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.csproj", "{BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{0D4DF78D-7E5F-4516-B19F-E6AA71A1DBF4}" - ProjectSection(SolutionItems) = preProject - scripts\perf\perf.ps1 = scripts\perf\perf.ps1 - scripts\perf\perfconfig.csv = scripts\perf\perfconfig.csv - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330}" - ProjectSection(SolutionItems) = preProject - scripts\test.ps1 = scripts\test.ps1 - scripts\test.sh = scripts\test.sh - scripts\write-release-notes.ps1 = scripts\write-release-notes.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E344E0A2-7715-4C7F-BAF7-D64EA94CB19B}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - azure-pipelines.yml = azure-pipelines.yml - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - global.json = global.json - Nuget.config = Nuget.config - THIRD-PARTY-NOTICES.txt = THIRD-PARTY-NOTICES.txt - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{D27E1CB4-C641-4C6C-A140-EF5F6215AE29}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Build.UnitTests", "test\Microsoft.TestPlatform.Build.UnitTests\Microsoft.TestPlatform.Build.UnitTests.csproj", "{EFA38DEF-C2BB-42AE-8B68-B31D79F3107E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "datacollector.UnitTests", "test\datacollector.UnitTests\datacollector.UnitTests.csproj", "{0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.PlatformAbstractions", "src\Microsoft.TestPlatform.PlatformAbstractions\Microsoft.TestPlatform.PlatformAbstractions.csproj", "{CAE652AF-6801-425E-AAF3-AB20DE7DF88E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "datacollector.PlatformTests", "test\datacollector.PlatformTests\datacollector.PlatformTests.csproj", "{FF80D706-8309-4E02-BAC0-D28B4CBCF600}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{376C19DE-31E2-4FF6-88FC-0D0D6233C999}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranslationLayer.UnitTests", "test\TranslationLayer.UnitTests\TranslationLayer.UnitTests.csproj", "{7B48115A-B766-4B55-93A8-C08A42C37710}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.TestHostProvider.UnitTests", "test\Microsoft.TestPlatform.TestHostProvider.UnitTests\Microsoft.TestPlatform.TestHostProvider.UnitTests.csproj", "{FBF74C8F-695C-4967-84AC-358EEFB1376D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.TestHostProvider", "src\Microsoft.TestPlatform.TestHostProvider\Microsoft.TestPlatform.TestHostProvider.csproj", "{11ECCB8B-6958-42A7-BD58-88C09CB149B2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.BlameDataCollector", "src\Microsoft.TestPlatform.Extensions.BlameDataCollector\Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj", "{76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests", "test\Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests\Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj", "{488675EC-C8BB-40E0-AD4F-91F623D548B3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataCollectors", "DataCollectors", "{B705537C-B82C-4A30-AFA5-6244D9A7DAEB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.EventLogCollector", "src\DataCollectors\Microsoft.TestPlatform.Extensions.EventLogCollector\Microsoft.TestPlatform.Extensions.EventLogCollector.csproj", "{65A25D6E-C9CC-4F45-8925-04087AC82634}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataCollectors", "DataCollectors", "{D9A30E32-D466-4EC5-B4F2-62E17562279B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests", "test\DataCollectors\Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests\Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests.csproj", "{21DB138B-85B7-479E-91FE-01E0F972EC56}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator", "src\SettingsMigrator\SettingsMigrator.csproj", "{69F5FF81-5615-4F06-B83C-FCF979BB84CA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsMigrator.UnitTests", "test\SettingsMigrator.UnitTests\SettingsMigrator.UnitTests.csproj", "{E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.HtmlLogger", "src\Microsoft.TestPlatform.Extensions.HtmlLogger\Microsoft.TestPlatform.Extensions.HtmlLogger.csproj", "{236A71E3-01DA-4679-9DFF-16A8E079ACFF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests", "test\Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests\Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj", "{41248B96-6E15-4E5E-A78F-859897676814}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.collector", "test\coverlet.collector\coverlet.collector.csproj", "{074F5BD6-DC05-460B-B78F-044D125FD787}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.AdapterUtilities.UnitTests", "test\Microsoft.TestPlatform.AdapterUtilities.UnitTests\Microsoft.TestPlatform.AdapterUtilities.UnitTests.csproj", "{DCD0C39E-C78C-4A44-B0BD-7325254A2E97}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.AdapterUtilities", "src\Microsoft.TestPlatform.AdapterUtilities\Microsoft.TestPlatform.AdapterUtilities.csproj", "{2DE99835-A3A3-4922-82AD-6D10D284816D}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.TestPlatform.Execution.Shared", "src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.shproj", "{7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool", "src\DataCollectors\DumpMinitool\DumpMinitool.csproj", "{33A20B85-7024-4112-B1E7-00CD0E4A9F96}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool.x86", "src\DataCollectors\DumpMinitool.x86\DumpMinitool.x86.csproj", "{2C88C923-3D7A-4492-9241-7A489750CAB7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttachVS", "src\AttachVS\AttachVS.csproj", "{8238A052-D626-49EB-A011-51DC6D0DBA30}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "playground", "playground", "{6CE2F530-582B-4695-A209-41065E103426}" - ProjectSection(SolutionItems) = preProject - playground\README.md = playground\README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestPlatform.Playground", "playground\TestPlatform.Playground\TestPlatform.Playground.csproj", "{545A88D3-1AE2-4D39-9B7C-C691768AD17F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vstest.ProgrammerTests", "test\vstest.ProgrammerTests\vstest.ProgrammerTests.csproj", "{B1F84FD8-6150-4ECA-9AD7-C316E04E17D8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intent", "test\Intent\Intent.csproj", "{BFBB35C9-6437-480A-8DCC-AE3700110E7D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intent.Primitives", "test\Intent.Primitives\Intent.Primitives.csproj", "{29270853-90DC-4C39-9621-F47AE40A79B6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testhost.arm64", "src\testhost.arm64\testhost.arm64.csproj", "{186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool.arm64", "src\DataCollectors\DumpMinitool.arm64\DumpMinitool.arm64.csproj", "{62E9D32B-B989-43CF-81A2-B38B3367FCA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest1", "playground\MSTest1\MSTest1.csproj", "{4DA57968-F547-4019-8381-03A218B6C385}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest2", "playground\MSTest2\MSTest2.csproj", "{874841C0-DCB9-4678-A952-5E06286CA2B0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "datacollector.arm64", "src\datacollector.arm64\datacollector.arm64.csproj", "{D27C951E-1935-4D76-A0E5-A00D7829C654}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vstest.console.arm64", "src\vstest.console.arm64\vstest.console.arm64.csproj", "{AD0EB901-E227-42E0-B7AE-28C3150B9A8B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeCoverage", "src\package\Microsoft.CodeCoverage\Microsoft.CodeCoverage.csproj", "{AE13C83A-C01A-4C25-9A29-A440E014F998}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Test.Sdk", "src\package\Microsoft.NET.Test.Sdk\Microsoft.NET.Test.Sdk.csproj", "{B9340314-6A79-496C-8BF9-88F1F6E3F096}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CLI", "src\package\Microsoft.TestPlatform.CLI\Microsoft.TestPlatform.CLI.csproj", "{75054BFF-DC0F-4094-8F05-62776663DF0D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Internal.Uwp", "src\package\Microsoft.TestPlatform.Internal.Uwp\Microsoft.TestPlatform.Internal.Uwp.csproj", "{D7B948FC-C4E7-41A9-84A7-8F1A725458E3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Portable", "src\package\Microsoft.TestPlatform.Portable\Microsoft.TestPlatform.Portable.csproj", "{3162D387-7424-481A-9A1E-8193BF7FABE5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.TestHost", "src\package\Microsoft.TestPlatform.TestHost\Microsoft.TestPlatform.TestHost.csproj", "{9297D1FA-F9C0-42D2-84FA-06DA323357C9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{DCCF71EC-14DB-4FB8-8F9E-2DFAB69D9F15}" - ProjectSection(SolutionItems) = preProject - eng\AfterSolutionBuild.targets = eng\AfterSolutionBuild.targets - eng\Analyzers.props = eng\Analyzers.props - eng\Publishing.props = eng\Publishing.props - eng\Signing.props = eng\Signing.props - eng\SourceBuild.props = eng\SourceBuild.props - eng\verify-nupkgs.ps1 = eng\verify-nupkgs.ps1 - eng\Versions.props = eng\Versions.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI", "src\package\Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI\Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.csproj", "{A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform", "src\package\Microsoft.TestPlatform\Microsoft.TestPlatform.csproj", "{4454139C-174A-4DD8-8E76-657D9F03BA09}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Acceptance.IntegrationTests", "test\Microsoft.TestPlatform.Acceptance.IntegrationTests\Microsoft.TestPlatform.Acceptance.IntegrationTests.csproj", "{450D5371-32A1-4667-987E-40D75F035233}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests", "test\Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests\Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests.csproj", "{E602AB2B-048F-4317-8A6B-A225C00173FA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {50C00046-0DA3-4B5C-9F6F-7BE1145E156A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50C00046-0DA3-4B5C-9F6F-7BE1145E156A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50C00046-0DA3-4B5C-9F6F-7BE1145E156A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50C00046-0DA3-4B5C-9F6F-7BE1145E156A}.Release|Any CPU.Build.0 = Release|Any CPU - {01409D95-A5F1-4EBE-94B1-909D5D2D0DC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01409D95-A5F1-4EBE-94B1-909D5D2D0DC3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01409D95-A5F1-4EBE-94B1-909D5D2D0DC3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01409D95-A5F1-4EBE-94B1-909D5D2D0DC3}.Release|Any CPU.Build.0 = Release|Any CPU - {2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8}.Release|Any CPU.Build.0 = Release|Any CPU - {6F5EC38C-4A11-40D3-827C-F607B90BEFF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F5EC38C-4A11-40D3-827C-F607B90BEFF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F5EC38C-4A11-40D3-827C-F607B90BEFF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F5EC38C-4A11-40D3-827C-F607B90BEFF0}.Release|Any CPU.Build.0 = Release|Any CPU - {E19B5128-3469-492E-82E1-725631C4A68C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E19B5128-3469-492E-82E1-725631C4A68C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E19B5128-3469-492E-82E1-725631C4A68C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E19B5128-3469-492E-82E1-725631C4A68C}.Release|Any CPU.Build.0 = Release|Any CPU - {68ADC720-316E-4895-9F8E-C3CCADD262BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {68ADC720-316E-4895-9F8E-C3CCADD262BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68ADC720-316E-4895-9F8E-C3CCADD262BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {68ADC720-316E-4895-9F8E-C3CCADD262BE}.Release|Any CPU.Build.0 = Release|Any CPU - {1621415E-7723-4F46-A589-4C4620C0751A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1621415E-7723-4F46-A589-4C4620C0751A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1621415E-7723-4F46-A589-4C4620C0751A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1621415E-7723-4F46-A589-4C4620C0751A}.Release|Any CPU.Build.0 = Release|Any CPU - {987898D9-724E-4324-BF91-77B1A6DBE8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {987898D9-724E-4324-BF91-77B1A6DBE8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {987898D9-724E-4324-BF91-77B1A6DBE8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {987898D9-724E-4324-BF91-77B1A6DBE8F1}.Release|Any CPU.Build.0 = Release|Any CPU - {FD63F778-3938-45D2-900B-51EC770F70E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD63F778-3938-45D2-900B-51EC770F70E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD63F778-3938-45D2-900B-51EC770F70E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD63F778-3938-45D2-900B-51EC770F70E5}.Release|Any CPU.Build.0 = Release|Any CPU - {61F7F446-9EF3-4768-B33A-4D75F60E1059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61F7F446-9EF3-4768-B33A-4D75F60E1059}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61F7F446-9EF3-4768-B33A-4D75F60E1059}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61F7F446-9EF3-4768-B33A-4D75F60E1059}.Release|Any CPU.Build.0 = Release|Any CPU - {D5296435-3A3F-4B1A-81D1-434EC9E97DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5296435-3A3F-4B1A-81D1-434EC9E97DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5296435-3A3F-4B1A-81D1-434EC9E97DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5296435-3A3F-4B1A-81D1-434EC9E97DEF}.Release|Any CPU.Build.0 = Release|Any CPU - {790B8030-00C2-4121-B125-EDC4CE329BA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {790B8030-00C2-4121-B125-EDC4CE329BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {790B8030-00C2-4121-B125-EDC4CE329BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {790B8030-00C2-4121-B125-EDC4CE329BA3}.Release|Any CPU.Build.0 = Release|Any CPU - {27DFBD04-64B2-4F1B-82B2-006620CCA6F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27DFBD04-64B2-4F1B-82B2-006620CCA6F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27DFBD04-64B2-4F1B-82B2-006620CCA6F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27DFBD04-64B2-4F1B-82B2-006620CCA6F8}.Release|Any CPU.Build.0 = Release|Any CPU - {71CB42FF-E750-4A3B-9C3A-AC938853CC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71CB42FF-E750-4A3B-9C3A-AC938853CC89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71CB42FF-E750-4A3B-9C3A-AC938853CC89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71CB42FF-E750-4A3B-9C3A-AC938853CC89}.Release|Any CPU.Build.0 = Release|Any CPU - {10B6ADE1-F808-4612-801D-4452F5B52242}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10B6ADE1-F808-4612-801D-4452F5B52242}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10B6ADE1-F808-4612-801D-4452F5B52242}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10B6ADE1-F808-4612-801D-4452F5B52242}.Release|Any CPU.Build.0 = Release|Any CPU - {0D59BA81-6279-4650-AEBB-4EA735C28A1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D59BA81-6279-4650-AEBB-4EA735C28A1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D59BA81-6279-4650-AEBB-4EA735C28A1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D59BA81-6279-4650-AEBB-4EA735C28A1A}.Release|Any CPU.Build.0 = Release|Any CPU - {DE730F17-7D5C-4D9D-B479-025024BF4F1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE730F17-7D5C-4D9D-B479-025024BF4F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE730F17-7D5C-4D9D-B479-025024BF4F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE730F17-7D5C-4D9D-B479-025024BF4F1D}.Release|Any CPU.Build.0 = Release|Any CPU - {E062FFD6-DEB1-4DB4-8B6E-ADBF04129545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E062FFD6-DEB1-4DB4-8B6E-ADBF04129545}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E062FFD6-DEB1-4DB4-8B6E-ADBF04129545}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E062FFD6-DEB1-4DB4-8B6E-ADBF04129545}.Release|Any CPU.Build.0 = Release|Any CPU - {F582949D-8B92-47BD-9DD7-9F2BFCCC290C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F582949D-8B92-47BD-9DD7-9F2BFCCC290C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F582949D-8B92-47BD-9DD7-9F2BFCCC290C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F582949D-8B92-47BD-9DD7-9F2BFCCC290C}.Release|Any CPU.Build.0 = Release|Any CPU - {376A7588-50DF-46CD-955B-0309F491D830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {376A7588-50DF-46CD-955B-0309F491D830}.Debug|Any CPU.Build.0 = Debug|Any CPU - {376A7588-50DF-46CD-955B-0309F491D830}.Release|Any CPU.ActiveCfg = Release|Any CPU - {376A7588-50DF-46CD-955B-0309F491D830}.Release|Any CPU.Build.0 = Release|Any CPU - {5DF3CF65-3E11-4639-964D-7BEB4109DCF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DF3CF65-3E11-4639-964D-7BEB4109DCF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DF3CF65-3E11-4639-964D-7BEB4109DCF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DF3CF65-3E11-4639-964D-7BEB4109DCF9}.Release|Any CPU.Build.0 = Release|Any CPU - {D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34}.Release|Any CPU.Build.0 = Release|Any CPU - {9EFCEFB5-253E-4DE2-8A70-821D7B8189DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9EFCEFB5-253E-4DE2-8A70-821D7B8189DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9EFCEFB5-253E-4DE2-8A70-821D7B8189DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9EFCEFB5-253E-4DE2-8A70-821D7B8189DF}.Release|Any CPU.Build.0 = Release|Any CPU - {3A8080FB-9C93-45B9-8EB5-828DDC31FDF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A8080FB-9C93-45B9-8EB5-828DDC31FDF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A8080FB-9C93-45B9-8EB5-828DDC31FDF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A8080FB-9C93-45B9-8EB5-828DDC31FDF0}.Release|Any CPU.Build.0 = Release|Any CPU - {BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03}.Release|Any CPU.Build.0 = Release|Any CPU - {EFA38DEF-C2BB-42AE-8B68-B31D79F3107E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFA38DEF-C2BB-42AE-8B68-B31D79F3107E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFA38DEF-C2BB-42AE-8B68-B31D79F3107E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFA38DEF-C2BB-42AE-8B68-B31D79F3107E}.Release|Any CPU.Build.0 = Release|Any CPU - {0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C}.Release|Any CPU.Build.0 = Release|Any CPU - {CAE652AF-6801-425E-AAF3-AB20DE7DF88E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAE652AF-6801-425E-AAF3-AB20DE7DF88E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAE652AF-6801-425E-AAF3-AB20DE7DF88E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAE652AF-6801-425E-AAF3-AB20DE7DF88E}.Release|Any CPU.Build.0 = Release|Any CPU - {FF80D706-8309-4E02-BAC0-D28B4CBCF600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF80D706-8309-4E02-BAC0-D28B4CBCF600}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF80D706-8309-4E02-BAC0-D28B4CBCF600}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF80D706-8309-4E02-BAC0-D28B4CBCF600}.Release|Any CPU.Build.0 = Release|Any CPU - {7B48115A-B766-4B55-93A8-C08A42C37710}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B48115A-B766-4B55-93A8-C08A42C37710}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B48115A-B766-4B55-93A8-C08A42C37710}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B48115A-B766-4B55-93A8-C08A42C37710}.Release|Any CPU.Build.0 = Release|Any CPU - {FBF74C8F-695C-4967-84AC-358EEFB1376D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBF74C8F-695C-4967-84AC-358EEFB1376D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBF74C8F-695C-4967-84AC-358EEFB1376D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBF74C8F-695C-4967-84AC-358EEFB1376D}.Release|Any CPU.Build.0 = Release|Any CPU - {11ECCB8B-6958-42A7-BD58-88C09CB149B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {11ECCB8B-6958-42A7-BD58-88C09CB149B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {11ECCB8B-6958-42A7-BD58-88C09CB149B2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {11ECCB8B-6958-42A7-BD58-88C09CB149B2}.Release|Any CPU.Build.0 = Release|Any CPU - {76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A}.Release|Any CPU.Build.0 = Release|Any CPU - {488675EC-C8BB-40E0-AD4F-91F623D548B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {488675EC-C8BB-40E0-AD4F-91F623D548B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {488675EC-C8BB-40E0-AD4F-91F623D548B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {488675EC-C8BB-40E0-AD4F-91F623D548B3}.Release|Any CPU.Build.0 = Release|Any CPU - {65A25D6E-C9CC-4F45-8925-04087AC82634}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65A25D6E-C9CC-4F45-8925-04087AC82634}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65A25D6E-C9CC-4F45-8925-04087AC82634}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65A25D6E-C9CC-4F45-8925-04087AC82634}.Release|Any CPU.Build.0 = Release|Any CPU - {21DB138B-85B7-479E-91FE-01E0F972EC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21DB138B-85B7-479E-91FE-01E0F972EC56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21DB138B-85B7-479E-91FE-01E0F972EC56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21DB138B-85B7-479E-91FE-01E0F972EC56}.Release|Any CPU.Build.0 = Release|Any CPU - {69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69F5FF81-5615-4F06-B83C-FCF979BB84CA}.Release|Any CPU.Build.0 = Release|Any CPU - {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E}.Release|Any CPU.Build.0 = Release|Any CPU - {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {236A71E3-01DA-4679-9DFF-16A8E079ACFF}.Release|Any CPU.Build.0 = Release|Any CPU - {41248B96-6E15-4E5E-A78F-859897676814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41248B96-6E15-4E5E-A78F-859897676814}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41248B96-6E15-4E5E-A78F-859897676814}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41248B96-6E15-4E5E-A78F-859897676814}.Release|Any CPU.Build.0 = Release|Any CPU - {074F5BD6-DC05-460B-B78F-044D125FD787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {074F5BD6-DC05-460B-B78F-044D125FD787}.Debug|Any CPU.Build.0 = Debug|Any CPU - {074F5BD6-DC05-460B-B78F-044D125FD787}.Release|Any CPU.ActiveCfg = Release|Any CPU - {074F5BD6-DC05-460B-B78F-044D125FD787}.Release|Any CPU.Build.0 = Release|Any CPU - {DCD0C39E-C78C-4A44-B0BD-7325254A2E97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DCD0C39E-C78C-4A44-B0BD-7325254A2E97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DCD0C39E-C78C-4A44-B0BD-7325254A2E97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DCD0C39E-C78C-4A44-B0BD-7325254A2E97}.Release|Any CPU.Build.0 = Release|Any CPU - {2DE99835-A3A3-4922-82AD-6D10D284816D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DE99835-A3A3-4922-82AD-6D10D284816D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DE99835-A3A3-4922-82AD-6D10D284816D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DE99835-A3A3-4922-82AD-6D10D284816D}.Release|Any CPU.Build.0 = Release|Any CPU - {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|Any CPU.Build.0 = Release|Any CPU - {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|Any CPU.Build.0 = Release|Any CPU - {8238A052-D626-49EB-A011-51DC6D0DBA30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8238A052-D626-49EB-A011-51DC6D0DBA30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8238A052-D626-49EB-A011-51DC6D0DBA30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8238A052-D626-49EB-A011-51DC6D0DBA30}.Release|Any CPU.Build.0 = Release|Any CPU - {545A88D3-1AE2-4D39-9B7C-C691768AD17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {545A88D3-1AE2-4D39-9B7C-C691768AD17F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {545A88D3-1AE2-4D39-9B7C-C691768AD17F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {545A88D3-1AE2-4D39-9B7C-C691768AD17F}.Release|Any CPU.Build.0 = Release|Any CPU - {B1F84FD8-6150-4ECA-9AD7-C316E04E17D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1F84FD8-6150-4ECA-9AD7-C316E04E17D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1F84FD8-6150-4ECA-9AD7-C316E04E17D8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1F84FD8-6150-4ECA-9AD7-C316E04E17D8}.Release|Any CPU.Build.0 = Release|Any CPU - {BFBB35C9-6437-480A-8DCC-AE3700110E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFBB35C9-6437-480A-8DCC-AE3700110E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFBB35C9-6437-480A-8DCC-AE3700110E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFBB35C9-6437-480A-8DCC-AE3700110E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {29270853-90DC-4C39-9621-F47AE40A79B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29270853-90DC-4C39-9621-F47AE40A79B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29270853-90DC-4C39-9621-F47AE40A79B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29270853-90DC-4C39-9621-F47AE40A79B6}.Release|Any CPU.Build.0 = Release|Any CPU - {186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {186069FE-E1E8-4DE1-BEA4-0FF1484D22D1}.Release|Any CPU.Build.0 = Release|Any CPU - {62E9D32B-B989-43CF-81A2-B38B3367FCA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62E9D32B-B989-43CF-81A2-B38B3367FCA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62E9D32B-B989-43CF-81A2-B38B3367FCA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62E9D32B-B989-43CF-81A2-B38B3367FCA3}.Release|Any CPU.Build.0 = Release|Any CPU - {4DA57968-F547-4019-8381-03A218B6C385}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4DA57968-F547-4019-8381-03A218B6C385}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4DA57968-F547-4019-8381-03A218B6C385}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4DA57968-F547-4019-8381-03A218B6C385}.Release|Any CPU.Build.0 = Release|Any CPU - {874841C0-DCB9-4678-A952-5E06286CA2B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {874841C0-DCB9-4678-A952-5E06286CA2B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {874841C0-DCB9-4678-A952-5E06286CA2B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {874841C0-DCB9-4678-A952-5E06286CA2B0}.Release|Any CPU.Build.0 = Release|Any CPU - {D27C951E-1935-4D76-A0E5-A00D7829C654}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D27C951E-1935-4D76-A0E5-A00D7829C654}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D27C951E-1935-4D76-A0E5-A00D7829C654}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D27C951E-1935-4D76-A0E5-A00D7829C654}.Release|Any CPU.Build.0 = Release|Any CPU - {AD0EB901-E227-42E0-B7AE-28C3150B9A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD0EB901-E227-42E0-B7AE-28C3150B9A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD0EB901-E227-42E0-B7AE-28C3150B9A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD0EB901-E227-42E0-B7AE-28C3150B9A8B}.Release|Any CPU.Build.0 = Release|Any CPU - {AE13C83A-C01A-4C25-9A29-A440E014F998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE13C83A-C01A-4C25-9A29-A440E014F998}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE13C83A-C01A-4C25-9A29-A440E014F998}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE13C83A-C01A-4C25-9A29-A440E014F998}.Release|Any CPU.Build.0 = Release|Any CPU - {B9340314-6A79-496C-8BF9-88F1F6E3F096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9340314-6A79-496C-8BF9-88F1F6E3F096}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9340314-6A79-496C-8BF9-88F1F6E3F096}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9340314-6A79-496C-8BF9-88F1F6E3F096}.Release|Any CPU.Build.0 = Release|Any CPU - {75054BFF-DC0F-4094-8F05-62776663DF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75054BFF-DC0F-4094-8F05-62776663DF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75054BFF-DC0F-4094-8F05-62776663DF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75054BFF-DC0F-4094-8F05-62776663DF0D}.Release|Any CPU.Build.0 = Release|Any CPU - {D7B948FC-C4E7-41A9-84A7-8F1A725458E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7B948FC-C4E7-41A9-84A7-8F1A725458E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7B948FC-C4E7-41A9-84A7-8F1A725458E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7B948FC-C4E7-41A9-84A7-8F1A725458E3}.Release|Any CPU.Build.0 = Release|Any CPU - {3162D387-7424-481A-9A1E-8193BF7FABE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3162D387-7424-481A-9A1E-8193BF7FABE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3162D387-7424-481A-9A1E-8193BF7FABE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3162D387-7424-481A-9A1E-8193BF7FABE5}.Release|Any CPU.Build.0 = Release|Any CPU - {9297D1FA-F9C0-42D2-84FA-06DA323357C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9297D1FA-F9C0-42D2-84FA-06DA323357C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9297D1FA-F9C0-42D2-84FA-06DA323357C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9297D1FA-F9C0-42D2-84FA-06DA323357C9}.Release|Any CPU.Build.0 = Release|Any CPU - {A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF}.Release|Any CPU.Build.0 = Release|Any CPU - {4454139C-174A-4DD8-8E76-657D9F03BA09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4454139C-174A-4DD8-8E76-657D9F03BA09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4454139C-174A-4DD8-8E76-657D9F03BA09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4454139C-174A-4DD8-8E76-657D9F03BA09}.Release|Any CPU.Build.0 = Release|Any CPU - {450D5371-32A1-4667-987E-40D75F035233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {450D5371-32A1-4667-987E-40D75F035233}.Debug|Any CPU.Build.0 = Debug|Any CPU - {450D5371-32A1-4667-987E-40D75F035233}.Release|Any CPU.ActiveCfg = Release|Any CPU - {450D5371-32A1-4667-987E-40D75F035233}.Release|Any CPU.Build.0 = Release|Any CPU - {E602AB2B-048F-4317-8A6B-A225C00173FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E602AB2B-048F-4317-8A6B-A225C00173FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E602AB2B-048F-4317-8A6B-A225C00173FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E602AB2B-048F-4317-8A6B-A225C00173FA}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {50C00046-0DA3-4B5C-9F6F-7BE1145E156A} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {01409D95-A5F1-4EBE-94B1-909D5D2D0DC3} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {2C7CE1F8-E73E-4987-8023-B5A0EBAC86E8} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {6F5EC38C-4A11-40D3-827C-F607B90BEFF0} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {E19B5128-3469-492E-82E1-725631C4A68C} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {68ADC720-316E-4895-9F8E-C3CCADD262BE} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {1621415E-7723-4F46-A589-4C4620C0751A} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {987898D9-724E-4324-BF91-77B1A6DBE8F1} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {FD63F778-3938-45D2-900B-51EC770F70E5} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {61F7F446-9EF3-4768-B33A-4D75F60E1059} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {D5296435-3A3F-4B1A-81D1-434EC9E97DEF} = {5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF} - {790B8030-00C2-4121-B125-EDC4CE329BA3} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {27DFBD04-64B2-4F1B-82B2-006620CCA6F8} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {71CB42FF-E750-4A3B-9C3A-AC938853CC89} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {10B6ADE1-F808-4612-801D-4452F5B52242} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {46250E12-4CF1-4051-B4A7-80C8C06E0068} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {020E15EA-731F-4667-95AF-226671E0C3AE} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {0D59BA81-6279-4650-AEBB-4EA735C28A1A} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {DE730F17-7D5C-4D9D-B479-025024BF4F1D} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {E062FFD6-DEB1-4DB4-8B6E-ADBF04129545} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {F582949D-8B92-47BD-9DD7-9F2BFCCC290C} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {376A7588-50DF-46CD-955B-0309F491D830} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {5DF3CF65-3E11-4639-964D-7BEB4109DCF9} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {D3E8A13B-92EE-45A8-BB24-40EC3CC9DB34} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {9EFCEFB5-253E-4DE2-8A70-821D7B8189DF} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {3A8080FB-9C93-45B9-8EB5-828DDC31FDF0} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {BFF7714C-E5A3-4EEB-B04B-5FA47F29AD03} = {020E15EA-731F-4667-95AF-226671E0C3AE} - {0D4DF78D-7E5F-4516-B19F-E6AA71A1DBF4} = {EE49F5DC-5835-4AE3-B3BA-8BDE0AD56330} - {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {EFA38DEF-C2BB-42AE-8B68-B31D79F3107E} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {0C6EFAF9-CE3E-4C11-8DD8-D7DABB206E5C} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {CAE652AF-6801-425E-AAF3-AB20DE7DF88E} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {FF80D706-8309-4E02-BAC0-D28B4CBCF600} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {376C19DE-31E2-4FF6-88FC-0D0D6233C999} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {7B48115A-B766-4B55-93A8-C08A42C37710} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {FBF74C8F-695C-4967-84AC-358EEFB1376D} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {11ECCB8B-6958-42A7-BD58-88C09CB149B2} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {76D4BB7E-D981-42D5-BE96-6FAD8DEF9A4A} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} - {488675EC-C8BB-40E0-AD4F-91F623D548B3} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {65A25D6E-C9CC-4F45-8925-04087AC82634} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} - {D9A30E32-D466-4EC5-B4F2-62E17562279B} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {21DB138B-85B7-479E-91FE-01E0F972EC56} = {D9A30E32-D466-4EC5-B4F2-62E17562279B} - {69F5FF81-5615-4F06-B83C-FCF979BB84CA} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {E7D4921C-F12D-4E1C-85AC-8B7F91C59B0E} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {236A71E3-01DA-4679-9DFF-16A8E079ACFF} = {5E7F18A8-F843-4C8A-AB02-4C7D9205C6CF} - {41248B96-6E15-4E5E-A78F-859897676814} = {020E15EA-731F-4667-95AF-226671E0C3AE} - {074F5BD6-DC05-460B-B78F-044D125FD787} = {D9A30E32-D466-4EC5-B4F2-62E17562279B} - {DCD0C39E-C78C-4A44-B0BD-7325254A2E97} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - {2DE99835-A3A3-4922-82AD-6D10D284816D} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} - {7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {33A20B85-7024-4112-B1E7-00CD0E4A9F96} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} - {2C88C923-3D7A-4492-9241-7A489750CAB7} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} - {8238A052-D626-49EB-A011-51DC6D0DBA30} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {545A88D3-1AE2-4D39-9B7C-C691768AD17F} = {6CE2F530-582B-4695-A209-41065E103426} - {B1F84FD8-6150-4ECA-9AD7-C316E04E17D8} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {BFBB35C9-6437-480A-8DCC-AE3700110E7D} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {29270853-90DC-4C39-9621-F47AE40A79B6} = {B27FAFDF-2DBA-4AB0-BA85-FD5F21D359D6} - {186069FE-E1E8-4DE1-BEA4-0FF1484D22D1} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {62E9D32B-B989-43CF-81A2-B38B3367FCA3} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} - {4DA57968-F547-4019-8381-03A218B6C385} = {6CE2F530-582B-4695-A209-41065E103426} - {874841C0-DCB9-4678-A952-5E06286CA2B0} = {6CE2F530-582B-4695-A209-41065E103426} - {D27C951E-1935-4D76-A0E5-A00D7829C654} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {AD0EB901-E227-42E0-B7AE-28C3150B9A8B} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {AE13C83A-C01A-4C25-9A29-A440E014F998} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} - {B9340314-6A79-496C-8BF9-88F1F6E3F096} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {75054BFF-DC0F-4094-8F05-62776663DF0D} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {D7B948FC-C4E7-41A9-84A7-8F1A725458E3} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {3162D387-7424-481A-9A1E-8193BF7FABE5} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {9297D1FA-F9C0-42D2-84FA-06DA323357C9} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {DCCF71EC-14DB-4FB8-8F9E-2DFAB69D9F15} = {E344E0A2-7715-4C7F-BAF7-D64EA94CB19B} - {A9470366-2CEB-4743-9B5D-1DD3C2C4D9EF} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {4454139C-174A-4DD8-8E76-657D9F03BA09} = {D27E1CB4-C641-4C6C-A140-EF5F6215AE29} - {450D5371-32A1-4667-987E-40D75F035233} = {46250E12-4CF1-4051-B4A7-80C8C06E0068} - {E602AB2B-048F-4317-8A6B-A225C00173FA} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD} - EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{10b6ade1-f808-4612-801d-4452f5b52242}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{186069fe-e1e8-4de1-bea4-0ff1484d22d1}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{27dfbd04-64b2-4f1b-82b2-006620cca6f8}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{2c7ce1f8-e73e-4987-8023-b5a0ebac86e8}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{71cb42ff-e750-4a3b-9c3a-ac938853cc89}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{76d4bb7e-d981-42d5-be96-6fad8def9a4a}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{7f26eda3-c8c4-4b7f-a9b6-d278c2f40a13}*SharedItemsImports = 13 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{ad0eb901-e227-42e0-b7ae-28c3150b9a8b}*SharedItemsImports = 5 - src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{d27c951e-1935-4d76-a0e5-a00d7829c654}*SharedItemsImports = 5 - EndGlobalSection -EndGlobal diff --git a/TestPlatform.slnx b/TestPlatform.slnx new file mode 100644 index 0000000000..d7036c6a12 --- /dev/null +++ b/TestPlatform.slnx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/azure-pipelines-insertion.yml b/azure-pipelines-insertion.yml new file mode 100644 index 0000000000..ac34a636cc --- /dev/null +++ b/azure-pipelines-insertion.yml @@ -0,0 +1,131 @@ +parameters: + - name: 'ComponentBranchName' + default: 'main' + - name: 'CreateDraftPR' + default: 'false' + - name: 'OptionalTitlePrefix' + default: '[Auto Insertion]' + - name: 'VisualStudioBranchName' + default: 'main' + +variables: +- group: DotNet-Roslyn-Insertion-Variables +- template: /eng/common/templates-official/variables/pool-providers.yml@self +- name: RequiredValueSentinel + value: 'REQUIRED' +- name: DefaultValueSentinel + value: '(default)' +- name: BuildQueueName + value: 'microsoft-vstest' +- name: ComponentAzdoUri + value: 'https://dnceng.visualstudio.com/DefaultCollection' +- name: ComponentProjectName + value: 'internal' +- name: ComponentName + value: 'VS Test Platform' +- name: DropPath + value: '/dp=microsoft-vstest/VSSetupArtifacts' +- name: InsertCore + value: 'false' +- name: InsertDevDiv + value: 'false' +- name: InsertToolset + value: 'false' +- name: QueueValidation + value: '(default)' +- name: UpdateAssemblyVersions + value: '(default)' +- name: UpdateCoreXTLibraries + value: 'false' +- name: InsertionCount + value: '1' +- name: CherryPick + value: '(default)' +- name: ComponentGitHubRepoName + value: '(default)' +- name: SpecificBuildNumber + value: '(default)' +- name: AutoComplete + value: 'true' +- name: ComponentBranchName + value: '${{ parameters.ComponentBranchName }}' +- name: CreateDraftPR + value: '${{ parameters.CreateDraftPR }}' +- name: OptionalTitlePrefix + value: '${{ parameters.OptionalTitlePrefix }}' +- name: VisualStudioBranchName + value: '${{ parameters.VisualStudioBranchName }}' + +trigger: none + +schedules: + # insert at 1am on monday, wednesday and friday + - cron: 0 1 * * 1,3,5 + displayName: Daily midnight insertion to VS + branches: + include: + - main + always: true + +jobs: +- job: insert + displayName: Insert + pool: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 + timeoutInMinutes: 90 + + steps: + + - task: NuGetCommand@2 + displayName: 'Install RIT from Azure Artifacts' + inputs: + command: custom + arguments: 'install RoslynTools.VisualStudioInsertionTool -PreRelease -Source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + + - powershell: | + mv RoslynTools.VisualStudioInsertionTool.* RIT + + # Path is like .\RIT\tools\net472\OneOffInsertion.ps1, the TFM can change as the + # tools gets updated, so we look for it with a wildcard, and use the first one found + $script = Get-ChildItem .\RIT\tools\*\OneOffInsertion.ps1 | Select-Object -First 1 -ExpandProperty FullName + # Add VS Interactive experiences team (from devdiv) as reviewer. To get the user guid, make PR search with createdBy (in the target organization => devdiv), and you will get the guid in URL. OR https://dev.azure.com/devdiv/_apis/teams?$mine=true + $team = "10a2f33b-3b3d-47a9-a22f-6bb09247bb63" + + "Found tool: '$script'" + $params = @{ + userName = "dn-bot@microsoft.com" + password = "$(dn-bot-devdiv-build-e-code-full-release-e-packaging-r)" + componentUserName = "dn-bot@microsoft.com" + componentPassword = "$(dn-bot-dnceng-build-e-code-full-release-e-packaging-r)" + requiredValueSentinel = "$(RequiredValueSentinel)" + defaultValueSentinel = "$(DefaultValueSentinel)" + buildQueueName = "$(BuildQueueName)" + componentAzdoUri = "$(ComponentAzdoUri)" + componentProjectName = "$(ComponentProjectName)" + componentBranchName = "$(ComponentBranchName)" + componentName = "$(ComponentName)" + dropPath = "$(DropPath)" + insertCore = "$(InsertCore)" + insertDevDiv = "$(InsertDevDiv)" + insertToolset = "$(InsertToolset)" + queueValidation = "$(QueueValidation)" + specificBuild = "$(SpecificBuildNumber)" + updateAssemblyVersions = "$(UpdateAssemblyVersions)" + updateCoreXTLibraries = "$(UpdateCoreXTLibraries)" + visualStudioBranchName = "$(VisualStudioBranchName)" + titlePrefix = "$(OptionalTitlePrefix)" + insertionCount = "$(InsertionCount)" + writePullRequest = "$(System.DefaultWorkingDirectory)\prid.txt" + autoComplete = "$(AutoComplete)" + createDraftPR = "$(CreateDraftPR)" + cherryPick = "$(CherryPick)" + componentGitHubRepoName = "$(ComponentGitHubRepoName)" + reviewerGuid = $team + } + & $script @params + workingDirectory: '$(System.DefaultWorkingDirectory)\' + displayName: 'Run OneOffInsertion.ps1' + + - script: 'echo. && echo. && type "$(System.DefaultWorkingDirectory)\prid.txt" && echo. && echo.' + displayName: 'Report PR URL' diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 74309bc0ae..2dee553857 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -47,6 +47,10 @@ parameters: displayName: "Skip tests" type: boolean default: False +- name: codeQLForce + displayName: "Try collecting CodeQL before default CodeQL cadence" + type: boolean + default: False - name: otherOsPools type: object @@ -55,7 +59,7 @@ parameters: image: 1es-ubuntu-2204 os: linux - name: Azure Pipelines - image: macOS-12 + image: macos-14 os: macOS variables: @@ -71,11 +75,6 @@ variables: value: False - name: _ReleaseVersionKind value: '' - # Arcade is using global cache of nugets in non-windows build - # under some circumstances, but we don't respect that in our code and try to find them - # in .packages. Force the location of packages to that folder. - - name: NUGET_PACKAGES - value: '$(Build.SourcesDirectory)/.packages/' # Publish Logs seems to depend on this name of variable, so we define it # even when we don't use matrix. - name: _BuildConfig @@ -85,6 +84,13 @@ variables: - name: _ReleaseVersionKind value: release + # Set to 0 to force CodeQL scan without waiting 72 from last attempt (even if it failed). + # This is only a hint for the tool, it might reject to run it if it already has up to date data. + # See the Starting Code QL step for details. + - ${{ if eq(parameters.codeQLForce, True) }}: + - name: Codeql.Cadence + value: 0 + # Group gives access to $microsoft-symbol-server-pat and $symweb-symbol-server-pat - group: DotNet-Symbol-Server-Pats # Group gives access to $dn-bot-devdiv-drop-rw-code-rw and dn-bot-dnceng-build-rw-code-rw @@ -127,8 +133,10 @@ extends: enabled: true pool: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026preview.scout.amd64 os: windows + settings: + networkIsolationPolicy: Permissive,CFSClean customBuildTags: - ES365AIMigrationTooling stages: @@ -151,7 +159,7 @@ extends: timeoutInMinutes: 120 pool: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026preview.scout.amd64 os: windows steps: # This steps help to understand versions of .NET runtime installed on the machine, @@ -177,14 +185,27 @@ extends: - ${{ if eq(parameters.SkipTests, False) }}: # -ci is allowing to import some environment variables and some required configurations # -nobl avoid overwriting binlog of the main Build + - script: Test.cmd + -configuration Release + -ci + -nobl + name: Test + displayName: Unit Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet + # -ci is allowing to import some environment variables and some required configurations + # -nobl avoid overwriting binlog of the main Build - script: Test.cmd -configuration Release -ci -nobl -integrationTest + -compatibilityTest -performanceTest - name: Test - displayName: Test + name: IntegrationTest + displayName: Integration Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet # This step is only helpful for diagnosing some issues with vstest/test host that would not appear # through the console or trx @@ -194,13 +215,22 @@ extends: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/TestResults/Release' ArtifactName: TestResults condition: failed() + + # Remove generated test projects from the compatibility matrix because they use older versions of our packages + # and some of them use dependencies that are now vulnerable. + # We don't depend on those vulnerable versions anymore, but need to keep restoring them to ensure backwards compatibility. + - pwsh: | + if (Test-Path -LiteralPath "$(Build.SourcesDirectory)/artifacts/tmp/GeneratedTestAssets") { + Remove-Item -LiteralPath "$(Build.SourcesDirectory)/artifacts/tmp/GeneratedTestAssets" -Recurse -Force + } + displayName: Remove artifacts/tmp/GeneratedTestAssets - task: 1ES.PublishBuildArtifacts@1 displayName: 'Publish VSSetup' inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/VSSetup/Release' ArtifactName: VSSetupArtifacts - + - ${{ each pool in parameters.otherOsPools }}: - job: ${{ pool.os }} dependsOn: Windows @@ -239,6 +269,18 @@ extends: --performanceTest name: Test displayName: Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet + + # Remove generated test projects from the compatibility matrix because they use older versions of our packages + # and some of them use dependencies that are now vulnerable. + # We don't depend on those vulnerable versions anymore, but need to keep restoring them to ensure backwards compatibility. + - pwsh: | + $generatedAssetsPath = "$(Build.SourcesDirectory)/artifacts/tmp/GeneratedTestAssets" + if (Test-Path -LiteralPath $generatedAssetsPath) { + Remove-Item -LiteralPath $generatedAssetsPath -Recurse -Force + } + displayName: Remove artifacts/tmp/GeneratedTestAssets # This step is only helpful for diagnosing some issues with vstest/test host that would not appear # through the console or trx @@ -248,14 +290,14 @@ extends: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/TestResults/Release' ArtifactName: TestResults condition: failed() - + - job: Publish - dependsOn: + dependsOn: - ${{ each pool in parameters.otherOsPools }}: - ${{ pool.os }} pool: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026preview.scout.amd64 os: windows steps: # The template job needs a log, otherwise it writes a warning. We can disable log uploading only for @@ -275,22 +317,23 @@ extends: artifactName: VSSetupArtifacts targetPath: '$(Build.SourcesDirectory)/artifacts/VSSetup/Release' - - task: NuGetAuthenticate@1 - displayName: 'NuGet Authenticate to dotnet-tools and test-tools feeds' + - ${{ if eq(parameters.isRTM, False) }}: + - task: NuGetAuthenticate@1 + displayName: 'NuGet Authenticate to dotnet-tools and test-tools feeds' - - task: 1ES.PublishNuget@1 - displayName: 'Publish NuGet packages to dotnet-tools feed' - inputs: - packageParentPath: '$(Build.SourcesDirectory)/artifacts/packages/Release' - packagesToPush: '$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.nupkg;!$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.symbols.nupkg' - publishVstsFeed: 'public/dotnet-tools' - - - task: 1ES.PublishNuget@1 - displayName: 'Publish NuGet packages to test-tools feed' - inputs: - packageParentPath: '$(Build.SourcesDirectory)/artifacts/packages/Release' - packagesToPush: '$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.nupkg;!$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.symbols.nupkg' - publishVstsFeed: 'public/test-tools' + - task: 1ES.PublishNuget@1 + displayName: 'Publish NuGet packages to dotnet-tools feed' + inputs: + packageParentPath: '$(Build.SourcesDirectory)/artifacts/packages/Release' + packagesToPush: '$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.nupkg;!$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.symbols.nupkg' + publishVstsFeed: 'public/dotnet-tools' + + - task: 1ES.PublishNuget@1 + displayName: 'Publish NuGet packages to test-tools feed' + inputs: + packageParentPath: '$(Build.SourcesDirectory)/artifacts/packages/Release' + packagesToPush: '$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.nupkg;!$(Build.SourcesDirectory)/artifacts/packages/Release/**/*.symbols.nupkg' + publishVstsFeed: 'public/test-tools' # Publishes setup VSIXes to a drop. # Note: The insertion tool looks for the display name of this task in the logs. @@ -317,10 +360,7 @@ extends: LclSource: lclFilesfromPackage LclPackageId: 'LCL-JUNO-PROD-VSTEST' - - template: eng\common\templates-official\post-build\post-build.yml@self + - template: eng/common/templates-official/post-build/post-build.yml@self parameters: - publishingInfraVersion: 3 - SDLValidationParameters: - enable: false - continueOnError: false - params: ' -SourceToolsList @("policheck","credscan")' + # Temporarily skip validation until infra issues are resolved, based on advice from Arcade. + enableSigningValidation: false diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f4cf78e518..02891eb3a2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,6 +32,7 @@ pr: - CONTRIBUTING.md - README.md - SECURITY.md + - azure-pipelines-official.yml variables: # Cannot use key:value syntax in root defined variables @@ -54,11 +55,6 @@ variables: value: ' ' - name: _ReleaseVersionKind value: '' - # Arcade is using global cache of nugets in non-windows build - # under some circumstances, but we don't respect that in our code and try to find them - # in .packages. Force the location of packages to that folder. - - name: NUGET_PACKAGES - value: '$(Build.SourcesDirectory)/.packages/' stages: @@ -81,7 +77,7 @@ stages: timeoutInMinutes: 120 pool: name: NetCore-Public - demands: ImageOverride -equals windows.vs2022.amd64.open + demands: ImageOverride -equals windows.vs2026preview.scout.amd64.open strategy: matrix: Release: @@ -103,6 +99,18 @@ stages: /p:SourceBranchName=$(Build.SourceBranchName) name: Build displayName: Build + + # -ci is allowing to import some environment variables and some required configurations + # -nobl avoid overwriting binlog of the main Build + - script: Test.cmd + -configuration $(_BuildConfig) + -ci + -nobl + /bl:$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/UnitTest.binlog + name: Test + displayName: Unit Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet # -ci is allowing to import some environment variables and some required configurations # -nobl avoid overwriting binlog of the main Build @@ -111,9 +119,11 @@ stages: -ci -nobl -integrationTest - -performanceTest - name: Test - displayName: Test + /bl:$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/IntegrationTest.binlog + name: IntegrationTest + displayName: Integration Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet # This step is only helpful for diagnosing some issues with vstest/test host that would not appear # through the console or trx @@ -131,7 +141,7 @@ stages: ArtifactName: PackageArtifacts - task: PublishBuildArtifacts@1 - displayName: 'Publish NonShippping Packages' + displayName: 'Publish NonShipping Packages' inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/NonShipping' ArtifactName: PackageArtifacts @@ -153,11 +163,11 @@ stages: value: Release strategy: matrix: - Ubuntu_22_04: + ubuntu: vmImage: ubuntu-22.04 pwsh: true - macOS_12: - vmImage: macOS-12 + macOS: + vmImage: macos-14 pwsh: true pool: vmImage: $[ variables['vmImage'] ] @@ -188,6 +198,8 @@ stages: --performanceTest name: Test displayName: Test + env: + DOTNET_ROOT: $(Build.SourcesDirectory)/.dotnet # This step is only helpful for diagnosing some issues with vstest/test host that would not appear # through the console or trx diff --git a/build.cmd b/build.cmd index 4afad04714..10a90a684c 100644 --- a/build.cmd +++ b/build.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\Build.ps1""" -restore -build %*" +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\Build.ps1""" -restore -build %*" exit /b %ErrorLevel% diff --git a/build.sh b/build.sh index 8477d5af88..37a475881e 100755 --- a/build.sh +++ b/build.sh @@ -13,4 +13,7 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -"$scriptroot/eng/common/build.sh" --build --restore $@ +if [[ -z "${DOTNET_ROOT:-}" && -d "$scriptroot/.dotnet" ]]; then + export DOTNET_ROOT="$scriptroot/.dotnet" +fi +"$scriptroot/eng/common/build.sh" --build --restore "$@" diff --git a/docs/Overview.md b/docs/Overview.md index 411f0bb4b4..b75d2a68b8 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -1,10 +1,10 @@ -# Test Platform messaging +# Test Platform -- [Test Platform messaging](#test-platform-messaging) +- [Test Platform](#test-platform) - [What is TestPlatform?](#what-is-testplatform) - [How it works?](#how-it-works) - [Workflows](#workflows) - - [Specification](#specification) + - [Communication Protocol](#communication-protocol) - [Base Protocol](#base-protocol) - [Header Part](#header-part) - [Content Part](#content-part) @@ -49,9 +49,16 @@ - [TestExecution.StatsChange notification (Runner)](#testexecutionstatschange-notification-runner) - [TestExecution.StatsChange notification (Client)](#testexecutionstatschange-notification-client) - [Datacollection](#datacollection) + - [Extensibility](#extensibility) + - [DLL Extension points](#dll-extension-points) + - [ObjectModel](#objectmodel) + - [Test Adapter](#test-adapter) + - [Test Logger](#test-logger) + - [Runtime Provider](#runtime-provider) + - [TranslationLayer extension points](#translationlayer-extension-points) - [.NET Implementation](#net-implementation) - [Architecture](#architecture) - - [Extension points](#extension-points) + ## What is TestPlatform? @@ -128,7 +135,7 @@ The Run workflow described above is very common in command line tools, and proba - *TestSession* - starts a set of testhosts for given test sources, to make the ready to run. - *AttachmentProcessing* - processes a given set of attachments that were produced during a previous test run, e.g. merges code coverage files. -## Specification +## Communication Protocol ### Base Protocol @@ -243,7 +250,7 @@ The version is negotiated between the components at the beginning of every workf The server supports processing only a single request at a time. Unless the request is [Cancel](#cancel) or [Abort](#abort) request. -All notifications are sent before as response is sent. +All notifications are sent before a response is sent. #### Message documentation @@ -286,7 +293,7 @@ The version is determined by choosing the highest common supported version. When The receiving side should remember the agreed value, and use it as the highest supported version for any downstream component. In the case above runner should send 6 to testhost, even though the runner supports versions up to 7. -The request was introduced in TestPlatform version `16.0.0`. Runners before this version are not allowed. Testhosts before this version are allowed, the version of testhost if figured out by scanning the assembly, and the request is not sent to them. Version 0 is used for communication. +The request was introduced in TestPlatform version `16.0.0`. Runners before this version are not allowed. Testhosts before this version are allowed, the version of testhost is figured out by scanning the assembly, and the request is not sent to them. Version 0 is used for communication. Versions: @@ -382,7 +389,7 @@ sequenceDiagram participant c as Client
(Visual Studio) participant r as Runner
(vstest.console.exe) c->>r: Run vstest.console -port X -r->>r: Connect to port Y +r->>r: Connect to port X r->>c: TestSession.Connected ``` @@ -590,7 +597,7 @@ public class DiscoveryCompletePayload // If true TotalCount is also set to -1. public bool IsAborted { get; set; } - // Metrics. + // Telemetry. public IDictionary? Metrics { get; set; } // Sources which were fully discovered. @@ -710,11 +717,12 @@ public class DiscoveryCriteria // Discovered test event will be raised after discovering at minimum this number of tests, - // or when DiscoveredTestEventTimeout is passed. + // or when DiscoveredTestEventTimeout is passed. This helps batching the results, making communication between processes faster. public long FrequencyOfDiscoveredTestsEvent { get; private set; } // Discovered test event will be raised after this much time since last test - // when FrequencyOfDiscoveredTestsEvent is passed. + // when FrequencyOfDiscoveredTestsEvent is passed. This helps sending the batches more frequently, + // when the batch is large, but there is small amount of tests, or the discovery is slow. Giving more frequent feedback to user. public TimeSpan DiscoveredTestEventTimeout { get; private set; } // Run settings for the discovery. @@ -764,7 +772,7 @@ public class DiscoveryCompletePayload // If true TotalCount is also set to -1. public bool IsAborted { get; set; } - // Metrics. + // Telemetry. public IDictionary? Metrics { get; set; } // Sources which were fully discovered. @@ -1108,7 +1116,7 @@ public class TestRunCompleteEventArgs // Value is set to TimeSpan.Zero in case of any error. public TimeSpan ElapsedTimeInRunningTests { get; private set; } - // Metrics. + // Telemetry. public IDictionary? Metrics { get; set; } // Extensions that were discovered in this run. @@ -1229,10 +1237,10 @@ public class TestRunCriteriaWithTests public class TestExecutionContext { - // Gets or sets the frequency of run stats event. + // Gets or sets the batch size for sending test results. public long FrequencyOfRunStatsChangeEvent { get; set; } - // Gets or sets the timeout that triggers sending results regardless of cache size. + // Gets or sets the timeout that triggers sending results regardless of batch size. public TimeSpan RunStatsChangeEventTimeout { get; set; } // Gets or sets a value indicating whether execution is out of process. @@ -1241,7 +1249,7 @@ public class TestExecutionContext // Gets or sets a value indicating whether testhost process should be kept running after test run completion. public bool KeepAlive { get; set; } - // Gets or sets a value indicating whether test case level events need to be sent or not. + // Gets or sets a value indicating whether test case level events need to be sent or not. TODO: what is it? Is there since first commit, no usages on grep.app. public bool AreTestCaseLevelEventsRequired { get; set; } // Gets or sets a value indicating whether execution is in debug mode. @@ -1358,7 +1366,7 @@ public class TestRunCompleteEventArgs // Value is set to TimeSpan.Zero in case of any error. public TimeSpan ElapsedTimeInRunningTests { get; private set; } - // Metrics. + // Telemetry. public IDictionary? Metrics { get; set; } // Extensions that were discovered in this run. @@ -1717,10 +1725,141 @@ Same as above [TestExecution.StatsChange notification (Runner)](#testexecutionst ### Datacollection +## Extensibility + +Test Platform offers multiple points for extensibility. These extensibility points allow test framework authors to integrate their test frameworks with VSTest by providing a test adapter. Similarly a dataCollector extension can be provided that observes the run, or a logger extension can be provided that can observe the run and logs (reports) on the tests that were executed. + +IDEs (and other clients) can integrate with VSTest using the TranslationLayer package, which manages communication between the client and the runner. + + +### DLL Extension points + +Extensions are looked up by Reflection from the dlls provided to the run, based on a naming convention. Dlls named `*TestAdapter.dll`, `*TestLogger.dll`, `*Collector.dll`, `*RuntimeProvider.dll` are considered by the plugin loader. + +The extensions should target netstandard2.0, and not have any dll dependencies. The no-dependency rule is more lose for the TestAdapter extension point, because the tests target the framework that they will eventually run as. While the other extensions (data collector, and logger) get loaded into a process that might not align with the project target framework, e.g. in case where .NET Core project is ran from Visual Studio, where vstest.console.exe is using .NET Framework, and datacollector.exe is also using .NET Framework. + +#### ObjectModel + +ObjectModel is Test platform dll that holds all types and abstractions needed for extensibility. It is often used as reference dll and it is not typically shipped together with the extension. The runner / testhost / datacollector provides its own version of ObjectModel. + +```xml + +``` + +This reference uses `PrivateAssets="All"` to avoid copying the dll into the output folder. + +#### Test Adapter + +Test adapter is the most used extension point that allows you to write your own test framework integration. This extension point is used by MSTest, NUnit, XUnit.net and other, to implement a `test adapter`, an adaptation layer that communicates with the particular test framework. + +The test adapter implements two interfaces: `ITestExecutor` or `ITestExecutor2` and `ITestDiscoverer`, both need to be implemented. + +The class implementing the discoverer has to be decorated with `DefaultExecutorUri`, that links it to the executor for which this discoverer discovers tests. + +Optionally the discoverer can be decorated with additional attributes: +- `FileExtensionAttribute` to filter the sources which it is able to inspect typically .exe and .dll are specified, but test frameworks don't have to limit themselves to just that. +- `CategoryAttribute` to specify `managed` or `native` assembly type. This only applies to adapters that specify .exe or .dll file extensions. https://github.com/microsoft/vstest/blob/main/docs/RFCs/0020-Improving-Logic-To-Pass-Sources-To-Adapters.md +- `DirectoryBasedTestDiscovererAttribute` - specifies a discoverer that is able to handle directories, rather than files. Currently used by PythonPyTestAdapter. Does not work in commandline - providing directory fails validation, but works via TranslationLayer. + +The class implementing the executor has to be decorated with `ExtensionUriAttribute`, specifying the name of the executor in Uri form. Such as `executor://mstestadapter/v2`. There is no specified schema or validation, but using `executor:///` is the norm. This is used for identification in telemetry, for logging and reporting, and for linking to the discoverer. + +An example of a test adapter: + +```cs +[ExtensionUri(Id)] +[DefaultExecutorUri(Id)] +public class Perfy : ITestDiscoverer, ITestExecutor +{ + public const string Id = "executor://ExampleTestAdapter/v1"; + public static readonly Uri Uri = new Uri(Id); + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext context, + IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + // Sources are the files (one ore more) to be discovered, typically those are dlls that we need to inspect for tests. But can be any file. + // The test framework somehow finds the tests, and will report them back by creating a TestCase object and reporting it to the sink. + var tests = ... + + foreach (var test in tests) + { + // TestCase sends back at least: + // - test case name, this should be unique, but stable across multiple discoveries and runs + // - uri of the executor. + // - the source (dll) in which the test case was found. + // It can also populate the additional information on the object, such as in which code file and on which line the test case is defined. + var tc = new TestCase(test.Name, Uri, source); + + // Send test case sends it to local cache, which batches the updates and sends them to the runner. + discoverySink.SendTestCase(tc); + } + } + + public void Cancel() + { + // Cancel the work that is happening. This is called "asynchronously", during running or discovering tests. + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + // Runs all tests specified in the collection of tests, those tests are typically discovered by discovery, sent to IDE. + // And then the IDE sends them back to a new instance of the the test runner. + // The test framework will do internal discovery of the tests (for MSTest that means scanning all methods in classes to find the ones decorated with [TestMethod]), + // finding the names of those tests, and filtering them down to the provided list. + // This method is used primarily by IDEs such as Visual Studio. + + // Ultimately is does not differ much from RunTests below. See that for details. + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + // The test framework inspects all the sources (files / dlls) that are provided, and finds all tests in them. + // It runs all tests, and when they finish it reports each testcase, and test result. These results are batched and sent to the runner. + // Typically this callback would be injected into the framework, so results can be sent as soon as the test finishes. For simplicity we + // show pseudo-code with synchronous loop. + + foreach (var source in sources) + { + // In MSTest this means finding all methods decorated with [TestMethod], and returning an object with test information, and the related method. + var tests = testFramework.FindTests(source); + + foreach (var test in tests) + { + var tc = new TestCase(test.Name, Uri, source); + // Notify datacollectors that test started + frameworkHandle.RecordStart(test); + TestOutcome result = testFramework.RunTest(test); + // Notify datacollectors that test ended + frameworkHandle.RecordEnd(test, result); + + frameworkHandle.RecordResult(new TestResult(tc) + { + Outcome = result; // e.g. Passed + }); + } + } + } +} +``` + +Additional information can be sourced from the context parameters that contain the run configuration. Additional information can be reported to the platform by using the `IMessageLogger` which is provided to discovery, and from which `IFrameworkHandle` also derives. + +`ITestExecutor2` provides additional methods `ShouldAttachToTestHost` that return true or false. When false is returned, the debugger does not attache to the testhost process. This is used when execution is delegated to a sub-process, e.g. a .py file is provided, the run is routed via .NET Framework testhost, but eventually delegated to python executable. In that case the adapter returns false from the callback to avoid attaching Visual Studio to testhost.exe. + +The instance of `IFrameworkHandle` provided to the extension point, can be upcast to `IFrameworkHandle2` to use additional capabilities that allow attaching debugger to child processes. The attaching assumes that the child process uses the same TFM as the current process. + +Additional example of a toy test framework and adapter can be found in . Or in the respective implementations of MSTest, NUnit, XUnit and similar. + +#### Test Logger + +#### Runtime Provider + + +### TranslationLayer extension points + TODO ### .NET Implementation #### Architecture -#### Extension points + diff --git a/docs/RunSettingsArguments.md b/docs/RunSettingsArguments.md index 2ee4c09e73..3a0e5d1554 100644 --- a/docs/RunSettingsArguments.md +++ b/docs/RunSettingsArguments.md @@ -63,3 +63,48 @@ dotnet test -- TestRunParameters.Parameter\(name=\"myParam\",\ value=\"value\"\) ``` In this example, `\"myParam\"` corresponds to the name of you parameter and `\"value\"` - the value of your parameter. Note, that `\` are escaping characters and they should stay as shown above, unless you are in PowerShell 7.3+. For more examples in PowerShell, such as using variables for the data, please [refer here](https://github.com/microsoft/vstest/issues/4637). + +### Handling semicolons and special characters in parameter values + +Parameter values can contain special characters such as semicolons (`;`), which is +common when passing connection strings. The test platform preserves these characters +as-is — no URL encoding or `%3B` escaping is needed. + +However, semicolons have special meaning in most shells: PowerShell treats `;` as a +statement separator, and bash treats it as a command separator. You must use the +correct quoting for your shell so the semicolon is passed through literally. + +```cmd +# cmd — semicolons are not special, no extra escaping needed +dotnet test -- TestRunParameters.Parameter(name=\"connectionString\", value=\"Server=localhost;Database=mydb;Trusted_Connection=True\") +``` + +```powershell +# powershell (prior 7.3, or with $PSNativeCommandArgumentPassing = "legacy") +# The --% stop-parsing token prevents PowerShell from interpreting the semicolons +dotnet test --% -- TestRunParameters.Parameter(name=\"connectionString\", value=\"Server=localhost;Database=mydb;Trusted_Connection=True\") + +# powershell (7.3+) +dotnet test --% -- TestRunParameters.Parameter(name="connectionString", value="Server=localhost;Database=mydb;Trusted_Connection=True") +``` + +```bash +# bash — escape semicolons (and parentheses/quotes as usual) +dotnet test -- TestRunParameters.Parameter\(name=\"connectionString\",\ value=\"Server=localhost\;Database=mydb\;Trusted_Connection=True\"\) +``` + +If your value contains many special characters, consider using a `.runsettings` file +instead: + +```xml + + + + + + +``` + +```shell +dotnet test --settings my.runsettings +``` diff --git a/docs/contribute.md b/docs/contribute.md deleted file mode 100644 index 2a5100be9d..0000000000 --- a/docs/contribute.md +++ /dev/null @@ -1,224 +0,0 @@ -# Contribution Guide - -This article will help you build, test and try out local builds of the VS test -platform. - -## Prerequisites - -Please ensure you have a `.net 4.6.2` or higher installed on the machine. - -Clone the repository to a local directory. Rest of this article assumes -`/src/vstest` as the location of source enlistment. - -```shell -> git clone https://github.com/Microsoft/vstest.git -``` - -If you're planning to use **Visual Studio** as development environment, please -install `VS 2022` with .NET Desktop development workload, and install individual component `.NET Portable Library targeting pack`. - -If you're _not_ planning to use **Visual Studio** and only use CLI. You will need to install [.Net 46 targeting pack](https://www.microsoft.com/download/details.aspx?id=48136). The download link has two msis. Both needs to be installed. Otherwise build will fail asking to install net 46. - -### Unix requirements - -Install common tools - -```shell -sudo apt install libcurl4-openssl-dev -``` - -Follow the instructions on [Mono Installation][mono-linux] page to install latest bits. - -[mono-linux]: http://www.mono-project.com/download/#download-lin - -Rest of the article will provide steps for VS and CLI/Editors development. - -## Build - -### Building with Visual Studio - -Open `/src/vstest/TestPlatform.sln` in VS. - -Use `Build Solution` to build the source code. - -Binaries for each assembly are produced in the -`artifacts/src//bin/Debug` directory. - -### Building with CLI, CI, Editors - -To build the repository, run the following command: - -```shell -> cd /src/vstest -> build.cmd -``` - -This command will fetch the latest `dotnet-cli` into `/src/vstest/tools/dotnet` -directory. It will use the `dotnet` executable present there to build the source -code. All the nuget required for build will be downloaded to -`/src/vstest/.packages` directory. - -Build will produce following assets: - -* A portable `vstest.console` for desktop (net46 target) and xplat (netcoreapp) - target -* A visual studio extension `Microsoft.TestPlatform.vsix` with the test platform - components required in VS test explorer -* Test platform SDK packages for ObjectModel and TranslationLayer - -We will discuss more on each assets are in the [Deployment](#Deployment) section below. - -Binaries for each assembly is produced along side the source. E.g. ObjectModel -assemblies can be found at -`/src/vstest/src/Microsoft.TestPlatform.ObjectModel/bin/Debug/net46/*.dll`. - -To build a particular configuration, use the `-c` option. E.g. to trigger a -release build use - -```shell -> build.cmd -c Release -``` - -For other options, check `/src/vstest/scripts/build.ps1`. - -## Test - -There are following sets of tests: - -* Unit tests - * Very fast tests primarily validating individual units - * Named as `.UnitTests` where UnitUnderTest is any product - assembly -* Smoke tests - * Slower end to end tests. Typically these cover P0 scenarios (99% of users - will use these, if these are broken, PR will not be merged) - * Driven by a real `vstest.console` executable - * Named as `Microsoft.TestPlatform.SmokeTests` -* End to end tests - * Slower end to end tests for extensive coverage - * Driven by a real `vstest.console` executable - * Named as `Microsoft.TestPlatform.AcceptanceTests` - -As a principle, most of tests are unit tests (~70-80%), few smoke tests -(~20-15%), fewer acceptance tests (~10-5%). - -Unit tests and smoke/acceptance tests are run with the `vstest.console` built by -the `build` step above. - -### Running tests (Visual Studio) - -Currently tests are run in VS 2017 RC using the test platform bits. You need to -install the testplatform.vsix generated by build if you're using an older version -of VS (< RC.3). See [Deployment](#visual-studio) for installation steps. - -Run the tests in Test Explorer. Use a search filter like `project:"Unit"` to -run only unit tests. For running smoke tests, use the `project:"Smoke"` filter. - -### Running tests (CLI, CI, Editors) - -To execute tests, run the following command: - -```shell -> cd /src/vstest -> test.cmd -``` - -By default, only unit tests are run. To run the smoke tests, provide the `-p` -option to `test.cmd` to set test assembly pattern: - -```shell -> test.cmd -p smoke -``` - -The `-p` option can be used to run tests for any assembly as well. E.g. -following command will run tests for _datacollector_: - -```shell -> test.cmd -p datacollector -``` - -Tests for a particular configuration can be run with following command. By -default, `Debug` configuration is run. - -```shell -> test.cmd -c release -``` - -If you want to run a particular test. Eg: Test Name that contains Blame in Acceptance test - -```shell -> test.cmd -p accept -f net451 -filter blame -``` - -## Deployment - -This section will discuss the steps to use the `vstest.console` we've built -using previous instructions. - -### Visual Studio - -Visual Studio 2017 RC ships with the test platform `vsix` generated by build.cmd. To use the locally -built version, use the following steps in a developer command prompt. - -```shell -> vsixinstaller /src/vstest/artifacts/Debug/TestPlatform.vsix -# (replace Debug with Release as appropriate) -``` - -### Command line (XPlat) - -A `netcoreapp` target of vstest.console is dropped at -`/src/vstest/artifacts//netcoreapp1.0/vstest.console.dll`. It can be -executed with any dotnet executable. You may choose to use the dotnet-cli we -have in `/src/vstest/tools/dotnet/dotnet` as well :) - -```shell -> /src/vstest/tools/dotnet/dotnet /src/vstest/artifacts/Debug/netcoreapp1.0/vstest.console.dll /? -``` - -### Command line (Windows desktop) - -A `net46` target of vstest.console is dropped at -`src/vstest/artifacts//net46/win7-x64`. It can be run directly as -follows: - -```shell -> /src/vstest/artifacts/Debug/net46/win7-x64/vstest.console.exe /? -``` - -## Diagnostics - -Try to isolate the failure scenario. In the best case it's just a command line -that can demonstrate a bug. For example, a bug in discovery of tests can show up -in following command line: - -```shell -> /src/vstest/artifacts/Debug/net46/win7-x64/vstest.console.exe mytest.dll /listTests /tests:*&&*#Ed -``` - -Next step is to enable [verbose logging](diagnose.md) to understand details. - -Another add a `Debugger.Launch` at the process launch points. E.g. -`testhost.exe` or `vstest.console.exe`. Select the appropriate debugger (choose -CoreCLR for netcoreapp scenario) and step through the code. - -## Running Tests with TPv2 using Test Explorer - -Test Platform (TPv2) is packaged as a `vsix` with VS 2017 RC releases and lights up the .NET core and Live Unit Testing scenarios. It currently does not support UWP & data collector scenarios (code coverage & fakes). It is placed @ `"%programfiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\Extensions\TestPlatform"`. - -Desktop, UWP & Native unit testing continues to use the test platform (TPv1) located @ `"%programfiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow"`. - -To use TPv2 for desktop - place `testplatform.config` @ `"%programfiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\Extensions\TestPlatform"` with the contents below. - -````xml - - - - - - - - -```` - -If `feature.net35` and `feature.net40` are set to `true`, VS Test Explorer will use TPv2 for desktop flow for tests targeting net35 and net40 frameworks respectively. If `feature.datacollector` is set to `true`, VS Test Explorer will use TPv2 when data collectors are enabled. diff --git a/docs/diagnose.md b/docs/diagnose.md index cd7a965ab2..2ec2a57bc5 100644 --- a/docs/diagnose.md +++ b/docs/diagnose.md @@ -101,11 +101,13 @@ Add the following content to the config file (`vstest.console.exe.config` or `te ``` -## Collect trace in VSTS CI (VSTest Task) +## Collect trace in Azure DevOps CI (VSTest Task) Add a variable to the build definition named `System.Debug` with value `True`. This can be done while a new build is queued. +![image](https://github.com/user-attachments/assets/893eee2d-0e20-4b1f-999b-a1a4f8862d12) + This variable will ensure: * Exact command line of `vstest.console.exe` is reported. It can be used to isolate if there @@ -113,6 +115,10 @@ are errors in translating user inputs in the task to command line options of vst * The task invokes `vstest.console.exe` with `/diag` option. The log files can be uploaded as artifacts for diagnosing errors. +Logs can be downloaded by clicking the right hand `...` menu: + +![image](https://github.com/user-attachments/assets/e8369817-f06a-41c1-b4ae-b042cdc4eaf9) + ## Debug test platform components The runner and test host processes support waiting for debugger attach. You can diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 0000000000..cbe53ba821 --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,309 @@ +# VSTest Environment Variables + +This document lists all environment variables that are understood and handled by the Visual Studio Test Platform (VSTest). These variables can be used to configure various aspects of test execution, debugging, diagnostics, and feature behavior. + +## Connection and Timeout Variables + +### VSTEST_CONNECTION_TIMEOUT +- **Description**: Sets the timeout in seconds for establishing connections between various test platform components (vstest.console, testhost, datacollector). +- **Default**: 90 seconds +- **Example**: `VSTEST_CONNECTION_TIMEOUT=120` +- **Usage**: Useful on slow machines or when network latency causes connection timeouts. + +### VSTEST_TESTHOST_SHUTDOWN_TIMEOUT +- **Description**: Sets the timeout in milliseconds to wait for testhost to safely shut down. +- **Default**: 100 milliseconds +- **Example**: `VSTEST_TESTHOST_SHUTDOWN_TIMEOUT=500` +- **Usage**: Allows testhost more time to clean up resources before forceful termination. + +## Diagnostics and Logging Variables + +### VSTEST_DIAG +- **Description**: Enables diagnostic logging and specifies the path to the log file. +- **Format**: Path to log directory and log file (e.g., "logs\log.txt") +- **Example**: `VSTEST_DIAG=C:\temp\logs\vstest.log` +- **Usage**: Equivalent to the `--diag` command line parameter. + +### VSTEST_DIAG_VERBOSITY +- **Description**: Sets the verbosity level for diagnostic logging when VSTEST_DIAG is enabled. +- **Valid Values**: `Verbose`, `Info`, `Warning`, `Error` +- **Default**: `Verbose` +- **Example**: `VSTEST_DIAG_VERBOSITY=Info` + +### VSTEST_LOGFOLDER +- **Description**: Specifies the folder where test logs should be written. +- **Example**: `VSTEST_LOGFOLDER=C:\TestLogs` + +## Debug Variables + +### VSTEST_HOST_DEBUG +- **Description**: Enables debugging of the testhost process. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_HOST_DEBUG=1` + +### VSTEST_HOST_DEBUG_ATTACHVS +- **Description**: Enables debugging of the testhost process and attempts to attach Visual Studio debugger. Requires AttachVS tool (that can be built in this repo) on PATH. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_HOST_DEBUG_ATTACHVS=1` + +### VSTEST_HOST_NATIVE_DEBUG +- **Description**: Enables native debugging of the testhost process. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_HOST_NATIVE_DEBUG=1` + +### VSTEST_RUNNER_DEBUG +- **Description**: Enables debugging of the test runner (vstest.console). +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_RUNNER_DEBUG=1` + +### VSTEST_RUNNER_DEBUG_ATTACHVS +- **Description**: Enables debugging of the test runner and attempts to attach Visual Studio debugger. Requires AttachVS tool (that can be built in this repo) on PATH. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_RUNNER_DEBUG_ATTACHVS=1` + +### VSTEST_RUNNER_NATIVE_DEBUG +- **Description**: Enables native debugging of the test runner. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_RUNNER_NATIVE_DEBUG=1` + +### VSTEST_DATACOLLECTOR_DEBUG +- **Description**: Enables debugging of data collector processes. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_DATACOLLECTOR_DEBUG=1` + +### VSTEST_DATACOLLECTOR_DEBUG_ATTACHVS +- **Description**: Enables debugging of data collector processes and attempts to attach Visual Studio debugger. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_DATACOLLECTOR_DEBUG_ATTACHVS=1` + +### VSTEST_BLAMEDATACOLLECTOR_DEBUG +- **Description**: Enables debugging of the blame data collector. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_BLAMEDATACOLLECTOR_DEBUG=1` + +### VSTEST_DUMPTOOL_DEBUG +- **Description**: Enables debugging of dump collection tools. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_DUMPTOOL_DEBUG=1` + +### VSTEST_DEBUG_ATTACHVS_PATH +- **Description**: Specifies the path for AttachVS tool, when not found on PATH. AttachVS tool can be built from this repo. +- **Example**: `VSTEST_DEBUG_ATTACHVS_PATH=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\devenv.exe` + +### VSTEST_DEBUG_NOBP +- **Description**: Disables breakpoints on executable entry points, to for more seemless debugging when using AttachVS. +- **Values**: Set to "1" to disable breakpoints +- **Example**: `VSTEST_DEBUG_NOBP=1` + +## Crash Dump and Blame Collection Variables + +### VSTEST_DUMP_PATH +- **Description**: Overrides the default directory where crash dumps are stored. This disables automatic dump upload via attachments. +- **Example**: `VSTEST_DUMP_PATH=C:\CrashDumps` + +### VSTEST_DUMP_TEMP_PATH +- **Description**: Specifies the temporary directory for dump collection operations. +- **Fallback**: Falls back to AGENT_TEMPDIRECTORY, then system temp directory +- **Example**: `VSTEST_DUMP_TEMP_PATH=C:\temp\dumps` + +### VSTEST_DUMP_FORCEPROCDUMP +- **Description**: Forces the use of ProcDump for crash dump collection. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_DUMP_FORCEPROCDUMP=1` + +### VSTEST_DUMP_FORCENETDUMP (Removed in 18.5, selection of dumper is automatic.) +- **Description**: Forces the use of dotnet-dump for crash dump collection. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_DUMP_FORCENETDUMP=1` + +### VSTEST_DUMP_PROCDUMPARGUMENTS +- **Description**: Specifies custom arguments for ProcDump when collecting crash dumps. +- **Example**: `VSTEST_DUMP_PROCDUMPARGUMENTS=-e 1 -g -t -ma` + +### VSTEST_DUMP_PROCDUMPADDITIONALARGUMENTS +- **Description**: Specifies additional arguments to append to ProcDump command line. +- **Example**: `VSTEST_DUMP_PROCDUMPADDITIONALARGUMENTS=-r` + +## Feature Control Variables (Disable Features) + +### VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING +- **Description**: Disables artifact post-processing functionality. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING=1` +- **Added**: Version 17.2-preview, 7.0-preview + +### VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX +- **Description**: Disables new SDK UX for artifact post-processing, showing old output format. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX=1` +- **Usage**: Useful when parsing console output and need to maintain compatibility +- **Added**: Version 17.2-preview, 7.0-preview + +### VSTEST_DISABLE_FASTER_JSON_SERIALIZATION +- **Description**: Disables the faster JSON serialization mechanism and falls back to standard serialization. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_FASTER_JSON_SERIALIZATION=1` + +### VSTEST_DISABLE_MULTI_TFM_RUN +- **Description**: Forces vstest.console to run all sources using the same target framework (TFM) and architecture instead of allowing multiple different TFMs and architectures to run simultaneously. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_MULTI_TFM_RUN=1` + +### VSTEST_DISABLE_SERIALTESTRUN_DECORATOR +- **Description**: Disables the SerialTestRun decorator. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_SERIALTESTRUN_DECORATOR=1` + +### VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST +- **Description**: Disables sharing of .NET Framework testhosts, returning to the behavior of sharing testhosts when they are running .NET Framework DLLs and are not disabling appdomains or running in parallel. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST=1` + +### VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING +- **Description**: Disables capturing standard output from testhost processes. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING=1` + +### VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING +- **Description**: Disables forwarding standard output from testhost processes. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING=1` + +### VSTEST_DISABLE_THREADPOOL_SIZE_INCREASE +- **Description**: Disables setting a higher value for ThreadPool.SetMinThreads. The higher value allows testhost to connect back faster by avoiding waits for ThreadPool to start more threads. +- **Values**: Set to any non-zero value to disable +- **Example**: `VSTEST_DISABLE_THREADPOOL_SIZE_INCREASE=1` + +### VSTEST_DISABLE_UTF8_CONSOLE_ENCODING +- **Description**: Disables setting UTF-8 encoding in console output. +- **Values**: Set to "1" to disable +- **Example**: `VSTEST_DISABLE_UTF8_CONSOLE_ENCODING=1` + +## Build and MSBuild Integration Variables + +### VSTEST_BUILD_DEBUG +- **Description**: Enables debug output for VSTest build tasks. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_BUILD_DEBUG=1` + +### VSTEST_BUILD_TRACE +- **Description**: Enables trace output for VSTest build tasks. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_BUILD_TRACE=1` + +### VSTEST_MSBUILD_NOLOGO +- **Description**: Suppresses the display of the copyright banner when running tests via MSBuild. +- **Values**: Set to "1" to suppress logo +- **Example**: `VSTEST_MSBUILD_NOLOGO=1` + +## Telemetry Variables + +### VSTEST_TELEMETRY_OPTEDIN +- **Description**: Controls whether telemetry data is collected and sent. +- **Values**: Set to "1" to opt in, any other value opts out +- **Example**: `VSTEST_TELEMETRY_OPTEDIN=1` + +### VSTEST_LOGTELEMETRY +- **Description**: Enables logging of telemetry data to files. +- **Values**: Set to any non-empty value to enable +- **Example**: `VSTEST_LOGTELEMETRY=1` + +### VSTEST_LOGTELEMETRY_PATH +- **Description**: Specifies the directory where telemetry log files should be written. +- **Example**: `VSTEST_LOGTELEMETRY_PATH=C:\TelemetryLogs` + +## Performance and Parallelization Variables + +### VSTEST_HOSTPRESTART_COUNT +- **Description**: Sets the number of testhosts to pre-start for improved performance in parallel test execution. +- **Format**: Integer value +- **Example**: `VSTEST_HOSTPRESTART_COUNT=4` + +## Configuration and Path Variables + +### VSTEST_CONSOLE_PATH +- **Description**: Specifies the path to the vstest.console executable. +- **Example**: `VSTEST_CONSOLE_PATH=C:\Tools\VSTest\vstest.console.exe` + +### VSTEST_IGNORE_DOTNET_ROOT +- **Description**: When set to a non-zero value, ignores the DOTNET_ROOT environment variable during testhost selection. +- **Values**: Set to any non-zero value to ignore DOTNET_ROOT +- **Default**: "0" (respects DOTNET_ROOT) +- **Example**: `VSTEST_IGNORE_DOTNET_ROOT=1` + +### VSTEST_SKIP_FAKES_CONFIGURATION +- **Description**: Skips Microsoft Fakes configuration during test execution. +- **Values**: Set to "1" to skip +- **Example**: `VSTEST_SKIP_FAKES_CONFIGURATION=1` + +## UWP (Universal Windows Platform) Variables + +### VSTEST_UWP_DEPLOY_LOCAL_PATH +- **Description**: Overrides the local path for UWP application deployment. +- **Example**: `VSTEST_UWP_DEPLOY_LOCAL_PATH=C:\UWPApps\LocalDeploy` + +### VSTEST_UWP_DEPLOY_REMOTE_PATH +- **Description**: Overrides the remote path for UWP application deployment. +- **Example**: `VSTEST_UWP_DEPLOY_REMOTE_PATH=\\RemoteDevice\Deploy` + +## Windows App Host Variables + +### VSTEST_WINAPPHOST_* +- **Description**: Various environment variables related to Windows App Host configuration. +- **Pattern**: Variables following the pattern `VSTEST_WINAPPHOST_{VARIABLE_NAME}` +- **Usage**: Used internally for Windows App Host test execution scenarios + +## Legacy/Experimental Variables + +### VSTEST_EXPERIMENTAL_FORWARD_OUTPUT_FEATURE +- **Description**: (Deprecated) Previously used to enable output forwarding feature. +- **Status**: Replaced by VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING and VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING +- **Note**: This variable is no longer used as the feature is now enabled by default + +### VSTEST_DISABLE_PROTOCOL_3_VERSION_DOWNGRADE +- **Description**: Disables automatic downgrade to protocol version 3 for compatibility. +- **Values**: Set to any non-empty value to disable downgrade +- **Example**: `VSTEST_DISABLE_PROTOCOL_3_VERSION_DOWNGRADE=1` + +## Usage Examples + +### Debugging a Test Run +```bash +# Enable testhost debugging and increase connection timeout +set VSTEST_HOST_DEBUG=1 +set VSTEST_CONNECTION_TIMEOUT=300 +dotnet test MyTests.dll +``` + +### Collecting Diagnostics +```bash +# Enable detailed diagnostic logging +set VSTEST_DIAG=C:\temp\vstest.log +set VSTEST_DIAG_VERBOSITY=Verbose +dotnet test MyTests.dll +``` + +### Performance Optimization +```bash +# Pre-start testhosts for better parallel performance +set VSTEST_HOSTPRESTART_COUNT=4 +dotnet test MyTests.dll --parallel +``` + +### Crash Dump Collection +```bash +# Configure crash dump collection +set VSTEST_DUMP_PATH=C:\CrashDumps +set VSTEST_DUMP_FORCEPROCDUMP=1 +dotnet test MyTests.dll --collect:"blame;collectdump=true" +``` + +## Notes + +- Most debug variables require only being set to any non-empty value to be enabled. +- Feature disable variables typically use "1" or any non-zero value to disable the feature. +- Connection timeout values are in seconds unless otherwise specified. +- Paths should use the appropriate path separator for your operating system. +- Some variables are only effective in specific scenarios (e.g., UWP variables only apply to UWP test projects). + +For more information about VSTest and its features, see the [VSTest documentation](https://github.com/Microsoft/vstest/tree/main/docs). \ No newline at end of file diff --git a/docs/extensions/video-and-voice-recorder.md b/docs/extensions/video-and-voice-recorder.md new file mode 100644 index 0000000000..6d8d48b14c --- /dev/null +++ b/docs/extensions/video-and-voice-recorder.md @@ -0,0 +1,41 @@ +Video and Voice recorder data collector is a VSTest data collector that ships in `Microsoft.TestPlatform` nuget package and `Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI` (the VSIX we insert into VisualStudio). + +It serves to record video and sound of each test, and optionally publish the result only for failing tests. + +Example usage is: + +```bash +vstest.console.exe --collect:"Screen and Voice Recorder" bin\Debug\net10.0\mstest320.dll +``` + +This will record video to the TestResults folder (under some guid). + +Additional options can be provided via runsettings. + +```xml + + + + + + + + + +``` + + +Official examples are here, including the runsettings shown above: +https://learn.microsoft.com/en-us/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file?view=visualstudio#videorecorder-data-collector + +https://learn.microsoft.com/en-us/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file?view=visualstudio#example-runsettings-file + +Files in the shipment (and similar layout in the VSIX): + +tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model.dll +tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector.dll +tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\VideoRecorder\Microsoft.VisualStudio.QualityTools.VideoRecorderEngine.dll +tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\VideoRecorder\Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model.dll +tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\VideoRecorder\VSTestVideoRecorder.exe + +Previously there was also recorder for `V1`. That was removed with TPv0 removal from VS2026 in https://github.com/microsoft/vstest/pull/15247, where VSTest Video recorder was also removed by mistake. diff --git a/docs/filter.md b/docs/filter.md index ecd6bf9351..9cd974f300 100644 --- a/docs/filter.md +++ b/docs/filter.md @@ -20,6 +20,7 @@ supported by popular unit test frameworks. | -------------- | -------------------- | | MSTest |
  • FullyQualifiedName
  • Name
  • ClassName
  • Priority
  • TestCategory
| | Xunit |
  • FullyQualifiedName
  • DisplayName
  • Traits
| +| NUnit |
  • FullyQualifiedName
  • Name
  • Priority
  • TestCategory
  • Category
  • Property
| Allowed **operators**: @@ -145,3 +146,49 @@ In above code we defined traits with keys `Category` and `Priority` which can be | `dotnet test --filter "FullyQualifiedName~TestClass1\|Category=Nightly"` | Runs tests which have `TestClass1` in FullyQualifiedName **or** Category is Nightly. | | `dotnet test --filter "FullyQualifiedName~TestClass1&Category=Nightly"` | Runs tests which have `TestClass1` in FullyQualifiedName **and** Category is Nightly. | | `dotnet test --filter "(FullyQualifiedName~TestClass1&Category=Nightly)\|Priority=1"` | Runs tests which have either FullyQualifiedName contains `TestClass1` and Category is CategoryA or Priority is 1. | + +### NUnit + +```csharp +namespace NUnitTestNamespace; + +public class TestClass +{ + [Property("Priority","1")] + [Test] + public void Test1() + { + Assert.Pass(); + } + + [Property("Whatever", "SomeValue")] + [Test] + public void Test2() + { + Assert.Pass(); + } + + [Category("SomeCategory")] + [Test] + public void Test3() + { + Assert.Pass(); + } +} +``` + +#### Usage of the filters + +| Expression | What it does? | +| ---------- | ------------- | +| `dotnet test --filter FullyQualifiedName=NUnitTestNamespace.TestClass.Test1` | Runs only the given test | +| `dotnet test --filter Name=Test1` | Runs all tests whose test name (method) equals `Test1`. | +| `dotnet test --filter Name=TestClass` | Runs tests within all classes named `TestClass`. | +| `dotnet test --filter Name=NUnitTestNamespace` | Runs all tests within the namespace `NUnitTestNamespace`. | +| `dotnet test --filter Priority=1` | Runs tests with property named Priority and value = 1`. | +| `dotnet test --filter Whatever=TestClass` | Runs tests with property named `Whatever` and value = `SomeValue`. | +| `dotnet test --filter Category=SomeCategory` | Runs tests with category set to `SomeCategory`. Note: You can also use TestCategory in the filter. | + +Logical operators works the same as for the other frameworks. + + diff --git a/docs/preview.md b/docs/preview.md new file mode 100644 index 0000000000..02dc7fce32 --- /dev/null +++ b/docs/preview.md @@ -0,0 +1,52 @@ +# Early access to VSTest packages + +Stable versions (and selected previews) of VSTest, and related packages, are distributed through . + +We also publish every successful merge to main and release branches to our preview NuGet channel called `test-tools`. + +To use this channel, you will need to add or edit your [NuGet.Config](https://learn.microsoft.com/nuget/reference/nuget-config-file) file with the following content: + +```xml + + + + + + + +``` + +You can also browse interactively the available versions using `https://dev.azure.com/dnceng/public/_artifacts/feed/test-tools/NuGet//versions`, where `` is the name of the package you are looking for. For example, for Microsoft.TestPlatform, the link is . + +## Warranty + +Packages from `test-tools` feed are considered experimental. They might not have the usual quality, may contain experimental and breaking changes, and come without warranty. + +## Feed information + +### NuGet.config placement + +NuGet.Config file can be placed next to solution file, or next to project file when you don't have solution file. But in cases where you have solution file, you should always place it next to solution file, to ensure consistent behavior in Visual Studio and in command line. + +### Dependency confusion attack + +Adding additional NuGet feeds might lead to warnings or errors from build systems that check compliance. This is because using multiple public and private sources might lead to possible dependency confusion attacks. All the packages we publish to nuget.org are using a reserved prefix. But this might not mitigate the risk in your setup. If this is a concern to you, please discuss with your internal security department. + +### Usage with central package management + +Solutions that use central package management through `Directory.Packages.props` will see `NU1507` warnings about multiple package sources. To solve this add this section to your `NuGet.Config` file: + +```xml + + + + + + + + + + +``` + +Full documentation of package source mapping can be [found here](https://learn.microsoft.com/nuget/consume-packages/package-source-mapping#enable-by-manually-editing-nugetconfig). diff --git a/docs/report.md b/docs/report.md index 1829d7fdeb..0dee97d6bf 100644 --- a/docs/report.md +++ b/docs/report.md @@ -19,7 +19,7 @@ if you're interested in the architecture of a test logger. ### Available test loggers -| Scenario | Nuget Package | Source Repository | +| Scenario | NuGet Package | Source Repository | | -------- | ------------- | ----------------- | | Local, CI, CD | Inbuilt | [Trx Logger][] | | Local, CI, CD | Inbuilt | [Console Logger][] | @@ -30,7 +30,7 @@ if you're interested in the architecture of a test logger. | AppVeyor | [AppVeyor.TestLogger][appveyor.nuget] | [AppVeyor Logger][] | | Azure Pipelines | [AzurePipelines.TestLogger][azurepipelines.nuget] | [Azure Pipelines Logger][] | | GitHub Actions | [GitHubActionsTestLogger][githubactions.nuget] | [GitHub Actions Test Logger][] | -| TeamCity | [TeamCity.VSTest.TestAdapter][teamcity.nuget] | [Teamcity Logger][] | +| TeamCity | [TeamCity.VSTest.TestAdapter][teamcity.nuget] | [TeamCity Logger][] | [Trx Logger]: https://github.com/Microsoft/vstest/tree/main/src/Microsoft.TestPlatform.Extensions.TrxLogger [Html Logger]: https://github.com/Microsoft/vstest/tree/main/src/Microsoft.TestPlatform.Extensions.HtmlLogger @@ -71,25 +71,27 @@ to one of the following locations: dotnet-cli, the path could be `/sdk//Extensions` directory. 2. any well known location on the filesystem -> Version Note: new in 15.1 -In case of #2, user can specify the full path to the location using `/TestAdapterPath:` -command line switch. Test platform will locate extensions from the provided -directory. +> [!NOTE] +> **New in 15.1** +> +> In case of #2, user can specify the full path to the location using `/TestAdapterPath:` +> command line switch. Test platform will locate extensions from the provided +> directory. ## Naming Test platform will look for assemblies named `*.testlogger.dll` when it's trying to load test loggers. -> Version Note: < 15.1 -> For 15.0 version, the test loggers are also discovered from *.testadapter.dll +> [!NOTE] +> For the 15.0 version, the test loggers are also discovered from `*.testadapter.dll` ## Create a test logger Go through the following steps to create your own logger 1) Add a nuget reference of package `Microsoft.TestPlatform.ObjectModel`. -2) Implement ITestLoggerWithParameters (or ITestLogger, if your logger is not expecting any parameter). [Logger Example](https://github.com/spekt/xunit.testlogger/blob/master/src/Xunit.Xml.TestLogger/XunitXmlTestLogger.cs#L19) +2) Implement `ITestLoggerWithParameters` (or `ITestLogger`, if your logger is not expecting any parameters). [Logger Example](https://github.com/spekt/xunit.testlogger/blob/49d2416f24acb30225adc6e65753cc829010bec9/src/Xunit.Xml.TestLogger/XunitXmlTestLogger.cs#L19) 3) Name your logger assembly `*.testlogger.dll`. [Detailed](./report.md#naming) ## Enable a test logger @@ -100,7 +102,7 @@ A test logger must be explicitly enabled using the command line. E.g. vstest.console test_project.dll /logger:mylogger ``` -Where `mylogger` is the LoggerUri or FriendlyName of the logger. +Where `mylogger` is the `LoggerUri` or `FriendlyName` of the logger. ## Configure reporting @@ -110,40 +112,48 @@ Additional arguments to a logger can also be passed in the command line. E.g. vstest.console test_project.dll /logger:mylogger;Setting=Value ``` -Where `mylogger` is the LoggerUri or FriendlyName of the logger. -`Setting` is the name of the additional argument and `Value`is its value. +Where `mylogger` is the `LoggerUri` or `FriendlyName` of the logger. +`Setting` is the name of the additional argument and `Value` is its value. -It is upto the logger implementation to support additional arguments. +It is up to the logger implementation to support additional arguments. ## Syntax of default loggers ### 1) Console logger -Console logger is the default logger and it is used to output the test results into console window. +Console logger is the default logger and it is used to output the test results to a terminal. #### Syntax +For dotnet test or dotnet vstest: + ```shell -For dotnet test or dotnet vstest : --logger:console[;verbosity=] +``` -For vstest.console.exe : +For vstest.console.exe: + +```shell /logger:console[;verbosity=] - -Argument "verbosity" define the verbosity level of console logger. Allowed values for verbosity are "quiet", "minimal", "normal" and "detailed". ``` + +Argument `verbosity` defines the verbosity level of the console logger. Allowed values for verbosity are `quiet`, `minimal`, `normal` and `detailed`. #### Example ```shell vstest.console.exe Tests.dll /logger:"console;verbosity=normal" +``` -If you are using "dotnet test", then use the following command +If you are using `dotnet test`, then use the following command: +```shell dotnet test Tests.csproj --logger:"console;verbosity=normal" +``` -or you can also use argument "-v | --verbosity" of "dotnet test" +or you can also use argument `-v | --verbosity` of `dotnet test`: +```shell dotnet test Tests.csproj -v normal ``` @@ -155,28 +165,35 @@ Trx logger is used to log test results into a Visual Studio Test Results File (T ```shell /logger:trx [;LogFileName=] - -Where "LogFileName" can be absolute or relative path. If path is relative, it will be relative to "TestResults" directory, created under current working directory. ``` +Where `LogFileName` can be absolute or relative path. If the path is relative, it will be relative to the `TestResults` directory, created under current working directory. + + #### Examples -Suppose the current working directory is "c:\tempDirecory". +Suppose the current working directory is `c:\tempDirectory`. ```shell -1) vstest.console.exe Tests.dll /logger:trx -trx file will get generated in location "c:\tempDirecory\TestResults" +vstest.console.exe Tests.dll /logger:trx +``` + +trx file will get generated in location `c:\tempDirectory\TestResults`. + +```shell +vstest.console.exe Tests.dll /logger:"trx;LogFileName=relativeDir\logFile.txt" -2) vstest.console.exe Tests.dll /logger:"trx;LogFileName=relativeDir\logFile.txt" -trx file will be "c:\tempDirecory\TestResults\relativeDir\logFile.txt" +trx file will be `c:\tempDirectory\TestResults\relativeDir\logFile.txt`. -3) vstest.console.exe Tests.dll /logger:"trx;LogFileName=c:\temp\logFile.txt" -trx file will be "c:\temp\logFile.txt" +```shell +vstest.console.exe Tests.dll /logger:"trx;LogFileName=c:\temp\logFile.txt" ``` +trx file will be `c:\temp\logFile.txt`. + ### 3) Html logger -Html logger is used to log test results into a html file. +Html logger is used to log test results into a HTML file. #### Syntax @@ -189,19 +206,26 @@ Where "LogFileName" can be absolute or relative path. If path is relative, it wi #### Examples -Suppose the current working directory is "c:\tempDirecory". +Suppose the current working directory is `c:\tempDirectory`. ```shell -1) vstest.console.exe Tests.dll /logger:html -Html file will get generated in location "c:\tempDirecory\TestResults" +vstest.console.exe Tests.dll /logger:html +``` -2) vstest.console.exe Tests.dll /logger:"html;LogFileName=relativeDir\logFile.html" -Html file will be "c:\tempDirecory\TestResults\relativeDir\logFile.html" +HTML file will get generated in location `c:\tempDirectory\TestResults`. -3) vstest.console.exe Tests.dll /logger:"html;LogFileName=c:\temp\logFile.html" -Html file will be "c:\temp\logFile.html" +```shell +vstest.console.exe Tests.dll /logger:"html;LogFileName=relativeDir\logFile.html" +``` + +HTML file will be `c:\tempDirectory\TestResults\relativeDir\logFile.html`. + +```shell +vstest.console.exe Tests.dll /logger:"html;LogFileName=c:\temp\logFile.html" ``` +HTML file will be `c:\temp\logFile.html`. + ## Related links TODO: link to author a test logger diff --git a/docs/toc.yml b/docs/toc.yml index e7f77697e7..351e968fd3 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -4,6 +4,8 @@ #href: quickstart.md #- name: Concepts #href: concepts.md +- name: Environment Variables + href: environment-variables.md - name: Contribute href: contribute.md - name: Release Notes diff --git a/eng/AfterSolutionBuild.targets b/eng/AfterSolutionBuild.targets index c10d1b34c7..c7ca696b2a 100644 --- a/eng/AfterSolutionBuild.targets +++ b/eng/AfterSolutionBuild.targets @@ -12,8 +12,7 @@ This is tricky. When you run tests in part of Build, this target won't run after you've run your tests, if you run as separate step you have no way of saying that you will run tests later. If you'd move this into Directory.Build.targets you would run it for every single integration tests project. So running it after - pack is probably the best. - Don't invoke when running inside the VMR until https://github.com/dotnet/arcade/issues/14283 is resolved. + pack is probably the best. Don't invoke in .NET product build mode. --> diff --git a/eng/DotNetBuild.props b/eng/DotNetBuild.props deleted file mode 100644 index ca7b2fbbeb..0000000000 --- a/eng/DotNetBuild.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - vstest - true - - - diff --git a/eng/Publishing.props b/eng/Publishing.props index 10bc8c8684..5f9650d32d 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -1,6 +1,9 @@ - - - 3 - + + + + + + diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml deleted file mode 100644 index 60430ce23a..0000000000 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eng/Version.Details.props b/eng/Version.Details.props new file mode 100644 index 0000000000..3744f5500d --- /dev/null +++ b/eng/Version.Details.props @@ -0,0 +1,38 @@ + + + + + 10.0.0-beta.26257.4 + + 2.0.0 + + 0.2.0-preview.26179.102 + + 6.0.2 + + 1.1.0-beta2-19575-01 + 1.1.0-beta2-19575-01 + + 18.6.2 + + + + + $(MicrosoftDotNetArcadeSdkPackageVersion) + + $(MicrosoftExtensionsFileSystemGlobbingPackageVersion) + + $(MicrosoftDiagnosticsNETCoreClientPackageVersion) + + $(MicrosoftExtensionsDependencyModelPackageVersion) + + $(MicrosoftDiaSymReaderConverterPackageVersion) + $(MicrosoftDiaSymReaderPdb2PdbPackageVersion) + + $(MicrosoftInternalCodeCoveragePackageVersion) + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 669af05aef..3cdd2a6e0c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,39 +1,17 @@ + - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage - 3889f929bc86d4f819c62bfa687b01660c9e195d + 413a357aba09b9199f81be9336422ea30bd8082c - - https://github.com/dotnet/diagnostics - 8c505ca6921b5f7e9b8acc234cc8f15035537ee4 - - - - https://github.com/dotnet/diagnostics - 8c505ca6921b5f7e9b8acc234cc8f15035537ee4 - - - - - https://github.com/dotnet/source-build-externals - 4df883d781a4290873b3b968afc0ff0df7132507 - - - - - https://github.com/dotnet/source-build-reference-packages - 1ebd9ce245112164207d961c0d2faea741c7c489 - - - - - https://github.com/dotnet/corefx - 30ab651fcb4354552bd4891619a0bdd81e0ebdbf + + https://github.com/dotnet/dotnet + d70206844a95b337601237466bfc6cbb7d52d6d4 - + https://github.com/dotnet/runtime 7d57652f33493fa022125b7f63aad0d70c52d810 @@ -44,15 +22,9 @@ - + https://github.com/dotnet/arcade - 1c7e09a8d9c9c9b15ba574cd6a496553505559de - - - - https://github.com/dotnet/arcade - 1c7e09a8d9c9c9b15ba574cd6a496553505559de - + 3454b2fe822e52373f2604856417b0e6bce71d70 https://github.com/dotnet/symreader-converter @@ -62,9 +34,5 @@ https://github.com/dotnet/symreader-converter c5ba7c88f92e2dde156c324a8c8edc04d9fa4fe0 - - https://github.com/dotnet/arcade - 1c7e09a8d9c9c9b15ba574cd6a496553505559de - diff --git a/eng/Versions.props b/eng/Versions.props index 208b92ffb4..f3ef3e1262 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,7 +1,8 @@ - + + + - false - 17.13.0 - preview + 18.6.0 + release + + + + true true + 1.2.0 - 17.8.3 + + 17.11.48 $(MicrosoftBuildFrameworkPackageVersion) $(MicrosoftBuildFrameworkPackageVersion) $(MicrosoftBuildFrameworkPackageVersion) $(MicrosoftBuildFrameworkPackageVersion) $(MicrosoftBuildFrameworkPackageVersion) - 3.11.0 - 3.11.0-beta1.23525.2 - 3.11.0-beta1.23525.2 + 4.11.0 + 3.3.4 + 3.3.4 17.7.0 - 0.2.0-preview.24566.1 - 3.1.0 - 2.0.0 - 17.9.0-beta.24058.4 - 17.14.0-preview.25056.1 - 17.10.34924.118 + 18.0.0-beta.25430.1 + + 18.3.11401.5 $(MicrosoftVisualStudioDiagnosticsUtilitiesVersion) - 17.10.525-preview.1 - 17.6.46 - 16.3.42 - 17.4.2124 - 13.0.1 - 13.0.3 - 1.5.0 - 4.5.0 + 17.13.39960 + 17.13.24 + 16.3.90 + + <_MicrosoftVSSDKBuildToolsVersion_>17.14.2119 + 5.0.0 + 13.0.3 + 10.0.0 4.5.5 - 4.3.4 - 1.6.0 - 4.3.2 - 17.10.0-preview-2-34602-162 - 17.10.0-preview-2-34602-162 - 17.9.34504.149 + 8.0.0 + 18.0.0-preview-1-10911-061 + 18.3.11611.365 + 18.0.11024.295 + 8.0.1 + 5.0.0 + 6.1.0 + - 6.11.0 + 8.1.0 + 2.1.0 4.16.1 - 17.9.0 + 18.3.0 - 3.4.3 - 3.4.3 - 1.0.3-preview - + in TestAssets.slnx to allow running and debugging tests in that solution directly in VS without having to run them via AcceptanceTests. --> + 4.1.0 + 4.1.0 2.4.2 2.4.5 2.4.2 - 3.14.0 - 3.17.0 - 3.16.3 + 4.5.1 + 6.1.0 + 3.22.0 - [3.4.3] - [3.4.3] - [3.3.1] + [4.1.0] + [4.1.0] + [4.0.2] [2.2.10] - [2.2.10] + [3.11.1] [1.4.0] - [17.10.0] - [17.10.0] - [17.9.0] - [17.6.2] - [16.11.0] + [18.3.0] + [18.3.0] + [18.0.1] + [17.12.0] + [17.14.1] [15.9.2] 5.0.0 + diff --git a/eng/build.ps1 b/eng/build.ps1 new file mode 100644 index 0000000000..960c29708d --- /dev/null +++ b/eng/build.ps1 @@ -0,0 +1,148 @@ +[CmdletBinding(PositionalBinding = $false)] +Param( + [string][Alias('c')]$configuration = "Debug", + [string]$platform = $null, + [string] $projects, + [string][Alias('v')]$verbosity = "minimal", + [string] $msbuildEngine = $null, + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch] $buildCheck = $false, + [switch][Alias('r')]$restore, + [switch] $deployDeps, + [switch][Alias('b')]$build, + [switch] $rebuild, + [switch] $deploy, + [switch][Alias('t')]$test, + [string] $filter, + [switch] $smokeTest, + [switch] $integrationTest, + [switch] $performanceTest, + [switch] $compatibilityTest, + # Skip the build when running multiple categories from the integration tests. This is useful mostly in CI where we want to split the runs to different jobs, but + # they all fall back to the same project and initialization. + [switch] $skipIntegrationTestBuild, + [switch] $compatibilityTestBuild, + [switch] $sign, + [switch] $pack, + [switch] $publish, + [switch] $clean, + [switch][Alias('pb')]$productBuild, + [switch]$fromVMR, + [switch][Alias('bl')]$binaryLog, + [switch][Alias('nobl')]$excludeCIBinarylog, + [switch] $ci, + [switch] $prepareMachine, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $excludePrereleaseVS, + [switch] $nativeToolsOnMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments = $true)][String[]]$properties +) + +# Add steps that need to happen before build here + +if ($properties -like "*TestRunnerAdditionalArguments*--filter*") { + throw "Use --filter instead of passing filter as an additional argument to TestRunnerAdditionalArguments." +} + +if ($smokeTest -and $integrationTest) { + throw "Cannot specify both smoke and integration tests. Smoke tests are a subset of integration tests, so specifying both is redundant and will run all integration tests." +} + +$filters = @() +# This translates to properties on test context, the only way MSTest allows us to pass info dynamically to AssemblyInitialize +$testParameters = @{} +if ($skipIntegrationTestBuild) { + $testParameters['SkipIntegrationTestBuild'] = $true +} + +if ($filter) { + $filters += $filter +} + +if ([System.Environment]::OSVersion.Platform -notlike "Win*") { + $filters += "TestCategory!=Windows&TestCategory!=Windows-Review" +} + +if ($smokeTest) { + $filters += "TestCategory=Smoke" +} +else { + # Don't exclude smoke tests if not specified explicitly, those are integration tests that should + # run by default when running integration tests. We want to make sure we can run just Smoke tests, + # but should not skip them when not specified. + # $filters += "TestCategory!=Smoke" +} + +if ($compatibilityTestBuild) { + $testParameters['CompatibilityTestBuild'] = $true +} + +if ($compatibilityTest) { + $testParameters['BuildCompatibility'] = $true + if (-not $integrationTest) { + # We specified just compatibility so run just compatibility tests. If there are both + # we need to run all, but we don't have a filter for uncategorized tests, https://github.com/microsoft/testfx/issues/5136 + # so we simply don't provide an include filter. + $filters += "TestCategory=Compatibility" + } +} +else { + $filters += "TestCategory!=Compatibility" +} + +if ($performanceTest) { + # We don't have any perf tests in the library.integrationtest.csproj, so providing this alone will fail + # but we will use it together with compatibility tests in a nightly run, so good enough for now. + if (-not $integrationTest) { + # We specified just perf tests so run just perf tests. If there are both + # we need to run all, but we don't have a filter for uncategorized tests, https://github.com/microsoft/testfx/issues/5136 + # so we simply don't provide an include filter. + $filters += "TestCategory=TelemetryPerf" + } + +} +else { + $filters += "TestCategory!=TelemetryPerf" +} + +$null = $PSBoundParameters.Remove("filter") +$null = $PSBoundParameters.Remove("smokeTest") +$null = $PSBoundParameters.Remove("compatibilityTest") +$null = $PSBoundParameters.Remove("performanceTest") +$null = $PSBoundParameters.Remove("skipIntegrationTestBuild") +$null = $PSBoundParameters.Remove("compatibilityTestBuild") + +if ($integrationTest -or $performanceTest -or $compatibilityTest -or $smokeTest) { + # Rest of the infra knows nothing about or additional categories for tests. They simply consider them + # integration tests, so mark that. + $PSBoundParameters['integrationTest'] = $true + # This is also non-default, normally we would run also unit tests, but if we filter anything that matches 0 tests in a project + # the project will fail. + $PSBoundParameters['test'] = $false +} + +if ($filters.Count -gt 0 -or $testParameters.Count -gt 0) { + if ($filters.Count -gt 0) { + + # We have to double escape by '\"', otherwise the filter is passed as string with & in it and interpreted directly as a separate command to run. + # Ignoring exit code 8 which means no tests found, otherwise we will fail the build when we run with a filter that doesn't match any test in a project, which is common when we have multiple projects and some of them don't have certain categories of tests. https://github.com/microsoft/testfx/issues/7457 + $filterString = "--filter \`"$($filters -join '&')\`"" + $filterParameters = "$filterString --ignore-exit-code 8" + } + + $testParameterString = ($testParameters.GetEnumerator() | ForEach-Object { "--test-parameter $($_.Key)=$($_.Value)" }) -join ' ' + + if (-not $PSBoundParameters.ContainsKey('properties')) { + $PSBoundParameters['properties'] = @() + } + $PSBoundParameters['properties'] += "/p:TestRunnerExternalArguments=$filterParameters $testParameterString" +} + +# Call the build script provided by Arcade +& $PSScriptRoot/common/build.ps1 @PSBoundParameters + +# Forward exit code of the parent script +exit $LastExitCode diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd index 56c2f25ac2..ac1f72bf94 100644 --- a/eng/common/CIBuild.cmd +++ b/eng/common/CIBuild.cmd @@ -1,2 +1,2 @@ @echo off -powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 5db4ad71ee..65ed3a8ade 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -1,17 +1,18 @@ # 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. # # See example call for this script below. # # - task: PowerShell@2 -# displayName: Setup Private Feeds Credentials +# displayName: Setup internal Feeds Credentials # condition: eq(variables['Agent.OS'], 'Windows_NT') # inputs: -# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 -# arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token +# filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 +# arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token # env: # Token: $(dn-bot-dnceng-artifact-feeds-rw) # @@ -34,19 +35,28 @@ Set-StrictMode -Version 2.0 . $PSScriptRoot\tools.ps1 +# Adds or enables the package source with the given name +function AddOrEnablePackageSource($sources, $disabledPackageSources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { + if ($disabledPackageSources -eq $null -or -not (EnableInternalPackageSource -DisabledPackageSources $disabledPackageSources -Creds $creds -PackageSourceName $SourceName)) { + AddPackageSource -Sources $sources -SourceName $SourceName -SourceEndPoint $SourceEndPoint -Creds $creds -Username $userName -pwd $Password + } +} + # Add source entry to PackageSources function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") if ($packageSource -eq $null) { + Write-Host "Adding package source $SourceName" + $packageSource = $doc.CreateElement("add") $packageSource.SetAttribute("key", $SourceName) $packageSource.SetAttribute("value", $SourceEndPoint) $sources.AppendChild($packageSource) | Out-Null } else { - Write-Host "Package source $SourceName already present." + Write-Host "Package source $SourceName already present and enabled." } AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd @@ -59,6 +69,8 @@ function AddCredential($creds, $source, $username, $pwd) { return; } + Write-Host "Inserting credential for feed: " $source + # Looks for credential configuration for the given SourceName. Create it if none is found. $sourceElement = $creds.SelectSingleNode($Source) if ($sourceElement -eq $null) @@ -91,24 +103,27 @@ function AddCredential($creds, $source, $username, $pwd) { $passwordElement.SetAttribute("value", $pwd) } -function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $pwd) { - $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") - - Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." - - ForEach ($PackageSource in $maestroPrivateSources) { - Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key - AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -pwd $pwd +# Enable all darc-int package sources. +function EnableMaestroInternalPackageSources($DisabledPackageSources, $Creds) { + $maestroInternalSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") + ForEach ($DisabledPackageSource in $maestroInternalSources) { + EnableInternalPackageSource -DisabledPackageSources $DisabledPackageSources -Creds $Creds -PackageSourceName $DisabledPackageSource.key } } -function EnablePrivatePackageSources($DisabledPackageSources) { - $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") - ForEach ($DisabledPackageSource in $maestroPrivateSources) { - Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled by deleting it from disabledPackageSource" +# Enables an internal package source by name, if found. Returns true if the package source was found and enabled, false otherwise. +function EnableInternalPackageSource($DisabledPackageSources, $Creds, $PackageSourceName) { + $DisabledPackageSource = $DisabledPackageSources.SelectSingleNode("add[@key='$PackageSourceName']") + if ($DisabledPackageSource) { + Write-Host "Enabling internal source '$($DisabledPackageSource.key)'." + # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries $DisabledPackageSources.RemoveChild($DisabledPackageSource) + + AddCredential -Creds $creds -Source $DisabledPackageSource.Key -Username $userName -pwd $Password + return $true } + return $false } if (!(Test-Path $ConfigFile -PathType Leaf)) { @@ -121,15 +136,17 @@ $doc = New-Object System.Xml.XmlDocument $filename = (Get-Item $ConfigFile).FullName $doc.Load($filename) -# Get reference to or create one if none exist already +# Get reference to - fail if none exist $sources = $doc.DocumentElement.SelectSingleNode("packageSources") if ($sources -eq $null) { - $sources = $doc.CreateElement("packageSources") - $doc.DocumentElement.AppendChild($sources) | Out-Null + Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. NuGet config file must contain a packageSources section: $ConfigFile" + ExitWithExitCode 1 } $creds = $null +$feedSuffix = "v3/index.json" if ($Password) { + $feedSuffix = "v2" # Looks for a node. Create it if none is found. $creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") if ($creds -eq $null) { @@ -138,34 +155,35 @@ if ($Password) { } } +$userName = "dn-bot" + # Check for disabledPackageSources; we'll enable any darc-int ones we find there $disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") if ($disabledSources -ne $null) { Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" - EnablePrivatePackageSources -DisabledPackageSources $disabledSources + EnableMaestroInternalPackageSources -DisabledPackageSources $disabledSources -Creds $creds } - -$userName = "dn-bot" - -# Insert credential nodes for Maestro's private feeds -InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -pwd $Password - -# 3.1 uses a different feed url format so it's handled differently here -$dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") -if ($dotnet31Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password -} - -$dotnetVersions = @('5','6','7','8','9') +$dotnetVersions = @('5','6','7','8','9','10') foreach ($dotnetVersion in $dotnetVersions) { $feedPrefix = "dotnet" + $dotnetVersion; $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") if ($dotnetSource -ne $null) { - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password + AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password } } +# 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 4604b61b03..b2163abbe7 100644 --- 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. # @@ -11,8 +12,8 @@ # - task: Bash@3 # displayName: Setup Internal Feeds # inputs: -# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh -# arguments: $(Build.SourcesDirectory)/NuGet.config +# filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh +# arguments: $(System.DefaultWorkingDirectory)/NuGet.config # condition: ne(variables['Agent.OS'], 'Windows_NT') # - task: NuGetAuthenticate@1 # @@ -52,81 +53,139 @@ if [[ `uname -s` == "Darwin" ]]; then TB='' fi -# Ensure there is a ... section. -grep -i "" $ConfigFile -if [ "$?" != "0" ]; then - echo "Adding ... section." - ConfigNodeHeader="" - PackageSourcesTemplate="${TB}${NL}${TB}" +# Enables an internal package source by name, if found. Returns 0 if found and enabled, 1 if not found. +EnableInternalPackageSource() { + local PackageSourceName="$1" + + # Check if disabledPackageSources section exists + grep -i "" "$ConfigFile" > /dev/null + if [ "$?" != "0" ]; then + return 1 # No disabled sources section + fi + + # Check if this source name is disabled + grep -i " /dev/null + if [ "$?" == "0" ]; then + echo "Enabling internal source '$PackageSourceName'." + # Remove the disabled entry (including any surrounding comments or whitespace on the same line) + sed -i.bak "//d" "$ConfigFile" + + # Add the source name to PackageSources for credential handling + PackageSources+=("$PackageSourceName") + return 0 # Found and enabled + fi + + return 1 # Not found in disabled sources +} + +# Add source entry to PackageSources +AddPackageSource() { + local SourceName="$1" + local SourceEndPoint="$2" + + # Check if source already exists + grep -i " /dev/null + if [ "$?" == "0" ]; then + echo "Package source $SourceName already present and enabled." + PackageSources+=("$SourceName") + return + fi + + echo "Adding package source $SourceName" + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" "$ConfigFile" + PackageSources+=("$SourceName") +} + +# Adds or enables the package source with the given name +AddOrEnablePackageSource() { + local SourceName="$1" + local SourceEndPoint="$2" + + # Try to enable if disabled, if not found then add new source + EnableInternalPackageSource "$SourceName" + if [ "$?" != "0" ]; then + AddPackageSource "$SourceName" "$SourceEndPoint" + fi +} - sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile -fi +# Enable all darc-int package sources +EnableMaestroInternalPackageSources() { + # Check if disabledPackageSources section exists + grep -i "" "$ConfigFile" > /dev/null + if [ "$?" != "0" ]; then + return # No disabled sources section + fi + + # Find all darc-int disabled sources + local DisabledDarcIntSources=() + DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' "$ConfigFile" | tr -d '"') + + for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do + if [[ $DisabledSourceName == darc-int* ]]; then + EnableInternalPackageSource "$DisabledSourceName" + fi + done +} -# Ensure there is a ... section. -grep -i "" $ConfigFile +# Ensure there is a ... section. +grep -i "" $ConfigFile if [ "$?" != "0" ]; then - echo "Adding ... section." - - PackageSourcesNodeFooter="" - PackageSourceCredentialsTemplate="${TB}${NL}${TB}" - - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile + Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. NuGet config file must contain a packageSources section: $ConfigFile" + ExitWithExitCode 1 fi PackageSources=() -# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present -grep -i "... section. + grep -i "" $ConfigFile if [ "$?" != "0" ]; then - echo "Adding dotnet3.1-internal to the packageSources." - PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" + echo "Adding ... section." - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile - fi - PackageSources+=('dotnet3.1-internal') - - grep -i "" $ConfigFile - if [ "$?" != "0" ]; then - echo "Adding dotnet3.1-internal-transport to the packageSources." PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" + PackageSourceCredentialsTemplate="${TB}${NL}${TB}" - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile fi - PackageSources+=('dotnet3.1-internal-transport') fi -DotNetVersions=('5' '6' '7' '8' '9') +# Check for disabledPackageSources; we'll enable any darc-int ones we find there +grep -i "" $ConfigFile > /dev/null +if [ "$?" == "0" ]; then + echo "Checking for any darc-int disabled package sources in the disabledPackageSources node" + EnableMaestroInternalPackageSources +fi + +DotNetVersions=('5' '6' '7' '8' '9' '10') for DotNetVersion in ${DotNetVersions[@]} ; do FeedPrefix="dotnet${DotNetVersion}"; - grep -i " /dev/null if [ "$?" == "0" ]; then - grep -i "" - - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile - fi - PackageSources+=("$FeedPrefix-internal") - - grep -i "" $ConfigFile - if [ "$?" != "0" ]; then - echo "Adding $FeedPrefix-internal-transport to the packageSources." - PackageSourcesNodeFooter="" - PackageSourceTemplate="${TB}" - - sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile - fi - PackageSources+=("$FeedPrefix-internal-transport") + AddOrEnablePackageSource "$FeedPrefix-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$FeedPrefix-internal/nuget/$FeedSuffix" + AddOrEnablePackageSource "$FeedPrefix-internal-transport" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$FeedPrefix-internal-transport/nuget/$FeedSuffix" 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' @@ -139,29 +198,12 @@ if [ "$CredToken" ]; then # Check if there is no existing credential for this FeedName grep -i "<$FeedName>" $ConfigFile if [ "$?" != "0" ]; then - echo "Adding credentials for $FeedName." + echo " Inserting credential for feed: $FeedName" PackageSourceCredentialsNodeFooter="" - NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" + NewCredential="${TB}${TB}<$FeedName>${NL}${TB}${NL}${TB}${TB}${NL}${TB}${TB}" sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile fi done fi - -# Re-enable any entries in disabledPackageSources where the feed name contains darc-int -grep -i "" $ConfigFile -if [ "$?" == "0" ]; then - DisabledDarcIntSources=() - echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" - DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') - for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do - if [[ $DisabledSourceName == darc-int* ]] - then - OldDisableValue="" - NewDisableValue="" - sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile - echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" - fi - done -fi diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 438f9920c4..8cfee107e7 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -7,6 +7,7 @@ Param( [string] $msbuildEngine = $null, [bool] $warnAsError = $true, [bool] $nodeReuse = $true, + [switch] $buildCheck = $false, [switch][Alias('r')]$restore, [switch] $deployDeps, [switch][Alias('b')]$build, @@ -20,6 +21,7 @@ Param( [switch] $publish, [switch] $clean, [switch][Alias('pb')]$productBuild, + [switch]$fromVMR, [switch][Alias('bl')]$binaryLog, [switch][Alias('nobl')]$excludeCIBinarylog, [switch] $ci, @@ -71,6 +73,9 @@ function Print-Usage() { 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)" + Write-Host " -nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + Write-Host " -buildCheck Sets /check msbuild parameter" + Write-Host " -fromVMR Set when building from within the VMR" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." @@ -97,6 +102,7 @@ function Build { $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + $check = if ($buildCheck) { '/check' } else { '' } if ($projects) { # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. @@ -113,6 +119,7 @@ function Build { MSBuild $toolsetBuildProj ` $bl ` $platformArg ` + $check ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` /p:Restore=$restore ` @@ -122,11 +129,13 @@ function Build { /p:Deploy=$deploy ` /p:Test=$test ` /p:Pack=$pack ` - /p:DotNetBuildRepo=$productBuild ` + /p:DotNetBuild=$productBuild ` + /p:DotNetBuildFromVMR=$fromVMR ` /p:IntegrationTest=$integrationTest ` /p:PerformanceTest=$performanceTest ` /p:Sign=$sign ` /p:Publish=$publish ` + /p:RestoreStaticGraphEnableBinaryLogger=$binaryLog ` @properties } diff --git a/eng/common/build.sh b/eng/common/build.sh index ac1ee8620c..9767bb411a 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -42,6 +42,8 @@ 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 " --buildCheck Sets /check msbuild parameter" + echo " --fromVMR Set when building from within the VMR" echo "" echo "Command line arguments not listed above are passed thru to msbuild." echo "Arguments can also be passed in with a single hyphen." @@ -63,6 +65,7 @@ restore=false build=false source_build=false product_build=false +from_vmr=false rebuild=false test=false integration_test=false @@ -76,6 +79,7 @@ clean=false warn_as_error=true node_reuse=true +build_check=false binary_log=false exclude_ci_binary_log=false pipelines_log=false @@ -87,7 +91,7 @@ verbosity='minimal' runtime_source_feed='' runtime_source_feed_key='' -properties='' +properties=() while [[ $# > 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in @@ -127,19 +131,22 @@ while [[ $# > 0 ]]; do -pack) pack=true ;; - -sourcebuild|-sb) + -sourcebuild|-source-build|-sb) build=true source_build=true product_build=true restore=true pack=true ;; - -productBuild|-pb) + -productbuild|-product-build|-pb) build=true product_build=true restore=true pack=true ;; + -fromvmr|-from-vmr) + from_vmr=true + ;; -test|-t) test=true ;; @@ -173,6 +180,9 @@ while [[ $# > 0 ]]; do node_reuse=$2 shift ;; + -buildcheck) + build_check=true + ;; -runtimesourcefeed) runtime_source_feed=$2 shift @@ -182,7 +192,7 @@ while [[ $# > 0 ]]; do shift ;; *) - properties="$properties $1" + properties+=("$1") ;; esac @@ -216,7 +226,7 @@ function Build { InitializeCustomToolset if [[ ! -z "$projects" ]]; then - properties="$properties /p:Projects=$projects" + properties+=("/p:Projects=$projects") fi local bl="" @@ -224,15 +234,21 @@ function Build { bl="/bl:\"$log_dir/Build.binlog\"" fi + local check="" + if [[ "$build_check" == true ]]; then + check="/check" + fi + MSBuild $_InitializeToolset \ $bl \ + $check \ /p:Configuration=$configuration \ /p:RepoRoot="$repo_root" \ /p:Restore=$restore \ /p:Build=$build \ - /p:DotNetBuildRepo=$product_build \ - /p:ArcadeBuildFromSource=$source_build \ + /p:DotNetBuild=$product_build \ /p:DotNetBuildSourceOnly=$source_build \ + /p:DotNetBuildFromVMR=$from_vmr \ /p:Rebuild=$rebuild \ /p:Test=$test \ /p:Pack=$pack \ @@ -240,7 +256,8 @@ function Build { /p:PerformanceTest=$performance_test \ /p:Sign=$sign \ /p:Publish=$publish \ - $properties + /p:RestoreStaticGraphEnableBinaryLogger=$binary_log \ + ${properties[@]+"${properties[@]}"} ExitWithExitCode 0 } diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh index 1a02c0dec8..66e3b0ac61 100644 --- a/eng/common/cibuild.sh +++ b/eng/common/cibuild.sh @@ -13,4 +13,4 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index ba53ebfbd5..eaed6d87e6 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -19,16 +19,17 @@ parameters: # publishing defaults artifacts: '' enableMicrobuild: false + enableMicrobuildForMacAndLinux: false + microbuildUseESRP: true enablePublishBuildArtifacts: false enablePublishBuildAssets: false enablePublishTestResults: false - enablePublishUsingPipelines: false + enablePublishing: false enableBuildRetry: false mergeTestResults: false testRunTitle: '' testResultsFormat: '' name: '' - componentGovernanceSteps: [] preSteps: [] artifactPublishSteps: [] runAsPublic: false @@ -73,9 +74,6 @@ jobs: - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' - - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: - - name: EnableRichCodeNavigation - value: 'true' # Retry signature validation up to three times, waiting 2 seconds between attempts. # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY @@ -127,18 +125,12 @@ jobs: - ${{ preStep }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin - inputs: - signType: $(_SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - env: - TeamName: $(_TeamName) - MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + - template: /eng/common/core-templates/steps/install-microbuild.yml + parameters: + enableMicrobuild: ${{ parameters.enableMicrobuild }} + enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} + microbuildUseESRP: ${{ parameters.microbuildUseESRP }} continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: - task: NuGetAuthenticate@1 @@ -154,27 +146,12 @@ jobs: - ${{ each step in parameters.steps }}: - ${{ step }} - - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: - - task: RichCodeNavIndexer@0 - displayName: RichCodeNav Upload - inputs: - languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} - environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'internal') }} - richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin - uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} - continueOnError: true - - - ${{ each step in parameters.componentGovernanceSteps }}: - - ${{ step }} - - - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - task: MicroBuildCleanup@1 - displayName: Execute Microbuild cleanup tasks - condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: /eng/common/core-templates/steps/cleanup-microbuild.yml + parameters: + enableMicrobuild: ${{ parameters.enableMicrobuild }} + enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} - env: - TeamName: $(_TeamName) # Publish test results - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: @@ -183,7 +160,7 @@ jobs: inputs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + searchFolder: '$(System.DefaultWorkingDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true @@ -194,7 +171,7 @@ jobs: inputs: testResultsFormat: 'VSTest' testResultsFiles: '*.trx' - searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + searchFolder: '$(System.DefaultWorkingDirectory)/artifacts/TestResults/$(_BuildConfig)' testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true @@ -238,7 +215,7 @@ jobs: - task: CopyFiles@2 displayName: Gather buildconfiguration for build retry inputs: - SourceFolder: '$(Build.SourcesDirectory)/eng/common/BuildConfiguration' + SourceFolder: '$(System.DefaultWorkingDirectory)/eng/common/BuildConfiguration' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/eng/common/BuildConfiguration' continueOnError: true diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml index 00feec8ebb..eefed3b667 100644 --- a/eng/common/core-templates/job/onelocbuild.yml +++ b/eng/common/core-templates/job/onelocbuild.yml @@ -4,11 +4,11 @@ parameters: # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: '' - + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex GithubPat: $(BotAccount-dotnet-bot-repo-PAT) - SourcesDirectory: $(Build.SourcesDirectory) + SourcesDirectory: $(System.DefaultWorkingDirectory) CreatePr: true AutoCompletePr: false ReusePr: true @@ -27,7 +27,7 @@ parameters: is1ESPipeline: '' jobs: - job: OneLocBuild${{ parameters.JobNameSuffix }} - + dependsOn: ${{ parameters.dependsOn }} displayName: OneLocBuild${{ parameters.JobNameSuffix }} @@ -52,13 +52,13 @@ jobs: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026.amd64 os: windows steps: @@ -68,7 +68,7 @@ jobs: - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: - task: Powershell@2 inputs: - filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + filePath: $(System.DefaultWorkingDirectory)/eng/common/generate-locproject.ps1 arguments: $(_GenerateLocProjectArguments) displayName: Generate LocProject.json condition: ${{ parameters.condition }} @@ -86,8 +86,7 @@ jobs: isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} ${{ if eq(parameters.CreatePr, true) }}: isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} - ${{ if eq(parameters.RepoType, 'gitHub') }}: - isShouldReusePrSelected: ${{ parameters.ReusePr }} + isShouldReusePrSelected: ${{ parameters.ReusePr }} packageSourceAuth: patAuth patVariable: ${{ parameters.CeapexPat }} ${{ if eq(parameters.RepoType, 'gitHub') }}: @@ -100,22 +99,20 @@ jobs: mirrorBranch: ${{ parameters.MirrorBranch }} condition: ${{ parameters.condition }} - - template: /eng/common/core-templates/steps/publish-build-artifacts.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} - args: - displayName: Publish Localization Files - pathToPublish: '$(Build.ArtifactStagingDirectory)/loc' - publishLocation: Container - artifactName: Loc - condition: ${{ parameters.condition }} + # Copy the locProject.json to the root of the Loc directory, then publish a pipeline artifact + - task: CopyFiles@2 + displayName: Copy LocProject.json + inputs: + SourceFolder: '$(System.DefaultWorkingDirectory)/eng/Localize/' + Contents: 'LocProject.json' + TargetFolder: '$(Build.ArtifactStagingDirectory)/loc' + condition: ${{ parameters.condition }} - - template: /eng/common/core-templates/steps/publish-build-artifacts.yml + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: - displayName: Publish LocProject.json - pathToPublish: '$(Build.SourcesDirectory)/eng/Localize/' - publishLocation: Container - artifactName: Loc - condition: ${{ parameters.condition }} \ No newline at end of file + targetPath: '$(Build.ArtifactStagingDirectory)/loc' + artifactName: 'Loc' + displayName: 'Publish Localization Files' + condition: ${{ parameters.condition }} diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index 3d3356e319..06f2eed032 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -20,9 +20,6 @@ parameters: # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing - publishUsingPipelines: false - # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing publishAssetsImmediately: false @@ -32,6 +29,19 @@ parameters: is1ESPipeline: '' + # Optional: 🌤️ or not the build has assets it wants to publish to BAR + isAssetlessBuild: false + + # Optional, publishing version + publishingVersion: 3 + + # Optional: A minimatch pattern for the asset manifests to publish to BAR + assetManifestsPattern: '*/manifests/**/*.xml' + + repositoryAlias: self + + officialBuildId: '' + jobs: - job: Asset_Registry_Publish @@ -54,51 +64,85 @@ jobs: value: false # unconditional - needed for logs publishing (redactor tool version) - template: /eng/common/core-templates/post-build/common-variables.yml + - name: OfficialBuildId + ${{ if ne(parameters.officialBuildId, '') }}: + value: ${{ parameters.officialBuildId }} + ${{ else }}: + value: $(Build.BuildNumber) pool: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: NetCore1ESPool-Publishing-Internal - image: windows.vs2019.amd64 + image: windows.vs2026.amd64 os: windows steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - checkout: self + - checkout: ${{ parameters.repositoryAlias }} fetchDepth: 3 clean: true - - - task: DownloadBuildArtifacts@0 - displayName: Download artifact - inputs: - artifactName: AssetManifests - downloadPath: '$(Build.StagingDirectory)/Download' - checkDownloadedFiles: true - condition: ${{ parameters.condition }} - continueOnError: ${{ parameters.continueOnError }} + + - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: + - ${{ if eq(parameters.publishingVersion, 3) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Asset Manifests + inputs: + artifactName: AssetManifests + targetPath: '$(Build.StagingDirectory)/AssetManifests' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - ${{ if eq(parameters.publishingVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download V4 asset manifests + inputs: + itemPattern: '*/manifests/**/*.xml' + targetPath: '$(Build.StagingDirectory)/AllAssetManifests' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: CopyFiles@2 + displayName: Copy V4 asset manifests to AssetManifests + inputs: + SourceFolder: '$(Build.StagingDirectory)/AllAssetManifests' + Contents: ${{ parameters.assetManifestsPattern }} + TargetFolder: '$(Build.StagingDirectory)/AssetManifests' + flattenFolders: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} - task: NuGetAuthenticate@1 + # Populate internal runtime variables. + - template: /eng/common/templates/steps/enable-internal-sources.yml + ${{ 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 displayName: Publish Build Assets inputs: azureSubscription: "Darc: Maestro Production" scriptType: ps scriptLocation: scriptPath - scriptPath: $(Build.SourcesDirectory)/eng/common/sdk-task.ps1 + scriptPath: $(System.DefaultWorkingDirectory)/eng/common/sdk-task.ps1 arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet - /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:ManifestsPath='$(Build.StagingDirectory)/AssetManifests' + /p:IsAssetlessBuild=${{ parameters.isAssetlessBuild }} /p:MaestroApiEndpoint=https://maestro.dot.net - /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} - /p:OfficialBuildId=$(Build.BuildNumber) + /p:OfficialBuildId=$(OfficialBuildId) + -runtimeSourceFeed https://ci.dot.net/internal + -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' + condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} @@ -113,36 +157,53 @@ jobs: Add-Content -Path $filePath -Value "$(DefaultChannels)" Add-Content -Path $filePath -Value $(IsStableBuild) - $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + $symbolExclusionfile = "$(System.DefaultWorkingDirectory)/eng/SymbolPublishingExclusionsFile.txt" if (Test-Path -Path $symbolExclusionfile) { Write-Host "SymbolExclusionFile exists" Copy-Item -Path $symbolExclusionfile -Destination "$(Build.StagingDirectory)/ReleaseConfigs" } - - template: /eng/common/core-templates/steps/publish-build-artifacts.yml + - ${{ if eq(parameters.publishingVersion, 4) }}: + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} + args: + targetPath: '$(Build.ArtifactStagingDirectory)/MergedManifest.xml' + artifactName: AssetManifests + displayName: 'Publish Merged Manifest' + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # just metadata for publishing + + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish ReleaseConfigs Artifact - pathToPublish: '$(Build.StagingDirectory)/ReleaseConfigs' - publishLocation: Container + targetPath: '$(Build.StagingDirectory)/ReleaseConfigs' artifactName: ReleaseConfigs + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # just metadata for publishing - - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - ${{ if or(eq(parameters.publishAssetsImmediately, 'true'), eq(parameters.isAssetlessBuild, 'true')) }}: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} + # 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: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 @@ -150,9 +211,13 @@ jobs: -WaitPublishingFinish true -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + -SkipAssetsPublishing '${{ parameters.isAssetlessBuild }}' + -runtimeSourceFeed https://ci.dot.net/internal + -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - template: /eng/common/core-templates/steps/publish-logs.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - JobLabel: 'Publish_Artifacts_Logs' + StageLabel: 'BuildAssetRegistry' + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/core-templates/job/source-build.yml b/eng/common/core-templates/job/source-build.yml index c4713c8b6e..1997c2ae00 100644 --- a/eng/common/core-templates/job/source-build.yml +++ b/eng/common/core-templates/job/source-build.yml @@ -12,9 +12,10 @@ parameters: # The name of the job. This is included in the job ID. # targetRID: '' # The name of the target RID to use, instead of the one auto-detected by Arcade. - # nonPortable: false + # portableBuild: false # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than - # linux-x64), and compiling against distro-provided packages rather than portable ones. + # linux-x64), and compiling against distro-provided packages rather than portable ones. The + # default is portable mode. # skipPublishValidation: false # Disables publishing validation. By default, a check is performed to ensure no packages are # published by source-build. @@ -26,6 +27,8 @@ parameters: # Specifies the build script to invoke to perform the build in the repo. The default # './build.sh' should work for typical Arcade repositories, but this is customizable for # difficult situations. + # buildArguments: '' + # Specifies additional build arguments to pass to the build script. # jobProperties: {} # A list of job properties to inject at the top level, for potential extensibility beyond # container and pool. @@ -57,19 +60,19 @@ jobs: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] - demands: ImageOverride -equals build.ubuntu.2004.amd64 + demands: ImageOverride -equals build.azurelinux.3.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2 + image: build.azurelinux.3.amd64 os: linux ${{ else }}: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] - demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open + demands: ImageOverride -equals build.azurelinux.3.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 + demands: ImageOverride -equals build.azurelinux.3.amd64 ${{ if ne(parameters.platform.pool, '') }}: pool: ${{ parameters.platform.pool }} diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml index 205fb5b3a3..76baf5c272 100644 --- a/eng/common/core-templates/job/source-index-stage1.yml +++ b/eng/common/core-templates/job/source-index-stage1.yml @@ -1,12 +1,9 @@ parameters: runAsPublic: false - sourceIndexUploadPackageVersion: 2.0.0-20240522.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20240522.1 - sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" preSteps: [] binlogPath: artifacts/log/Debug/Build.binlog - condition: '' + condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') dependsOn: '' pool: '' is1ESPipeline: '' @@ -16,12 +13,6 @@ jobs: dependsOn: ${{ parameters.dependsOn }} condition: ${{ parameters.condition }} variables: - - name: SourceIndexUploadPackageVersion - value: ${{ parameters.sourceIndexUploadPackageVersion }} - - name: SourceIndexProcessBinlogPackageVersion - value: ${{ parameters.sourceIndexProcessBinlogPackageVersion }} - - name: SourceIndexPackageSource - value: ${{ parameters.sourceIndexPackageSource }} - name: BinlogPath value: ${{ parameters.binlogPath }} - template: /eng/common/core-templates/variables/pool-providers.yml @@ -34,12 +25,10 @@ jobs: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $(DncEngPublicBuildPool) - image: 1es-windows-2022-open - os: windows + image: windows.vs2026preview.scout.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 - os: windows + image: windows.vs2026preview.scout.amd64 steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: @@ -47,35 +36,9 @@ jobs: - ${{ each preStep in parameters.preSteps }}: - ${{ preStep }} - - - task: UseDotNet@2 - displayName: Use .NET 8 SDK - inputs: - packageType: sdk - version: 8.0.x - installationPath: $(Agent.TempDirectory)/dotnet - workingDirectory: $(Agent.TempDirectory) - - - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(sourceIndexProcessBinlogPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(sourceIndexUploadPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools - displayName: Download 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) - - script: ${{ parameters.sourceIndexBuildCommand }} displayName: Build Repository - - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output - displayName: Process Binlog into indexable sln - - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - task: AzureCLI@2 - displayName: Log in to Azure and upload stage1 artifacts to source index - inputs: - azureSubscription: 'SourceDotNet Stage1 Publish' - addSpnToEnvironment: true - scriptType: 'ps' - scriptLocation: 'inlineScript' - inlineScript: | - $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 + - template: /eng/common/core-templates/steps/source-index-stage1-publish.yml + parameters: + binLogPath: ${{ parameters.binLogPath }} diff --git a/eng/common/core-templates/jobs/codeql-build.yml b/eng/common/core-templates/jobs/codeql-build.yml index f2144252cc..dbc14ac580 100644 --- a/eng/common/core-templates/jobs/codeql-build.yml +++ b/eng/common/core-templates/jobs/codeql-build.yml @@ -15,7 +15,6 @@ jobs: enablePublishBuildArtifacts: false enablePublishTestResults: false enablePublishBuildAssets: false - enablePublishUsingPipelines: false enableTelemetry: true variables: @@ -25,7 +24,7 @@ jobs: - name: DefaultGuardianVersion value: 0.109.0 - name: GuardianPackagesConfigFile - value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config - name: GuardianVersion value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} diff --git a/eng/common/core-templates/jobs/jobs.yml b/eng/common/core-templates/jobs/jobs.yml index ea69be4341..cc8cce4527 100644 --- a/eng/common/core-templates/jobs/jobs.yml +++ b/eng/common/core-templates/jobs/jobs.yml @@ -5,9 +5,6 @@ parameters: # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false - # Optional: Enable publishing using release pipelines - enablePublishUsingPipelines: false - # Optional: Enable running the source-build jobs to build repo from source enableSourceBuild: false @@ -30,6 +27,9 @@ parameters: # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. publishAssetsImmediately: false + # Optional: 🌤️ or not the build has assets it wants to publish to BAR + isAssetlessBuild: false + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' @@ -44,6 +44,12 @@ parameters: artifacts: {} is1ESPipeline: '' + # Publishing version w/default. + publishingVersion: 3 + + repositoryAlias: self + officialBuildId: '' + # Internal resources (telemetry, microbuild) can only be accessed from non-public projects, # and some (Microbuild) should only be applied to non-PR cases for internal builds. @@ -83,7 +89,6 @@ jobs: - template: /eng/common/core-templates/jobs/source-build.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} - allCompletedJobId: Source_Build_Complete ${{ each parameter in parameters.sourceBuildParameters }}: ${{ parameter.key }}: ${{ parameter.value }} @@ -96,11 +101,12 @@ jobs: ${{ parameter.key }}: ${{ parameter.value }} - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, ''), eq(parameters.isAssetlessBuild, true)) }}: - template: ../job/publish-build-assets.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} continueOnError: ${{ parameters.continueOnError }} + publishingVersion: ${{ parameters.publishingVersion }} dependsOn: - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.publishBuildAssetsDependsOn }}: @@ -108,12 +114,12 @@ jobs: - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - ${{ each job in parameters.jobs }}: - ${{ job.job }} - - ${{ if eq(parameters.enableSourceBuild, true) }}: - - Source_Build_Complete runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} - publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + publishAssetsImmediately: ${{ or(parameters.publishAssetsImmediately, parameters.isAssetlessBuild) }} + isAssetlessBuild: ${{ parameters.isAssetlessBuild }} enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} + repositoryAlias: ${{ parameters.repositoryAlias }} + officialBuildId: ${{ parameters.officialBuildId }} diff --git a/eng/common/core-templates/jobs/source-build.yml b/eng/common/core-templates/jobs/source-build.yml index a10ccfbee6..d92860cba2 100644 --- a/eng/common/core-templates/jobs/source-build.yml +++ b/eng/common/core-templates/jobs/source-build.yml @@ -2,19 +2,13 @@ parameters: # This template adds arcade-powered source-build to CI. A job is created for each platform, as # well as an optional server job that completes when all platform jobs complete. - # The name of the "join" job for all source-build platforms. If set to empty string, the job is - # not included. Existing repo pipelines can use this job depend on all source-build jobs - # completing without maintaining a separate list of every single job ID: just depend on this one - # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. - allCompletedJobId: '' - # See /eng/common/core-templates/job/source-build.yml jobNamePrefix: 'Source_Build' # This is the default platform provided by Arcade, intended for use by a managed-only repo. defaultManagedPlatform: name: 'Managed' - container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64' # Defines the platforms on which to run build jobs. One job is created for each platform, and the # object in this array is sent to the job template as 'platform'. If no platforms are specified, @@ -31,16 +25,6 @@ parameters: jobs: -- ${{ if ne(parameters.allCompletedJobId, '') }}: - - job: ${{ parameters.allCompletedJobId }} - displayName: Source-Build Complete - pool: server - dependsOn: - - ${{ each platform in parameters.platforms }}: - - ${{ parameters.jobNamePrefix }}_${{ platform.name }} - - ${{ if eq(length(parameters.platforms), 0) }}: - - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} - - ${{ each platform in parameters.platforms }}: - template: /eng/common/core-templates/job/source-build.yml parameters: diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml index 454fd75c7a..905a6315e2 100644 --- a/eng/common/core-templates/post-build/post-build.yml +++ b/eng/common/core-templates/post-build/post-build.yml @@ -9,6 +9,7 @@ parameters: default: 3 values: - 3 + - 4 - name: BARBuildId displayName: BAR Build Id @@ -44,6 +45,11 @@ parameters: 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 @@ -55,6 +61,11 @@ parameters: 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 @@ -110,18 +121,18 @@ stages: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) - image: windows.vs2022.amd64 + image: windows.vs2026preview.scout.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2022.amd64 + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml @@ -130,21 +141,35 @@ stages: PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true + - ${{ 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: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - job: @@ -154,18 +179,18 @@ stages: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2022.amd64 + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: @@ -173,19 +198,30 @@ stages: PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true - itemPattern: | - ** - !**/Microsoft.SourceBuild.Intermediate.*.nupkg + - ${{ 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 @@ -201,7 +237,7 @@ stages: filePath: eng\common\sdk-task.ps1 arguments: -task SigningValidation -restore -msbuildEngine vs /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' - /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' ${{ parameters.signingValidationAdditionalParameters }} - template: /eng/common/core-templates/steps/publish-logs.yml @@ -218,18 +254,18 @@ stages: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022 + image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2022.amd64 + demands: ImageOverride -equals windows.vs2026preview.scout.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: @@ -237,21 +273,35 @@ stages: PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: BlobArtifacts - checkDownloadedFiles: true + - ${{ if ne(parameters.publishingInfraVersion, 4) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.publishingInfraVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts (V4) + inputs: + itemPattern: '*/assets/**' + targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + - task: CopyFiles@2 + displayName: Flatten assets to BlobArtifacts + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + Contents: '**/*' + TargetFolder: '$(Build.ArtifactStagingDirectory)/BlobArtifacts' + flattenFolders: true - task: PowerShell@2 displayName: Validate inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/sourcelink-validation.ps1 arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ -ExtractPath $(Agent.BuildDirectory)/Extract/ -GHRepoName $(Build.Repository.Name) @@ -279,18 +329,18 @@ stages: # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: name: AzurePipelines-EO - image: 1ESPT-Windows2022 + image: 1ESPT-Windows2025 demands: Cmd os: windows # If it's not devdiv, it's dnceng ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: NetCore1ESPool-Publishing-Internal - image: windows.vs2019.amd64 + image: windows.vs2026.amd64 os: windows ${{ else }}: name: NetCore1ESPool-Publishing-Internal - demands: ImageOverride -equals windows.vs2019.amd64 + demands: ImageOverride -equals windows.vs2026.amd64 steps: - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml parameters: @@ -300,17 +350,33 @@ stages: - task: NuGetAuthenticate@1 + # 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 + + # 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: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 arguments: > -BuildId $(BARBuildId) - -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' -WaitPublishingFinish true + -RequireDefaultChannels ${{ parameters.requireDefaultChannels }} -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + -SkipAssetsPublishing '${{ parameters.isAssetlessBuild }}' + -runtimeSourceFeed https://ci.dot.net/internal + -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' diff --git a/eng/common/core-templates/post-build/setup-maestro-vars.yml b/eng/common/core-templates/post-build/setup-maestro-vars.yml index f7602980db..6dfa99ec5e 100644 --- a/eng/common/core-templates/post-build/setup-maestro-vars.yml +++ b/eng/common/core-templates/post-build/setup-maestro-vars.yml @@ -8,12 +8,11 @@ steps: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: - - task: DownloadBuildArtifacts@0 + - task: DownloadPipelineArtifact@2 displayName: Download Release Configs inputs: - buildType: current artifactName: ReleaseConfigs - checkDownloadedFiles: true + targetPath: '$(Build.StagingDirectory)/ReleaseConfigs' - task: AzureCLI@2 name: setReleaseVars @@ -36,7 +35,7 @@ steps: $AzureDevOpsBuildId = $Env:Build_BuildId } else { - . $(Build.SourcesDirectory)\eng\common\tools.ps1 + . $(System.DefaultWorkingDirectory)\eng\common\tools.ps1 $darc = Get-Darc $buildInfo = & $darc get-build ` --id ${{ parameters.BARBuildId }} ` diff --git a/eng/common/core-templates/steps/cleanup-microbuild.yml b/eng/common/core-templates/steps/cleanup-microbuild.yml new file mode 100644 index 0000000000..c0fdcd3379 --- /dev/null +++ b/eng/common/core-templates/steps/cleanup-microbuild.yml @@ -0,0 +1,28 @@ +parameters: + # Enable cleanup tasks for MicroBuild + enableMicrobuild: false + # Enable cleanup tasks for MicroBuild on Mac and Linux + # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' + enableMicrobuildForMacAndLinux: false + continueOnError: false + +steps: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and( + always(), + or( + and( + eq(variables['Agent.Os'], 'Windows_NT'), + in(variables['_SignType'], 'real', 'test') + ), + and( + ${{ eq(parameters.enableMicrobuildForMacAndLinux, true) }}, + ne(variables['Agent.Os'], 'Windows_NT'), + eq(variables['_SignType'], 'real') + ) + )) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) diff --git a/eng/common/core-templates/steps/component-governance.yml b/eng/common/core-templates/steps/component-governance.yml deleted file mode 100644 index cf0649aa95..0000000000 --- a/eng/common/core-templates/steps/component-governance.yml +++ /dev/null @@ -1,16 +0,0 @@ -parameters: - disableComponentGovernance: false - componentGovernanceIgnoreDirectories: '' - is1ESPipeline: false - displayName: 'Component Detection' - -steps: -- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" - displayName: Set skipComponentGovernanceDetection variable -- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - - task: ComponentGovernanceComponentDetection@0 - continueOnError: true - displayName: ${{ parameters.displayName }} - inputs: - ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} diff --git a/eng/common/core-templates/steps/enable-internal-sources.yml b/eng/common/core-templates/steps/enable-internal-sources.yml index 64f881bffc..4085512b69 100644 --- a/eng/common/core-templates/steps/enable-internal-sources.yml +++ b/eng/common/core-templates/steps/enable-internal-sources.yml @@ -17,8 +17,8 @@ steps: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token + filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token env: Token: ${{ parameters.legacyCredential }} # If running on dnceng (internal project), just use the default behavior for NuGetAuthenticate. @@ -29,8 +29,8 @@ steps: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config + filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config - ${{ else }}: - template: /eng/common/templates/steps/get-federated-access-token.yml parameters: @@ -39,8 +39,8 @@ steps: - task: PowerShell@2 displayName: Setup Internal Feeds inputs: - filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 - arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token) + filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 + arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token) # This is required in certain scenarios to install the ADO credential provider. # It installed by default in some msbuild invocations (e.g. VS msbuild), but needs to be installed for others # (e.g. dotnet msbuild). diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml index d938b60e1b..aad0a8aeda 100644 --- a/eng/common/core-templates/steps/generate-sbom.yml +++ b/eng/common/core-templates/steps/generate-sbom.yml @@ -1,54 +1,14 @@ -# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. -# PackageName - The name of the package this SBOM represents. -# PackageVersion - The version of the package this SBOM represents. -# ManifestDirPath - The path of the directory where the generated manifest files will be placed -# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. - parameters: - PackageVersion: 9.0.0 - BuildDropPath: '$(Build.SourcesDirectory)/artifacts' - PackageName: '.NET' - ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom - IgnoreDirectories: '' - sbomContinueOnError: true - is1ESPipeline: false - # disable publishArtifacts if some other step is publishing the artifacts (like job.yml). - publishArtifacts: true + PackageVersion: unused + BuildDropPath: unused + PackageName: unused + ManifestDirPath: unused + IgnoreDirectories: unused + sbomContinueOnError: unused + is1ESPipeline: unused + publishArtifacts: unused steps: -- task: PowerShell@2 - displayName: Prep for SBOM generation in (Non-linux) - condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) - inputs: - filePath: ./eng/common/generate-sbom-prep.ps1 - arguments: ${{parameters.manifestDirPath}} - -# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 - script: | - chmod +x ./eng/common/generate-sbom-prep.sh - ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} - displayName: Prep for SBOM generation in (Linux) - condition: eq(variables['Agent.Os'], 'Linux') - continueOnError: ${{ parameters.sbomContinueOnError }} - -- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: 'Generate SBOM manifest' - continueOnError: ${{ parameters.sbomContinueOnError }} - inputs: - PackageName: ${{ parameters.packageName }} - BuildDropPath: ${{ parameters.buildDropPath }} - PackageVersion: ${{ parameters.packageVersion }} - ManifestDirPath: ${{ parameters.manifestDirPath }} - ${{ if ne(parameters.IgnoreDirectories, '') }}: - AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' - -- ${{ if eq(parameters.publishArtifacts, 'true')}}: - - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} - args: - displayName: Publish SBOM manifest - continueOnError: ${{parameters.sbomContinueOnError}} - targetPath: '${{ parameters.manifestDirPath }}' - artifactName: $(ARTIFACT_NAME) - + echo "##vso[task.logissue type=warning]Including generate-sbom.yml is deprecated, SBOM generation is handled 1ES PT now. Remove this include." + displayName: Issue generate-sbom.yml deprecation warning diff --git a/eng/common/core-templates/steps/get-delegation-sas.yml b/eng/common/core-templates/steps/get-delegation-sas.yml index 9db5617ea7..d2901470a7 100644 --- a/eng/common/core-templates/steps/get-delegation-sas.yml +++ b/eng/common/core-templates/steps/get-delegation-sas.yml @@ -31,16 +31,7 @@ steps: # Calculate the expiration of the SAS token and convert to UTC $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - # Temporarily work around a helix issue where SAS tokens with / in them will cause incorrect downloads - # of correlation payloads. https://github.com/dotnet/dnceng/issues/3484 - $sas = "" - do { - $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to generate SAS token." - exit 1 - } - } while($sas.IndexOf('/') -ne -1) + $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv if ($LASTEXITCODE -ne 0) { Write-Error "Failed to generate SAS token." diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml new file mode 100644 index 0000000000..553fce66b9 --- /dev/null +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -0,0 +1,110 @@ +parameters: + # Enable install tasks for MicroBuild + enableMicrobuild: false + # Enable install tasks for MicroBuild on Mac and Linux + # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' + enableMicrobuildForMacAndLinux: 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. + # Doing so will cause the service connection to be authorized for the pipeline, which isn't allowed and won't work for non-prod. + # Unfortunately, _SignType can't be used to exclude the use of the service connection in non-real sign scenarios. The + # variable is not available in template expression. _SignType has a very large proliferation across .NET, so replacing it is tough. + microbuildUseESRP: true + # Microbuild installation directory + microBuildOutputFolder: $(Agent.TempDirectory)/MicroBuild + + continueOnError: false + +steps: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, 'true') }}: + # Needed to download the MicroBuild plugin nupkgs on Mac and Linux when nuget.exe is unavailable + - task: UseDotNet@2 + displayName: Install .NET 8.0 SDK for MicroBuild Plugin + inputs: + packageType: sdk + version: 8.0.x + installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet-microbuild + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) + + - script: | + set -euo pipefail + + # UseDotNet@2 prepends the dotnet executable path to the PATH variable, so we can call dotnet directly + version=$(dotnet --version) + cat << 'EOF' > ${{ parameters.microBuildOutputFolder }}/global.json + { + "sdk": { + "version": "$version", + "paths": [ + "${{ parameters.microBuildOutputFolder }}/.dotnet-microbuild" + ], + "errorMessage": "The .NET SDK version $version is required to install the MicroBuild signing plugin." + } + } + EOF + displayName: 'Add global.json to MicroBuild Installation path' + workingDirectory: ${{ parameters.microBuildOutputFolder }} + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) + + - script: | + REM Check if ESRP is disabled while SignType is real + if /I "${{ parameters.microbuildUseESRP }}"=="false" if /I "$(_SignType)"=="real" ( + echo Error: ESRP must be enabled when SignType is real. + exit /b 1 + ) + displayName: 'Validate ESRP usage (Windows)' + condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) + - script: | + # Check if ESRP is disabled while SignType is real + if [ "${{ parameters.microbuildUseESRP }}" = "false" ] && [ "$(_SignType)" = "real" ]; then + echo "Error: ESRP must be enabled when SignType is real." + exit 1 + fi + displayName: 'Validate ESRP usage (Non-Windows)' + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) + + # Two different MB install steps. This is due to not being able to use the agent OS during + # 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. + - 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 + 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 + env: + 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')) diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml index 80788c5231..4eed0312b8 100644 --- a/eng/common/core-templates/steps/publish-logs.yml +++ b/eng/common/core-templates/steps/publish-logs.yml @@ -12,29 +12,32 @@ steps: inputs: targetType: inline script: | - New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ - Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + New-Item -ItemType Directory $(System.DefaultWorkingDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(System.DefaultWorkingDirectory)/artifacts/log/Debug/* $(System.DefaultWorkingDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ continueOnError: true condition: always() - task: PowerShell@2 displayName: Redact Logs inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/redact-logs.ps1 + filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/redact-logs.ps1 # For now this needs to have explicit list of all sensitive data. Taken from eng/publishing/v3/publish.yml - # Sensitive data can as well be added to $(Build.SourcesDirectory)/eng/BinlogSecretsRedactionFile.txt' + # Sensitive data can as well be added to $(System.DefaultWorkingDirectory)/eng/BinlogSecretsRedactionFile.txt' # If the file exists - sensitive data for redaction will be sourced from it # (single entry per line, lines starting with '# ' are considered comments and skipped) - arguments: -InputPath '$(Build.SourcesDirectory)/PostBuildLogs' - -BinlogToolVersion ${{parameters.BinlogToolVersion}} - -TokensFilePath '$(Build.SourcesDirectory)/eng/BinlogSecretsRedactionFile.txt' + arguments: -InputPath '$(System.DefaultWorkingDirectory)/PostBuildLogs' + -BinlogToolVersion '${{parameters.BinlogToolVersion}}' + -TokensFilePath '$(System.DefaultWorkingDirectory)/eng/BinlogSecretsRedactionFile.txt' + -runtimeSourceFeed https://ci.dot.net/internal + -runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)' '$(publishing-dnceng-devdiv-code-r-build-re)' - '$(MaestroAccessToken)' '$(dn-bot-all-orgs-artifact-feeds-rw)' '$(akams-client-id)' '$(microsoft-symbol-server-pat)' '$(symweb-symbol-server-pat)' + '$(dnceng-symbol-server-pat)' '$(dn-bot-all-orgs-build-rw-code-rw)' + '$(System.AccessToken)' ${{parameters.CustomSensitiveDataList}} continueOnError: true condition: always() @@ -42,17 +45,19 @@ steps: - task: CopyFiles@2 displayName: Gather post build logs inputs: - SourceFolder: '$(Build.SourcesDirectory)/PostBuildLogs' + SourceFolder: '$(System.DefaultWorkingDirectory)/PostBuildLogs' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' + condition: always() -- template: /eng/common/core-templates/steps/publish-build-artifacts.yml +- template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish Logs - pathToPublish: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' - publishLocation: Container - artifactName: PostBuildLogs + targetPath: '$(Build.ArtifactStagingDirectory)/PostBuildLogs' + artifactName: PostBuildLogs_${{ parameters.StageLabel }}_${{ parameters.JobLabel }}_Attempt$(System.JobAttempt) continueOnError: true condition: always() + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # logs are non-production artifacts diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml index 2915d29bb7..09ae5cd73a 100644 --- a/eng/common/core-templates/steps/source-build.yml +++ b/eng/common/core-templates/steps/source-build.yml @@ -19,25 +19,12 @@ steps: set -x df -h - # If file changes are detected, set CopyWipIntoInnerSourceBuildRepo to copy the WIP changes into the inner source build repo. - internalRestoreArgs= - if ! git diff --quiet; then - internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' - # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. - # This only works if there is a username/email configured, which won't be the case in most CI runs. - git config --get user.email - if [ $? -ne 0 ]; then - git config user.email dn-bot@microsoft.com - git config user.name dn-bot - fi - fi - # If building on the internal project, the internal storage variable may be available (usually only if needed) # In that case, add variables to allow the download of internal runtimes if the specified versions are not found # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.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 @@ -46,84 +33,33 @@ steps: buildConfig='$(_BuildConfig)' fi - officialBuildArgs= - if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then - officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' - fi - targetRidArgs= if [ '${{ parameters.platform.targetRID }}' != '' ]; then targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' fi - runtimeOsArgs= - if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then - runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' - fi - - baseOsArgs= - if [ '${{ parameters.platform.baseOS }}' != '' ]; then - baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' - fi - - publishArgs= - if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then - publishArgs='--publish' - fi - - assetManifestFileName=SourceBuild_RidSpecific.xml - if [ '${{ parameters.platform.name }}' != '' ]; then - assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + portableBuildArgs= + if [ '${{ parameters.platform.portableBuild }}' != '' ]; then + portableBuildArgs='/p:PortableBuild=${{ parameters.platform.portableBuild }}' fi ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ - --restore --build --pack $publishArgs -bl \ - $officialBuildArgs \ + --restore --build --pack -bl \ + --source-build \ + ${{ parameters.platform.buildArguments }} \ $internalRuntimeDownloadArgs \ - $internalRestoreArgs \ $targetRidArgs \ - $runtimeOsArgs \ - $baseOsArgs \ - /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ - /p:ArcadeBuildFromSource=true \ - /p:DotNetBuildSourceOnly=true \ - /p:DotNetBuildRepo=true \ - /p:AssetManifestFileName=$assetManifestFileName + $portableBuildArgs \ displayName: Build -# Upload build logs for diagnosis. -- task: CopyFiles@2 - displayName: Prepare BuildLogs staging directory - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - **/*.log - **/*.binlog - artifacts/sb/prebuilt-report/** - TargetFolder: '$(Build.StagingDirectory)/BuildLogs' - CleanTargetFolder: true - continueOnError: true - condition: succeededOrFailed() - - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} args: displayName: Publish BuildLogs - targetPath: '$(Build.StagingDirectory)/BuildLogs' + targetPath: artifacts/log/${{ coalesce(variables._BuildConfig, 'Release') }} artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) continueOnError: true condition: succeededOrFailed() - sbomEnabled: false # we don't need SBOM for logs - -# Manually inject component detection so that we can ignore the source build upstream cache, which contains -# a nupkg cache of input packages (a local feed). -# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' -# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets -- template: /eng/common/core-templates/steps/component-governance.yml - parameters: - displayName: Component Detection (Exclude upstream cache) - is1ESPipeline: ${{ parameters.is1ESPipeline }} - componentGovernanceIgnoreDirectories: '$(Build.SourcesDirectory)/artifacts/sb/src/artifacts/obj/source-built-upstream-cache' - disableComponentGovernance: ${{ eq(variables['System.TeamProject'], 'public') }} + isProduction: false # logs are non-production artifacts diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml new file mode 100644 index 0000000000..e9a694afa5 --- /dev/null +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -0,0 +1,35 @@ +parameters: + 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 + +steps: +- task: UseDotNet@2 + displayName: "Source Index: Use .NET 9 SDK" + inputs: + packageType: sdk + version: 9.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + +- script: | + $(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) + +- script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i ${{parameters.BinlogPath}} -r $(System.DefaultWorkingDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: "Source Index: Process Binlog into indexable sln" + +- ${{ if and(ne(parameters.runAsPublic, 'true'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: AzureCLI@2 + displayName: "Source Index: Upload Source Index stage1 artifacts to Azure" + inputs: + azureSubscription: 'SourceDotNet Stage1 Publish' + addSpnToEnvironment: true + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 diff --git a/eng/common/cross/arm64/tizen/tizen.patch b/eng/common/cross/arm64/tizen/tizen.patch index af7c8be059..2cebc54738 100644 --- a/eng/common/cross/arm64/tizen/tizen.patch +++ b/eng/common/cross/arm64/tizen/tizen.patch @@ -5,5 +5,5 @@ diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf64-littleaarch64) --GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-aarch64.so.1 ) ) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-aarch64.so.1 ) ) +GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-aarch64.so.1 ) ) diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch deleted file mode 100644 index 2d26156193..0000000000 --- a/eng/common/cross/armel/armel.jessie.patch +++ /dev/null @@ -1,43 +0,0 @@ -diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h ---- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 -+++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 -@@ -69,10 +69,10 @@ - #endif - #ifdef UATOMIC_HAS_ATOMIC_SHORT - case 2: -- return __sync_val_compare_and_swap_2(addr, old, _new); -+ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); - #endif - case 4: -- return __sync_val_compare_and_swap_4(addr, old, _new); -+ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_val_compare_and_swap_8(addr, old, _new); -@@ -109,7 +109,7 @@ - return; - #endif - case 4: -- __sync_and_and_fetch_4(addr, val); -+ __sync_and_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -148,7 +148,7 @@ - return; - #endif - case 4: -- __sync_or_and_fetch_4(addr, val); -+ __sync_or_and_fetch_4((uint32_t*) addr, val); - return; - #if (CAA_BITS_PER_LONG == 64) - case 8: -@@ -187,7 +187,7 @@ - return __sync_add_and_fetch_2(addr, val); - #endif - case 4: -- return __sync_add_and_fetch_4(addr, val); -+ return __sync_add_and_fetch_4((uint32_t*) addr, val); - #if (CAA_BITS_PER_LONG == 64) - case 8: - return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh index 7e9ba2b75e..fbd8d80848 100644 --- a/eng/common/cross/build-android-rootfs.sh +++ b/eng/common/cross/build-android-rootfs.sh @@ -6,10 +6,11 @@ usage() { echo "Creates a toolchain and sysroot used for cross-compiling for Android." echo - echo "Usage: $0 [BuildArch] [ApiLevel]" + echo "Usage: $0 [BuildArch] [ApiLevel] [--ndk NDKVersion]" echo echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo "NDKVersion is the version of Android NDK. The default is r21. See https://developer.android.com/ndk/downloads/revision_history" echo echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" echo "by setting the TOOLCHAIN_DIR environment variable" @@ -25,10 +26,15 @@ __BuildArch=arm64 __AndroidArch=aarch64 __AndroidToolchain=aarch64-linux-android -for i in "$@" - do - lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" - case $lowerI in +while :; do + if [[ "$#" -le 0 ]]; then + break + fi + + i=$1 + + lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" + case $lowerI in -?|-h|--help) usage exit 1 @@ -43,6 +49,10 @@ for i in "$@" __AndroidArch=arm __AndroidToolchain=arm-linux-androideabi ;; + --ndk) + shift + __NDK_Version=$1 + ;; *[0-9]) __ApiLevel=$i ;; @@ -50,8 +60,17 @@ for i in "$@" __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" ;; esac + shift done +if [[ "$__NDK_Version" == "r21" ]] || [[ "$__NDK_Version" == "r22" ]]; then + __NDK_File_Arch_Spec=-x86_64 + __SysRoot=sysroot +else + __NDK_File_Arch_Spec= + __SysRoot=toolchains/llvm/prebuilt/linux-x86_64/sysroot +fi + # Obtain the location of the bash script to figure out where the root of the repo is. __ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -78,6 +97,7 @@ fi echo "Target API level: $__ApiLevel" echo "Target architecture: $__BuildArch" +echo "NDK version: $__NDK_Version" echo "NDK location: $__NDK_Dir" echo "Target Toolchain location: $__ToolchainDir" @@ -85,8 +105,8 @@ echo "Target Toolchain location: $__ToolchainDir" if [ ! -d $__NDK_Dir ]; then echo Downloading the NDK into $__NDK_Dir mkdir -p $__NDK_Dir - wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip - unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux$__NDK_File_Arch_Spec.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux.zip -d $__CrossDir fi if [ ! -d $__lldb_Dir ]; then @@ -116,16 +136,11 @@ for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/ fi done -cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/$__SysRoot/usr/" # Generate platform file for build.sh script to assign to __DistroRid echo "Generating platform file..." -echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform - -echo "Now to build coreclr, libraries and installers; run:" -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory coreclr -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory libraries -echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ - --subsetCategory installer +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/$__SysRoot/android_platform + +echo "Now to build coreclr, libraries and host; run:" +echo ROOTFS_DIR=$(realpath $__ToolchainDir/$__SysRoot) ./build.sh clr+libs+host --cross --arch $__BuildArch diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 4b5e8d7166..8abfb71f72 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -5,7 +5,7 @@ set -e usage() { echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" - echo "BuildArch can be: arm(default), arm64, armel, armv6, ppc64le, riscv64, s390x, x64, x86" + echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86" 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" @@ -15,6 +15,7 @@ usage() echo "llvmx[.y] - optional, LLVM version for LLVM related packages." echo "--skipunmount - optional, will skip the unmount of rootfs folder." echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." + echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems." echo "--use-mirror - optional, use mirror URL to fetch resources, when available." echo "--jobs N - optional, restrict to N jobs." exit 1 @@ -52,28 +53,27 @@ __UbuntuPackages+=" symlinks" __UbuntuPackages+=" libicu-dev" __UbuntuPackages+=" liblttng-ust-dev" __UbuntuPackages+=" libunwind8-dev" -__UbuntuPackages+=" libnuma-dev" __AlpinePackages+=" gettext-dev" __AlpinePackages+=" icu-dev" __AlpinePackages+=" libunwind-dev" __AlpinePackages+=" lttng-ust-dev" __AlpinePackages+=" compiler-rt" -__AlpinePackages+=" numactl-dev" # runtime libraries' dependencies __UbuntuPackages+=" libcurl4-openssl-dev" __UbuntuPackages+=" libkrb5-dev" __UbuntuPackages+=" libssl-dev" __UbuntuPackages+=" zlib1g-dev" +__UbuntuPackages+=" libbrotli-dev" __AlpinePackages+=" curl-dev" __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" -__FreeBSDBase="13.3-RELEASE" -__FreeBSDPkg="1.17.0" +__FreeBSDBase="13.4-RELEASE" +__FreeBSDPkg="1.21.3" __FreeBSDABI="13" __FreeBSDPackages="libunwind" __FreeBSDPackages+=" icu" @@ -91,18 +91,18 @@ __HaikuPackages="gcc_syslibs" __HaikuPackages+=" gcc_syslibs_devel" __HaikuPackages+=" gmp" __HaikuPackages+=" gmp_devel" -__HaikuPackages+=" icu66" -__HaikuPackages+=" icu66_devel" +__HaikuPackages+=" icu[0-9]+" +__HaikuPackages+=" icu[0-9]*_devel" __HaikuPackages+=" krb5" __HaikuPackages+=" krb5_devel" __HaikuPackages+=" libiconv" __HaikuPackages+=" libiconv_devel" -__HaikuPackages+=" llvm12_libunwind" -__HaikuPackages+=" llvm12_libunwind_devel" +__HaikuPackages+=" llvm[0-9]*_libunwind" +__HaikuPackages+=" llvm[0-9]*_libunwind_devel" __HaikuPackages+=" mpfr" __HaikuPackages+=" mpfr_devel" -__HaikuPackages+=" openssl" -__HaikuPackages+=" openssl_devel" +__HaikuPackages+=" openssl3" +__HaikuPackages+=" openssl3_devel" __HaikuPackages+=" zlib" __HaikuPackages+=" zlib_devel" @@ -128,10 +128,12 @@ __AlpineKeys=' 616adfeb:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0BFD1D4lIxQcsqEpQzU\npNCYM3aP1V/fxxVdT4DWvSI53JHTwHQamKdMWtEXetWVbP5zSROniYKFXd/xrD9X\n0jiGHey3lEtylXRIPxe5s+wXoCmNLcJVnvTcDtwx/ne2NLHxp76lyc25At+6RgE6\nADjLVuoD7M4IFDkAsd8UQ8zM0Dww9SylIk/wgV3ZkifecvgUQRagrNUdUjR56EBZ\nraQrev4hhzOgwelT0kXCu3snbUuNY/lU53CoTzfBJ5UfEJ5pMw1ij6X0r5S9IVsy\nKLWH1hiO0NzU2c8ViUYCly4Fe9xMTFc6u2dy/dxf6FwERfGzETQxqZvSfrRX+GLj\n/QZAXiPg5178hT/m0Y3z5IGenIC/80Z9NCi+byF1WuJlzKjDcF/TU72zk0+PNM/H\nKuppf3JT4DyjiVzNC5YoWJT2QRMS9KLP5iKCSThwVceEEg5HfhQBRT9M6KIcFLSs\nmFjx9kNEEmc1E8hl5IR3+3Ry8G5/bTIIruz14jgeY9u5jhL8Vyyvo41jgt9sLHR1\n/J1TxKfkgksYev7PoX6/ZzJ1ksWKZY5NFoDXTNYUgzFUTOoEaOg3BAQKadb3Qbbq\nXIrxmPBdgrn9QI7NCgfnAY3Tb4EEjs3ON/BNyEhUENcXOH6I1NbcuBQ7g9P73kE4\nVORdoc8MdJ5eoKBpO8Ww8HECAwEAAQ== 616ae350:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyduVzi1mWm+lYo2Tqt/0\nXkCIWrDNP1QBMVPrE0/ZlU2bCGSoo2Z9FHQKz/mTyMRlhNqTfhJ5qU3U9XlyGOPJ\npiM+b91g26pnpXJ2Q2kOypSgOMOPA4cQ42PkHBEqhuzssfj9t7x47ppS94bboh46\nxLSDRff/NAbtwTpvhStV3URYkxFG++cKGGa5MPXBrxIp+iZf9GnuxVdST5PGiVGP\nODL/b69sPJQNbJHVquqUTOh5Ry8uuD2WZuXfKf7/C0jC/ie9m2+0CttNu9tMciGM\nEyKG1/Xhk5iIWO43m4SrrT2WkFlcZ1z2JSf9Pjm4C2+HovYpihwwdM/OdP8Xmsnr\nDzVB4YvQiW+IHBjStHVuyiZWc+JsgEPJzisNY0Wyc/kNyNtqVKpX6dRhMLanLmy+\nf53cCSI05KPQAcGj6tdL+D60uKDkt+FsDa0BTAobZ31OsFVid0vCXtsbplNhW1IF\nHwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk\nOk9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6\n9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT\nJZDr++FjKrnnijbyNF8b98UCAwEAAQ== 616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== +66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ== ' __Keyring= __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 +__SkipEmulation=0 __UseMirror=0 __UnprocessedBuildArgs= @@ -162,9 +164,13 @@ while :; do armel) __BuildArch=armel __UbuntuArch=armel - __UbuntuRepo="http://ftp.debian.org/debian/" - __CodeName=jessie + __UbuntuRepo="http://archive.debian.org/debian/" + __CodeName=buster __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" + __LLDB_Package="liblldb-6.0-dev" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + __UbuntuSuites= ;; armv6) __BuildArch=armv6 @@ -180,6 +186,18 @@ while :; do __Keyring="--keyring $__KeyringFile" fi ;; + loongarch64) + __BuildArch=loongarch64 + __AlpineArch=loongarch64 + __QEMUArch=loongarch64 + __UbuntuArch=loong64 + __UbuntuSuites=unreleased + __LLDB_Package="liblldb-19-dev" + + if [[ "$__CodeName" == "sid" ]]; then + __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" + fi + ;; riscv64) __BuildArch=riscv64 __AlpineArch=riscv64 @@ -264,44 +282,21 @@ while :; do ;; xenial) # Ubuntu 16.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=xenial - fi - ;; - zesty) # Ubuntu 17.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=zesty - fi + __CodeName=xenial ;; bionic) # Ubuntu 18.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=bionic - fi + __CodeName=bionic ;; focal) # Ubuntu 20.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=focal - fi + __CodeName=focal ;; jammy) # Ubuntu 22.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=jammy - fi + __CodeName=jammy ;; noble) # Ubuntu 24.04 - if [[ "$__CodeName" != "jessie" ]]; then - __CodeName=noble - fi - if [[ -n "$__LLDB_Package" ]]; then - __LLDB_Package="liblldb-18-dev" - fi - ;; - jessie) # Debian 8 - __CodeName=jessie - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" - - if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __CodeName=noble + if [[ -z "$__LLDB_Package" ]]; then + __LLDB_Package="liblldb-19-dev" fi ;; stretch) # Debian 9 @@ -319,7 +314,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="http://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 @@ -340,10 +335,28 @@ while :; do ;; sid) # Debian sid __CodeName=sid - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" + __UbuntuSuites= - if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + # Debian-Ports architectures need different values + case "$__UbuntuArch" in + amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x) + __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + *) + __KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" + fi + ;; + esac + + if [[ -e "$__KeyringFile" ]]; then + __Keyring="--keyring $__KeyringFile" fi ;; tizen) @@ -370,7 +383,7 @@ while :; do ;; freebsd14) __CodeName=freebsd - __FreeBSDBase="14.0-RELEASE" + __FreeBSDBase="14.2-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; @@ -388,6 +401,9 @@ while :; do --skipsigcheck) __SkipSigCheck=1 ;; + --skipemulation) + __SkipEmulation=1 + ;; --rootfsdir|-rootfsdir) shift __RootfsDir="$1" @@ -420,16 +436,15 @@ case "$__AlpineVersion" in elif [[ "$__AlpineArch" == "x86" ]]; then __AlpineVersion=3.17 # minimum version that supports lldb-dev __AlpinePackages+=" llvm15-libs" - elif [[ "$__AlpineArch" == "riscv64" ]]; then + elif [[ "$__AlpineArch" == "riscv64" || "$__AlpineArch" == "loongarch64" ]]; then + __AlpineVersion=3.21 # minimum version that supports lldb-dev + __AlpinePackages+=" llvm19-libs" + elif [[ -n "$__AlpineMajorVersion" ]]; then + # use whichever alpine version is provided and select the latest toolchain libs __AlpineLlvmLibsLookup=1 - __AlpineVersion=edge # minimum version with APKINDEX.tar.gz (packages archive) else __AlpineVersion=3.13 # 3.13 to maximize compatibility __AlpinePackages+=" llvm10-libs" - - if [[ "$__AlpineArch" == "armv7" ]]; then - __AlpinePackages="${__AlpinePackages//numactl-dev/}" - fi fi esac @@ -439,15 +454,6 @@ if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" fi -if [[ "$__BuildArch" == "armel" ]]; then - __LLDB_Package="lldb-3.5-dev" -fi - -if [[ "$__CodeName" == "xenial" && "$__UbuntuArch" == "armhf" ]]; then - # libnuma-dev is not available on armhf for xenial - __UbuntuPackages="${__UbuntuPackages//libnuma-dev/}" -fi - __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then @@ -496,7 +502,7 @@ if [[ "$__CodeName" == "alpine" ]]; then arch="$(uname -m)" ensureDownloadTool - + if [[ "$__hasWget" == 1 ]]; then wget -P "$__ApkToolsDir" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v$__ApkToolsVersion/$arch/apk.static" else @@ -512,11 +518,6 @@ if [[ "$__CodeName" == "alpine" ]]; then echo "$__ApkToolsSHA512SUM $__ApkToolsDir/apk.static" | sha512sum -c chmod +x "$__ApkToolsDir/apk.static" - if [[ -f "/usr/bin/qemu-$__QEMUArch-static" ]]; then - mkdir -p "$__RootfsDir"/usr/bin - cp -v "/usr/bin/qemu-$__QEMUArch-static" "$__RootfsDir/usr/bin" - fi - if [[ "$__AlpineVersion" == "edge" ]]; then version=edge else @@ -536,6 +537,10 @@ if [[ "$__CodeName" == "alpine" ]]; then __ApkSignatureArg="--keys-dir $__ApkKeysDir" fi + if [[ "$__SkipEmulation" == "1" ]]; then + __NoEmulationArg="--no-scripts" + fi + # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ @@ -557,7 +562,7 @@ if [[ "$__CodeName" == "alpine" ]]; then "$__ApkToolsDir/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ - -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages rm -r "$__ApkToolsDir" @@ -573,7 +578,7 @@ elif [[ "$__CodeName" == "freebsd" ]]; then curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version fi echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf - echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"${__RootfsDir}/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf + echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf mkdir -p "$__RootfsDir"/tmp # get and build package manager if [[ "$__hasWget" == 1 ]]; then @@ -681,7 +686,7 @@ elif [[ "$__CodeName" == "haiku" ]]; then ensureDownloadTool - echo "Downloading Haiku package tool" + echo "Downloading Haiku package tools" git clone https://github.com/haiku/haiku-toolchains-ubuntu --depth 1 "$__RootfsDir/tmp/script" if [[ "$__hasWget" == 1 ]]; then wget -O "$__RootfsDir/tmp/download/hosttools.zip" "$("$__RootfsDir/tmp/script/fetch.sh" --hosttools)" @@ -691,34 +696,42 @@ elif [[ "$__CodeName" == "haiku" ]]; then unzip -o "$__RootfsDir/tmp/download/hosttools.zip" -d "$__RootfsDir/tmp/bin" - DepotBaseUrl="https://depot.haiku-os.org/__api/v2/pkg/get-pkg" - HpkgBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" + HaikuBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" + HaikuPortsBaseUrl="https://eu.hpkg.haiku-os.org/haikuports/master/$__HaikuArch/current" + + echo "Downloading HaikuPorts package repository index..." + if [[ "$__hasWget" == 1 ]]; then + wget -P "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" + else + curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuPortsBaseUrl/repo" + fi - # Download Haiku packages echo "Downloading Haiku packages" read -ra array <<<"$__HaikuPackages" for package in "${array[@]}"; do echo "Downloading $package..." - # API documented here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L60 - # The schema here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L598 + hpkgFilename="$(LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package_repo" list -f "$__RootfsDir/tmp/download/repo" | + grep -E "${package}-" | sort -V | tail -n 1 | xargs)" + if [ -z "$hpkgFilename" ]; then + >&2 echo "ERROR: package $package missing." + exit 1 + fi + echo "Resolved filename: $hpkgFilename..." + hpkgDownloadUrl="$HaikuPortsBaseUrl/packages/$hpkgFilename" if [[ "$__hasWget" == 1 ]]; then - hpkgDownloadUrl="$(wget -qO- --post-data '{"name":"'"$package"'","repositorySourceCode":"haikuports_'$__HaikuArch'","versionType":"LATEST","naturalLanguageCode":"en"}' \ - --header 'Content-Type:application/json' "$DepotBaseUrl" | jq -r '.result.versions[].hpkgDownloadURL')" wget -P "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" else - hpkgDownloadUrl="$(curl -sSL -XPOST --data '{"name":"'"$package"'","repositorySourceCode":"haikuports_'$__HaikuArch'","versionType":"LATEST","naturalLanguageCode":"en"}' \ - --header 'Content-Type:application/json' "$DepotBaseUrl" | jq -r '.result.versions[].hpkgDownloadURL')" curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" fi done for package in haiku haiku_devel; do echo "Downloading $package..." if [[ "$__hasWget" == 1 ]]; then - hpkgVersion="$(wget -qO- "$HpkgBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" - wget -P "$__RootfsDir/tmp/download" "$HpkgBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" + hpkgVersion="$(wget -qO- "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" + wget -P "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" else - hpkgVersion="$(curl -sSL "$HpkgBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" - curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HpkgBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" + hpkgVersion="$(curl -sSL "$HaikuBaseUrl" | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" + curl -SLO --create-dirs --output-dir "$__RootfsDir/tmp/download" "$HaikuBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" fi done @@ -744,25 +757,67 @@ elif [[ "$__CodeName" == "haiku" ]]; then popd rm -rf "$__RootfsDir/tmp" elif [[ -n "$__CodeName" ]]; then + __Suites="$__CodeName $(for suite in $__UbuntuSuites; do echo -n "$__CodeName-$suite "; done)" + + if [[ "$__SkipEmulation" == "1" ]]; then + if [[ -z "$AR" ]]; then + if command -v ar &>/dev/null; then + AR="$(command -v ar)" + elif command -v llvm-ar &>/dev/null; then + AR="$(command -v llvm-ar)" + else + echo "Unable to find ar or llvm-ar on PATH, add them to PATH or set AR environment variable pointing to the available AR tool" + exit 1 + fi + fi + + PYTHON=${PYTHON_EXECUTABLE:-python3} + + # shellcheck disable=SC2086,SC2046 + echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ + $__UbuntuPackages + + # shellcheck disable=SC2086,SC2046 + "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ + $__UbuntuPackages + exit 0 + fi + + __UpdateOptions= if [[ "$__SkipSigCheck" == "0" ]]; then __Keyring="$__Keyring --force-check-gpg" + else + __Keyring= + __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" fi # shellcheck disable=SC2086 echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" - debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" + # shellcheck disable=SC2086 + if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then + echo "debootstrap failed! dumping debootstrap.log" + cat "$__RootfsDir/debootstrap/debootstrap.log" + exit 1 + fi + + rm -rf "$__RootfsDir"/etc/apt/*.{sources,list} "$__RootfsDir"/etc/apt/sources.list.d mkdir -p "$__RootfsDir/etc/apt/sources.list.d/" + + # shellcheck disable=SC2086 cat > "$__RootfsDir/etc/apt/sources.list.d/$__CodeName.sources" < token2) - (token1 < token2) + else: + return -1 if isinstance(token1, str) else 1 + + return len(tokens1) - len(tokens2) + +def compare_debian_versions(version1, version2): + """Compare two Debian package versions.""" + epoch1, upstream1, revision1 = parse_debian_version(version1) + epoch2, upstream2, revision2 = parse_debian_version(version2) + + if epoch1 != epoch2: + return epoch1 - epoch2 + + result = compare_upstream_version(upstream1, upstream2) + if result != 0: + return result + + return compare_upstream_version(revision1, revision2) + +def resolve_dependencies(packages, aliases, desired_packages): + """Recursively resolves dependencies for the desired packages.""" + resolved = [] + to_process = deque(desired_packages) + + while to_process: + current = to_process.popleft() + resolved_package = current if current in packages else aliases.get(current, [None])[0] + + if not resolved_package: + print(f"Error: Package '{current}' was not found in the available packages.") + sys.exit(1) + + if resolved_package not in resolved: + resolved.append(resolved_package) + + deps = packages.get(resolved_package, {}).get("Depends", "") + if deps: + deps = [dep.split(' ')[0] for dep in deps.split(', ') if dep] + for dep in deps: + if dep not in resolved and dep not in to_process and dep in packages: + to_process.append(dep) + + return resolved + +def parse_package_index(content): + """Parses the Packages.gz file and returns package information.""" + packages = {} + aliases = {} + entries = re.split(r'\n\n+', content) + + for entry in entries: + fields = dict(re.findall(r'^(\S+): (.+)$', entry, re.MULTILINE)) + if "Package" in fields: + package_name = fields["Package"] + version = fields.get("Version") + filename = fields.get("Filename") + depends = fields.get("Depends") + provides = fields.get("Provides", None) + + # Only update if package_name is not in packages or if the new version is higher + if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0: + packages[package_name] = { + "Version": version, + "Filename": filename, + "Depends": depends + } + + # Update aliases if package provides any alternatives + if provides: + provides_list = [x.strip() for x in provides.split(",")] + for alias in provides_list: + # Strip version specifiers + alias_name = re.sub(r'\s*\(=.*\)', '', alias) + if alias_name not in aliases: + aliases[alias_name] = [] + if package_name not in aliases[alias_name]: + aliases[alias_name].append(package_name) + + return packages, aliases + +def install_packages(mirror, packages_info, aliases, tmp_dir, extract_dir, ar_tool, desired_packages): + """Downloads .deb files and extracts them.""" + resolved_packages = resolve_dependencies(packages_info, aliases, desired_packages) + print(f"Resolved packages (including dependencies): {resolved_packages}") + + packages_to_download = {} + + for pkg in resolved_packages: + if pkg in packages_info: + packages_to_download[pkg] = packages_info[pkg] + + if pkg in aliases: + for alias in aliases[pkg]: + if alias in packages_info: + packages_to_download[alias] = packages_info[alias] + + asyncio.run(download_deb_files_parallel(mirror, packages_to_download, tmp_dir)) + + package_to_deb_file_map = {} + for pkg in resolved_packages: + pkg_info = packages_info.get(pkg) + if pkg_info: + deb_filename = pkg_info.get("Filename") + if deb_filename: + deb_file_path = os.path.join(tmp_dir, os.path.basename(deb_filename)) + package_to_deb_file_map[pkg] = deb_file_path + + for pkg in reversed(resolved_packages): + deb_file = package_to_deb_file_map.get(pkg) + if deb_file and os.path.exists(deb_file): + extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool) + + print("All done!") + +def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): + """Extract .deb file contents""" + + os.makedirs(extract_dir, exist_ok=True) + + with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir: + result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + tar_filename = None + for line in result.stdout.decode().splitlines(): + if line.startswith("data.tar"): + tar_filename = line.strip() + break + + if not tar_filename: + raise FileNotFoundError(f"Could not find 'data.tar.*' in {deb_file}.") + + tar_file_path = os.path.join(tmp_subdir, tar_filename) + print(f"Extracting {tar_filename} from {deb_file}..") + + subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True) + + file_extension = os.path.splitext(tar_file_path)[1].lower() + + if file_extension == ".xz": + mode = "r:xz" + elif file_extension == ".gz": + mode = "r:gz" + elif file_extension == ".zst": + # zstd is not supported by standard library yet + decompressed_tar_path = tar_file_path.replace(".zst", "") + with open(tar_file_path, "rb") as zst_file, open(decompressed_tar_path, "wb") as decompressed_file: + dctx = zstandard.ZstdDecompressor() + dctx.copy_stream(zst_file, decompressed_file) + + tar_file_path = decompressed_tar_path + mode = "r" + else: + raise ValueError(f"Unsupported compression format: {file_extension}") + + with tarfile.open(tar_file_path, mode) as tar: + tar.extractall(path=extract_dir, filter='fully_trusted') + +def finalize_setup(rootfsdir): + lib_dir = os.path.join(rootfsdir, 'lib') + usr_lib_dir = os.path.join(rootfsdir, 'usr', 'lib') + + if os.path.exists(lib_dir): + if os.path.islink(lib_dir): + os.remove(lib_dir) + else: + os.makedirs(usr_lib_dir, exist_ok=True) + + for item in os.listdir(lib_dir): + src = os.path.join(lib_dir, item) + dest = os.path.join(usr_lib_dir, item) + + if os.path.isdir(src): + shutil.copytree(src, dest, dirs_exist_ok=True) + else: + shutil.copy2(src, dest) + + shutil.rmtree(lib_dir) + + os.symlink(usr_lib_dir, lib_dir) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS") + parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)") + parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)") + parser.add_argument("--rootfsdir", required=True, help="Destination directory.") + parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.') + parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") + parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)") + parser.add_argument("packages", nargs="+", help="List of package names to be installed.") + + args = parser.parse_args() + + if args.mirror is None: + if args.distro == "ubuntu": + args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports" + elif args.distro == "debian": + args.mirror = "http://ftp.debian.org/debian-ports" + else: + raise Exception("Unsupported distro") + + DESIRED_PACKAGES = args.packages + [ # base packages + "dpkg", + "busybox", + "libc-bin", + "base-files", + "base-passwd", + "debianutils" + ] + + print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") + + package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite)) + + packages_info, aliases = parse_package_index(package_index_content) + + with tempfile.TemporaryDirectory() as tmp_dir: + install_packages(args.mirror, packages_info, aliases, tmp_dir, args.rootfsdir, args.artool, DESIRED_PACKAGES) + + finalize_setup(args.rootfsdir) diff --git a/eng/common/cross/tizen-fetch.sh b/eng/common/cross/tizen-fetch.sh index 28936ceef3..37c3a61f1d 100644 --- a/eng/common/cross/tizen-fetch.sh +++ b/eng/common/cross/tizen-fetch.sh @@ -156,13 +156,8 @@ fetch_tizen_pkgs() done } -if [ "$TIZEN_ARCH" == "riscv64" ]; then - BASE="Tizen-Base-RISCV" - UNIFIED="Tizen-Unified-RISCV" -else - BASE="Tizen-Base" - UNIFIED="Tizen-Unified" -fi +BASE="Tizen-Base" +UNIFIED="Tizen-Unified" Inform "Initialize ${TIZEN_ARCH} base" fetch_tizen_pkgs_init standard $BASE diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 9a4e285a5a..0ff85cf036 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -40,7 +40,7 @@ if(TARGET_ARCH_NAME STREQUAL "arm") set(TOOLCHAIN "arm-linux-gnueabihf") endif() if(TIZEN) - set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf/9.2.0") + set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf") endif() elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(CMAKE_SYSTEM_PROCESSOR aarch64) @@ -49,7 +49,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") elseif(LINUX) set(TOOLCHAIN "aarch64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") @@ -58,7 +58,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) set(TOOLCHAIN "arm-linux-gnueabi") if(TIZEN) - set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0") + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi") endif() elseif(TARGET_ARCH_NAME STREQUAL "armv6") set(CMAKE_SYSTEM_PROCESSOR armv6l) @@ -67,6 +67,13 @@ elseif(TARGET_ARCH_NAME STREQUAL "armv6") else() set(TOOLCHAIN "arm-linux-gnueabihf") endif() +elseif(TARGET_ARCH_NAME STREQUAL "loongarch64") + set(CMAKE_SYSTEM_PROCESSOR "loongarch64") + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/loongarch64-alpine-linux-musl) + set(TOOLCHAIN "loongarch64-alpine-linux-musl") + else() + set(TOOLCHAIN "loongarch64-linux-gnu") + endif() elseif(TARGET_ARCH_NAME STREQUAL "ppc64le") set(CMAKE_SYSTEM_PROCESSOR ppc64le) if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl) @@ -81,7 +88,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "riscv64") else() set(TOOLCHAIN "riscv64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu/13.1.0") + set(TIZEN_TOOLCHAIN "riscv64-tizen-linux-gnu") endif() endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") @@ -98,7 +105,7 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") elseif(LINUX) set(TOOLCHAIN "x86_64-linux-gnu") if(TIZEN) - set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") @@ -115,10 +122,10 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") set(TOOLCHAIN "i686-linux-gnu") endif() if(TIZEN) - set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu/9.2.0") + set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu") endif() else() - message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, ppc64le, riscv64, s390x, x64 and x86 are supported!") + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64 and x86 are supported!") endif() if(DEFINED ENV{TOOLCHAIN}) @@ -127,32 +134,46 @@ endif() # Specify include paths if(TIZEN) - if(TARGET_ARCH_NAME STREQUAL "arm") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7hl-tizen-linux-gnueabihf) - endif() - if(TARGET_ARCH_NAME STREQUAL "armel") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) - endif() - if(TARGET_ARCH_NAME STREQUAL "arm64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "x86") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/i586-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "x64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/x86_64-tizen-linux-gnu) - endif() - if(TARGET_ARCH_NAME STREQUAL "riscv64") - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) - include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/riscv64-tizen-linux-gnu) + function(find_toolchain_dir prefix) + # Dynamically find the version subdirectory + file(GLOB DIRECTORIES "${prefix}/*") + list(GET DIRECTORIES 0 FIRST_MATCH) + get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME) + + set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE) + endfunction() + + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + else() + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() + + message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") + + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() +function(locate_toolchain_exec exec var) + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) +endfunction() + if(ANDROID) if(TARGET_ARCH_NAME STREQUAL "arm") set(ANDROID_ABI armeabi-v7a) @@ -183,66 +204,24 @@ elseif(FREEBSD) set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld") elseif(ILLUMOS) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") include_directories(SYSTEM ${CROSS_ROOTFS}/include) - set(TOOLSET_PREFIX ${TOOLCHAIN}-) - function(locate_toolchain_exec exec var) - string(TOUPPER ${exec} EXEC_UPPERCASE) - if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") - set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) - return() - endif() - - find_program(EXEC_LOCATION_${exec} - NAMES - "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" - "${TOOLSET_PREFIX}${exec}") - - if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") - message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") - endif() - set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) - endfunction() - - set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") - locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) - - set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") - set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") elseif(HAIKU) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") - - set(TOOLSET_PREFIX ${TOOLCHAIN}-) - function(locate_toolchain_exec exec var) - string(TOUPPER ${exec} EXEC_UPPERCASE) - if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") - set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) - return() - endif() - - find_program(EXEC_LOCATION_${exec} - NAMES - "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" - "${TOOLSET_PREFIX}${exec}") - - if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") - message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") - endif() - set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) - endfunction() - set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") locate_toolchain_exec(gcc CMAKE_C_COMPILER) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) - set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") - set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") - # let CMake set up the correct search paths include(Platform/Haiku) else() @@ -272,21 +251,21 @@ endif() if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64|riscv64)$") if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") - add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}") endif() elseif(TARGET_ARCH_NAME STREQUAL "s390x") add_toolchain_linker_flag("--target=${TOOLCHAIN}") @@ -297,10 +276,10 @@ elseif(TARGET_ARCH_NAME STREQUAL "x86") endif() add_toolchain_linker_flag(-m32) if(TIZEN) - add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") - add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") endif() elseif(ILLUMOS) add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") @@ -312,7 +291,7 @@ endif() # Specify compile options -if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) 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.sh b/eng/common/darc-init.sh index 36dbd45e1c..e889f439b8 100644 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -68,7 +68,7 @@ function InstallDarcCli { fi fi - local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" 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." diff --git a/eng/common/dotnet.cmd b/eng/common/dotnet.cmd new file mode 100644 index 0000000000..527fa4bb38 --- /dev/null +++ b/eng/common/dotnet.cmd @@ -0,0 +1,7 @@ +@echo off + +:: This script is used to install the .NET SDK. +:: It will also invoke the SDK with any provided arguments. + +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet.ps1""" %*" +exit /b %ErrorLevel% diff --git a/eng/common/dotnet.ps1 b/eng/common/dotnet.ps1 new file mode 100644 index 0000000000..45e5676c9e --- /dev/null +++ b/eng/common/dotnet.ps1 @@ -0,0 +1,11 @@ +# This script is used to install the .NET SDK. +# It will also invoke the SDK with any provided arguments. + +. $PSScriptRoot\tools.ps1 +$dotnetRoot = InitializeDotNetCli -install:$true + +# Invoke acquired SDK with args if they are provided +if ($args.count -gt 0) { + $env:DOTNET_NOLOGO=1 + & "$dotnetRoot\dotnet.exe" $args +} diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh new file mode 100644 index 0000000000..2ef6823567 --- /dev/null +++ b/eng/common/dotnet.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# This script is used to install the .NET SDK. +# It will also invoke the SDK with any provided arguments. + +source="${BASH_SOURCE[0]}" +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +source $scriptroot/tools.sh +InitializeDotNetCli true # install + +# Invoke acquired SDK with args if they are provided +if [[ $# > 0 ]]; then + __dotnetDir=${_InitializeDotNetCli} + dotnetPath=${__dotnetDir}/dotnet + ${dotnetPath} "$@" +fi diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 index 524aaa57f2..fa1cdc2b30 100644 --- a/eng/common/generate-locproject.ps1 +++ b/eng/common/generate-locproject.ps1 @@ -33,15 +33,27 @@ $jsonTemplateFiles | ForEach-Object { $jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern +$wxlFilesV3 = @() +$wxlFilesV5 = @() $wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them if (-not $wxlFiles) { $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files if ($wxlEnFiles) { - $wxlFiles = @() - $wxlEnFiles | ForEach-Object { - $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" - $wxlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru - } + $wxlFiles = @() + $wxlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $content = Get-Content $_.FullName -Raw + + # Split files on schema to select different parser settings in the generated project. + if ($content -like "*http://wixtoolset.org/schemas/v4/wxl*") + { + $wxlFilesV5 += Copy-Item $_.FullName -Destination $destinationFile -PassThru + } + elseif ($content -like "*http://schemas.microsoft.com/wix/2006/localization*") + { + $wxlFilesV3 += Copy-Item $_.FullName -Destination $destinationFile -PassThru + } + } } } @@ -114,7 +126,32 @@ $locJson = @{ CloneLanguageSet = "WiX_CloneLanguages" LssFiles = @( "wxl_loc.lss" ) LocItems = @( - $wxlFiles | ForEach-Object { + $wxlFilesV3 | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) + { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "WiX_CloneLanguages" + LssFiles = @( "P210WxlSchemaV4.lss" ) + LocItems = @( + $wxlFilesV5 | ForEach-Object { $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 deleted file mode 100644 index 3e5c1c74a1..0000000000 --- a/eng/common/generate-sbom-prep.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -Param( - [Parameter(Mandatory=$true)][string] $ManifestDirPath # Manifest directory where sbom will be placed -) - -. $PSScriptRoot\pipeline-logging-functions.ps1 - -Write-Host "Creating dir $ManifestDirPath" -# create directory for sbom manifest to be placed -if (!(Test-Path -path $ManifestDirPath)) -{ - New-Item -ItemType Directory -path $ManifestDirPath - Write-Host "Successfully created directory $ManifestDirPath" -} -else{ - Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." -} - -Write-Host "Updating artifact name" -$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' -Write-Host "Artifact name $artifact_name" -Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh deleted file mode 100644 index d5c76dc827..0000000000 --- a/eng/common/generate-sbom-prep.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -source="${BASH_SOURCE[0]}" - -# resolve $SOURCE until the file is no longer a symlink -while [[ -h $source ]]; do - scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" - source="$(readlink "$source")" - - # if $source was a relative symlink, we need to resolve it relative to the path where the - # symlink file was located - [[ $source != /* ]] && source="$scriptroot/$source" -done -scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -. $scriptroot/pipeline-logging-functions.sh - -manifest_dir=$1 - -if [ ! -d "$manifest_dir" ] ; then - mkdir -p "$manifest_dir" - echo "Sbom directory created." $manifest_dir -else - Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." -fi - -artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" -echo "Artifact name before : "$artifact_name -# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. -safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" -echo "Artifact name after : "$safe_artifact_name -export ARTIFACT_NAME=$safe_artifact_name -echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" - -exit 0 diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 index 92b77347d9..c282d3ae40 100644 --- a/eng/common/internal-feed-operations.ps1 +++ b/eng/common/internal-feed-operations.ps1 @@ -26,7 +26,7 @@ function SetupCredProvider { $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." - Invoke-WebRequest $url -OutFile installcredprovider.ps1 + Invoke-WebRequest $url -UseBasicParsing -OutFile installcredprovider.ps1 Write-Host 'Installing plugin...' .\installcredprovider.ps1 -Force diff --git a/eng/common/internal/NuGet.config b/eng/common/internal/NuGet.config index 19d3d311b1..f70261ed68 100644 --- a/eng/common/internal/NuGet.config +++ b/eng/common/internal/NuGet.config @@ -4,4 +4,7 @@ + + +
diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj index 32f79dfb34..feaa6d2081 100644 --- a/eng/common/internal/Tools.csproj +++ b/eng/common/internal/Tools.csproj @@ -15,16 +15,6 @@ - - - - https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; - - - $(RestoreSources); - https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; - - diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh new file mode 100644 index 0000000000..477a44f335 --- /dev/null +++ b/eng/common/native/install-dependencies.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +set -e + +# This is a simple script primarily used for CI to install necessary dependencies +# +# Usage: +# +# ./install-dependencies.sh + +os="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + +if [ -z "$os" ]; then + . "$(dirname "$0")"/init-os-and-arch.sh +fi + +case "$os" in + linux) + if [ -e /etc/os-release ]; then + . /etc/os-release + fi + + if [ "$ID" = "debian" ] || [ "$ID_LIKE" = "debian" ]; then + 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 + + 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" ]; 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 + elif [ "$ID" = "alpine" ]; then + 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 + fi + ;; + + osx|maccatalyst|ios|iossimulator|tvos|tvossimulator) + echo "Installed xcode version: $(xcode-select -p)" + + export HOMEBREW_NO_INSTALL_CLEANUP=1 + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 + # brew update --preinstall + brew bundle --no-upgrade --file=- < Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -excludeCIBinaryLog When running on CI, allow no binary log (short: -nobl)" Write-Host "" Write-Host "Command line arguments not listed above are passed thru to msbuild." } @@ -34,10 +39,11 @@ function Print-Usage() { function Build([string]$target) { $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" + $binaryLogArg = if ($binaryLog) { "/bl:$log" } else { "" } $outputPath = Join-Path $ToolsetDir "$task\" MSBuild $taskProject ` - /bl:$log ` + $binaryLogArg ` /t:$target ` /p:Configuration=$configuration ` /p:RepoRoot=$RepoRoot ` @@ -64,7 +70,7 @@ try { $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 "17.10.0-pre.4.0" -MemberType NoteProperty + $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 diff --git a/eng/common/sdk-task.sh b/eng/common/sdk-task.sh new file mode 100644 index 0000000000..3270f83fa9 --- /dev/null +++ b/eng/common/sdk-task.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +show_usage() { + echo "Common settings:" + echo " --task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + echo " --restore Restore dependencies" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + echo " --help Print help and exit" + echo "" + + echo "Advanced settings:" + echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" + echo " --noWarnAsError Do not warn as error" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +Build() { + local target=$1 + local log_suffix="" + [[ "$target" != "Execute" ]] && log_suffix=".$target" + local log="$log_dir/$task$log_suffix.binlog" + local binaryLogArg="" + [[ $binary_log == true ]] && binaryLogArg="/bl:$log" + local output_path="$toolset_dir/$task/" + + MSBuild "$taskProject" \ + $binaryLogArg \ + /t:"$target" \ + /p:Configuration="$configuration" \ + /p:RepoRoot="$repo_root" \ + /p:BaseIntermediateOutputPath="$output_path" \ + /v:"$verbosity" \ + $properties +} + +binary_log=true +configuration="Debug" +verbosity="minimal" +exclude_ci_binary_log=false +restore=false +help=false +properties='' +warnAsError=true + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --task) + task=$2 + shift 2 + ;; + --restore) + restore=true + shift 1 + ;; + --verbosity) + verbosity=$2 + shift 2 + ;; + --excludecibinarylog|--nobl) + binary_log=false + exclude_ci_binary_log=true + shift 1 + ;; + --noWarnAsError) + warnAsError=false + shift 1 + ;; + --help) + help=true + shift 1 + ;; + *) + properties="$properties $1" + shift 1 + ;; + esac +done + +ci=true + +if $help; then + show_usage + exit 0 +fi + +. "$scriptroot/tools.sh" +InitializeToolset + +if [[ -z "$task" ]]; then + Write-PipelineTelemetryError -Category 'Task' -Name 'MissingTask' -Message "Missing required parameter '-task '" + ExitWithExitCode 1 +fi + +taskProject=$(GetSdkTaskProject "$task") +if [[ ! -e "$taskProject" ]]; then + Write-PipelineTelemetryError -Category 'Task' -Name 'UnknownTask' -Message "Unknown task: $task" + ExitWithExitCode 1 +fi + +if $restore; then + Build "Restore" +fi + +Build "Execute" + + +ExitWithExitCode 0 diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config index 4585cfd6bb..e5f543ea68 100644 --- a/eng/common/sdl/packages.config +++ b/eng/common/sdl/packages.config @@ -1,4 +1,4 @@ - + diff --git a/eng/common/template-guidance.md b/eng/common/template-guidance.md index 5ef6c30ba9..e2b07a865f 100644 --- a/eng/common/template-guidance.md +++ b/eng/common/template-guidance.md @@ -50,14 +50,14 @@ extends: - task: CopyFiles@2 displayName: Gather build output inputs: - SourceFolder: '$(Build.SourcesDirectory)/artifacts/marvel' + SourceFolder: '$(System.DefaultWorkingDirectory)/artifacts/marvel' Contents: '**' TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/marvel' ``` Note: Multiple outputs are ONLY applicable to 1ES PT publishing (only usable when referencing `templates-official`). -# Development notes +## Development notes **Folder / file structure** @@ -82,7 +82,6 @@ eng\common\ publish-build-artifacts.yml (logic) publish-pipeline-artifacts.yml (logic) component-governance.yml (shim) - generate-sbom.yml (shim) publish-logs.yml (shim) retain-build.yml (shim) send-to-helix.yml (shim) @@ -107,7 +106,6 @@ eng\common\ setup-maestro-vars.yml (logic) steps\ component-governance.yml (logic) - generate-sbom.yml (logic) publish-build-artifacts.yml (redirect) publish-logs.yml (logic) publish-pipeline-artifacts.yml (redirect) diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 605692d2fb..d68e9fbc26 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -1,23 +1,15 @@ parameters: -# Sbom related params - enableSbom: true runAsPublic: false - PackageVersion: 9.0.0 - BuildDropPath: '$(Build.SourcesDirectory)/artifacts' +# Sbom related params, unused now and can eventually be removed + enableSbom: unused + PackageVersion: unused + BuildDropPath: unused jobs: - template: /eng/common/core-templates/job/job.yml parameters: is1ESPipeline: true - componentGovernanceSteps: - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: - - template: /eng/common/templates/steps/generate-sbom.yml - parameters: - PackageVersion: ${{ parameters.packageVersion }} - BuildDropPath: ${{ parameters.buildDropPath }} - publishArtifacts: false - # publish artifacts # for 1ES managed templates, use the templateContext.output to handle multiple outputs. templateContext: @@ -25,11 +17,19 @@ jobs: outputs: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - - output: buildArtifacts + - output: pipelineArtifact displayName: Publish pipeline artifacts - PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' - ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} - condition: always() + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' + artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + condition: succeeded() + retryCountOnTaskFailure: 10 # for any files being locked + continueOnError: true + - output: pipelineArtifact + displayName: Publish pipeline artifacts + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' + artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }}_Attempt$(System.JobAttempt) + condition: not(succeeded()) + retryCountOnTaskFailure: 10 # for any files being locked continueOnError: true - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - output: pipelineArtifact @@ -38,17 +38,18 @@ jobs: displayName: 'Publish logs' continueOnError: true condition: always() - sbomEnabled: false # we don't need SBOM for logs + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # logs are non-production artifacts - ${{ if eq(parameters.enablePublishBuildArtifacts, true) }}: - - output: buildArtifacts + - output: pipelineArtifact displayName: Publish Logs - PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' - publishLocation: Container - ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' + artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)_Attempt$(System.JobAttempt)' ) }} continueOnError: true condition: always() - sbomEnabled: false # we don't need SBOM for logs + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # logs are non-production artifacts - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - output: pipelineArtifact @@ -56,14 +57,20 @@ jobs: artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true - sbomEnabled: false # we don't need SBOM for BuildConfiguration + retryCountOnTaskFailure: 10 # for any files being locked + isProduction: false # BuildConfiguration is a non-production artifact - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + # V4 publishing: automatically publish staged artifacts as a pipeline artifact. + # The artifact name matches the SDK's FutureArtifactName ($(System.PhaseName)_Artifacts), + # which is encoded in the asset manifest for downstream publishing to discover. + # Jobs can opt in by setting enablePublishing: true. + - ${{ if and(eq(parameters.publishingVersion, 4), eq(parameters.enablePublishing, 'true')) }}: - output: pipelineArtifact - displayName: Publish SBOM manifest + displayName: 'Publish V4 pipeline artifacts' + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' + artifactName: '$(System.PhaseName)_Artifacts' continueOnError: true - targetPath: $(Build.ArtifactStagingDirectory)/sbom - artifactName: $(ARTIFACT_NAME) + retryCountOnTaskFailure: 10 # for any files being locked # add any outputs provided via root yaml - ${{ if ne(parameters.templateContext.outputs, '') }}: diff --git a/eng/common/templates-official/steps/publish-build-artifacts.yml b/eng/common/templates-official/steps/publish-build-artifacts.yml index 100a3fc984..fcf6637b2e 100644 --- a/eng/common/templates-official/steps/publish-build-artifacts.yml +++ b/eng/common/templates-official/steps/publish-build-artifacts.yml @@ -24,6 +24,10 @@ parameters: - name: is1ESPipeline type: boolean default: true + +- name: retryCountOnTaskFailure + type: string + default: 10 steps: - ${{ if ne(parameters.is1ESPipeline, true) }}: @@ -38,4 +42,5 @@ steps: PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: ArtifactName: ${{ parameters.artifactName }} - + ${{ if parameters.retryCountOnTaskFailure }}: + retryCountOnTaskFailure: ${{ parameters.retryCountOnTaskFailure }} diff --git a/eng/common/templates-official/steps/publish-pipeline-artifacts.yml b/eng/common/templates-official/steps/publish-pipeline-artifacts.yml index 172f9f0fdc..9e5981365e 100644 --- a/eng/common/templates-official/steps/publish-pipeline-artifacts.yml +++ b/eng/common/templates-official/steps/publish-pipeline-artifacts.yml @@ -24,5 +24,7 @@ steps: artifactName: ${{ parameters.args.artifactName }} ${{ if parameters.args.properties }}: properties: ${{ parameters.args.properties }} - ${{ if parameters.args.sbomEnabled }}: + ${{ if ne(parameters.args.sbomEnabled, '') }}: sbomEnabled: ${{ parameters.args.sbomEnabled }} + ${{ if ne(parameters.args.isProduction, '') }}: + isProduction: ${{ parameters.args.isProduction }} diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/source-index-stage1-publish.yml similarity index 64% rename from eng/common/templates-official/steps/component-governance.yml rename to eng/common/templates-official/steps/source-index-stage1-publish.yml index 30bb3985ca..9b8b80942b 100644 --- a/eng/common/templates-official/steps/component-governance.yml +++ b/eng/common/templates-official/steps/source-index-stage1-publish.yml @@ -1,5 +1,5 @@ steps: -- template: /eng/common/core-templates/steps/component-governance.yml +- template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: true diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml index 1f308b24ef..2cc3ae305d 100644 --- a/eng/common/templates-official/variables/pool-providers.yml +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# image: 1es-windows-2022 +# image: windows.vs2026.amd64 variables: # Coalesce the target and source branches so we know when a PR targets a release branch diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml index dbdd66d4a4..f1311bbb1b 100644 --- a/eng/common/templates-official/variables/sdl-variables.yml +++ b/eng/common/templates-official/variables/sdl-variables.yml @@ -4,4 +4,4 @@ variables: - name: DefaultGuardianVersion value: 0.109.0 - name: GuardianPackagesConfigFile - value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config \ No newline at end of file + value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index d1aeb92fce..5e261f34db 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -1,12 +1,12 @@ parameters: enablePublishBuildArtifacts: false - disableComponentGovernance: '' - componentGovernanceIgnoreDirectories: '' -# Sbom related params - enableSbom: true runAsPublic: false - PackageVersion: 9.0.0 - BuildDropPath: '$(Build.SourcesDirectory)/artifacts' +# CG related params, unused now and can eventually be removed + disableComponentGovernance: unused +# Sbom related params, unused now and can eventually be removed + enableSbom: unused + PackageVersion: unused + BuildDropPath: unused jobs: - template: /eng/common/core-templates/job/job.yml @@ -21,31 +21,34 @@ jobs: - ${{ each step in parameters.steps }}: - ${{ step }} - componentGovernanceSteps: - - template: /eng/common/templates/steps/component-governance.yml - parameters: - ${{ if eq(parameters.disableComponentGovernance, '') }}: - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: - disableComponentGovernance: false - ${{ else }}: - disableComponentGovernance: true - ${{ else }}: - disableComponentGovernance: ${{ parameters.disableComponentGovernance }} - componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + # we don't run CG in public + - ${{ if eq(variables['System.TeamProject'], 'public') }}: + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable artifactPublishSteps: - ${{ if ne(parameters.artifacts.publish, '') }}: - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: - - template: /eng/common/core-templates/steps/publish-build-artifacts.yml + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish pipeline artifacts - pathToPublish: '$(Build.ArtifactStagingDirectory)/artifacts' - publishLocation: Container + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} continueOnError: true - condition: always() + condition: succeeded() + retryCountOnTaskFailure: 10 # for any files being locked + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml + parameters: + is1ESPipeline: false + args: + displayName: Publish pipeline artifacts + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts' + artifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: not(succeeded()) + retryCountOnTaskFailure: 10 # for any files being locked - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: @@ -56,27 +59,27 @@ jobs: displayName: 'Publish logs' continueOnError: true condition: always() - sbomEnabled: false # we don't need SBOM for logs + retryCountOnTaskFailure: 10 # for any files being locked - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: - - template: /eng/common/core-templates/steps/publish-build-artifacts.yml + - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: displayName: Publish Logs - pathToPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' - publishLocation: Container - artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + targetPath: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' + artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)_Attempt$(System.JobAttempt)' ) }} continueOnError: true condition: always() + retryCountOnTaskFailure: 10 # for any files being locked - ${{ if eq(parameters.enableBuildRetry, 'true') }}: - template: /eng/common/core-templates/steps/publish-pipeline-artifacts.yml parameters: is1ESPipeline: false args: - targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration' + targetPath: '$(System.DefaultWorkingDirectory)\eng\common\BuildConfiguration' artifactName: 'BuildConfiguration' displayName: 'Publish build retry configuration' continueOnError: true - sbomEnabled: false # we don't need SBOM for BuildConfiguration + retryCountOnTaskFailure: 10 # for any files being locked diff --git a/eng/common/templates/steps/publish-build-artifacts.yml b/eng/common/templates/steps/publish-build-artifacts.yml index 6428a98dfe..605e602e94 100644 --- a/eng/common/templates/steps/publish-build-artifacts.yml +++ b/eng/common/templates/steps/publish-build-artifacts.yml @@ -25,6 +25,10 @@ parameters: type: string default: 'Container' +- name: retryCountOnTaskFailure + type: string + default: 10 + steps: - ${{ if eq(parameters.is1ESPipeline, true) }}: - 'eng/common/templates cannot be referenced from a 1ES managed template': error @@ -37,4 +41,6 @@ steps: PublishLocation: ${{ parameters.publishLocation }} PathtoPublish: ${{ parameters.pathToPublish }} ${{ if parameters.artifactName }}: - ArtifactName: ${{ parameters.artifactName }} \ No newline at end of file + ArtifactName: ${{ parameters.artifactName }} + ${{ if parameters.retryCountOnTaskFailure }}: + retryCountOnTaskFailure: ${{ parameters.retryCountOnTaskFailure }} diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/source-index-stage1-publish.yml similarity index 64% rename from eng/common/templates/steps/component-governance.yml rename to eng/common/templates/steps/source-index-stage1-publish.yml index c12a5f8d21..182cec33a7 100644 --- a/eng/common/templates/steps/component-governance.yml +++ b/eng/common/templates/steps/source-index-stage1-publish.yml @@ -1,5 +1,5 @@ steps: -- template: /eng/common/core-templates/steps/component-governance.yml +- template: /eng/common/core-templates/steps/source-index-stage1-publish.yml parameters: is1ESPipeline: false diff --git a/eng/common/templates/steps/vmr-sync.yml b/eng/common/templates/steps/vmr-sync.yml new file mode 100644 index 0000000000..eb619c5026 --- /dev/null +++ b/eng/common/templates/steps/vmr-sync.yml @@ -0,0 +1,186 @@ +### These steps synchronize new code from product repositories into the VMR (https://github.com/dotnet/dotnet). +### They initialize the darc CLI and pull the new updates. +### Changes are applied locally onto the already cloned VMR (located in $vmrPath). + +parameters: +- name: targetRef + displayName: Target revision in dotnet/ to synchronize + type: string + default: $(Build.SourceVersion) + +- name: vmrPath + displayName: Path where the dotnet/dotnet is checked out to + type: string + default: $(Agent.BuildDirectory)/vmr + +- name: additionalSyncs + displayName: Optional list of package names whose repo's source will also be synchronized in the local VMR, e.g. NuGet.Protocol + type: object + default: [] + +steps: +- checkout: vmr + displayName: Clone dotnet/dotnet + path: vmr + clean: true + +- checkout: self + displayName: Clone $(Build.Repository.Name) + path: repo + fetchDepth: 0 + +# This step is needed so that when we get a detached HEAD / shallow clone, +# we still pull the commit into the temporary repo clone to use it during the sync. +# Also unshallow the clone so that forwardflow command would work. +- script: | + git branch repo-head + git rev-parse HEAD + displayName: Label PR commit + workingDirectory: $(Agent.BuildDirectory)/repo + +- script: | + git config --global user.name "dotnet-maestro[bot]" + git config --global user.email "dotnet-maestro[bot]@users.noreply.github.com" + displayName: Set git author to dotnet-maestro[bot] + workingDirectory: ${{ parameters.vmrPath }} + +- script: | + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(dn-bot-all-orgs-code-r)' \ + --ci \ + --debug + + if [ "$?" -ne 0 ]; then + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + fi + displayName: Sync repo into VMR (Unix) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- script: | + git config --global diff.astextplain.textconv echo + git config --system core.longpaths true + displayName: Configure Windows git (longpaths, astextplain) + condition: eq(variables['Agent.OS'], 'Windows_NT') + +- powershell: | + ./eng/common/vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(dn-bot-all-orgs-code-r)' ` + -ci ` + -debugOutput + + if ($LASTEXITCODE -ne 0) { + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + } + displayName: Sync repo into VMR (Windows) + condition: eq(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + +- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + - task: CopyFiles@2 + displayName: Collect failed patches + condition: failed() + inputs: + SourceFolder: '$(Agent.TempDirectory)' + Contents: '*.patch' + TargetFolder: '$(Build.ArtifactStagingDirectory)/FailedPatches' + + - publish: '$(Build.ArtifactStagingDirectory)/FailedPatches' + artifact: $(System.JobDisplayName)_FailedPatches + displayName: Upload failed patches + condition: failed() + +- ${{ each assetName in parameters.additionalSyncs }}: + # The vmr-sync script ends up staging files in the local VMR so we have to commit those + - script: + git commit --allow-empty -am "Forward-flow $(Build.Repository.Name)" + displayName: Commit local VMR changes + workingDirectory: ${{ parameters.vmrPath }} + + - script: | + set -ex + + echo "Searching for details of asset ${{ assetName }}..." + + # Use darc to get dependencies information + dependencies=$(./.dotnet/dotnet darc get-dependencies --name '${{ assetName }}' --ci) + + # Extract repository URL and commit hash + repository=$(echo "$dependencies" | grep 'Repo:' | sed 's/Repo:[[:space:]]*//' | head -1) + + if [ -z "$repository" ]; then + echo "##vso[task.logissue type=error]Asset ${{ assetName }} not found in the dependency list" + exit 1 + fi + + commit=$(echo "$dependencies" | grep 'Commit:' | sed 's/Commit:[[:space:]]*//' | head -1) + + echo "Updating the VMR from $repository / $commit..." + cd .. + git clone $repository ${{ assetName }} + cd ${{ assetName }} + git checkout $commit + git branch "sync/$commit" + + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(dn-bot-all-orgs-code-r)' \ + --ci \ + --debug + + if [ "$?" -ne 0 ]; then + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + fi + displayName: Sync ${{ assetName }} into (Unix) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo + + - powershell: | + $ErrorActionPreference = 'Stop' + + Write-Host "Searching for details of asset ${{ assetName }}..." + + $dependencies = .\.dotnet\dotnet darc get-dependencies --name '${{ assetName }}' --ci + + $repository = $dependencies | Select-String -Pattern 'Repo:\s+([^\s]+)' | Select-Object -First 1 + $repository -match 'Repo:\s+([^\s]+)' | Out-Null + $repository = $matches[1] + + if ($repository -eq $null) { + Write-Error "Asset ${{ assetName }} not found in the dependency list" + exit 1 + } + + $commit = $dependencies | Select-String -Pattern 'Commit:\s+([^\s]+)' | Select-Object -First 1 + $commit -match 'Commit:\s+([^\s]+)' | Out-Null + $commit = $matches[1] + + Write-Host "Updating the VMR from $repository / $commit..." + cd .. + git clone $repository ${{ assetName }} + cd ${{ assetName }} + git checkout $commit + git branch "sync/$commit" + + .\eng\common\vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(dn-bot-all-orgs-code-r)' ` + -ci ` + -debugOutput + + if ($LASTEXITCODE -ne 0) { + echo "##vso[task.logissue type=error]Failed to synchronize the VMR" + exit 1 + } + displayName: Sync ${{ assetName }} into (Windows) + condition: ne(variables['Agent.OS'], 'Windows_NT') + workingDirectory: $(Agent.BuildDirectory)/repo diff --git a/eng/common/templates/variables/pool-providers.yml b/eng/common/templates/variables/pool-providers.yml index e0b19c14a0..587770f0ad 100644 --- a/eng/common/templates/variables/pool-providers.yml +++ b/eng/common/templates/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# demands: ImageOverride -equals windows.vs2019.amd64 +# demands: ImageOverride -equals windows.vs2026.amd64 variables: - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - template: /eng/common/templates-official/variables/pool-providers.yml diff --git a/eng/common/templates/vmr-build-pr.yml b/eng/common/templates/vmr-build-pr.yml new file mode 100644 index 0000000000..2f3694fa13 --- /dev/null +++ b/eng/common/templates/vmr-build-pr.yml @@ -0,0 +1,43 @@ +# This pipeline is used for running the VMR verification of the PR changes in repo-level PRs. +# +# It will run a full set of verification jobs defined in: +# https://github.com/dotnet/dotnet/blob/10060d128e3f470e77265f8490f5e4f72dae738e/eng/pipelines/templates/stages/vmr-build.yml#L27-L38 +# +# For repos that do not need to run the full set, you would do the following: +# +# 1. Copy this YML file to a repo-specific location, i.e. outside of eng/common. +# +# 2. Add `verifications` parameter to VMR template reference +# +# Examples: +# - For source-build stage 1 verification, add the following: +# verifications: [ "source-build-stage1" ] +# +# - For Windows only verifications, add the following: +# verifications: [ "unified-build-windows-x64", "unified-build-windows-x86" ] + +trigger: none +pr: none + +variables: +- template: /eng/common/templates/variables/pool-providers.yml@self + +- name: skipComponentGovernanceDetection # we run CG on internal builds only + value: true + +- name: Codeql.Enabled # we run CodeQL on internal builds only + value: false + +resources: + repositories: + - repository: vmr + type: github + name: dotnet/dotnet + endpoint: dotnet + ref: refs/heads/main # Set to whatever VMR branch the PR build should insert into + +stages: +- template: /eng/pipelines/templates/stages/vmr-build.yml@vmr + parameters: + isBuiltFromVmr: false + scope: lite diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 22954477a5..977a2d4b10 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -42,7 +42,7 @@ [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -65,10 +65,8 @@ $ErrorActionPreference = 'Stop' # Base-64 encoded SAS token that has permission to storage container described by $runtimeSourceFeed [string]$runtimeSourceFeedKey = if (Test-Path variable:runtimeSourceFeedKey) { $runtimeSourceFeedKey } else { $null } -# True if the build is a product build -[bool]$productBuild = if (Test-Path variable:productBuild) { $productBuild } else { $false } - -[String[]]$properties = if (Test-Path variable:properties) { $properties } else { @() } +# True when the build is running within the VMR. +[bool]$fromVMR = if (Test-Path variable:fromVMR) { $fromVMR } else { $false } function Create-Directory ([string[]] $path) { New-Item -Path $path -Force -ItemType 'Directory' | Out-Null @@ -259,14 +257,27 @@ function Retry($downloadBlock, $maxRetries = 5) { function GetDotNetInstallScript([string] $dotnetRoot) { $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' + $shouldDownload = $false + if (!(Test-Path $installScript)) { + $shouldDownload = $true + } else { + # Check if the script is older than 30 days + $fileAge = (Get-Date) - (Get-Item $installScript).LastWriteTime + if ($fileAge.Days -gt 30) { + Write-Host "Existing install script is too old, re-downloading..." + $shouldDownload = $true + } + } + + if ($shouldDownload) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" - Invoke-WebRequest $uri -OutFile $installScript + Invoke-WebRequest $uri -UseBasicParsing -OutFile $installScript }) } @@ -320,7 +331,7 @@ function InstallDotNet([string] $dotnetRoot, $variations += @($installParameters) $dotnetBuilds = $installParameters.Clone() - $dotnetbuilds.AzureFeed = "https://dotnetbuilds.azureedge.net/public" + $dotnetbuilds.AzureFeed = "https://ci.dot.net/public" $variations += @($dotnetBuilds) if ($runtimeSourceFeed) { @@ -383,8 +394,8 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # 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/17.10.0-pre.4.0 - $defaultXCopyMSBuildVersion = '17.10.0-pre.4.0' + # 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') { @@ -416,7 +427,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # 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] @@ -499,7 +510,7 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { 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" -OutFile $packagePath + 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)) { @@ -533,7 +544,8 @@ function LocateVisualStudio([object]$vsRequirements = $null){ if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { - $vswhereVersion = '2.5.2' + # keep this in sync with the VSWhereVersion in DefaultVersions.props + $vswhereVersion = '3.1.7' } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" @@ -541,25 +553,33 @@ function LocateVisualStudio([object]$vsRequirements = $null){ if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir - Write-Host 'Downloading vswhere' + Write-Host "Downloading vswhere $vswhereVersion" + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit Retry({ - Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -UseBasicParsing -OutFile $vswhereExe }) } - if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } + if (!$vsRequirements) { + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs' -ErrorAction SilentlyContinue) { + $vsRequirements = $GlobalJson.tools.vs + } else { + $vsRequirements = $null + } + } + $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') if (!$excludePrereleaseVS) { $args += '-prerelease' } - if (Get-Member -InputObject $vsRequirements -Name 'version') { + if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'version' -ErrorAction SilentlyContinue)) { $args += '-version' $args += $vsRequirements.version } - if (Get-Member -InputObject $vsRequirements -Name 'components') { + if ($vsRequirements -and (Get-Member -InputObject $vsRequirements -Name 'components' -ErrorAction SilentlyContinue)) { foreach ($component in $vsRequirements.components) { $args += '-requires' $args += $component @@ -604,14 +624,7 @@ function InitializeBuildTool() { } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') - # Use override if it exists - commonly set by source-build - if ($null -eq $env:_OverrideArcadeInitializeBuildToolFramework) { - $initializeBuildToolFramework="net9.0" - } else { - $initializeBuildToolFramework=$env:_OverrideArcadeInitializeBuildToolFramework - } - - $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = $initializeBuildToolFramework } + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore @@ -620,7 +633,7 @@ function InitializeBuildTool() { ExitWithExitCode 1 } - $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472"; ExcludePrereleaseVS = $excludePrereleaseVS } + $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "netframework"; ExcludePrereleaseVS = $excludePrereleaseVS } } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 @@ -653,7 +666,6 @@ function GetNuGetPackageCachePath() { $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' } else { $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' - $env:RESTORENOHTTPCACHE = $true } } @@ -775,26 +787,13 @@ function MSBuild() { $toolsetBuildProject = InitializeToolset $basePath = Split-Path -parent $toolsetBuildProject - $possiblePaths = @( - # new scripts need to work with old packages, so we need to look for the old names/versions - (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), - (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), - (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.ArcadeLogging.dll')), - (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll')), - (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.ArcadeLogging.dll')), - (Join-Path $basePath (Join-Path net8.0 'Microsoft.DotNet.Arcade.Sdk.dll')) - ) - $selectedPath = $null - foreach ($path in $possiblePaths) { - if (Test-Path $path -PathType Leaf) { - $selectedPath = $path - break - } - } + $selectedPath = Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll') + if (-not $selectedPath) { - Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' + Write-PipelineTelemetryError -Category 'Build' -Message "Unable to find arcade sdk logger assembly: $selectedPath" ExitWithExitCode 1 } + $args += "/logger:$selectedPath" } @@ -825,6 +824,11 @@ function MSBuild-Core() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" + # Add -mt flag for MSBuild multithreaded mode if enabled via environment variable + if ($env:MSBUILD_MT_ENABLED -eq "1") { + $cmdArgs += ' -mt' + } + if ($warnAsError) { $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' } @@ -857,8 +861,8 @@ function MSBuild-Core() { } # When running on Azure Pipelines, override the returned exit code to avoid double logging. - # Skip this when the build is a child of the VMR orchestrator build. - if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$productBuild -and -not($properties -like "*DotNetBuildRepo=true*")) { + # Skip this when the build is a child of the VMR build. + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR) { Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 00473c9f91..1b296f646c 100644 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -5,6 +5,9 @@ # CI mode - set to true on CI server for PR validation build or official build. ci=${ci:-false} +# Build mode +source_build=${source_build:-false} + # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md # This flag is meant as a temporary opt-opt for the feature while validate it across @@ -54,11 +57,12 @@ warn_as_error=${warn_as_error:-true} use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. -if [[ "$ci" == true ]]; then +# Keep in sync with NuGetPackageroot in Arcade SDK's RepositoryLayout.props. +if [[ "$ci" == true || "$source_build" == true ]]; then use_global_nuget_cache=${use_global_nuget_cache:-false} else use_global_nuget_cache=${use_global_nuget_cache:-true} @@ -68,8 +72,8 @@ fi runtime_source_feed=${runtime_source_feed:-''} runtime_source_feed_key=${runtime_source_feed_key:-''} -# True if the build is a product build -product_build=${product_build:-false} +# True when the build is running within the VMR. +from_vmr=${from_vmr:-false} # Resolve any symlinks in the given path. function ResolvePath { @@ -232,7 +236,7 @@ function InstallDotNet { local public_location=("${installParameters[@]}") variations+=(public_location) - local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://dotnetbuilds.azureedge.net/public") + local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://ci.dot.net/public") variations+=(dotnetbuilds) if [[ -n "${6:-}" ]]; then @@ -295,9 +299,30 @@ function with_retries { function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" - local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + local timestamp_file="$root/.dotnet-install.timestamp" + local should_download=false if [[ ! -a "$install_script" ]]; then + should_download=true + elif [[ -f "$timestamp_file" ]]; then + # Check if the script is older than 30 days using timestamp file + local download_time=$(cat "$timestamp_file" 2>/dev/null || echo "0") + local current_time=$(date +%s) + local age_seconds=$((current_time - download_time)) + + # 30 days = 30 * 24 * 60 * 60 = 2592000 seconds + if [[ $age_seconds -gt 2592000 ]]; then + echo "Existing install script is too old, re-downloading..." + should_download=true + fi + else + # No timestamp file exists, assume script is old and re-download + echo "No timestamp found for existing install script, re-downloading..." + should_download=true + fi + + if [[ "$should_download" == true ]]; then mkdir -p "$root" echo "Downloading '$install_script_url'" @@ -324,6 +349,9 @@ function GetDotNetInstallScript { ExitWithExitCode $exit_code } fi + + # Create timestamp file to track download time in seconds from epoch + date +%s > "$timestamp_file" fi # return value _GetDotNetInstallScript="$install_script" @@ -339,22 +367,14 @@ function InitializeBuildTool { # return values _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" - # use override if it exists - commonly set by source-build - if [[ "${_OverrideArcadeInitializeBuildToolFramework:-x}" == "x" ]]; then - _InitializeBuildToolFramework="net9.0" - else - _InitializeBuildToolFramework="${_OverrideArcadeInitializeBuildToolFramework}" - fi } -# Set RestoreNoHttpCache as a workaround for https://github.com/NuGet/Home/issues/3116 function GetNuGetPackageCachePath { if [[ -z ${NUGET_PACKAGES:-} ]]; then if [[ "$use_global_nuget_cache" == true ]]; then export NUGET_PACKAGES="$HOME/.nuget/packages/" else export NUGET_PACKAGES="$repo_root/.packages/" - export RESTORENOHTTPCACHE=true fi fi @@ -451,25 +471,13 @@ function MSBuild { fi local toolset_dir="${_InitializeToolset%/*}" - # new scripts need to work with old packages, so we need to look for the old names/versions - local selectedPath= - local possiblePaths=() - possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" ) - possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" ) - possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.ArcadeLogging.dll" ) - possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.Arcade.Sdk.dll" ) - possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.ArcadeLogging.dll" ) - possiblePaths+=( "$toolset_dir/net8.0/Microsoft.DotNet.Arcade.Sdk.dll" ) - for path in "${possiblePaths[@]}"; do - if [[ -f $path ]]; then - selectedPath=$path - break - fi - done + local selectedPath="$toolset_dir/net/Microsoft.DotNet.ArcadeLogging.dll" + if [[ -z "$selectedPath" ]]; then - Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." + Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly: $selectedPath" ExitWithExitCode 1 fi + args+=( "-logger:$selectedPath" ) fi @@ -506,8 +514,8 @@ function MSBuild-Core { echo "Build failed with exit code $exit_code. Check errors above." # When running on Azure Pipelines, override the returned exit code to avoid double logging. - # Skip this when the build is a child of the VMR orchestrator build. - if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$product_build" != true && "$properties" != *"DotNetBuildRepo=true"* ]]; then + # Skip this when the build is a child of the VMR build. + if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true ]]; then Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error @@ -518,7 +526,13 @@ function MSBuild-Core { } } - RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" + # Add -mt flag for MSBuild multithreaded mode if enabled via environment variable + local mt_switch="" + if [[ "${MSBUILD_MT_ENABLED:-}" == "1" ]]; then + mt_switch="-mt" + fi + + 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 { @@ -530,6 +544,13 @@ function GetDarc { fi "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version + darc_tool="$darc_path/darc" +} + +# Returns a full path to an Arcade SDK task project file. +function GetSdkTaskProject { + taskName=$1 + echo "$(dirname $_InitializeToolset)/SdkTasks/$taskName.proj" } ResolvePath "${BASH_SOURCE[0]}" diff --git a/eng/common/vmr-sync.ps1 b/eng/common/vmr-sync.ps1 new file mode 100644 index 0000000000..b37992d91c --- /dev/null +++ b/eng/common/vmr-sync.ps1 @@ -0,0 +1,164 @@ +<# +.SYNOPSIS + +This script is used for synchronizing the current repository into a local VMR. +It pulls the current repository's code into the specified VMR directory for local testing or +Source-Build validation. + +.DESCRIPTION + +The tooling used for synchronization will clone the VMR repository into a temporary folder if +it does not already exist. These clones can be reused in future synchronizations, so it is +recommended to dedicate a folder for this to speed up re-runs. + +.EXAMPLE + Synchronize current repository into a local VMR: + ./vmr-sync.ps1 -vmrDir "$HOME/repos/dotnet" -tmpDir "$HOME/repos/tmp" + +.PARAMETER tmpDir +Required. Path to the temporary folder where repositories will be cloned + +.PARAMETER vmrBranch +Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch + +.PARAMETER azdevPat +Optional. Azure DevOps PAT to use for cloning private repositories. + +.PARAMETER vmrDir +Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder + +.PARAMETER debugOutput +Optional. Enables debug logging in the darc vmr command. + +.PARAMETER ci +Optional. Denotes that the script is running in a CI environment. +#> +param ( + [Parameter(Mandatory=$true, HelpMessage="Path to the temporary folder where repositories will be cloned")] + [string][Alias('t', 'tmp')]$tmpDir, + [string][Alias('b', 'branch')]$vmrBranch, + [string]$remote, + [string]$azdevPat, + [string][Alias('v', 'vmr')]$vmrDir, + [switch]$ci, + [switch]$debugOutput +) + +function Fail { + Write-Host "> $($args[0])" -ForegroundColor 'Red' +} + +function Highlight { + Write-Host "> $($args[0])" -ForegroundColor 'Cyan' +} + +$verbosity = 'verbose' +if ($debugOutput) { + $verbosity = 'debug' +} +# Validation + +if (-not $tmpDir) { + Fail "Missing -tmpDir argument. Please specify the path to the temporary folder where the repositories will be cloned" + exit 1 +} + +# Sanitize the input + +if (-not $vmrDir) { + $vmrDir = Join-Path $tmpDir 'dotnet' +} + +if (-not (Test-Path -Path $tmpDir -PathType Container)) { + New-Item -ItemType Directory -Path $tmpDir | Out-Null +} + +# Prepare the VMR + +if (-not (Test-Path -Path $vmrDir -PathType Container)) { + Highlight "Cloning 'dotnet/dotnet' into $vmrDir.." + git clone https://github.com/dotnet/dotnet $vmrDir + + if ($vmrBranch) { + git -C $vmrDir switch -c $vmrBranch + } +} +else { + if ((git -C $vmrDir diff --quiet) -eq $false) { + Fail "There are changes in the working tree of $vmrDir. Please commit or stash your changes" + exit 1 + } + + if ($vmrBranch) { + Highlight "Preparing $vmrDir" + git -C $vmrDir checkout $vmrBranch + git -C $vmrDir pull + } +} + +Set-StrictMode -Version Latest + +# Prepare darc + +Highlight 'Installing .NET, preparing the tooling..' +. .\eng\common\tools.ps1 +$dotnetRoot = InitializeDotNetCli -install:$true +$env:DOTNET_ROOT = $dotnetRoot +$darc = Get-Darc + +Highlight "Starting the synchronization of VMR.." + +# Synchronize the VMR +$versionDetailsPath = Resolve-Path (Join-Path $PSScriptRoot '..\Version.Details.xml') | Select-Object -ExpandProperty Path +[xml]$versionDetails = Get-Content -Path $versionDetailsPath +$repoName = $versionDetails.SelectSingleNode('//Source').Mapping +if (-not $repoName) { + Fail "Failed to resolve repo mapping from $versionDetailsPath" + exit 1 +} + +$darcArgs = ( + "vmr", "forwardflow", + "--tmp", $tmpDir, + "--$verbosity", + $vmrDir +) + +if ($ci) { + $darcArgs += ("--ci") +} + +if ($azdevPat) { + $darcArgs += ("--azdev-pat", $azdevPat) +} + +& "$darc" $darcArgs + +if ($LASTEXITCODE -eq 0) { + Highlight "Synchronization succeeded" +} +else { + Highlight "Failed to flow code into the local VMR. Falling back to resetting the VMR to match repo contents..." + git -C $vmrDir reset --hard + + $resetArgs = ( + "vmr", "reset", + "${repoName}:HEAD", + "--vmr", $vmrDir, + "--tmp", $tmpDir, + "--additional-remotes", "${repoName}:${repoRoot}" + ) + + & "$darc" $resetArgs + + if ($LASTEXITCODE -eq 0) { + Highlight "Successfully reset the VMR using 'darc vmr reset'" + } + else { + Fail "Synchronization of repo to VMR failed!" + Fail "'$vmrDir' is left in its last state (re-run of this script will reset it)." + Fail "Please inspect the logs which contain path to the failing patch file (use -debugOutput to get all the details)." + Fail "Once you make changes to the conflicting VMR patch, commit it locally and re-run this script." + exit 1 + } +} diff --git a/eng/common/vmr-sync.sh b/eng/common/vmr-sync.sh new file mode 100644 index 0000000000..198caec59b --- /dev/null +++ b/eng/common/vmr-sync.sh @@ -0,0 +1,227 @@ +#!/bin/bash + +### This script is used for synchronizing the current repository into a local VMR. +### It pulls the current repository's code into the specified VMR directory for local testing or +### Source-Build validation. +### +### The tooling used for synchronization will clone the VMR repository into a temporary folder if +### it does not already exist. These clones can be reused in future synchronizations, so it is +### recommended to dedicate a folder for this to speed up re-runs. +### +### USAGE: +### Synchronize current repository into a local VMR: +### ./vmr-sync.sh --tmp "$HOME/repos/tmp" "$HOME/repos/dotnet" +### +### Options: +### -t, --tmp, --tmp-dir PATH +### Required. Path to the temporary folder where repositories will be cloned +### +### -b, --branch, --vmr-branch BRANCH_NAME +### Optional. Branch of the 'dotnet/dotnet' repo to synchronize. The VMR will be checked out to this branch +### +### --debug +### Optional. Turns on the most verbose logging for the VMR tooling +### +### --remote name:URI +### Optional. Additional remote to use during the synchronization +### This can be used to synchronize to a commit from a fork of the repository +### Example: 'runtime:https://github.com/yourfork/runtime' +### +### --azdev-pat +### Optional. Azure DevOps PAT to use for cloning private repositories. +### +### -v, --vmr, --vmr-dir PATH +### Optional. Path to the dotnet/dotnet repository. When null, gets cloned to the temporary folder + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +function print_help () { + sed -n '/^### /,/^$/p' "$source" | cut -b 5- +} + +COLOR_RED=$(tput setaf 1 2>/dev/null || true) +COLOR_CYAN=$(tput setaf 6 2>/dev/null || true) +COLOR_CLEAR=$(tput sgr0 2>/dev/null || true) +COLOR_RESET=uniquesearchablestring +FAILURE_PREFIX='> ' + +function fail () { + echo "${COLOR_RED}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_RED}}${COLOR_CLEAR}" >&2 +} + +function highlight () { + echo "${COLOR_CYAN}$FAILURE_PREFIX${1//${COLOR_RESET}/${COLOR_CYAN}}${COLOR_CLEAR}" +} + +tmp_dir='' +vmr_dir='' +vmr_branch='' +additional_remotes='' +verbosity=verbose +azdev_pat='' +ci=false + +while [[ $# -gt 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -t|--tmp|--tmp-dir) + tmp_dir=$2 + shift + ;; + -v|--vmr|--vmr-dir) + vmr_dir=$2 + shift + ;; + -b|--branch|--vmr-branch) + vmr_branch=$2 + shift + ;; + --remote) + additional_remotes="$additional_remotes $2" + shift + ;; + --azdev-pat) + azdev_pat=$2 + shift + ;; + --ci) + ci=true + ;; + -d|--debug) + verbosity=debug + ;; + -h|--help) + print_help + exit 0 + ;; + *) + fail "Invalid argument: $1" + print_help + exit 1 + ;; + esac + + shift +done + +# Validation + +if [[ -z "$tmp_dir" ]]; then + fail "Missing --tmp-dir argument. Please specify the path to the temporary folder where the repositories will be cloned" + exit 1 +fi + +# Sanitize the input + +if [[ -z "$vmr_dir" ]]; then + vmr_dir="$tmp_dir/dotnet" +fi + +if [[ ! -d "$tmp_dir" ]]; then + mkdir -p "$tmp_dir" +fi + +if [[ "$verbosity" == "debug" ]]; then + set -x +fi + +# Prepare the VMR + +if [[ ! -d "$vmr_dir" ]]; then + highlight "Cloning 'dotnet/dotnet' into $vmr_dir.." + git clone https://github.com/dotnet/dotnet "$vmr_dir" + + if [[ -n "$vmr_branch" ]]; then + git -C "$vmr_dir" switch -c "$vmr_branch" + fi +else + if ! git -C "$vmr_dir" diff --quiet; then + fail "There are changes in the working tree of $vmr_dir. Please commit or stash your changes" + exit 1 + fi + + if [[ -n "$vmr_branch" ]]; then + highlight "Preparing $vmr_dir" + git -C "$vmr_dir" checkout "$vmr_branch" + git -C "$vmr_dir" pull + fi +fi + +set -e + +# Prepare darc + +highlight 'Installing .NET, preparing the tooling..' +source "./eng/common/tools.sh" +InitializeDotNetCli true +GetDarc +dotnetDir=$( cd ./.dotnet/; pwd -P ) +dotnet=$dotnetDir/dotnet + +highlight "Starting the synchronization of VMR.." +set +e + +if [[ -n "$additional_remotes" ]]; then + additional_remotes="--additional-remotes $additional_remotes" +fi + +if [[ -n "$azdev_pat" ]]; then + azdev_pat="--azdev-pat $azdev_pat" +fi + +ci_arg='' +if [[ "$ci" == "true" ]]; then + ci_arg="--ci" +fi + +# Synchronize the VMR + +version_details_path=$(cd "$scriptroot/.."; pwd -P)/Version.Details.xml +repo_name=$(grep -m 1 ' $actualVersion (oldVersion: $currentOldVersion -> $newOldVersion)" -ForegroundColor Yellow + } + } + } + + # Apply fixes using XML DOM with whitespace preservation. + if (-not $script:isCI) { + foreach ($configPath in $configsToFix.Keys) { + $xmlDoc = New-Object System.Xml.XmlDocument + $xmlDoc.PreserveWhitespace = $true + $xmlDoc.Load($configPath) + + $nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) + $nsMgr.AddNamespace("asm", "urn:schemas-microsoft-com:asm.v1") + + foreach ($fix in $configsToFix[$configPath]) { + $nodes = $xmlDoc.SelectNodes("//asm:dependentAssembly[asm:assemblyIdentity[@name='$($fix.AssemblyName)']]/asm:bindingRedirect", $nsMgr) + $applied = $false + foreach ($node in $nodes) { + if ($node.GetAttribute("newVersion") -eq $fix.OldNewVersion) { + $node.SetAttribute("oldVersion", $fix.NewOldVersion) + $node.SetAttribute("newVersion", $fix.NewNewVersion) + $applied = $true + } + } + + if (-not $applied) { + Write-Error "Failed to apply binding redirect fix for '$($fix.AssemblyName)' in '$configPath'. The expected redirect node was not found." + } + } + + # Preserve the original BOM if present. + $bom = [System.IO.File]::ReadAllBytes($configPath) + $hasBom = $bom.Length -ge 3 -and $bom[0] -eq 0xEF -and $bom[1] -eq 0xBB -and $bom[2] -eq 0xBF + $encoding = if ($hasBom) { New-Object System.Text.UTF8Encoding($true) } else { New-Object System.Text.UTF8Encoding($false) } + $writer = New-Object System.IO.StreamWriter($configPath, $false, $encoding) + $xmlDoc.Save($writer) + $writer.Dispose() + Write-Host "Updated '$configPath'." -ForegroundColor Green + } + } + + if ($errors) { + if ($script:isCI) { + $message = "Assembly binding redirect errors detected:`n" + $message += ($errors -join "`n") + if ($configsToFix.Count -gt 0) { + $message += "`n`nFor version mismatches, run the following command locally after building and packing:`n" + $message += " .\build.cmd -c $Configuration`n" + $message += "This will rebuild, pack, and auto-update the app.config files with the correct versions.`n" + $message += "Then commit the updated app.config files." + } + if ($script:hasUnfixableErrors) { + $message += "`n`nFor missing-DLL errors, remove the binding redirect from the app.config or add the DLL to the package." + } + Write-Error $message + } + else { + if ($configsToFix.Count -gt 0) { + Write-Host "`nFixed $($configsToFix.Count) version mismatch(es). Please commit the updated app.config files." -ForegroundColor Green + } + if ($script:hasUnfixableErrors) { + Write-Error "Missing-DLL binding redirect errors detected - these cannot be auto-fixed. Remove the redirect(s) listed above from the app.config file(s)." + } + } + } + else { + Write-Host "All binding redirects match their DLL versions." + } +} diff --git a/eng/verify-nupkgs.ps1 b/eng/verify-nupkgs.ps1 index 6fb1274ada..e91df46f71 100644 --- a/eng/verify-nupkgs.ps1 +++ b/eng/verify-nupkgs.ps1 @@ -14,21 +14,25 @@ Param( $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.IO.Compression.FileSystem +# Import binding redirect verification. +. "$PSScriptRoot/verify-binding-redirects.ps1" + function Verify-Nuget-Packages { Write-Host "Starting Verify-Nuget-Packages." $expectedNumOfFiles = @{ - "Microsoft.CodeCoverage" = 59; - "Microsoft.NET.Test.Sdk" = 15; - "Microsoft.TestPlatform" = 609; - "Microsoft.TestPlatform.Build" = 20; - "Microsoft.TestPlatform.CLI" = 471; - "Microsoft.TestPlatform.Extensions.TrxLogger" = 34; - "Microsoft.TestPlatform.ObjectModel" = 92; - "Microsoft.TestPlatform.AdapterUtilities" = 75; - "Microsoft.TestPlatform.Portable" = 592; - "Microsoft.TestPlatform.TestHost" = 62; - "Microsoft.TestPlatform.TranslationLayer" = 122; - "Microsoft.TestPlatform.Internal.Uwp" = 38; + "Microsoft.CodeCoverage" = 76 + "Microsoft.NET.Test.Sdk" = 26 + "Microsoft.TestPlatform" = 545 + "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI" = 388 + "Microsoft.TestPlatform.Build" = 21 + "Microsoft.TestPlatform.CLI" = 483 + "Microsoft.TestPlatform.Extensions.TrxLogger" = 35 + "Microsoft.TestPlatform.ObjectModel" = 93 + "Microsoft.TestPlatform.AdapterUtilities" = 62 + "Microsoft.TestPlatform.Portable" = 609 + "Microsoft.TestPlatform.TestHost" = 64 + "Microsoft.TestPlatform.TranslationLayer" = 175 + "Microsoft.TestPlatform.Internal.Uwp" = 39 } $packageDirectory = Resolve-Path "$PSScriptRoot/../artifacts/packages/$configuration" @@ -63,10 +67,22 @@ function Verify-Nuget-Packages { Write-Host "Found $(@($nugetPackages).Count) nuget packages:`n $($nugetPackages.FullName -join "`n ")" - Write-Host "Unzipping NuGet packages." + + # In source build we don't build this. + $vsix = Get-Item "$PSScriptRoot/../artifacts/VSSetup/$configuration/Insertion/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix" -ErrorAction Ignore + if ($vsix) { + $nugetPackages += $vsix + } + + # Extract into the same location that IntegrationTestBuild.cs expects + # (artifacts/tmp/{Config}/extractedPackages/{PackageFileName}), so integration + # tests can reuse these extractions via the .cache marker files written below. + $extractedPackagesDir = Join-Path $tmpDirectory "extractedPackages" + New-Item -ItemType Directory -Path $extractedPackagesDir -Force | Out-Null + Write-Host "Unzipping NuGet packages to '$extractedPackagesDir'." $unzipNugetPackageDirs = @() foreach ($nugetPackage in $nugetPackages) { - $unzipNugetPackageDir = Join-Path $tmpDirectory $nugetPackage.BaseName + $unzipNugetPackageDir = Join-Path $extractedPackagesDir $nugetPackage.Name $unzipNugetPackageDirs += $unzipNugetPackageDir if (Test-Path -Path $unzipNugetPackageDir) { @@ -79,28 +95,23 @@ function Verify-Nuget-Packages { Write-Host "Verify NuGet packages files." $errors = @() foreach ($unzipNugetPackageDir in $unzipNugetPackageDirs) { - try { - $packageBaseName = (Get-Item $unzipNugetPackageDir).BaseName - $packageKey = $packageBaseName.Replace([string]".$version", [string]"") - Write-Host "Verifying package '$packageBaseName'." - - $actualNumOfFiles = (Get-ChildItem -Recurse -File -Path $unzipNugetPackageDir | Where-Object { $_.Name -ne '.signature.p7s' }).Count - if (-not $expectedNumOfFiles.ContainsKey($packageKey)) { - $errors += "Package '$packageKey' is not present in file expectedNumOfFiles table. Is that package known?" - continue - } - if ($expectedNumOfFiles[$packageKey] -ne $actualNumOfFiles) { - $errors += "Number of files are not equal for '$packageBaseName', expected: $($expectedNumOfFiles[$packageKey]) actual: $actualNumOfFiles" - } - - if ($packageKey -eq "Microsoft.TestPlatform") { - Verify-Version -nugetDir $unzipNugetPackageDir -errors $errors - } + # Directory is named after the package file (e.g. "Microsoft.TestPlatform.18.6.0-dev.nupkg"), + # so strip the extension to get the base name for key derivation. + $packageBaseName = [System.IO.Path]::GetFileNameWithoutExtension((Get-Item $unzipNugetPackageDir).Name) + $packageKey = $packageBaseName.Replace([string]".$version", [string]"") + Write-Host "Verifying package '$packageBaseName'." + + $actualNumOfFiles = (Get-ChildItem -Recurse -File -Path $unzipNugetPackageDir | Where-Object { $_.Name -ne '.signature.p7s' }).Count + if (-not $expectedNumOfFiles.ContainsKey($packageKey)) { + $errors += "Package '$packageKey' is not present in file expectedNumOfFiles table. Is that package known?" + continue + } + if ($expectedNumOfFiles[$packageKey] -ne $actualNumOfFiles) { + $errors += "Number of files are not equal for '$packageBaseName', expected: $($expectedNumOfFiles[$packageKey]) actual: $actualNumOfFiles" } - finally { - # if ($null -ne $unzipNugetPackageDir -and (Test-Path $unzipNugetPackageDir)) { - # Remove-Item -Force -Recurse $unzipNugetPackageDir | Out-Null - # } + + if ($packageKey -eq "Microsoft.TestPlatform") { + Verify-Version -nugetDir $unzipNugetPackageDir -errors $errors } } @@ -108,6 +119,16 @@ function Verify-Nuget-Packages { Write-Error "There are $($errors.Count) errors:`n$($errors -join "`n")" } + # Write .cache marker files so IntegrationTestBuild.cs detects these + # extractions and skips re-extracting the same packages during test init. + Write-Host "Writing cache markers for extracted packages." + foreach ($nugetPackage in $nugetPackages) { + $unzipNugetPackageDir = Join-Path $extractedPackagesDir $nugetPackage.Name + $cacheMarkerPath = Join-Path $unzipNugetPackageDir ($nugetPackage.Name + ".cache") + $lastWriteTimeUtc = $nugetPackage.LastWriteTimeUtc.ToString("o", [System.Globalization.CultureInfo]::InvariantCulture) + [System.IO.File]::WriteAllText($cacheMarkerPath, $lastWriteTimeUtc) + } + Write-Host "Completed Verify-Nuget-Packages." $unzipNugetPackageDirs } @@ -179,18 +200,12 @@ function Verify-NugetPackageExe { "dump\DumpMinitool.exe" = "x86-64" - "QTAgent32.exe" = "x86" - "QTAgent32_35.exe" = "x86" - "QTAgent32_40.exe" = "x86" - "QTDCAgent32.exe" = "x86" - - "V1\VSTestVideoRecorder.exe" = "x86" "VideoRecorder\VSTestVideoRecorder.exe" = "x86" } $errs = @() - $exes = $UnzipNugetPackages | Get-ChildItem -Filter *.exe -Recurse -Force - if (0 -eq @($exes).Length) { + $exes = $UnzipNugetPackages | Get-ChildItem -Filter *.exe -Recurse -Force + if (0 -eq @($exes).Length) { throw "No exe files were found." } @@ -215,7 +230,7 @@ function Verify-NugetPackageExe { $fullName = $_.FullName $name = $_.Name - if ("x86" -eq $platform) { + if ("x86" -eq $platform) { $corFlagsOutput = & $corFlags $fullName # this is an native x86 exe or a .net x86 that requires of prefers 32bit $platform = if ($corFlagsOutput -like "*does not have a valid managed header*" -or $corFlagsOutput -like "*32BITREQ : 1*" -or $corFlagsOutput -like "*32BITPREF : 1*") { @@ -268,7 +283,7 @@ function Verify-NugetPackageExe { "Success: $name is $platform - $fullName" } - if ($errs) { + if ($errs) { throw "Fail!:`n$($errs -join "`n")" } } @@ -282,7 +297,7 @@ function Verify-NugetPackageVersion { ) # look for vstest.console.dll because unified build for .NET does not produce vstest.console.exe - $exes = $UnzipNugetPackages | Get-ChildItem -Filter vstest.console.dll -Recurse -Force + $exes = $UnzipNugetPackages | Get-ChildItem -Filter vstest.console.dll -Recurse -Force if (0 -eq @($exes).Length) { throw "No vstest.console.dll files were found." } @@ -294,7 +309,7 @@ function Verify-NugetPackageVersion { else { "$_ version $($_.VersionInfo.ProductVersion) is ok." } - } + } } @@ -304,3 +319,6 @@ Start-sleep -Seconds 10 # skipped, it is hard to find the right dumpbin.exe and corflags tools on server # Verify-NugetPackageExe -configuration $configuration -UnzipNugetPackages $unzipNugetPackages Verify-NugetPackageVersion -configuration $configuration -UnzipNugetPackages $unzipNugetPackages + +Write-Host "`nVerifying binding redirects..." +Verify-BindingRedirects -PackageDirs $unzipNugetPackages -Configuration $configuration diff --git a/es-metadata.yml b/es-metadata.yml new file mode 100644 index 0000000000..11af1e321e --- /dev/null +++ b/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: 88877424-b87d-45a2-8ab8-48321d99a5d2 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\Testing Platforms diff --git a/global.json b/global.json index d6470481a5..612f589cab 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,11 @@ { "sdk": { - "version": "9.0.100-rc.2.24474.11", + "version": "11.0.100-preview.3.26170.106", + "paths": [ + ".dotnet", + "$host$" + ], + "errorMessage": "The .NET SDK could not be found, please run ./build.cmd on Windows or ./build.sh on Linux and macOS.", "rollForward": "minor", "allowPrerelease": false, "architecture": "x64" @@ -8,30 +13,25 @@ "tools": { "runtimes": { "dotnet/x64": [ - "2.1.30", - "3.1.32", - "5.0.17", - "6.0.32", - "7.0.20", - "8.0.7" + "8.0.25", + "9.0.14", + "10.0.4" ], "dotnet/x86": [ - "2.1.30", - "3.1.32", - "5.0.17", - "6.0.32", - "7.0.20", - "8.0.7" + "8.0.25", + "9.0.14", + "10.0.4" ] }, "vs": { "version": "17.8.0" }, - "xcopy-msbuild": "17.8.5", - "vswhere": "2.2.7", - "dotnet": "9.0.100-rc.2.24474.11" + "dotnet": "11.0.100-preview.3.26170.106" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24562.13" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26257.4" + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } diff --git a/playground/MSTest1/MSTest1.csproj b/playground/MSTest1/MSTest1.csproj index 407633246a..302cbc6a14 100644 --- a/playground/MSTest1/MSTest1.csproj +++ b/playground/MSTest1/MSTest1.csproj @@ -1,7 +1,7 @@ - $(TargetFrameworks);net472;net5.0;net6.0;net7.0 + $(TargetFrameworks);net48;net9.0;net8.0 false false @@ -11,7 +11,7 @@ - + diff --git a/playground/MSTest2/MSTest2.csproj b/playground/MSTest2/MSTest2.csproj index 407633246a..302cbc6a14 100644 --- a/playground/MSTest2/MSTest2.csproj +++ b/playground/MSTest2/MSTest2.csproj @@ -1,7 +1,7 @@ - $(TargetFrameworks);net472;net5.0;net6.0;net7.0 + $(TargetFrameworks);net48;net9.0;net8.0 false false @@ -11,7 +11,7 @@ - + diff --git a/playground/TestPlatform.Playground/Environment.cs b/playground/TestPlatform.Playground/Environment.cs index d7ba86c869..14518b5109 100644 --- a/playground/TestPlatform.Playground/Environment.cs +++ b/playground/TestPlatform.Playground/Environment.cs @@ -9,7 +9,7 @@ internal class EnvironmentVariables { public static readonly Dictionary Variables = new() { - ["VSTEST_CONNECTION_TIMEOUT"] = "999", + ["VSTEST_CONNECTION_TIMEOUT"] = "0.1", ["VSTEST_DEBUG_NOBP"] = "1", ["VSTEST_RUNNER_DEBUG_ATTACHVS"] = "0", ["VSTEST_HOST_DEBUG_ATTACHVS"] = "0", diff --git a/playground/TestPlatform.Playground/Program.cs b/playground/TestPlatform.Playground/Program.cs index 0cebf6d996..c41c50e17d 100644 --- a/playground/TestPlatform.Playground/Program.cs +++ b/playground/TestPlatform.Playground/Program.cs @@ -37,6 +37,8 @@ static void Main() var here = Path.GetDirectoryName(thisAssemblyPath)!; var playground = Path.GetFullPath(Path.Combine(here, "..", "..", "..", "..")); + Environment.SetEnvironmentVariable("VSTEST_DEBUG_ATTACHVS_PATH", Path.Combine(here, "AttachVS.exe")); + var console = Path.Combine(here, "vstest.console", "netfx", "vstest.console.exe"); var sourceSettings = $$$""" @@ -56,7 +58,7 @@ static void Main() --> - False + true True @@ -88,8 +90,8 @@ static void Main() """; var sources = new[] { - Path.Combine(playground, "bin", "MSTest1", "Debug", "net472", "MSTest1.dll"), - Path.Combine(playground, "bin", "MSTest2", "Debug", "net472", "MSTest2.dll"), + Path.Combine(playground, "bin", "MSTest1", "Debug", "net48", "MSTest1.dll"), + Path.Combine(playground, "bin", "MSTest2", "Debug", "net48", "MSTest2.dll"), // The built in .NET projects don't now work right now in Playground, there is some conflict with Arcade. // But if you create one outside of Playground it will work. //Path.Combine(playground, "bin", "MSTest1", "Debug", "net7.0", "MSTest1.dll"), @@ -126,8 +128,8 @@ static void Main() var consoleOptions = new ConsoleParameters { EnvironmentVariables = EnvironmentVariables.Variables, - // LogFilePath = Path.Combine(here, "logs", "log.txt"), - // TraceLevel = TraceLevel.Verbose, + LogFilePath = Path.Combine(here, "logs", "log.txt"), + TraceLevel = TraceLevel.Verbose, }; var options = new TestPlatformOptions { diff --git a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj index 9a5dcbffbe..7259dc0728 100644 --- a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj +++ b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj @@ -4,23 +4,37 @@ None $(MSBuildWarningsAsMessages);MSB3276 + + false + + false Exe - $(TargetFrameworks);net472 + $(RunnerTargetFrameworks) - - + + - - - - + + + + + + + + @@ -34,7 +48,7 @@ - + @@ -50,27 +64,29 @@ - - + + - - + + - + - + - + - + + + - + @@ -89,7 +105,7 @@ - + --> - + diff --git a/samples/UnitTestProject/UnitTestProject.csproj b/samples/UnitTestProject/UnitTestProject.csproj index 523277f73d..3f8880c53a 100644 --- a/samples/UnitTestProject/UnitTestProject.csproj +++ b/samples/UnitTestProject/UnitTestProject.csproj @@ -24,9 +24,7 @@ - - - + $(DefineConstants);RELEASE diff --git a/scripts/build-compatibility-matrix.ps1 b/scripts/build-compatibility-matrix.ps1 deleted file mode 100644 index e5be1374ef..0000000000 --- a/scripts/build-compatibility-matrix.ps1 +++ /dev/null @@ -1,234 +0,0 @@ - -param ( - [VerifyNotNull] - [string] $RootPath, - [VerifyNotNull] - [string] $TestAssetsPath, - [VerifyNotNull] - [string] $TestArtifactsPath, - [VerifyNotNull] - [string] $PackagesPath, - [VerifySet("Debug", "Release")] - [string] $Configuration, - [VerifyNotNull] - [string] $DotnetExe, - [switch] $Ci, - [switch] $LocalizedBuild, - [VerifyNotNull] - [string] $NugetExeVersion -) - -. $PSScriptRoot\common.lib.ps1 - -Write-Log "Invoke-CompatibilityTestAssetsBuild: Start test assets build." -$timer = Start-Timer -$generated = Join-Path (Split-Path -Path $TestAssetsPath) -ChildPath "GeneratedTestAssets" -$generatedSln = Join-Path $generated "CompatibilityTestAssets.sln" - -# Figure out if the versions or the projects to build changed, and if they did not -# and the solution is already in place just build it. -# Otherwise delete everything and regenerate and re-build. -$dependenciesPath = "$RootPath\scripts\build\TestPlatform.Dependencies.props" -$dependenciesXml = [xml](Get-Content -Raw -Encoding UTF8 $dependenciesPath) - -$cacheId = [ordered]@{ } - -# Restore previous versions of TestPlatform (for vstest.console.exe), and TestPlatform.CLI (for vstest.console.dll). -# These properties are coming from TestPlatform.Dependencies.props. -$vstestConsoleVersionProperties = @( - "VSTestConsoleLatestVersion" - "VSTestConsoleLatestPreviewVersion" - "VSTestConsoleLatestStableVersion" - "VSTestConsoleRecentStableVersion" - "VSTestConsoleMostDownloadedVersion" - "VSTestConsolePreviousStableVersion" - "VSTestConsoleLegacyStableVersion" -) - -# Build with multiple versions of MSTest. The projects are directly in the root. -# The folder structure in VS is not echoed in the TestAssets directory. -$projects = @( - "$RootPath\test\TestAssets\MSTestProject1\MSTestProject1.csproj" - "$RootPath\test\TestAssets\MSTestProject2\MSTestProject2.csproj" - # Don't use this one, it does not use the variables for mstest and test sdk. - # "$RootPath\test\TestAssets\SimpleTestProject2\SimpleTestProject2.csproj" -) - -$msTestVersionProperties = @( - "MSTestFrameworkLatestPreviewVersion" - "MSTestFrameworkLatestStableVersion" - "MSTestFrameworkRecentStableVersion" - "MSTestFrameworkMostDownloadedVersion" - "MSTestFrameworkPreviousStableVersion" - "MSTestFrameworkLegacyStableVersion" -) - -# We use the same version properties for NET.Test.Sdk as for VSTestConsole, for now. -foreach ($sdkPropertyName in $vstestConsoleVersionProperties) { - if ("VSTestConsoleLatestVersion" -eq $sdkPropertyName) { - # NETTestSdkVersion has the version of the locally built package. - $netTestSdkVersion = $dependenciesXml.Project.PropertyGroup."NETTestSdkVersion" - } - else { - $netTestSdkVersion = $dependenciesXml.Project.PropertyGroup.$sdkPropertyName - } - - if (-not $netTestSdkVersion) { - throw "NetTestSdkVersion for $sdkPropertyName is empty." - } - - $cacheId[$sdkPropertyName] = $netTestSdkVersion - - # We don't use the results of this build anywhere, we just use them to restore the packages to nuget cache - # because using nuget.exe install errors out in various weird ways. - Invoke-Exe $dotnetExe -Arguments "build $RootPath\test\TestAssets\Tools\Tools.csproj --configuration $Configuration -v:minimal -p:CIBuild=$Ci -p:LocalizedBuild=$LocalizedBuild -p:NETTestSdkVersion=$netTestSdkVersion" -} - -foreach ($propertyName in $msTestVersionProperties) { - $mstestVersion = $dependenciesXml.Project.PropertyGroup.$propertyName - - if (-not $mstestVersion) { - throw "MSTestVersion for $propertyName is empty." - } - - $cacheId[$propertyName] = $mstestVersion -} - -$cacheId["projects"] = $projects - -$cacheIdText = $cacheId | ConvertTo-Json - -$currentCacheId = if (Test-Path "$generated/checksum.json") { Get-Content "$generated/checksum.json" -Raw } - -$rebuild = $true -if ($cacheIdText -eq $currentCacheId) { - if (Test-Path $generatedSln) { - Write-Log ".. .. Build: Source: $generatedSln, cache is up to date, just building the solution." - Invoke-Exe $dotnetExe -Arguments "build $generatedSln --configuration $Configuration -v:minimal -p:CIBuild=$Ci -p:LocalizedBuild=$LocalizedBuild" - $rebuild = $false - } -} - -if ($rebuild) { - if (Test-Path $generated) { - Remove-Item $generated -Recurse -Force - } - - New-Item -ItemType Directory -Force -Path $generated | Out-Null - - Write-Log ".. .. Generate: Source: $generatedSln" - $nugetExe = Join-Path $PackagesPath -ChildPath "Nuget.CommandLine" | Join-Path -ChildPath $NugetExeVersion | Join-Path -ChildPath "tools\NuGet.exe" - $nugetConfigSource = Join-Path $TestAssetsPath "NuGet.config" - $nugetConfig = Join-Path $generated "NuGet.config" - - Invoke-Exe $dotnetExe -Arguments "new sln --name CompatibilityTestAssets --output ""$generated""" - - Write-Log ".. .. Build: Source: $generatedSln" - try { - $projectsToAdd = @() - $nugetConfigSource = Join-Path $TestAssetsPath "NuGet.config" - $nugetConfig = Join-Path $generated "NuGet.config" - - Copy-Item -Path $nugetConfigSource -Destination $nugetConfig - - Write-Log ".. .. Build: Source: $generatedSln -- add NuGet source" - Invoke-Exe -IgnoreExitCode 1 $nugetExe -Arguments "sources add -Name ""locally-built-testplatform-packages"" -Source $TestArtifactsPath\packages\ -ConfigFile ""$nugetConfig""" - - Write-Log ".. .. Build: Source: $generatedSln -- generate solution" - foreach ($project in $projects) { - $projectName = Split-Path -Path $project -Leaf - $projectBaseName = [IO.Path]::GetFileNameWithoutExtension($projectName) - $projectDir = Split-Path -Path $project - $projectItems = Get-ChildItem $projectDir | Where-Object { $_.Name -notin "bin", "obj" } | ForEach-Object { if ($_.PsIsContainer) { Get-ChildItem $_ -Recurse -File } else { $_ } } - - Write-Log ".. .. .. Project $project has $($projectItems.Count) project items." - # We use the same version properties for NET.Test.Sdk as for VSTestConsole, for now. - foreach ($sdkPropertyName in $vstestConsoleVersionProperties) { - if ("VSTestConsoleLatestVersion" -eq $sdkPropertyName) { - # NETTestSdkVersion has the version of the locally built package. - $netTestSdkVersion = $dependenciesXml.Project.PropertyGroup."NETTestSdkVersion" - } - else { - $netTestSdkVersion = $dependenciesXml.Project.PropertyGroup.$sdkPropertyName - } - - if (-not $netTestSdkVersion) { - throw "NetTestSdkVersion for $sdkPropertyName is empty." - } - - $dirNetTestSdkVersion = $netTestSdkVersion -replace "\[|\]" - $dirNetTestSdkPropertyName = $sdkPropertyName -replace "Framework" -replace "Version" -replace "VSTestConsole", "NETTestSdk" - - foreach ($propertyName in $msTestVersionProperties) { - $mstestVersion = $dependenciesXml.Project.PropertyGroup.$propertyName - - if (-not $mstestVersion) { - throw "MSTestVersion for $propertyName is empty." - } - - $dirMSTestVersion = $mstestVersion -replace "\[|\]" - $dirMSTestPropertyName = $propertyName -replace "Framework" -replace "Version" - - # Do not make this a folder structure, it will break the relative reference to scripts\build\TestAssets.props that we have in the project, - # because the relative path will be different. - # - # It would be nice to use fully descriptive name but it is too long, hash the versions instead. - # $compatibilityProjectDir = "$generated/$projectBaseName--$dirNetTestSdkPropertyName-$dirNetTestSdkVersion--$dirMSTestPropertyName-$dirMSTestVersion" - $versions = "$dirNetTestSdkPropertyName-$dirNetTestSdkVersion--$dirMSTestPropertyName-$dirMSTestVersion" - $hash = Get-Hash $versions - Write-Host Hashed $versions to $hash - $projectShortName = "$projectBaseName--" + $hash - $compatibilityProjectDir = "$generated/$projectShortName" - - if (Test-path $compatibilityProjectDir) { - throw "Path '$compatibilityProjectDir' does not exist" - } - New-Item -ItemType Directory -Path $compatibilityProjectDir | Out-Null - $compatibilityProjectDir = Resolve-Path $compatibilityProjectDir - foreach ($projectItem in $projectItems) { - $relativePath = ($projectItem.FullName -replace [regex]::Escape($projectDir)).TrimStart("\") - $fullPath = Join-Path $compatibilityProjectDir $relativePath - try { - Copy-Item -Path $projectItem.FullName -Destination $fullPath -Verbose - } - catch { - # can throw on wrong path, this makes the error more verbose - throw "$_, Source: '$($projectItem.FullName)', Destination: '$fullPath'" - } - } - - $compatibilityCsproj = Get-ChildItem -Path $compatibilityProjectDir -Filter *.csproj - if (-not $compatibilityCsproj) { - throw "No .csproj files found in directory $compatibilityProjectDir." - } - - $compatibilityCsproj = $compatibilityCsproj.FullName - $csprojContent = (Get-Content $compatibilityCsproj -Encoding UTF8) ` - -replace "\$\(MSTestFrameworkVersion\)", $mstestVersion ` - -replace "\$\(MSTestAdapterVersion\)", $mstestVersion ` - -replace "\$\(NETTestSdkVersion\)", $netTestSdkVersion - $csprojContent | Set-Content -Encoding UTF8 -Path $compatibilityCsproj -Force - - $uniqueCsprojName = Join-Path $compatibilityProjectDir "$projectShortName.csproj" - Rename-Item $compatibilityCsproj $uniqueCsprojName - $projectsToAdd += $uniqueCsprojName - - Write-Log ".. .. .. Generated: $uniqueCsprojName" - } - } - } - - Write-Log ".. .. .. Building: generatedSln" - Invoke-Exe $dotnetExe -Arguments "sln $generatedSln add $projectsToAdd" - Invoke-Exe $dotnetExe -Arguments "build $generatedSln --configuration $Configuration -v:minimal -p:CIBuild=$Ci -p:LocalizedBuild=$LocalizedBuild" - $cacheIdText | Set-Content "$generated/checksum.json" -NoNewline - } - finally { - Write-Log ".. .. Build: Source: $TestAssetsPath_Solution -- remove NuGet source" - Invoke-Exe -IgnoreExitCode 1 $nugetExe -Arguments "sources remove -Name ""locally-built-testplatform-packages"" -ConfigFile ""$nugetConfig""" - } -} -Write-Log ".. .. Build: Complete." -Write-Log "Invoke-CompatibilityTestAssetsBuild: Complete. {$(Get-ElapsedTime($timer))}" - - diff --git a/scripts/perf/perf.ps1 b/scripts/perf/perf.ps1 index 29de7c6867..a4daca2484 100644 --- a/scripts/perf/perf.ps1 +++ b/scripts/perf/perf.ps1 @@ -6,12 +6,12 @@ Param( [System.String] $Configuration = "Release", [Parameter(Mandatory=$false)] - [ValidateSet("win7-x64", "win7-x86")] + [ValidateSet("win7-x64", "win-x86")] [Alias("r")] [System.String] $TargetRuntime = "win7-x64", [Parameter(Mandatory=$false)] - [ValidateSet("netcoreapp3.1", "net462")] + [ValidateSet("net9.0", "net462")] [Alias("f")] [System.String] $TargetFramework, @@ -40,11 +40,11 @@ $env:TP_OUT_DIR = Join-Path $env:TP_ROOT_DIR "artifacts" # Test configuration # $TPT_TargetFrameworkFullCLR = "net462" -$TPT_TargetFramework31Core = "netcoreapp3.1" +$TPT_TargetFramework6Core = "net6.0" Write-Verbose "Setup build configuration." $Script:TPT_Configuration = $Configuration $Script:TPT_SourceFolders = @(Join-Path $env:TP_ROOT_DIR "test\TestAssets") -$Script:TPT_TargetFrameworks =@($TPT_TargetFramework31Core, $TPT_TargetFrameworkFullCLR) +$Script:TPT_TargetFrameworks =@($TPT_TargetFramework6Core, $TPT_TargetFrameworkFullCLR) $Script:TPT_TargetFramework = $TargetFramework $Script:TPT_TargetRuntime = $TargetRuntime $Script:TPT_Pattern = $Pattern @@ -151,11 +151,11 @@ function Get-ConsoleRunnerPath($runner, $targetFrameWork) { if($runner -eq "vstest.console") { - if($targetFrameWork -eq $TPT_TargetFramework31Core) + if($targetFrameWork -eq $TPT_TargetFramework6Core) { $vstestConsoleFileName = "vstest.console.dll" $targetRunTime = "" - $vstestConsolePath = Join-Path (Get-PackageDirectory $TPT_TargetFramework31Core $targetRuntime) $vstestConsoleFileName + $vstestConsolePath = Join-Path (Get-PackageDirectory $TPT_TargetFramework6Core $targetRuntime) $vstestConsoleFileName } else { $vstestConsoleFileName = "vstest.console.exe" $targetRunTime = $Script:TPT_TargetRuntime diff --git a/scripts/test.ps1 b/scripts/test.ps1 index a3aa6de7f3..db554d0044 100644 --- a/scripts/test.ps1 +++ b/scripts/test.ps1 @@ -9,12 +9,12 @@ Param( [System.String] $Configuration = "Debug", [Parameter(Mandatory=$false)] - [ValidateSet("win7-x64", "win7-x86")] + [ValidateSet("win7-x64", "win-x86")] [Alias("r")] [System.String] $TargetRuntime = "win7-x64", [Parameter(Mandatory=$false)] - [ValidateSet("net48", "net6.0")] + [ValidateSet("net48", "net9.0")] [Alias("f")] [System.String] $TargetFramework, @@ -68,8 +68,6 @@ $env:PATH = "$(Split-Path $(Get-DotNetPath));$env:PATH" $env:DOTNET_ROOT = Join-Path $env:TP_TOOLS_DIR "dotnet" # set the root for x86 runtimes as well ${env:DOTNET_ROOT(x86)} = "${env:DOTNET_ROOT}_x86" -# disable looking up other dotnets in programfiles -$env:DOTNET_MULTILEVEL_LOOKUP = 0 # Disable first run since we want to control all package sources $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 @@ -96,12 +94,12 @@ $env:NUGET_PACKAGES = $env:TP_PACKAGES_DIR # $TPT_TargetFrameworkNet462 = "net462" $TPT_TargetFrameworkNet48 = "net48" -$TPT_TargetFrameworkCore31 = "netcoreapp3.1" -$TPT_TargetFrameworkNet60 = "net6.0" +$TPT_TargetFrameworkNet80 = "net8.0" +$TPT_TargetFrameworkNet90 = "net9.0" Write-Verbose "Setup build configuration." $Script:TPT_Configuration = $Configuration $Script:TPT_SourceFolders = @("test") -$Script:TPT_TargetFrameworks =@($TPT_TargetFrameworkNet48, $TPT_TargetFrameworkNet60) +$Script:TPT_TargetFrameworks =@($TPT_TargetFrameworkNet48, $TPT_TargetFrameworkNet90) $Script:TPT_TargetFramework = $TargetFramework $Script:TPT_TargetRuntime = $TargetRuntime $Script:TPT_SkipProjects = @("_none_"); @@ -227,11 +225,11 @@ function Invoke-Test $testFilter = "/testCaseFilter:`"$TPT_TestFilter`"" } - if($fx -eq $TPT_TargetFrameworkNet60) + if($fx -eq $TPT_TargetFrameworkNet90) { $vstestConsoleFileName = "vstest.console.dll" $targetRunTime = "" - $vstestConsolePath = Join-Path (Get-PackageDirectory $TPT_TargetFrameworkCore31 $targetRuntime) $vstestConsoleFileName + $vstestConsolePath = Join-Path (Get-PackageDirectory $TPT_TargetFrameworkNet80 $targetRuntime) $vstestConsoleFileName } else { diff --git a/scripts/test.sh b/scripts/test.sh index cc7ab86f90..0fe2993aa8 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -57,7 +57,7 @@ done # # Variables # -PROJECT_NAME_PATTERNS=**$PROJECT_NAME_PATTERNS*bin*$CONFIGURATION*net6.0*$PROJECT_NAME_PATTERNS*Tests*dll +PROJECT_NAME_PATTERNS=**$PROJECT_NAME_PATTERNS*bin*$CONFIGURATION*net9.0*$PROJECT_NAME_PATTERNS*Tests*dll TP_ROOT_DIR=$(cd "$(dirname "$0")"; pwd -P) TP_TOOLS_DIR="$TP_ROOT_DIR/tools" TP_PACKAGES_DIR="$TP_ROOT_DIR/packages" @@ -75,8 +75,8 @@ DOTNET_CLI_VERSION="latest" # # Build configuration # -TPB_Solution="TestPlatform.sln" -TPB_TargetFrameworkCore="netcoreapp3.1" +TPB_Solution="TestPlatform.slnx" +TPB_TargetFrameworkCore="net8.0" TPB_Configuration=$CONFIGURATION TPB_TargetRuntime=$TARGET_RUNTIME TPB_Verbose=$VERBOSE diff --git a/src/AttachVS/AttachVS.csproj b/src/AttachVS/AttachVS.csproj index 480945c0e3..9e6fbc3166 100644 --- a/src/AttachVS/AttachVS.csproj +++ b/src/AttachVS/AttachVS.csproj @@ -2,7 +2,7 @@ Exe - net7;net472 + $(TestHostMinimumTargetFrameworks) AttachVS true diff --git a/src/DataCollectors/DumpMinitool.arm64/DumpMinitool.arm64.csproj b/src/DataCollectors/DumpMinitool.arm64/DumpMinitool.arm64.csproj index 1163b54034..9e59a211fa 100644 --- a/src/DataCollectors/DumpMinitool.arm64/DumpMinitool.arm64.csproj +++ b/src/DataCollectors/DumpMinitool.arm64/DumpMinitool.arm64.csproj @@ -2,8 +2,13 @@ - net7.0;net6.0;$(NetFrameworkMinimum) - false + + $(NetFrameworkMinimum) + false Exe false win10-arm64 diff --git a/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj index 6216d6b5c0..e1613e1b01 100644 --- a/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj +++ b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj @@ -2,12 +2,17 @@ - net7.0;$(NetFrameworkMinimum) + + $(NetFrameworkMinimum) AnyCPU - true + true Exe false - win7-x86 + win-x86 false true + $(NetFrameworkMinimum) AnyCPU - false + false Exe false win7-x64 diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/Microsoft.TestPlatform.Extensions.EventLogCollector.csproj b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/Microsoft.TestPlatform.Extensions.EventLogCollector.csproj index 2fea4d4fb1..f7c86f1bae 100644 --- a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/Microsoft.TestPlatform.Extensions.EventLogCollector.csproj +++ b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/Microsoft.TestPlatform.Extensions.EventLogCollector.csproj @@ -3,7 +3,7 @@ Microsoft.TestPlatform.Extensions.EventLogCollector - net7.0;$(NetFrameworkMinimum) + $(NetCoreMinimum);$(NetFrameworkRunnerTargetFramework) false true diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net462/PublicAPI.Shipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net462/PublicAPI.Shipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net462/PublicAPI.Unshipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net9.0/PublicAPI.Shipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net9.0/PublicAPI.Shipped.txt diff --git a/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net9.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector/PublicAPI/net9.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs index b5c931be59..a1eb37da9c 100644 --- a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs +++ b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs @@ -13,6 +13,20 @@ namespace Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; public static partial class ManagedNameHelper { + private readonly struct AssemblyNameCache + { + public AssemblyNameCache(Assembly? assembly, string simpleName) + { + Assembly = assembly; + SimpleName = simpleName; + } + + public Assembly? Assembly { get; } + public string SimpleName { get; } + } + + private static AssemblyNameCache? s_lastAssemblyNameCache; + /// /// Gets fully qualified managed type and method name from given instance. /// @@ -80,8 +94,18 @@ public static void GetManagedName(MethodBase method, out string managedTypeName, /// public static void GetManagedName(MethodBase method, out string managedTypeName, out string managedMethodName, out string?[] hierarchyValues) { - GetManagedName(method, out managedTypeName, out managedMethodName); - GetManagedNameAndHierarchy(method, true, out _, out _, out hierarchyValues); + if (!method.IsGenericMethod && ReflectionHelpers.GetReflectedType(method) is { } semanticType && !ReflectionHelpers.IsGenericType(semanticType)) + { + // We are dealing with non-generic method in non-generic type. + // So, it doesn't matter what we pass as "useClosedTypes". + // Instead of calling GetManagedNameAndHierarchy that does repeated work, we call it only once. + GetManagedNameAndHierarchy(method, false, out managedTypeName, out managedMethodName, out hierarchyValues); + } + else + { + GetManagedNameAndHierarchy(method, false, out managedTypeName, out managedMethodName, out _); + GetManagedNameAndHierarchy(method, true, out _, out _, out hierarchyValues); + } } /// @@ -201,7 +225,19 @@ private static void GetManagedNameAndHierarchy(MethodBase method, bool useClosed hierarchyValues[HierarchyConstants.Levels.ClassIndex] = managedTypeName.Substring(hierarchyPos[1] + 1, hierarchyPos[2] - hierarchyPos[1] - 1); hierarchyValues[HierarchyConstants.Levels.NamespaceIndex] = managedTypeName.Substring(hierarchyPos[0], hierarchyPos[1] - hierarchyPos[0]); } - hierarchyValues[HierarchyConstants.Levels.ContainerIndex] = method.DeclaringType?.Assembly?.GetName()?.Name ?? string.Empty; + + var assembly = method.DeclaringType?.Assembly; + if (s_lastAssemblyNameCache is { } cache && + cache.Assembly == assembly) + { + hierarchyValues[HierarchyConstants.Levels.ContainerIndex] = cache.SimpleName; + } + else + { + var assemblyName = assembly?.GetName()?.Name ?? string.Empty; + hierarchyValues[HierarchyConstants.Levels.ContainerIndex] = assemblyName; + s_lastAssemblyNameCache = new AssemblyNameCache(assembly, assemblyName); + } } /// @@ -344,7 +380,7 @@ bool Filter(MemberInfo mbr, object? param) hierarchies[1] = hierarchies[0]; } - AppendNestedTypeName(b, type, closedType); + AppendNestedTypeName(b, type); if (closedType) { AppendGenericTypeParameters(b, type); @@ -456,7 +492,7 @@ private static void NormalizeAndAppendString(StringBuilder b, string name) b.Append('\''); } - private static int AppendNestedTypeName(StringBuilder b, Type? type, bool closedType) + private static int AppendNestedTypeName(StringBuilder b, Type? type) { if (type is null) { @@ -466,7 +502,7 @@ private static int AppendNestedTypeName(StringBuilder b, Type? type, bool closed var outerArity = 0; if (type.IsNested) { - outerArity = AppendNestedTypeName(b, type.DeclaringType, closedType); + outerArity = AppendNestedTypeName(b, type.DeclaringType); b.Append('+'); } diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj index 53049b4413..5d34ff1083 100644 --- a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj +++ b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj @@ -1,7 +1,13 @@ - netstandard2.0;$(NetFrameworkMinimum);net6.0;net8.0;$(NetCurrent) + + $(TestHostMinimumTargetFrameworks);$(ExtensionTargetFrameworks);net9.0 Microsoft.TestPlatform.AdapterUtilities Microsoft.TestPlatform.AdapterUtilities @@ -17,16 +23,10 @@ - + false - - - - - - True diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.nuspec b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.nuspec index c3a40455c8..c61355b15b 100644 --- a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.nuspec +++ b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.nuspec @@ -2,11 +2,11 @@ $CommonMetadataElements$ + README.md - @@ -14,10 +14,10 @@ $CommonFileElements$ + - @@ -49,20 +49,6 @@ - - - - - - - - - - - - - - diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/README.md b/src/Microsoft.TestPlatform.AdapterUtilities/README.md new file mode 100644 index 0000000000..e1ca70a348 --- /dev/null +++ b/src/Microsoft.TestPlatform.AdapterUtilities/README.md @@ -0,0 +1,23 @@ +# Microsoft.TestPlatform.AdapterUtilities + +Utility helpers for test adapters targeting the Visual Studio Test Platform. Provides support for modern functionality such as standardized fully qualified names and hierarchical test case names. + +## Usage + +Add this package to your test adapter project: + +```xml + +``` + +## Key Features + +- `TestIdProvider` — generates stable, unique test IDs +- `ManagedNameHelper` — converts between method info and managed type/method name pairs +- Hierarchical test case name support for Test Explorer + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Adapter Extensibility](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0004-Adapter-Extensibility.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj index 0fdd27966b..352f99bca8 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.csproj @@ -3,7 +3,12 @@ Microsoft.TestPlatform.Build - netstandard2.0 + + $(ExtensionTargetFrameworks) false true @@ -12,8 +17,8 @@ true Microsoft.TestPlatform.Build.nuspec - Microsoft.TestPlatform.Build.sourcebuild.nuspec - Microsoft.TestPlatform.Build.sourcebuild.product.nuspec + Microsoft.TestPlatform.Build.sourcebuild.nuspec + Microsoft.TestPlatform.Build.sourcebuild.product.nuspec $(OutputPath) Microsoft.TestPlatform.Build vstest visual-studio unittest testplatform mstest microsoft test testing @@ -30,7 +35,6 @@ - diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.nuspec b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.nuspec index c529c32611..f66507da05 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.nuspec +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -10,6 +11,7 @@ $CommonFileElements$ + diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.nuspec b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.nuspec index cfafc21c32..fd69d9436c 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.nuspec +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.nuspec @@ -2,22 +2,21 @@ $CommonMetadataElements$ + README.md - $CommonFileElements$ + - - + - diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.product.nuspec b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.product.nuspec index e71fe4aefc..fd69d9436c 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.product.nuspec +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.Build.sourcebuild.product.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -10,6 +11,7 @@ $CommonFileElements$ + diff --git a/src/Microsoft.TestPlatform.Build/README.md b/src/Microsoft.TestPlatform.Build/README.md new file mode 100644 index 0000000000..add3cac943 --- /dev/null +++ b/src/Microsoft.TestPlatform.Build/README.md @@ -0,0 +1,17 @@ +# Microsoft.TestPlatform.Build + +Build tasks and targets for running tests with the Visual Studio Test Platform. This package provides MSBuild integration that enables `dotnet test` to discover and execute tests. + +## Usage + +This package is consumed by .NET SDK. Direct references are needed only for advanced build customization scenarios. + +```xml + +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform Architecture](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0001-Test-Platform-Architecture.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/Microsoft.TestPlatform.Build/Tasks/TestTaskUtils.cs b/src/Microsoft.TestPlatform.Build/Tasks/TestTaskUtils.cs index d81a03b48e..f34eae92db 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/TestTaskUtils.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/TestTaskUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using Microsoft.Build.Utilities; @@ -237,4 +238,16 @@ public static string CreateCommandLineArguments(ITestTask task) return builder.ToString(); } + + /// + /// Resolves the full path to the dotnet host from the DOTNET_HOST_PATH environment variable. + /// + /// + /// The resolved full path to the dotnet host, or if the variable is not set or empty. + /// + internal static string? ResolveDotnetPath() + { + var dotnetHostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + return StringUtils.IsNullOrEmpty(dotnetHostPath) ? null : Path.GetFullPath(dotnetHostPath); + } } diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestLogsTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestLogsTask.cs index d5dfe8c7db..7e1a0dd8b1 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestLogsTask.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestLogsTask.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Utilities; namespace Microsoft.TestPlatform.Build.Tasks; + public class VSTestLogsTask : Task { public string? LogType { get; set; } diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs index 413e58acad..11f949fc9b 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs @@ -15,8 +15,6 @@ public class VSTestTask : Task, ITestTask { private int _activeProcessId; - private const string DotnetExe = "dotnet"; - [Required] public ITaskItem? TestFileFullPath { get; set; } public string? VSTestSetting { get; set; } @@ -73,7 +71,7 @@ public override bool Execute() var processInfo = new ProcessStartInfo { - FileName = DotnetExe, + FileName = TestTaskUtils.ResolveDotnetPath() ?? "dotnet", Arguments = TestTaskUtils.CreateCommandLineArguments(this), UseShellExecute = false, }; diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs index cbc866d928..1779321385 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask2.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -282,37 +281,7 @@ private bool TryGetMessage(string singleLine, out string name, out string?[] dat protected override string? GenerateFullPathToTool() { - if (!ToolPath.IsNullOrEmpty()) - { - return Path.Combine(Path.GetDirectoryName(Path.GetFullPath(ToolPath))!, ToolExe); - } - - //TODO: https://github.com/dotnet/sdk/issues/20 Need to get the dotnet path from MSBuild? - - var dhp = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); - if (!dhp.IsNullOrEmpty()) - { - var path = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(dhp))!, ToolExe); - if (File.Exists(path)) - { - return path; - } - } - - if (File.Exists(ToolExe)) - { - return Path.GetFullPath(ToolExe); - } - - var values = Environment.GetEnvironmentVariable("PATH"); - foreach (var p in values!.Split(Path.PathSeparator)) - { - var fullPath = Path.Combine(p, ToolExe); - if (File.Exists(fullPath)) - return fullPath; - } - - return null; + return TestTaskUtils.ResolveDotnetPath(); } /// diff --git a/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj index 26f5d313c7..ec5cfaeae2 100644 --- a/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj +++ b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj @@ -3,7 +3,10 @@ Microsoft.VisualStudio.TestPlatform.Client - net7.0;netstandard2.0;$(NetFrameworkMinimum) + + $(NetFrameworkRunnerTargetFramework);$(ExtensionTargetFrameworks) false @@ -17,7 +20,7 @@ true - + @@ -46,5 +49,7 @@ + + diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net462/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net462/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net462/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net9.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net9.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/net9.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Client/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Client/PublicAPI/net9.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.cs.xlf index b75d2bec98..a6f5639990 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.cs.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.de.xlf index b722981722..738dcf18b8 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.de.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.es.xlf index 795f155215..f62e1af154 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.es.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.fr.xlf index de3857c34d..385d2c0c98 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.fr.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.it.xlf index 5c1c78429e..d412aadcaa 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.it.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ja.xlf index 8a41f55e84..1765c355ed 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ja.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ko.xlf index 4ab193fad0..13daa6986b 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ko.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pl.xlf index 85452cc58a..af1327bc6c 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pl.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pt-BR.xlf index 66beb85c5a..9c03089380 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.pt-BR.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ru.xlf index 1f3f922fc0..15eea1c74c 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.ru.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.tr.xlf index 45f5697dfb..038b50e60d 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.tr.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hans.xlf index d2e08e3ee2..9d11a82f1e 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hans.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hant.xlf index 3d4a773655..0b66e40635 100644 --- a/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.Client/Resources/xlf/Resources.zh-Hant.xlf @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs index 0ec01aa802..891caeee80 100644 --- a/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/DataCollectionAttachmentManager.cs @@ -51,7 +51,7 @@ internal class DataCollectionAttachmentManager : IDataCollectionAttachmentManage /// /// Attachment transfer tasks associated with a given datacollection context. /// - private readonly ConcurrentDictionary> _attachmentTasks; + private readonly ConcurrentDictionary> _attachmentTasks; /// /// Use to cancel attachment transfers if test run is canceled. @@ -79,7 +79,7 @@ protected DataCollectionAttachmentManager(IFileHelper fileHelper) { _fileHelper = fileHelper; _cancellationTokenSource = new CancellationTokenSource(); - _attachmentTasks = new ConcurrentDictionary>(); + _attachmentTasks = new ConcurrentDictionary>(); AttachmentSets = new ConcurrentDictionary>(); } @@ -170,12 +170,8 @@ public void AddAttachment(FileTransferInformation fileTransferInfo, AsyncComplet return; } - if (!AttachmentSets.ContainsKey(fileTransferInfo.Context)) - { - var uriAttachmentSetMap = new ConcurrentDictionary(); - AttachmentSets.TryAdd(fileTransferInfo.Context, uriAttachmentSetMap); - _attachmentTasks.TryAdd(fileTransferInfo.Context, new List()); - } + AttachmentSets.GetOrAdd(fileTransferInfo.Context, _ => new ConcurrentDictionary()); + _attachmentTasks.GetOrAdd(fileTransferInfo.Context, _ => new ConcurrentBag()); if (!AttachmentSets[fileTransferInfo.Context].ContainsKey(uri)) { diff --git a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/ExtensionDecoratorFactory.cs b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/ExtensionDecoratorFactory.cs index 7a82106c77..498593b484 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/ExtensionDecoratorFactory.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/ExtensionDecoratorFactory.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionDecorators; + internal class ExtensionDecoratorFactory { private readonly IFeatureFlag _featureFlag; diff --git a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecorator.cs b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecorator.cs index e89183d3a4..6358417eb9 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecorator.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecorator.cs @@ -112,7 +112,8 @@ public SerializeTestRunDecoratorFrameworkHandle(IFrameworkHandle frameworkHandle _testEnd = testEnd; } - public bool EnableShutdownAfterTestRun { get => _frameworkHandle.EnableShutdownAfterTestRun; set => _frameworkHandle.EnableShutdownAfterTestRun = value; } + [Obsolete("This property has no effect", error: false)] +public bool EnableShutdownAfterTestRun { get => _frameworkHandle.EnableShutdownAfterTestRun; set => _frameworkHandle.EnableShutdownAfterTestRun = value; } public int LaunchProcessWithDebuggerAttached(string filePath, string? workingDirectory, string? arguments, IDictionary? environmentVariables) => _frameworkHandle.LaunchProcessWithDebuggerAttached(filePath, workingDirectory, arguments, environmentVariables); diff --git a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecoratorFrameworkHandle.cs b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecoratorFrameworkHandle.cs index 2fe58fea6a..1b0e81367e 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecoratorFrameworkHandle.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionDecorators/SerialTestRunDecoratorFrameworkHandle.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Threading; @@ -21,6 +22,7 @@ public SerialTestRunDecoratorFrameworkHandle(IFrameworkHandle frameworkHandle, S _testEnd = testEnd; } + [Obsolete("This property has no effect", error: false)] public bool EnableShutdownAfterTestRun { get => _frameworkHandle.EnableShutdownAfterTestRun; set => _frameworkHandle.EnableShutdownAfterTestRun = value; } public int LaunchProcessWithDebuggerAttached(string filePath, string? workingDirectory, string? arguments, IDictionary? environmentVariables) diff --git a/src/Microsoft.TestPlatform.Common/ExternalAssemblyVersions.cs b/src/Microsoft.TestPlatform.Common/ExternalAssemblyVersions.cs index 7e831df12f..f05cf8adc3 100644 --- a/src/Microsoft.TestPlatform.Common/ExternalAssemblyVersions.cs +++ b/src/Microsoft.TestPlatform.Common/ExternalAssemblyVersions.cs @@ -10,5 +10,5 @@ internal class ExternalAssemblyVersions /// Microsoft.QualityTools.Testing.Fakes.TestRunnerHarness package. /// The Package version can be found in "eng\Versions.props" /// - internal const string MicrosoftFakesAssemblyVersion = "17.0.0.0"; + internal const string MicrosoftFakesAssemblyVersion = "18.0.0.0"; } diff --git a/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs b/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs index 6d112c3fd3..b0477c6022 100644 --- a/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs +++ b/src/Microsoft.TestPlatform.Common/Filtering/Condition.cs @@ -40,7 +40,7 @@ internal enum Operator /// /// Represents a condition in filter expression. /// -internal class Condition +internal sealed class Condition { /// /// Default property name which will be used when filter has only property value. @@ -52,37 +52,60 @@ internal class Condition /// public const Operation DefaultOperation = Operation.Contains; + internal Condition(string name, Operation operation, string value) + { + Name = name; + Operation = operation; + Value = value; + } + /// /// Name of the property used in condition. /// - internal string Name - { - get; - private set; - } + internal string Name { get; } /// /// Value for the property. /// - internal string Value - { - get; - private set; - } + internal string Value { get; } /// /// Operation to be performed. /// - internal Operation Operation + internal Operation Operation { get; } + + private bool EvaluateEqualOperation(string[]? multiValue) { - get; - private set; + // if any value in multi-valued property matches 'this.Value', for Equal to evaluate true. + if (multiValue != null) + { + foreach (string propertyValue in multiValue) + { + if (string.Equals(propertyValue, Value, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; } - internal Condition(string name, Operation operation, string value) + + private bool EvaluateContainsOperation(string[]? multiValue) { - Name = name; - Operation = operation; - Value = value; + if (multiValue != null) + { + foreach (string propertyValue in multiValue) + { + TPDebug.Assert(null != propertyValue, "PropertyValue can not be null."); + if (propertyValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } + } + } + + return false; } /// @@ -91,78 +114,20 @@ internal Condition(string name, Operation operation, string value) internal bool Evaluate(Func propertyValueProvider) { ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider)); - var result = false; var multiValue = GetPropertyValue(propertyValueProvider); - switch (Operation) + var result = Operation switch { - case Operation.Equal: - // if any value in multi-valued property matches 'this.Value', for Equal to evaluate true. - if (null != multiValue) - { - foreach (string propertyValue in multiValue) - { - result = result || string.Equals(propertyValue, Value, StringComparison.OrdinalIgnoreCase); - if (result) - { - break; - } - } - } - break; - - - case Operation.NotEqual: - // all values in multi-valued property should not match 'this.Value' for NotEqual to evaluate true. - result = true; - - // if value is null. - if (null != multiValue) - { - foreach (string propertyValue in multiValue) - { - result = result && !string.Equals(propertyValue, Value, StringComparison.OrdinalIgnoreCase); - if (!result) - { - break; - } - } - } - break; - - case Operation.Contains: - // if any value in multi-valued property contains 'this.Value' for 'Contains' to be true. - if (null != multiValue) - { - foreach (string propertyValue in multiValue) - { - TPDebug.Assert(null != propertyValue, "PropertyValue can not be null."); - result = result || propertyValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1; - if (result) - { - break; - } - } - } - break; - - case Operation.NotContains: - // all values in multi-valued property should not contain 'this.Value' for NotContains to evaluate true. - result = true; + // if any value in multi-valued property matches 'this.Value', for Equal to evaluate true. + Operation.Equal => EvaluateEqualOperation(multiValue), + // all values in multi-valued property should not match 'this.Value' for NotEqual to evaluate true. + Operation.NotEqual => !EvaluateEqualOperation(multiValue), + // if any value in multi-valued property contains 'this.Value' for 'Contains' to be true. + Operation.Contains => EvaluateContainsOperation(multiValue), + // all values in multi-valued property should not contain 'this.Value' for NotContains to evaluate true. + Operation.NotContains => !EvaluateContainsOperation(multiValue), + _ => false, + }; - if (null != multiValue) - { - foreach (string propertyValue in multiValue) - { - TPDebug.Assert(null != propertyValue, "PropertyValue can not be null."); - result = result && propertyValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) == -1; - if (!result) - { - break; - } - } - } - break; - } return result; } diff --git a/src/Microsoft.TestPlatform.Common/Filtering/FastFilter.cs b/src/Microsoft.TestPlatform.Common/Filtering/FastFilter.cs index 856a1c1487..1566671d36 100644 --- a/src/Microsoft.TestPlatform.Common/Filtering/FastFilter.cs +++ b/src/Microsoft.TestPlatform.Common/Filtering/FastFilter.cs @@ -14,14 +14,6 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering; internal sealed class FastFilter { - internal ImmutableDictionary> FilterProperties { get; } - - internal bool IsFilteredOutWhenMatched { get; } - - internal Regex? PropertyValueRegex { get; set; } - - internal string? PropertyValueRegexReplacement { get; set; } - internal FastFilter(ImmutableDictionary> filterProperties, Operation filterOperation, Operator filterOperator) { ValidateArg.NotNullOrEmpty(filterProperties, nameof(filterProperties)); @@ -35,6 +27,14 @@ internal FastFilter(ImmutableDictionary> filterProperties, : throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Resources.FastFilterException))); } + internal ImmutableDictionary> FilterProperties { get; } + + internal bool IsFilteredOutWhenMatched { get; } + + internal Regex? PropertyValueRegex { get; set; } + + internal string? PropertyValueRegexReplacement { get; set; } + internal string[]? ValidForProperties(IEnumerable? properties) { if (properties is null) diff --git a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs index 249b1b5916..88102867d4 100644 --- a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs +++ b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs @@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering; /// Equality Operators: =, != /// Parenthesis (, ) for grouping. /// -internal class FilterExpression +internal sealed class FilterExpression { /// /// Condition, if expression is conditional expression. @@ -60,17 +60,13 @@ private FilterExpression(Condition condition) /// Create a new filter expression 'And'ing 'this' with 'filter'. /// private FilterExpression And(FilterExpression filter) - { - return new FilterExpression(this, filter, true); - } + => new FilterExpression(this, filter, true); /// /// Create a new filter expression 'Or'ing 'this' with 'filter'. /// private FilterExpression Or(FilterExpression filter) - { - return new FilterExpression(this, filter, false); - } + => new FilterExpression(this, filter, false); /// /// Process the given operator from the filterStack. @@ -119,11 +115,7 @@ private static void ProcessOperator(Stack filterStack, Operato /// internal string[]? ValidForProperties(IEnumerable? properties, Func? propertyProvider) { - if (null == properties) - { - // if null, initialize to empty list so that invalid properties can be found. - properties = []; - } + properties ??= []; return IterateFilterExpression((current, result) => { @@ -139,11 +131,11 @@ private static void ProcessOperator(Stack filterStack, Operato var invalidRight = current._right != null ? result.Pop() : null; var invalidProperties = current._left != null ? result.Pop() : null; - if (null == invalidProperties) + if (invalidProperties == null) { invalidProperties = invalidRight; } - else if (null != invalidRight) + else if (invalidRight != null) { invalidProperties = invalidProperties.Concat(invalidRight).ToArray(); } @@ -190,7 +182,7 @@ internal static FilterExpression Parse(string filterString, out FastFilter? fast case "|": Operator currentOperator = Operator.And; - if (string.Equals("|", token)) + if (string.Equals("|", token, StringComparison.Ordinal)) { currentOperator = Operator.Or; } @@ -210,9 +202,11 @@ internal static FilterExpression Parse(string filterString, out FastFilter? fast operatorStack.Push(currentOperator); break; } + stackTopOperator = operatorStack.Pop(); ProcessOperator(filterStack, stackTopOperator); } + break; case "(": @@ -250,6 +244,7 @@ internal static FilterExpression Parse(string filterString, out FastFilter? fast break; } } + while (operatorStack.Count != 0) { Operator temp = operatorStack.Pop(); @@ -265,12 +260,13 @@ internal static FilterExpression Parse(string filterString, out FastFilter? fast return filterStack.Pop(); } + private T IterateFilterExpression(Func, T> getNodeValue) { FilterExpression? current = this; // Will have the nodes. Stack filterStack = new(); - // Will contain the nodes results to use them in thier parent result's calculation + // Will contain the nodes results to use them in their parent result's calculation // and at the end will have the root result. Stack result = new(); @@ -283,6 +279,7 @@ private T IterateFilterExpression(Func, T> getNode { filterStack.Push(current._right); } + filterStack.Push(current); current = current._left; } @@ -300,7 +297,8 @@ private T IterateFilterExpression(Func, T> getNode result.Push(getNodeValue(current, result)); current = null; - } while (filterStack.Count > 0); + } + while (filterStack.Count > 0); TPDebug.Assert(result.Count == 1, "Result stack should have one element at the end."); return result.Peek(); @@ -316,21 +314,19 @@ internal bool Evaluate(Func propertyValueProvider) return IterateFilterExpression((current, result) => { - bool filterResult = false; // Only the leaves have a condition value. - if (null != current._condition) + if (current._condition != null) { - filterResult = current._condition.Evaluate(propertyValueProvider); + return current._condition.Evaluate(propertyValueProvider); } else { // & or | operator - bool rightResult = current._right != null ? result.Pop() : false; - bool leftResult = current._left != null ? result.Pop() : false; + bool rightResult = current._right != null && result.Pop(); + bool leftResult = current._left != null && result.Pop(); // Concatenate the children node's result to get their parent result. - filterResult = current._areJoinedByAnd ? leftResult && rightResult : leftResult || rightResult; + return current._areJoinedByAnd ? leftResult && rightResult : leftResult || rightResult; } - return filterResult; }); } diff --git a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs index d01e6b45a9..f532ce1a17 100644 --- a/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs +++ b/src/Microsoft.TestPlatform.Common/Filtering/FilterExpressionWrapper.cs @@ -26,9 +26,6 @@ public class FilterExpressionWrapper /// internal readonly FastFilter? FastFilter; - [MemberNotNullWhen(true, nameof(FastFilter))] - private bool UseFastFilter => FastFilter != null; - /// /// Initializes FilterExpressionWrapper with given filterString and options. /// @@ -51,7 +48,7 @@ public FilterExpressionWrapper(string filterString, FilterOptions? options) // Property value regex is only supported for fast filter, // so we ignore it if no fast filter is constructed. - // TODO: surface an error message to suer. + // TODO: surface an error message to user. var regexString = options?.FilterRegEx; if (!regexString.IsNullOrEmpty()) { @@ -81,32 +78,23 @@ public FilterExpressionWrapper(string filterString) { } + [MemberNotNullWhen(true, nameof(FastFilter))] + private bool UseFastFilter => FastFilter != null; + /// /// User specified filter criteria. /// - public string FilterString - { - get; - private set; - } + public string FilterString { get; } /// /// User specified additional filter options. /// - public FilterOptions? FilterOptions - { - get; - private set; - } + public FilterOptions? FilterOptions { get; } /// /// Parsing error (if any), when parsing 'FilterString' with built-in parser. /// - public string? ParseError - { - get; - private set; - } + public string? ParseError { get; } /// /// Validate if underlying filter expression is valid for given set of supported properties. diff --git a/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs b/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs index 2af8bce930..401332903b 100644 --- a/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs +++ b/src/Microsoft.TestPlatform.Common/Filtering/TestCaseFilterExpression.cs @@ -18,7 +18,7 @@ public class TestCaseFilterExpression : ITestCaseFilterExpression /// /// If filter Expression is valid for performing TestCase matching - /// (has only supported properties, syntax etc) + /// (has only supported properties, syntax etc). /// private readonly bool _validForMatch; @@ -33,27 +33,21 @@ public TestCaseFilterExpression(FilterExpressionWrapper filterWrapper) } /// - /// User specified filter criteria. + /// Gets the user specified filter criteria. /// - public string TestCaseFilterValue - { - get - { - return _filterWrapper.FilterString; - } - } + public string TestCaseFilterValue => _filterWrapper.FilterString; /// /// Validate if underlying filter expression is valid for given set of supported properties. /// public string[]? ValidForProperties(IEnumerable? supportedProperties, Func propertyProvider) { - string[]? invalidProperties = null; - if (null != _filterWrapper && _validForMatch) + if (_validForMatch) { - invalidProperties = _filterWrapper.ValidForProperties(supportedProperties, propertyProvider); + return _filterWrapper.ValidForProperties(supportedProperties, propertyProvider); } - return invalidProperties; + + return null; } /// @@ -64,18 +58,12 @@ public bool MatchTestCase(TestCase testCase, Func propertyValue ValidateArg.NotNull(testCase, nameof(testCase)); ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider)); - if (!_validForMatch) - { - return false; - } - - if (null == _filterWrapper) + if (_validForMatch) { - // can be null when parsing error occurs. Invalid filter results in no match. - return false; + return _filterWrapper.Evaluate(propertyValueProvider); } - return _filterWrapper.Evaluate(propertyValueProvider); + return false; } } diff --git a/src/Microsoft.TestPlatform.Common/Friends.cs b/src/Microsoft.TestPlatform.Common/Friends.cs index 57bc2e8561..e823d3a64d 100644 --- a/src/Microsoft.TestPlatform.Common/Friends.cs +++ b/src/Microsoft.TestPlatform.Common/Friends.cs @@ -27,6 +27,7 @@ [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.TestUtilities, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Acceptance.IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Library.IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.ProgrammerTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("TranslationLayer.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Perf.IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj index ba248dc576..d117107a34 100644 --- a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj +++ b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj @@ -3,11 +3,11 @@ Microsoft.VisualStudio.TestPlatform.Common - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum) + $(NetFrameworkMinimum);$(ExtensionTargetFrameworks) false true - + diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Common/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Common/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Common/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Common/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/net9.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Common/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Common/PublicAPI/net9.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/net9.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Common/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Common/PublicAPI/net9.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Common/Telemetry/MetricsCollection.cs b/src/Microsoft.TestPlatform.Common/Telemetry/MetricsCollection.cs index 180d228995..66debaefa6 100644 --- a/src/Microsoft.TestPlatform.Common/Telemetry/MetricsCollection.cs +++ b/src/Microsoft.TestPlatform.Common/Telemetry/MetricsCollection.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Concurrent; using System.Collections.Generic; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; @@ -13,14 +14,14 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.Telemetry; /// public class MetricsCollection : IMetricsCollection { - private readonly Dictionary _metricDictionary; + private readonly ConcurrentDictionary _metricDictionary; /// /// The Metrics Collection /// public MetricsCollection() { - _metricDictionary = new Dictionary(); + _metricDictionary = new ConcurrentDictionary(); } /// diff --git a/src/Microsoft.TestPlatform.Common/Utilities/AssemblyProperties.cs b/src/Microsoft.TestPlatform.Common/Utilities/AssemblyProperties.cs index 7e285db13f..f46c3e3735 100644 --- a/src/Microsoft.TestPlatform.Common/Utilities/AssemblyProperties.cs +++ b/src/Microsoft.TestPlatform.Common/Utilities/AssemblyProperties.cs @@ -45,7 +45,7 @@ public AssemblyType GetAssemblyType(string filePath) using var peReader = new PEReader(fileStream); // Resources for PEReader: // 1. https://msdn.microsoft.com/library/windows/desktop/ms680547(v=vs.85).aspx?id=19509 - // 2. https://github.com/dotnet/corefx/tree/master/src/System.Reflection.Metadata + // 2. https://github.com/dotnet/runtime/tree/master/src/libraries/System.Reflection.Metadata var peHeaders = peReader.PEHeaders; var corHeader = peHeaders.CorHeader; diff --git a/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs index 0193c4ebb4..89c9c66ab6 100644 --- a/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs +++ b/src/Microsoft.TestPlatform.Common/Utilities/FakesUtilities.cs @@ -97,11 +97,7 @@ private static bool TryAddFakesDataCollectorSettings( IEnumerable sources, FrameworkVersion framework) { - // A new Fakes Configurator API makes the decision to add the right datacollector uri to the configuration - // There now exist two data collector URIs to support two different scenarios. The new scenario involves - // using the CLRIE profiler, and the old involves using the Intellitrace profiler (which isn't supported in - // .NET Core scenarios). The old API still exists for fallback measures. - + // Only cross-platform (v2) Fakes is supported. Fallback to v1 is removed. var crossPlatformConfigurator = TryGetFakesCrossPlatformDataCollectorConfigurator(); if (crossPlatformConfigurator != null) { @@ -118,7 +114,8 @@ private static bool TryAddFakesDataCollectorSettings( return true; } - return AddFallbackFakesSettings(runSettings, sources, framework); + // Fakes v1 fallback support removed. + return false; } internal static void InsertOrReplaceFakesDataCollectorNode(XmlDocument runSettings, DataCollectorSettings settings) @@ -157,55 +154,6 @@ private static IDictionary CreateDictionary(IEnumerabl return dict; } - private static bool AddFallbackFakesSettings( - XmlDocument runSettings, - IEnumerable sources, - FrameworkVersion framework) - { - - // The fallback settings is for the old implementation of fakes - // that only supports .Net Framework versions - if (framework - is not FrameworkVersion.Framework35 - and not FrameworkVersion.Framework40 - and not FrameworkVersion.Framework45) - { - return false; - } - - Func, string>? netFrameworkConfigurator = TryGetNetFrameworkFakesDataCollectorConfigurator(); - if (netFrameworkConfigurator == null) - { - return false; - } - - // if no fakes, return settings unchanged - var fakesConfiguration = netFrameworkConfigurator(sources); - if (fakesConfiguration == null) - { - return false; - } - - // integrate fakes settings in configuration - // if the settings don't have any data collector settings, populate with empty settings - EnsureSettingsNode(runSettings, new DataCollectionRunSettings()); - - // embed fakes settings - var fakesSettings = CreateFakesDataCollectorSettings(); - var doc = new XmlDocument(); - using (var xmlReader = XmlReader.Create( - new StringReader(fakesConfiguration), - new XmlReaderSettings() { CloseInput = true })) - { - doc.Load(xmlReader); - } - - fakesSettings.Configuration = doc.DocumentElement; - XmlRunSettingsUtilities.InsertDataCollectorsNode(runSettings.CreateNavigator()!, fakesSettings); - - return true; - } - /// /// Ensures that an xml element corresponding to the test run settings exists in the setting document. /// @@ -225,27 +173,6 @@ private static void EnsureSettingsNode(XmlDocument settings, TestRunSettings set } } - private static Func, string>? TryGetNetFrameworkFakesDataCollectorConfigurator() - { -#if NETFRAMEWORK - try - { - var assembly = LoadTestPlatformAssembly(); - var type = assembly?.GetType(ConfiguratorAssemblyQualifiedName, false); - var method = type?.GetMethod(NetFrameworkConfiguratorMethodName, [typeof(IEnumerable)]); - if (method != null) - { - return (Func, string>)method.CreateDelegate(typeof(Func, string>)); - } - } - catch (Exception ex) - { - EqtTrace.Info("Failed to create Fakes Configurator. Reason:{0} ", ex); - } -#endif - return null; - } - private static Func, DataCollectorSettings>? TryGetFakesCrossPlatformDataCollectorConfigurator() { try @@ -279,25 +206,6 @@ private static void EnsureSettingsNode(XmlDocument settings, TestRunSettings set return null; } - /// - /// Adds the Fakes data collector settings in the run settings document. - /// - /// - /// The . - /// - private static DataCollectorSettings CreateFakesDataCollectorSettings() - { - // embed the fakes run settings - var settings = new DataCollectorSettings - { - AssemblyQualifiedName = FakesMetadata.DataCollectorAssemblyQualifiedName, - FriendlyName = FakesMetadata.FriendlyName, - IsEnabled = true, - Uri = new Uri(FakesMetadata.DataCollectorUriV1) - }; - return settings; - } - internal static class FakesMetadata { /// @@ -306,12 +214,12 @@ internal static class FakesMetadata public const string FriendlyName = "UnitTestIsolation"; /// - /// Gets the URI of the data collector + /// Gets the URI of the data collector (V1, deprecated and removed) /// public const string DataCollectorUriV1 = "datacollector://microsoft/unittestisolation/1.0"; /// - /// Gets the URI of the data collector + /// Gets the URI of the data collector (V2) /// public const string DataCollectorUriV2 = "datacollector://microsoft/unittestisolation/2.0"; diff --git a/src/Microsoft.TestPlatform.Common/Utilities/SimpleJSON.cs b/src/Microsoft.TestPlatform.Common/Utilities/SimpleJSON.cs index 73a65c5b20..896fa88b65 100644 --- a/src/Microsoft.TestPlatform.Common/Utilities/SimpleJSON.cs +++ b/src/Microsoft.TestPlatform.Common/Utilities/SimpleJSON.cs @@ -856,16 +856,13 @@ public override JSONNode this[string aKey] { get { - return _dict.ContainsKey(aKey) ? _dict[aKey] : new JSONLazyCreator(this, aKey); + return _dict.TryGetValue(aKey, out var value) ? value : new JSONLazyCreator(this, aKey); } set { if (value == null) value = JSONNull.CreateOrGet(); - if (_dict.ContainsKey(aKey)) - _dict[aKey] = value; - else - _dict.Add(aKey, value); + _dict[aKey] = value; } } @@ -898,10 +895,7 @@ public override void Add(string aKey, JSONNode aItem) if (aKey != null) { - if (_dict.ContainsKey(aKey)) - _dict[aKey] = aItem; - else - _dict.Add(aKey, aItem); + _dict[aKey] = aItem; } else _dict.Add(Guid.NewGuid().ToString(), aItem); @@ -909,9 +903,8 @@ public override void Add(string aKey, JSONNode aItem) public override JSONNode Remove(string aKey) { - if (!_dict.ContainsKey(aKey)) + if (!_dict.TryGetValue(aKey, out var tmp)) return null; - JSONNode tmp = _dict[aKey]; _dict.Remove(aKey); return tmp; } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs index 92c56960d6..a804beb0a0 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs @@ -103,7 +103,7 @@ public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload var isDataCollectionStarted = false; BeforeTestRunStartResult? result = null; - EqtTrace.Verbose("DataCollectionRequestSender.SendBeforeTestRunStartAndGetResult: Send BeforeTestRunStart message with settingsXml {0} and sources {1}: ", settingsXml, sources.ToString()); + EqtTrace.Verbose("DataCollectionRequestSender.SendBeforeTestRunStartAndGetResult: Send BeforeTestRunStart message with settingsXml {0} and sources {1}: ", settingsXml, string.Join(" ", sources)); var payload = new BeforeTestRunStartPayload { diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj index a8f956eba5..ba1a60a643 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj @@ -2,7 +2,7 @@ Microsoft.TestPlatform.CommunicationUtilities - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum) + $(NetFrameworkMinimum);$(ExtensionTargetFrameworks) false @@ -13,7 +13,7 @@ $(NewtonsoftJsonVersion) - + diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/ProtocolVersioning.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/ProtocolVersioning.cs index 20ce0c44d5..577c849a00 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/ProtocolVersioning.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/ProtocolVersioning.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + internal static class ProtocolVersioning { public const int HighestSupportedVersion = Version7; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt index 6505a7e4c2..888f6721e5 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt @@ -1,2 +1,5 @@ #nullable enable const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.MessageType.TelemetryEventMessage = "TestPlatform.TelemetryEvent" -> string! +~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutProcessDidNotStartErrorMessage.get -> string +~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutProcessExitedErrorMessage.get -> string +~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutWithDetailsErrorMessage.get -> string diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs index 7ed3cd840b..e22492d259 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs @@ -10,7 +10,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources { using System; - using System.Reflection; /// @@ -20,7 +19,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -40,7 +39,7 @@ internal Resources() { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources", typeof(Resources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -115,6 +114,33 @@ public static string ConnectionTimeoutErrorMessage { } } + /// + /// Looks up a localized string similar to {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting.. + /// + public static string ConnectionTimeoutProcessDidNotStartErrorMessage { + get { + return ResourceManager.GetString("ConnectionTimeoutProcessDidNotStartErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6}. + /// + public static string ConnectionTimeoutProcessExitedErrorMessage { + get { + return ResourceManager.GetString("ConnectionTimeoutProcessExitedErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout.. + /// + public static string ConnectionTimeoutWithDetailsErrorMessage { + get { + return ResourceManager.GetString("ConnectionTimeoutWithDetailsErrorMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Test host process crashed. /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx index a1fc8083ac..30c62b1f37 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx @@ -141,6 +141,9 @@ {0} process failed to connect to {1} process after {2} seconds. This may occur due to machine slowness, please set environment variable {3} to increase timeout. + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + Test host process crashed @@ -150,4 +153,10 @@ The active test discovery was aborted. Reason: {0} - \ No newline at end of file + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.cs.xlf index 8573e3d270..be03fe953e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.cs.xlf @@ -22,6 +22,21 @@ Aktivní zjišťování testu se přerušilo. Důvod: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Nepovedlo se navázat komunikaci s procesem hostitele testu. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.de.xlf index f94cf5c424..b67777951b 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.de.xlf @@ -22,6 +22,21 @@ Die aktive Testermittlung wurde abgebrochen. Grund: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Die Kommunikation mit dem Testhostprozess ist nicht möglich. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.es.xlf index 2143343451..b4e0c3aeec 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.es.xlf @@ -22,6 +22,21 @@ Se ha anulado la detección de pruebas activa. Motivo: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. No se puede establecer comunicación con el proceso del host de pruebas. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.fr.xlf index 6c7715e2d7..f14bfdaddb 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.fr.xlf @@ -22,6 +22,21 @@ La découverte de tests active a été abandonnée. Raison : {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Impossible de communiquer avec le processus hôte du test. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.it.xlf index 553b51cb16..a2c8908b0e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.it.xlf @@ -22,6 +22,21 @@ L'individuazione dei test attivi è stata interrotta. Motivo: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Non è possibile comunicare con il processo host dei test. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ja.xlf index bce2062c64..f0e0575135 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ja.xlf @@ -22,6 +22,21 @@ アクティブなテスト探索が中止されました。理由: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. テスト ホスト プロセスと通信できません。 diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ko.xlf index 52423f5968..0b61f0d600 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ko.xlf @@ -22,6 +22,21 @@ 활성 테스트 검색이 중단되었습니다. 이유: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. 테스트 호스트 프로세스와 통신할 수 없습니다. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pl.xlf index 32f9e407ec..003a7c0482 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pl.xlf @@ -22,6 +22,21 @@ Aktywny proces wykrywania testu został przerwany. Przyczyna: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Nie można nawiązać komunikacji z procesem hosta. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pt-BR.xlf index 848b7773d4..f9ad9e794e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.pt-BR.xlf @@ -22,6 +22,21 @@ A descoberta de teste ativa foi anulada. Motivo: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Não é possível se comunicar com o processo de host de teste. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ru.xlf index ba06838750..85c0a52a78 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.ru.xlf @@ -22,6 +22,21 @@ Активное обнаружение тестов прервано. Причина: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Не удалось связаться с хост-процессом теста. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.tr.xlf index d653842622..7e9153ac6c 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.tr.xlf @@ -22,6 +22,21 @@ Etkin test bulma iptal edildi. Nedeni: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. Test ana bilgisayarı işlemi ile iletişim kurulamıyor. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.xlf index 074b7f5a26..a9e232c3c1 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.xlf @@ -42,6 +42,11 @@ {0} process failed to connect to {1} process after {2} seconds. This may occur due to machine slowness, please set environment variable {3} to increase timeout. + + {0} process failed to connect to {1} process after {2} seconds. The process with id {3}, exited with exitCode {4}, and error output: \n{5} + {0} process failed to connect to {1} process after {2} seconds. The process with id {3}, exited with exitCode {4}, and error output: \n{5} + + Test host process crashed Test host process crashed diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hans.xlf index be99a9bc47..4739c71b0e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hans.xlf @@ -22,6 +22,21 @@ 活动的测试发现已中止。原因: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. 无法与测试宿主进程通信。 diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hant.xlf index 9e0062726f..a8ff3aaed4 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/xlf/Resources.zh-Hant.xlf @@ -22,6 +22,21 @@ 使用中的測試探索已中止。原因: {0} + + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + {0} process failed to connect to {1} process after {2} seconds. The process failed to start, this might happen because of antivirus preventing it from starting. + + + + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + {0} process failed to connect to {1} process after {2} seconds. The process {3} - {4}, exited with exitCode {5}, and error output: \n{6} + + + + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + {0} process failed to connect to {1} process after {2} seconds. When the timeout happened, the process {3} -{4} was still running. This most often happens because of machine slowness, please set environment variable {5} to increase timeout. + + Unable to communicate with test host process. 無法與測試主機處理序通訊。 diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Extensions/StringBuilderPolyfill.cs b/src/Microsoft.TestPlatform.CoreUtilities/Extensions/StringBuilderPolyfill.cs deleted file mode 100644 index 7da7dee54e..0000000000 --- a/src/Microsoft.TestPlatform.CoreUtilities/Extensions/StringBuilderPolyfill.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.Text; -internal static class StringBuilderPolyfill -{ -#if !NET7_0_OR_GREATER - [Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Added to match new API.")] - public static StringBuilder Append(this StringBuilder builder, IFormatProvider? provider, string? value) - => builder.Append(value); -#endif -} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs index 76f8ec1546..6479267683 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs @@ -72,6 +72,13 @@ private FeatureFlag() { } // Disable not sharing .NET Framework testhosts. Which will return behavior to sharing testhosts when they are running .NET Framework dlls, and are not disabling appdomains or running in parallel. public const string VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST = nameof(VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST); + // Disable setting DOTNET_ROOT environment variable on non-Windows platforms. We used to set it only only on Windows when we found testhost.exe, now we set it always to allow xunit v3 to run tests in child process. + public const string VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS = nameof(VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS); + + // Disable turning dynamic code coverage for native code to OFF by default. Setting this to 1 will skip adding the setting. + public const string VSTEST_DISABLE_DYNAMICNATIVE_CODECOVERAGE_DEFAULT_SETTING = nameof(VSTEST_DISABLE_DYNAMICNATIVE_CODECOVERAGE_DEFAULT_SETTING); + + [Obsolete("Only use this in tests.")] internal static void Reset() diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs b/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs index d20fd5f552..542dbfeacc 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Friends.cs @@ -10,16 +10,19 @@ [assembly: InternalsVisibleTo("testhost.net471, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net47.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net471.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net47.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net471.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CommunicationUtilities, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs index 0d10e0dee8..d9bc7043f4 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/DotnetHostHelper.cs @@ -23,6 +23,13 @@ public class DotnetHostHelper : IDotnetHostHelper { public const string MONOEXENAME = "mono"; + // Mach-O magic numbers from https://en.wikipedia.org/wiki/Mach-O + private const uint MachOMagic32BigEndian = 0xfeedface; // 32-bit big-endian + private const uint MachOMagic64BigEndian = 0xfeedfacf; // 64-bit big-endian + private const uint MachOMagic32LittleEndian = 0xcefaedfe; // 32-bit little-endian + private const uint MachOMagic64LittleEndian = 0xcffaedfe; // 64-bit little-endian + private const uint MachOMagicFatBigEndian = 0xcafebabe; // Multi-architecture big-endian + private readonly IFileHelper _fileHelper; private readonly IEnvironment _environment; private readonly IWindowsRegistryHelper _windowsRegistryHelper; @@ -409,12 +416,19 @@ public bool TryGetDotnetPathByArchitecture( using var headerReader = _fileHelper.GetStream(path, FileMode.Open, FileAccess.Read); var magicBytes = new byte[4]; var cpuInfoBytes = new byte[4]; -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' - headerReader.Read(magicBytes, 0, magicBytes.Length); - headerReader.Read(cpuInfoBytes, 0, cpuInfoBytes.Length); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + + ReadExactly(headerReader, magicBytes, 0, magicBytes.Length); + ReadExactly(headerReader, cpuInfoBytes, 0, cpuInfoBytes.Length); var magic = BitConverter.ToUInt32(magicBytes, 0); + + // Validate magic bytes to ensure this is a valid Mach-O binary + if (magic is not (MachOMagic32BigEndian or MachOMagic64BigEndian or MachOMagic32LittleEndian or MachOMagic64LittleEndian or MachOMagicFatBigEndian)) + { + EqtTrace.Error($"DotnetHostHelper.GetMuxerArchitectureByMachoOnMac: Invalid Mach-O magic bytes: 0x{magic:X8}"); + return null; + } + var cpuInfo = BitConverter.ToUInt32(cpuInfoBytes, 0); PlatformArchitecture? architecture = (MacOsCpuType)cpuInfo switch { @@ -435,6 +449,27 @@ public bool TryGetDotnetPathByArchitecture( return null; } +#if NET + private static void ReadExactly(Stream stream, byte[] buffer, int offset, int count) + { + stream.ReadExactly(buffer, offset, count); + } +#else + private static void ReadExactly(Stream 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 + internal enum MacOsCpuType : uint { Arm64Magic = 0x0100000c, diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj index ea9329bd81..6776a4de05 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj +++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj @@ -3,11 +3,11 @@ Microsoft.TestPlatform.CoreUtilities - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestHostMinimumTargetFrameworks);$(ExtensionTargetFrameworks) false - + @@ -16,13 +16,10 @@ - - - - - - + + + @@ -54,11 +51,11 @@ - - - - - + + + + + diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net481/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net481/PublicAPI.Shipped.txt new file mode 100644 index 0000000000..32196d81df --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net481/PublicAPI.Shipped.txt @@ -0,0 +1,15 @@ +#nullable enable +Microsoft.VisualStudio.TestPlatform.Utilities.ConsoleOutput +Microsoft.VisualStudio.TestPlatform.Utilities.ConsoleOutput.Write(string? message, Microsoft.VisualStudio.TestPlatform.Utilities.OutputLevel level) -> void +Microsoft.VisualStudio.TestPlatform.Utilities.ConsoleOutput.WriteLine(string? message, Microsoft.VisualStudio.TestPlatform.Utilities.OutputLevel level) -> void +Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions +static Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace.SetupListener(System.Diagnostics.TraceListener? listener) -> void +static Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace.SetupRemoteEqtTraceListeners(System.AppDomain? childDomain) -> void +static Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace.TraceLevel.get -> System.Diagnostics.TraceLevel +static Microsoft.VisualStudio.TestPlatform.ObjectModel.EqtTrace.TraceLevel.set -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.ConsoleOutput.Instance.get -> Microsoft.VisualStudio.TestPlatform.Utilities.ConsoleOutput! +static Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions.Error(this Microsoft.VisualStudio.TestPlatform.Utilities.IOutput! output, bool appendPrefix, string! format, params object?[]? args) -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions.Information(this Microsoft.VisualStudio.TestPlatform.Utilities.IOutput! output, bool appendPrefix, string! format, params object?[]? args) -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions.Information(this Microsoft.VisualStudio.TestPlatform.Utilities.IOutput! output, bool appendPrefix, System.ConsoleColor foregroundColor, string! format, params object?[]? args) -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions.Warning(this Microsoft.VisualStudio.TestPlatform.Utilities.IOutput! output, bool appendPrefix, string! format, params object?[]? args) -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.OutputExtensions.Write(this Microsoft.VisualStudio.TestPlatform.Utilities.IOutput! output, string! message, Microsoft.VisualStudio.TestPlatform.Utilities.OutputLevel level, System.ConsoleColor foregroundColor) -> void diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net6.0_and_net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net481/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net6.0_and_net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net481/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net6.0_and_net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net6.0_and_net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs index bb6ba1f463..8d8ee0be6c 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Adapter/FrameworkHandle.cs @@ -59,13 +59,14 @@ public FrameworkHandle(ITestCaseEventsHandler? testCaseEventsHandler, ITestRunCa /// and should be used only when absolutely required as using it degrades the performance of the subsequent run. /// It throws InvalidOperationException when it is attempted to be enabled when keepAlive is false. /// + [Obsolete("This property has no effect", error: false)] public bool EnableShutdownAfterTestRun { get; set; } /// /// Launch the specified process with the debugger attached. /// /// File path to the exe to launch. - /// Working directory that process should use. + /// Working directory that process should use. If null, the current directory will be used. /// Command line arguments the process should be launched with. /// Environment variables to be set in target process /// Process ID of the started process. @@ -89,7 +90,7 @@ public int LaunchProcessWithDebuggerAttached(string filePath, string? workingDir Arguments = arguments, EnvironmentVariables = environmentVariables, FileName = filePath, - WorkingDirectory = workingDirectory + WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory }; return _testRunEventsHandler.LaunchProcessWithDebuggerAttached(processInfo); diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/DiscoveryDataAggregator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/DiscoveryDataAggregator.cs index a6606083e8..581c5b058d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/DiscoveryDataAggregator.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/DiscoveryDataAggregator.cs @@ -144,15 +144,10 @@ public void AggregateMetrics(IDictionary? metrics) { var newValue = Convert.ToDouble(metric.Value, CultureInfo.InvariantCulture); - if (_metricsAggregator.TryGetValue(metric.Key, out object? oldValue)) - { - double oldDoubleValue = Convert.ToDouble(oldValue, CultureInfo.InvariantCulture); - _metricsAggregator[metric.Key] = newValue + oldDoubleValue; - } - else - { - _metricsAggregator.TryAdd(metric.Key, newValue); - } + _metricsAggregator.AddOrUpdate( + metric.Key, + newValue, + (_, oldValue) => newValue + Convert.ToDouble(oldValue, CultureInfo.InvariantCulture)); } } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs index 2073336a47..10cae4f28f 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs @@ -79,13 +79,14 @@ public ITestRunStatistics GetAggregatedRunStats() foreach (var runStats in _testRunStatsList) { // TODO: we get nullref here if the stats are empty. - foreach (var outcome in runStats.Stats!.Keys) + foreach (var kvp in runStats.Stats!) { - if (!testOutcomeMap.ContainsKey(outcome)) + if (!testOutcomeMap.TryGetValue(kvp.Key, out long currentCount)) { - testOutcomeMap.Add(outcome, 0); + currentCount = 0; } - testOutcomeMap[outcome] += runStats.Stats[outcome]; + + testOutcomeMap[kvp.Key] = currentCount + kvp.Value; } totalTests += runStats.ExecutedTests; } @@ -197,15 +198,10 @@ public void AggregateRunDataMetrics(IDictionary? metrics) { var newValue = Convert.ToDouble(metric.Value, CultureInfo.InvariantCulture); - if (_metricsAggregator.TryGetValue(metric.Key, out var oldValue)) - { - var oldDoubleValue = Convert.ToDouble(oldValue, CultureInfo.InvariantCulture); - _metricsAggregator[metric.Key] = newValue + oldDoubleValue; - } - else - { - _metricsAggregator.TryAdd(metric.Key, newValue); - } + _metricsAggregator.AddOrUpdate( + metric.Key, + newValue, + (_, oldValue) => newValue + Convert.ToDouble(oldValue, CultureInfo.InvariantCulture)); } } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs index a20d68a7c2..b0c527368a 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs @@ -196,20 +196,25 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve private void HandleException(Exception exception) { - EqtTrace.Error("ProxyDiscoveryManager.DiscoverTests: Failed to discover tests: {0}", exception); + // If requested abort and the code below was just sending data, we will get communication exception because we try to write the channel that is already closed. + // In such case don't report the exception because user cannot do anything about it. + if (!(_proxyOperationManager != null && _proxyOperationManager.CancellationTokenSource.IsCancellationRequested && exception is CommunicationException)) + { + EqtTrace.Error("ProxyDiscoveryManager.DiscoverTests: Failed to discover tests: {0}", exception); - // Log to vs ide test output - var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = exception.ToString() }; - var rawMessage = _dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); - HandleRawMessage(rawMessage); + // Log to vs ide test output + var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = exception.ToString() }; + var rawMessage = _dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); + HandleRawMessage(rawMessage); + + // Log to vstest.console + HandleLogMessage(TestMessageLevel.Error, exception.ToString()); + } - // Log to vstest.console // Send a discovery complete to caller. Similar logic is also used in ParallelProxyDiscoveryManager.DiscoverTestsOnConcurrentManager // Aborted is `true`: in case of parallel discovery (or non shared host), an aborted message ensures another discovery manager // created to replace the current one. This will help if the current discovery manager is aborted due to irreparable error // and the test host is lost as well. - HandleLogMessage(TestMessageLevel.Error, exception.ToString()); - var discoveryCompletePayload = new DiscoveryCompletePayload { IsAborted = true, diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs index 8c47267c76..3a17bcc276 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs @@ -540,6 +540,15 @@ private void ThrowExceptionOnConnectionFailure(IEnumerable sources, int CoreUtilitiesConstants.TesthostProcessName, connTimeout, EnvironmentHelper.VstestConnectionTimeout); + + // Add process ID info if available so the user knows which process is stuck. + if (_testHostProcessId > 0) + { + errorMsg += string.Format( + CultureInfo.CurrentCulture, + " The process with id {0} was still running when this message was reported.", + _testHostProcessId); + } } // After testhost process launched failed with error. diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/InProcDataCollectionSink.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/InProcDataCollectionSink.cs index cd90081849..76277781f5 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/InProcDataCollectionSink.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/InProcDataCollectionSink.cs @@ -53,16 +53,12 @@ public IDictionary GetDataCollectionDataSetForTestCase(Guid test private void AddKeyValuePairToDictionary(Guid testCaseId, string key, string value) { - if (!_testCaseDataCollectionDataMap.ContainsKey(testCaseId)) + if (!_testCaseDataCollectionDataMap.TryGetValue(testCaseId, out var testCaseCollectionData)) { - var testCaseCollectionData = new TestCaseDataCollectionData(); - testCaseCollectionData.AddOrUpdateData(key, value); + testCaseCollectionData = new TestCaseDataCollectionData(); _testCaseDataCollectionDataMap[testCaseId] = testCaseCollectionData; } - else - { - _testCaseDataCollectionDataMap[testCaseId].AddOrUpdateData(key, value); - } + testCaseCollectionData.AddOrUpdateData(key, value); } private class TestCaseDataCollectionData @@ -76,15 +72,11 @@ public TestCaseDataCollectionData() internal void AddOrUpdateData(string key, string value) { - if (!CollectionData.ContainsKey(key)) - { - CollectionData[key] = value; - } - else + if (CollectionData.ContainsKey(key)) { EqtTrace.Warning("The data for in-proc data collector with key {0} has already been set. Will be reset with new value", key); - CollectionData[key] = value; } + CollectionData[key] = value; } } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs index 538e6c0ceb..db062bd8e4 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; using System.Threading; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; @@ -67,7 +69,7 @@ public override void HandleTestRunComplete( _runDataAggregator.IsAborted, _runDataAggregator.GetAggregatedException(), _runDataAggregator.RunContextAttachments, - _runDataAggregator.InvokedDataCollectors, + new Collection(_runDataAggregator.InvokedDataCollectors.ToList()), _runDataAggregator.ElapsedTime); // Add Metrics from Test Host diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs index 9460f19b08..3f699402ec 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + internal class NullPathConverter : IPathConverter { private static readonly Lazy LazyInstance = new(() => new NullPathConverter()); diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs index 87c19a472e..2f9c975b7f 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs @@ -201,7 +201,6 @@ public void RunTests() TimeSpan? elapsedTime = null; Exception? exception = null; bool isAborted = false; - bool shutdownAfterRun = false; try { @@ -214,11 +213,6 @@ public void RunTests() EqtTrace.Error("BaseRunTests.RunTests: Failed to run the tests. Reason: GetExecutorUriExtensionMap returned null."); isAborted = true; } - else - { - // Check the adapter setting for shutting down this process after run - shutdownAfterRun = FrameworkHandle.EnableShutdownAfterTestRun; - } } catch (Exception ex) { diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs index 35ea0e6b25..f25602a188 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Friends.cs @@ -14,16 +14,19 @@ [assembly: InternalsVisibleTo("testhost.net471, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net47.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net471.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481.x86, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net47.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net471.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net472.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("testhost.net48.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("testhost.net481.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.ProgrammerTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj index a1d89d2589..c643cab47b 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj @@ -3,7 +3,7 @@ Microsoft.TestPlatform.CrossPlatEngine - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum) + $(NetFrameworkMinimum);$(ExtensionTargetFrameworks) false @@ -21,7 +21,7 @@ true - + diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs index ccb312023b..fcc33d5a80 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs @@ -257,8 +257,8 @@ public virtual ProxyOperationManager DequeueProxy(string source, string? runSett lock (_proxyOperationLockObject) { // No proxy available means the caller will have to create its own proxy. - if (!_proxyMap.ContainsKey(source) - || !_proxyContainerList[_proxyMap[source]].IsAvailable) + if (!_proxyMap.TryGetValue(source, out int proxyIndex) + || !_proxyContainerList[proxyIndex].IsAvailable) { throw new InvalidOperationException(CrossPlatResources.NoAvailableProxyForDeque); } @@ -273,7 +273,7 @@ public virtual ProxyOperationManager DequeueProxy(string source, string? runSett } // Get the actual proxy. - proxyContainer = _proxyContainerList[_proxyMap[source]]; + proxyContainer = _proxyContainerList[proxyIndex]; // Mark the proxy as unavailable. proxyContainer.IsAvailable = false; diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs index 7d1f1a497a..ae563f9c1a 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs @@ -101,13 +101,13 @@ public virtual bool KillSession(TestSessionInfo testSessionInfo, IRequestData re lock (_lockObject) { // Check if the session info exists. - if (!_sessionPool.ContainsKey(testSessionInfo)) + if (!_sessionPool.TryGetValue(testSessionInfo, out var proxyManagerFromPool)) { return false; } // Remove the session from the pool. - proxyManager = _sessionPool[testSessionInfo]; + proxyManager = proxyManagerFromPool; _sessionPool.Remove(testSessionInfo); } @@ -136,13 +136,10 @@ public virtual bool KillSession(TestSessionInfo testSessionInfo, IRequestData re ProxyTestSessionManager? sessionManager; lock (_lockObject) { - if (!_sessionPool.ContainsKey(testSessionInfo)) + if (!_sessionPool.TryGetValue(testSessionInfo, out sessionManager)) { return null; } - - // Gets the session manager reference from the pool. - sessionManager = _sessionPool[testSessionInfo]; } try @@ -184,13 +181,10 @@ public virtual bool ReturnProxy(TestSessionInfo testSessionInfo, int proxyId) ProxyTestSessionManager? sessionManager; lock (_lockObject) { - if (!_sessionPool.ContainsKey(testSessionInfo)) + if (!_sessionPool.TryGetValue(testSessionInfo, out sessionManager)) { return false; } - - // Gets the session manager reference from the pool. - sessionManager = _sessionPool[testSessionInfo]; } try diff --git a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs index 1bd71eb00f..819a30b28c 100644 --- a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs +++ b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs @@ -8,7 +8,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -96,29 +95,7 @@ private static bool AttachVs(Process process, int? vsPid) private static string? FindAttachVs() { - var fromPath = FindOnPath("AttachVS.exe"); - if (fromPath != null) - { - return fromPath; - } - - - // Don't use current process MainModule here, it resolves to dotnet if you invoke - // dotnet vstest.console.dll, or dotnet testhost.dll. Use the entry assembly instead. - var parent = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location); - while (parent != null) - { - var path = Path.Combine(parent, @"artifacts\bin\AttachVS\Debug\net472\AttachVS.exe"); - Debug.WriteLine($"Looking for AttachVS in: {path}."); - if (File.Exists(path)) - { - return path; - } - - parent = Path.GetDirectoryName(parent); - } - - return parent; + return FindOnPath("AttachVS.exe"); } private static string? FindOnPath(string exeName) diff --git a/src/Microsoft.TestPlatform.Execution.Shared/UILanguageOverride.cs b/src/Microsoft.TestPlatform.Execution.Shared/UILanguageOverride.cs index a4244f0441..019a391870 100644 --- a/src/Microsoft.TestPlatform.Execution.Shared/UILanguageOverride.cs +++ b/src/Microsoft.TestPlatform.Execution.Shared/UILanguageOverride.cs @@ -55,7 +55,7 @@ private static void ApplyOverrideToCurrentProcess(CultureInfo language, Action is set by VS and we respect that as well so that we will respect the VS @@ -67,8 +67,8 @@ private static void ApplyOverrideToCurrentProcess(CultureInfo language, Action? _testSequence; - private Dictionary? _testObjectDictionary; + private ConcurrentQueue? _testSequence; + private ConcurrentDictionary? _testObjectDictionary; private readonly IBlameReaderWriter _blameReaderWriter; private readonly IFileHelper _fileHelper; private readonly IProcessHelper _processHelper; private XmlElement? _configurationElement; - private int _testStartCount; private int _testEndCount; private bool _collectProcessDumpOnCrash; private bool _collectProcessDumpOnHang; @@ -137,8 +137,8 @@ public override void Initialize( _dataCollectionSink = dataSink; _context = environmentContext; _configurationElement = configurationElement; - _testSequence = new List(); - _testObjectDictionary = new Dictionary(); + _testSequence = new ConcurrentQueue(); + _testObjectDictionary = new ConcurrentDictionary(); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // Subscribing to events @@ -318,6 +318,7 @@ private void CollectDumpAndAbortTesthost() } catch (InvalidOperationException) { + // Process may have already exited — safe to ignore. } } catch (Exception ex) @@ -461,14 +462,11 @@ private void EventsTestCaseStart(object? sender, TestCaseStartEventArgs e) TPDebug.Assert(e.TestElement is not null, "e.TestElement is null"); var blameTestObject = new BlameTestObject(e.TestElement); - // Add guid to list of test sequence to maintain the order. - _testSequence.Add(blameTestObject.Id); + // Add guid to ordered collection of test sequence to maintain the order. + _testSequence.Enqueue(blameTestObject.Id); // Add the test object to the dictionary. - _testObjectDictionary.Add(blameTestObject.Id, blameTestObject); - - // Increment test start count. - _testStartCount++; + _testObjectDictionary.TryAdd(blameTestObject.Id, blameTestObject); } /// @@ -483,14 +481,11 @@ private void EventsTestCaseEnd(object? sender, TestCaseEndEventArgs e) EqtTrace.Info("BlameCollector.EventsTestCaseEnd: Test Case End"); - _testEndCount++; + Interlocked.Increment(ref _testEndCount); // Update the test object in the dictionary as the test has completed. TPDebug.Assert(e.TestElement is not null, "e.TestElement is null"); - if (_testObjectDictionary.ContainsKey(e.TestElement.Id)) - { - _testObjectDictionary[e.TestElement.Id].IsCompleted = true; - } + _testObjectDictionary[e.TestElement.Id].IsCompleted = true; } /// @@ -510,12 +505,17 @@ private void SessionEndedHandler(object? sender, SessionEndEventArgs args) // If the last test crashes, it will not invoke a test case end and therefore // In case of crash testStartCount will be greater than testEndCount and we need to write the sequence // And send the attachment. This won't indicate failure if there are 0 tests in the assembly, or when it fails in setup. - var processCrashedWhenRunningTests = _testStartCount > _testEndCount; + var processCrashedWhenRunningTests = _testSequence.Count > _testEndCount; if (processCrashedWhenRunningTests) { var filepath = Path.Combine(GetTempDirectory(), Constants.AttachmentFileName + "_" + _attachmentGuid); - filepath = _blameReaderWriter.WriteTestSequence(_testSequence, _testObjectDictionary, filepath); + List testSequenceCopy; + Dictionary testObjectDictionaryCopy; + + testSequenceCopy = [.. _testSequence]; + testObjectDictionaryCopy = new Dictionary(_testObjectDictionary); + filepath = _blameReaderWriter.WriteTestSequence(testSequenceCopy, testObjectDictionaryCopy, filepath); var fti = new FileTransferInformation(_context.SessionDataCollectionContext, filepath, true); _dataCollectionSink.SendFileAsync(fti); } @@ -577,10 +577,12 @@ private void SessionEndedHandler(object? sender, SessionEndEventArgs args) // The name of the file starts with the process name, that's the only filtering we can do. // So there's one possible benign race condition when another test is dumping an host and we take lock on the name but the dump is not finished. // In that case we'll fail for file locking but it's fine. The correct or subsequent "SessionEndedHandler" will move that one. - using MD5 md5LockName = MD5.Create(); - // BitConverter converts into something like EC-1B-B6-22-81-00-41-C8-31-1D-B6-61-27-6A-65-8A valid muxer name + using SHA256 hashedLockName = SHA256.Create(); // LPCSTR An LPCSTR is a 32-bit pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. - string muxerName = @$"Global\{BitConverter.ToString(md5LockName.ComputeHash(Encoding.UTF8.GetBytes(dumpFileNameFullPath)))}"; + var toGuid = new byte[16]; + Array.Copy(hashedLockName.ComputeHash(Encoding.UTF8.GetBytes(dumpFileNameFullPath)), toGuid, 16); + Guid id = new(toGuid); + string muxerName = @$"Global\{id}"; using Mutex lockFile = new(true, muxerName, out bool createdNew); EqtTrace.Info($"[MonitorPostmortemDump]Acquired global muxer '{muxerName}' for {dumpFileNameFullPath}"); if (createdNew) diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs index 3ca7beb8c4..fe7593a64d 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs @@ -19,8 +19,7 @@ public IHangDumper Create(string targetFramework) EqtTrace.Info($"HangDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); var procdumpOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_FORCEPROCDUMP")?.Trim(); - var netdumpOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_FORCENETDUMP")?.Trim(); - EqtTrace.Verbose($"HangDumperFactory: Overrides for dumpers: VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride};VSTEST_DUMP_FORCENETDUMP={netdumpOverride}"); + EqtTrace.Verbose($"HangDumperFactory: Overrides for dumpers: VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride}"); var tfm = Framework.FromString(targetFramework); @@ -40,20 +39,10 @@ public IHangDumper Create(string targetFramework) return new ProcDumpDumper(); } - // On some system the interop dumper will thrown AccessViolationException, add an option to force procdump. - var forceUsingNetdump = !netdumpOverride.IsNullOrWhiteSpace() && netdumpOverride != "0"; - if (forceUsingNetdump) + if (tfm.FrameworkName == ".NETCoreApp") { - var isLessThan50 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("5.0.0.0"); - if (!isLessThan50) - { - EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.FrameworkName} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, forcing use of .NetClientHangDumper"); - return new NetClientHangDumper(); - } - else - { - EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.FrameworkName} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, but only applies to .NET 5.0 and newer. Falling back to default hang dumper."); - } + EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.FrameworkName} {tfm.Version}, returning the standard NETClient library dumper."); + return new NetClientHangDumper(); } EqtTrace.Info($"HangDumperFactory: This is Windows, returning the default WindowsHangDumper that P/Invokes MiniDumpWriteDump."); @@ -62,29 +51,13 @@ public IHangDumper Create(string targetFramework) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - var isLessThan31 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("3.1.0.0"); - if (isLessThan31) - { - EqtTrace.Info($"HangDumperFactory: This is Linux on netcoreapp2.1, returning SigtrapDumper."); - - return new SigtrapDumper(); - } - - EqtTrace.Info($"HangDumperFactory: This is Linux netcoreapp3.1 or newer, returning the standard NETClient library dumper."); + EqtTrace.Info($"HangDumperFactory: This is Linux returning the standard NETClient library dumper."); return new NetClientHangDumper(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - var isLessThan50 = tfm.FrameworkName == ".NETCoreApp" && Version.Parse(tfm.Version) < Version.Parse("5.0.0.0"); - if (isLessThan50) - { - EqtTrace.Info($"HangDumperFactory: This is OSX on {targetFramework}, This combination of OS and framework is not supported."); - - throw new PlatformNotSupportedException($"Unsupported target framework {targetFramework} on OS {RuntimeInformation.OSDescription}"); - } - - EqtTrace.Info($"HangDumperFactory: This is OSX on net5.0 or newer, returning the standard NETClient library dumper."); + EqtTrace.Info($"HangDumperFactory: This is OSX returning the standard NETClient library dumper."); return new NetClientHangDumper(); } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj index c337afa54b..414e1bba56 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -19,7 +19,7 @@ Microsoft.TestPlatform.Extensions.BlameDataCollector - net7.0;netstandard2.0;net472 + $(NetFrameworkRunnerTargetFramework);$(ExtensionTargetFrameworks) false true @@ -33,11 +33,14 @@ - - - + + + + - + diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs index e5b1b7acdc..c6fedc100e 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientHangDumper.cs @@ -20,7 +20,10 @@ internal class NetClientHangDumper : IHangDumper public void Dump(int processId, string outputDirectory, DumpTypeOption type) { var process = Process.GetProcessById(processId); - var processTree = process.GetProcessTree(); + // There is 30s timeout hardcoded for the NetClient, so if we try to connect to a non-net process it will take 30s to timeout. + // https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcClient.cs#L15 + // We run this in parallel so it okay, but we are never interested in dumping the native helper processes of Windows, nor we can dump them. + var processTree = process.GetProcessTree().Where(p => p.Process?.ProcessName is not null and not "conhost" and not "WerFault" and not "createdump").ToList(); if (EqtTrace.IsVerboseEnabled) { @@ -62,7 +65,7 @@ public void Dump(int processId, string outputDirectory, DumpTypeOption type) try { var outputFile = Path.Combine(outputDirectory, $"{p.ProcessName}_{p.Id}_{DateTime.Now:yyyyMMddTHHmmss}_hangdump.dmp"); - EqtTrace.Verbose($"NetClientHangDumper.CollectDump: Selected dump type {type}. Dumping {process.Id} - {process.ProcessName} in {outputFile}. "); + EqtTrace.Verbose($"NetClientHangDumper.CollectDump: Selected dump type {type}. Dumping {p.Id} - {p.ProcessName} in {outputFile}. "); var client = new DiagnosticsClient(p.Id); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs index d68b9b569b..2893f41bc5 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Text; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; @@ -11,6 +10,8 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; +#pragma warning disable CA1305 + public class ProcDumpArgsBuilder : IProcDumpArgsBuilder { private readonly IEnvironmentVariableHelper _environmentVariableHelper; @@ -51,10 +52,10 @@ public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnu foreach (var exceptionFilter in procDumpExceptionsList) { - procDumpArgument.Append(CultureInfo.InvariantCulture, $"-f {exceptionFilter} "); + procDumpArgument.Append($"-f {exceptionFilter} "); } - procDumpArgument.Append(CultureInfo.InvariantCulture, $"{processId} {filename}.dmp"); + procDumpArgument.Append($"{processId} {filename}.dmp"); var argument = procdumpArgumentsFromEnv.IsNullOrWhiteSpace() ? procDumpArgument.ToString() @@ -80,9 +81,11 @@ public string BuildHangBasedProcDumpArgs(int processId, string filename, bool is procDumpArgument.Append(" -ma"); } - procDumpArgument.Append(CultureInfo.InvariantCulture, $" {processId} {filename}.dmp"); + procDumpArgument.Append($" {processId} {filename}.dmp"); var argument = procDumpArgument.ToString(); return argument; } } + +#pragma warning restore CA1305 diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net472/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net9.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net472/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net9.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net9.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net472/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net9.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs deleted file mode 100644 index 567a8f933c..0000000000 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Diagnostics; - -namespace Microsoft.TestPlatform.Extensions.BlameDataCollector; - -internal class SigtrapDumper : IHangDumper -{ - public void Dump(int processId, string outputDirectory, DumpTypeOption type) - { - Process.Start("kill", $"-s SIGTRAP {processId}"); - } -} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs index c5783adc54..2f372f1992 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs @@ -38,7 +38,7 @@ public WindowsHangDumper(IProcessHelper processHelper, Action? logWarnin public void Dump(int processId, string outputDirectory, DumpTypeOption type) { var process = Process.GetProcessById(processId); - var processTree = process.GetProcessTree().Where(p => p.Process?.ProcessName is not null and not "conhost" and not "WerFault").ToList(); + var processTree = process.GetProcessTree().Where(p => p.Process?.ProcessName is not null and not "conhost" and not "WerFault" and not "createdump").ToList(); if (processTree.Count > 1) { diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs index bf994ee5e2..f0000e35c8 100644 --- a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/HtmlLogger.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Runtime.Serialization; +using System.Threading; using Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -33,7 +34,6 @@ public class HtmlLogger : ITestLoggerWithParameters private readonly IFileHelper _fileHelper; private readonly XmlObjectSerializer _xmlSerializer; private readonly IHtmlTransformer _htmlTransformer; - private static readonly object FileCreateLockObject = new(); private Dictionary? _parametersDictionary; public HtmlLogger() @@ -70,25 +70,30 @@ public HtmlLogger(IFileHelper fileHelper, IHtmlTransformer htmlTransformer, /// public TestRunDetails? TestRunDetails { get; private set; } + private int _passedTests; + private int _failedTests; + private int _totalTests; + private int _skippedTests; + /// /// Total passed tests in the test results. /// - public int PassedTests { get; private set; } + public int PassedTests { get => _passedTests; private set => _passedTests = value; } /// /// Total failed tests in the test results. /// - public int FailedTests { get; private set; } + public int FailedTests { get => _failedTests; private set => _failedTests = value; } /// /// Total tests in the results. /// - public int TotalTests { get; private set; } + public int TotalTests { get => _totalTests; private set => _totalTests = value; } /// /// Total skipped tests in the results. /// - public int SkippedTests { get; private set; } + public int SkippedTests { get => _skippedTests; private set => _skippedTests = value; } /// /// Path to the xml file. @@ -214,17 +219,17 @@ public void TestResultHandler(object? sender, TestResultEventArgs e) TestRunDetails.ResultCollectionList!.Add(testResultCollection); } - TotalTests++; + Interlocked.Increment(ref _totalTests); switch (e.Result.Outcome) { case TestOutcome.Failed: - FailedTests++; + Interlocked.Increment(ref _failedTests); break; case TestOutcome.Passed: - PassedTests++; + Interlocked.Increment(ref _passedTests); break; case TestOutcome.Skipped: - SkippedTests++; + Interlocked.Increment(ref _skippedTests); break; default: break; @@ -287,7 +292,7 @@ public void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) logFilePrefixValue = logFilePrefixValue + "_" + framework; } - logFilePrefixValue = logFilePrefixValue + DateTime.Now.ToString("_yyyyMMddHHmmss", DateTimeFormatInfo.InvariantInfo) + $".{HtmlLoggerConstants.HtmlFileExtension}"; + logFilePrefixValue = logFilePrefixValue + DateTime.Now.ToString("_yyyyMMdd_HHmmss.fffffff", DateTimeFormatInfo.InvariantInfo) + $".{HtmlLoggerConstants.HtmlFileExtension}"; HtmlFilePath = Path.Combine(TestResultsDirPath!, logFilePrefixValue); } else @@ -350,13 +355,14 @@ private string GenerateUniqueFilePath(string fileName, string fileExtension) { var fileNameWithIter = i == 0 ? fileName : Path.GetFileNameWithoutExtension(fileName) + $"[{i}]"; fullFilePath = Path.Combine(TestResultsDirPath!, $"TestResult_{fileNameWithIter}.{fileExtension}"); - lock (FileCreateLockObject) + try { - if (!File.Exists(fullFilePath)) - { - using var _ = File.Create(fullFilePath); - return fullFilePath; - } + using var _ = new FileStream(fullFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); + return fullFilePath; + } + catch (IOException) when (File.Exists(fullFilePath)) + { + // File already exists (another process created it), try next iteration. } } @@ -365,7 +371,7 @@ private string GenerateUniqueFilePath(string fileName, string fileExtension) private static string FormatDateTimeForRunName(DateTime timeStamp) { - return timeStamp.ToString("yyyyMMdd_HHmmss", DateTimeFormatInfo.InvariantInfo); + return timeStamp.ToString("yyyyMMdd_HHmmss.fffffff", DateTimeFormatInfo.InvariantInfo); } /// diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj index e88c5447ff..5e87c3b024 100644 --- a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj @@ -3,19 +3,13 @@ Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger - net7.0;netstandard2.0;$(NetFrameworkMinimum) + $(NetFrameworkRunnerTargetFramework);$(ExtensionTargetFrameworks) false true - - - - - - diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj index cbe38fd2ab..d224d7bda0 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj @@ -3,7 +3,12 @@ Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger - netstandard2.0;$(NetFrameworkMinimum) + + $(ExtensionTargetFrameworks);net462 false @@ -23,13 +28,6 @@ - - - - - - - @@ -40,12 +38,9 @@ - - - - - - + + + diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.nuspec b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.nuspec index adfd9708bc..51a90efdef 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.nuspec +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -24,6 +25,7 @@ $CommonFileElements$ + diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs index 8093315689..33b1b1dfb3 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs @@ -30,16 +30,6 @@ internal class CollectorDataEntry : IXmlTestStore /// private readonly string _agentName; - /// - /// Display name of the agent from which we received the data - /// - private readonly string _agentDisplayName; - - /// - /// Flag indicating whether this data is coming from a remote (not hosted) agent - /// - private readonly bool _isFromRemoteAgent; - /// /// URI of the collector. /// @@ -63,21 +53,14 @@ internal class CollectorDataEntry : IXmlTestStore /// /// The agent Name. /// - /// - /// The agent Display Name. - /// - /// - /// Is From Remote Agent. - /// /// /// The attachments. /// - public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName, string agentDisplayName, bool isFromRemoteAgent, IList? attachments) + public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName, IList? attachments) { EqtAssert.ParameterNotNull(uri, nameof(uri)); EqtAssert.StringNotNullOrEmpty(collectorDisplayName, nameof(collectorDisplayName)); EqtAssert.StringNotNullOrEmpty(agentName, nameof(agentName)); - EqtAssert.StringNotNullOrEmpty(agentDisplayName, nameof(agentDisplayName)); if (null != attachments) { @@ -92,8 +75,6 @@ public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName _uri = uri; _collectorDisplayName = collectorDisplayName; _agentName = agentName.Trim(); - _agentDisplayName = agentDisplayName.Trim(); - _isFromRemoteAgent = isFromRemoteAgent; } /// @@ -105,7 +86,6 @@ public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName internal CollectorDataEntry() { _agentName = null!; - _agentDisplayName = null!; _uri = null!; _collectorDisplayName = null!; } @@ -135,8 +115,6 @@ public void Save(XmlElement element, XmlTestStoreParameters? parameters) XmlPersistence helper = new(); helper.SaveSimpleField(element, "@agentName", _agentName, null); - helper.SaveSimpleField(element, "@agentDisplayName", _agentDisplayName, _agentName); - helper.SaveSimpleField(element, "@isFromRemoteAgent", _isFromRemoteAgent, false); helper.SaveSimpleField(element, "@uri", _uri.AbsoluteUri, null); helper.SaveSimpleField(element, "@collectorDisplayName", _collectorDisplayName, string.Empty); @@ -164,17 +142,16 @@ internal void AddAttachment(IDataAttachment attachment) } /// - /// Clones the instance and attachments, with file paths in file attachments absolute or relative as specified + /// Clones the instance and attachments, with file paths in file attachments relative /// - /// The results directory to use to make paths in the data attachments relative or absolute - /// True to use absolute paths in this instance, false to use relative paths - /// A clone of the instance containing cloned attachments with file paths made absolute or relative - internal CollectorDataEntry Clone(string resultsDirectory, bool useAbsolutePaths) + /// The results directory to use to make paths in the data attachments relative + /// A clone of the instance containing cloned attachments with file paths made relative + internal CollectorDataEntry CloneWithRelativePath(string resultsDirectory) { TPDebug.Assert(!resultsDirectory.IsNullOrEmpty(), "'resultsDirectory' is null or empty"); TPDebug.Assert(resultsDirectory == resultsDirectory.Trim(), "'resultsDirectory' contains whitespace at the ends"); - var collector = new CollectorDataEntry(_uri, _collectorDisplayName, _agentName, _agentDisplayName, _isFromRemoteAgent, null); + var collector = new CollectorDataEntry(_uri, _collectorDisplayName, _agentName, null); // Clone the attachments foreach (IDataAttachment attachment in _attachments) @@ -183,7 +160,7 @@ internal CollectorDataEntry Clone(string resultsDirectory, bool useAbsolutePaths if (attachment is UriDataAttachment uriDataAttachment) { - collector._attachments.Add(uriDataAttachment.Clone(resultsDirectory, useAbsolutePaths)); + collector._attachments.Add(uriDataAttachment.CloneWithRelativePath(resultsDirectory)); } else { diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs index 07c66ea347..339ad7a382 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/TestResult.cs @@ -479,7 +479,7 @@ internal void AddCollectorDataEntries(IEnumerable collectorD TPDebug.Assert(collectorDataEntry != null, "'collectorDataEntry' is null"); TPDebug.Assert(!_collectorDataEntries.Contains(collectorDataEntry), "The collector data entry already exists in the collection"); - _collectorDataEntries.Add(collectorDataEntry.Clone(testResultsDirectory, false)); + _collectorDataEntries.Add(collectorDataEntry.CloneWithRelativePath(testResultsDirectory)); } } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs index 9beb7066c7..7d4c30820c 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/UriDataAttachment.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.IO; using System.Xml; using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; @@ -87,21 +86,18 @@ public void Save(XmlElement element, XmlTestStoreParameters? parameters) #region Internal Methods /// - /// Clones the instance and makes the URI in the clone absolute using the specified base directory + /// Clones the instance and makes the URI in the clone relative using the specified base directory /// - /// The base directory to use to make the URI absolute - /// True to use an absolute URI in the clone, false to use a relative URI - /// A clone of the instance, with the URI made absolute - internal UriDataAttachment Clone(string baseDirectory, bool useAbsoluteUri) + /// The base directory to use to make the URI relative + /// A clone of the instance, with the URI made relative + internal UriDataAttachment CloneWithRelativePath(string baseDirectory) { TPDebug.Assert(!baseDirectory.IsNullOrEmpty(), "'baseDirectory' is null or empty"); TPDebug.Assert(baseDirectory == baseDirectory.Trim(), "'baseDirectory' contains whitespace at the ends"); - if (useAbsoluteUri != Uri.IsAbsoluteUri) + if (Uri.IsAbsoluteUri) { - Uri uriToUse = useAbsoluteUri - ? new Uri(Path.Combine(baseDirectory, Uri.OriginalString), UriKind.Absolute) - : new Uri(TrxFileHelper.MakePathRelative(Uri.OriginalString, baseDirectory), UriKind.Relative); + var uriToUse = new Uri(TrxFileHelper.MakePathRelative(Uri.OriginalString, baseDirectory), UriKind.Relative); return new UriDataAttachment(Description, uriToUse, _trxFileHelper); } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/README.md b/src/Microsoft.TestPlatform.Extensions.TrxLogger/README.md new file mode 100644 index 0000000000..de7b721c6c --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/README.md @@ -0,0 +1,22 @@ +# Microsoft.TestPlatform.Extensions.TrxLogger + +The TRX (Visual Studio Test Results) logger for the Visual Studio Test Platform. This package enables generating `.trx` result files from test runs. + +## Usage + +```xml + +``` + +Run tests with the TRX logger: + +```sh +dotnet test --logger trx +dotnet test --logger "trx;LogFileName=results.trx" +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Loggers Information From RunSettings](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0016-Loggers-Information-From-RunSettings.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs index ea3720b118..545531f35a 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/TrxLogger.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.IO; using System.Text; +using System.Threading; using System.Xml; using Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; @@ -175,11 +176,15 @@ internal List GetRunLevelErrorsAndWarnings() internal TestRun? LoggerTestRun { get; private set; } - internal int TotalTestCount { get; private set; } + private int _totalTestCount; + private int _passedTestCount; + private int _failedTestCount; - internal int PassedTestCount { get; private set; } + internal int TotalTestCount { get => _totalTestCount; private set => _totalTestCount = value; } - internal int FailedTestCount { get; private set; } + internal int PassedTestCount { get => _passedTestCount; private set => _passedTestCount = value; } + + internal int FailedTestCount { get => _failedTestCount; private set => _failedTestCount = value; } internal int TestResultCount { @@ -298,15 +303,15 @@ internal void TestResultHandler(object? sender, TestResultEventArgs e) UpdateTestEntries(executionId, parentExecutionId, testElement, parentTestElement); // Set various counts (passed tests, failed tests, total tests) - TotalTestCount++; + Interlocked.Increment(ref _totalTestCount); if (testResult.Outcome == TrxLoggerObjectModel.TestOutcome.Failed) { TestResultOutcome = TrxLoggerObjectModel.TestOutcome.Failed; - FailedTestCount++; + Interlocked.Increment(ref _failedTestCount); } else if (testResult.Outcome == TrxLoggerObjectModel.TestOutcome.Passed) { - PassedTestCount++; + Interlocked.Increment(ref _passedTestCount); } } @@ -531,7 +536,18 @@ private string SetDefaultTrxFilePath() TPDebug.Assert(LoggerTestRun != null, "LoggerTestRun is null"); TPDebug.Assert(LoggerTestRun.RunConfiguration != null, "LoggerTestRun.RunConfiguration is null"); TPDebug.Assert(IsInitialized, "Logger is not initialized"); - var defaultTrxFileName = LoggerTestRun.RunConfiguration.RunDeploymentRootDirectory + ".trx"; + + var baseName = LoggerTestRun.RunConfiguration.RunDeploymentRootDirectory; + + if (_parametersDictionary is not null + && _parametersDictionary.TryGetValue(DefaultLoggerParameterNames.TargetFramework, out var framework) + && !framework.IsNullOrWhiteSpace()) + { + var shortName = Framework.FromString(framework)?.ShortName ?? framework; + baseName = baseName + "_" + shortName; + } + + var defaultTrxFileName = baseName + ".trx"; return TrxFileHelper.GetNextIterationFileName(_testResultsDirPath, defaultTrxFileName, false); } diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs index 5967dbb202..66a2ec33c6 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs @@ -488,8 +488,6 @@ private CollectorDataEntry ToCollectorEntry(AttachmentSet attachmentSet, Guid te attachmentSet.Uri, attachmentSet.DisplayName, Environment.MachineName, - Environment.MachineName, - false, uriDataAttachments); } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs index 13cb33256a..c4468a9ae2 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Adapter/Interfaces/IFrameworkHandle.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; @@ -17,13 +18,14 @@ public interface IFrameworkHandle : ITestExecutionRecorder, IMessageLogger /// and should be used only when absolutely required as using it degrades the performance of the subsequent run. /// It throws InvalidOperationException when it is attempted to be enabled when keepAlive is false. /// + [Obsolete("This property has no effect", error: false)] bool EnableShutdownAfterTestRun { get; set; } /// /// Launch the specified process with the debugger attached. /// /// File path to the exe to launch. - /// Working directory that process should use. + /// Working directory that process should use. If null, the current directory will be used. /// Command line arguments the process should be launched with. /// Environment variables to be set in target process /// Process ID of the started process. diff --git a/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs b/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs index 278c9c6b97..bfb7aae6df 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Architecture.cs @@ -14,4 +14,5 @@ public enum Architecture S390x, Ppc64le, RiscV64, + LoongArch64, } diff --git a/src/Microsoft.TestPlatform.ObjectModel/ConnectionInfo/TestRunnerConnectionInfoExtensions.cs b/src/Microsoft.TestPlatform.ObjectModel/ConnectionInfo/TestRunnerConnectionInfoExtensions.cs index 23adf2f3b8..18be292655 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/ConnectionInfo/TestRunnerConnectionInfoExtensions.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/ConnectionInfo/TestRunnerConnectionInfoExtensions.cs @@ -20,8 +20,7 @@ public static string ToCommandLineOptions(this TestRunnerConnectionInfo connecti var options = $"--port {connectionInfo.Port} --endpoint {connectionInfo.ConnectionInfo.Endpoint} --role {(connectionInfo.ConnectionInfo.Role == ConnectionRole.Client ? "client" : "host")} --parentprocessid {connectionInfo.RunnerProcessId}"; if (!StringUtils.IsNullOrEmpty(connectionInfo.LogFile)) { - options += " --diag " + connectionInfo.LogFile; - options += " --tracelevel " + connectionInfo.TraceLevel; + options = $"{options} --diag {connectionInfo.LogFile} --tracelevel {connectionInfo.TraceLevel}"; } return options; diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs index 773f3bd6b4..d5975a14c0 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/Events/SessionEvents.cs @@ -75,7 +75,7 @@ public SessionStartEventArgs(DataCollectionContext context, IDictionary diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs index 9fc75fa955..1d94147307 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/InProcDataCollector/TestSessionStartArgs.cs @@ -69,7 +69,7 @@ public TestSessionStartArgs(string configuration) { ValidateArg.NotNullOrEmpty(property, nameof(property)); TPDebug.Assert(_properties is not null, "_properties is null"); - return _properties.ContainsKey(property) ? (T?)_properties[property] : default; + return _properties.TryGetValue(property, out var propertyValue) ? (T?)propertyValue : default; } /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index 9278611cbd..0d20b23a19 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -3,7 +3,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel - net7.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum);netstandard2.0; + $(TestHostMinimumTargetFrameworks);$(ExtensionTargetFrameworks) $(NoWarn);SYSLIB0051 @@ -23,7 +23,7 @@ - + @@ -33,7 +33,11 @@ - + + + + diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec index 105050657a..60e43f160d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.nuspec @@ -2,18 +2,19 @@ $CommonMetadataElements$ + README.md - + - - + + - + @@ -31,6 +32,7 @@ $CommonFileElements$ + @@ -67,16 +69,16 @@ - - - - + + + + - - - + + + diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/FullSymbolReader.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/FullSymbolReader.cs index 856fbd2127..aae23bc0a5 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Navigation/FullSymbolReader.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/FullSymbolReader.cs @@ -315,9 +315,9 @@ private void PopulateCacheForTypeAndMethodSymbols() try { typeName = typeName.Replace('+', '.'); - if (_typeSymbols.ContainsKey(typeName)) + if (_typeSymbols.TryGetValue(typeName, out var cachedSymbol)) { - return _typeSymbols[typeName]; + return cachedSymbol; } TPDebug.Assert(_session is not null, "_session is null"); @@ -384,12 +384,12 @@ private void PopulateCacheForTypeAndMethodSymbols() try { typeSymbol.GetName(out string symbolName); - if (_methodSymbols.ContainsKey(symbolName)) + if (_methodSymbols.TryGetValue(symbolName, out var existingMethodSymbols)) { - methodSymbolsForType = _methodSymbols[symbolName]; - if (methodSymbolsForType.ContainsKey(methodName)) + methodSymbolsForType = existingMethodSymbols; + if (methodSymbolsForType.TryGetValue(methodName, out var cachedMethodSymbol)) { - return methodSymbolsForType[methodName]; + return cachedMethodSymbol; } } else diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs index 983e7fa3cd..2a93b5e6b9 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs @@ -65,17 +65,13 @@ public void Dispose() /// public INavigationData? GetNavigationData(string declaringTypeName, string methodName) { - INavigationData? navigationData = null; - if (_methodsNavigationDataForType.ContainsKey(declaringTypeName)) + if (_methodsNavigationDataForType.TryGetValue(declaringTypeName, out var methodDict) + && methodDict.TryGetValue(methodName, out var navigationData)) { - var methodDict = _methodsNavigationDataForType[declaringTypeName]; - if (methodDict.ContainsKey(methodName)) - { - navigationData = methodDict[methodName]; - } + return navigationData; } - return navigationData; + return null; } /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs index 5a35dad4bb..0bcbb19849 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Nuget.Frameworks/OneWayCompatibilityMappingEntry.cs @@ -49,6 +49,16 @@ public bool Equals(OneWayCompatibilityMappingEntry? other) return Comparer.Equals(this, other); } + public override bool Equals(object? obj) + { + return obj is OneWayCompatibilityMappingEntry other && Equals(other); + } + + public override int GetHashCode() + { + return Comparer.GetHashCode(this); + } + public override string ToString() { return String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", TargetFrameworkRange.ToString(), SupportedFrameworkRange.ToString()); diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt index 221f78d7d1..20ef96f893 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt @@ -970,9 +970,9 @@ static readonly Microsoft.VisualStudio.TestPlatform.ObjectModel.UapConstants.Uap virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.TestObject.Properties.get -> System.Collections.Generic.IEnumerable! virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.TestObject.ProtectedGetPropertyValue(Microsoft.VisualStudio.TestPlatform.ObjectModel.TestProperty! property, object? defaultValue) -> object? virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.TestObject.ProtectedSetPropertyValue(Microsoft.VisualStudio.TestPlatform.ObjectModel.TestProperty! property, object? value) -> void -virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateValueCallback.Invoke(object? value) -> bool Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.RiscV64 = 8 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.CaptureStandardOutput.get -> bool Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.ForwardStandardOutput.get -> bool Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.DisableSharedTestHost.get -> bool Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.SkipDefaultAdapters.get -> bool +Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.LoongArch64 = 9 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt index 343eac0f8d..b056213646 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt @@ -9,3 +9,4 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Name.get -> string! Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Properties.get -> System.Collections.Generic.IDictionary! Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.TelemetryEvent(string! name, System.Collections.Generic.IDictionary! properties) -> void +Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.CreateNoNewWindow.get -> bool diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net6.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net6.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net6.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.ObjectModel/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net6.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.ObjectModel/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net6.0_and_net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net6.0_and_net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.ObjectModel/README.md b/src/Microsoft.TestPlatform.ObjectModel/README.md new file mode 100644 index 0000000000..fc9807d608 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/README.md @@ -0,0 +1,24 @@ +# Microsoft.TestPlatform.ObjectModel + +The object model for the Visual Studio Test Platform. This package provides the public API surface for creating test adapters, loggers, and other test platform extensions. This package is typically used as a reference. + +## Usage + +Add this package to your test adapter or extension project: + +```xml + +``` + +## Key Types + +- `Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase` — represents a discovered test +- `Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult` — represents the result of a test execution +- `Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.ITestDiscoverer` — interface for test discovery +- `Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter.ITestExecutor` — interface for test execution + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Adapter Extensibility](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0004-Adapter-Extensibility.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs index 1de0786730..7531d622c8 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs @@ -96,6 +96,7 @@ public RunConfiguration() : base(Constants.RunConfigurationSettingsName) CaptureStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING); ForwardStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING); DisableSharedTestHost = FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST); + CreateNoNewWindow = true; } /// @@ -456,6 +457,13 @@ public bool ResultsDirectorySet /// public bool ForwardStandardOutput { get; private set; } + /// + /// When true, the testhost process is started with CreateNoNewWindow, preventing it from opening a new console window. + /// When false, the testhost process is allowed to create a new window, which enables child processes and native applications + /// to propagate their console output. Default is true (preserving existing behavior). + /// + public bool CreateNoNewWindow { get; private set; } + /// /// Disables sharing of .NET Framework testhosts. /// @@ -589,6 +597,10 @@ public override XmlElement ToXml() forwardStandardOutput.InnerXml = ForwardStandardOutput.ToString(); root.AppendChild(forwardStandardOutput); + XmlElement createNoNewWindow = doc.CreateElement(nameof(CreateNoNewWindow)); + createNoNewWindow.InnerXml = CreateNoNewWindow.ToString(); + root.AppendChild(createNoNewWindow); + XmlElement disableSharedTesthost = doc.CreateElement(nameof(DisableSharedTestHost)); disableSharedTesthost.InnerXml = DisableSharedTestHost.ToString(); root.AppendChild(disableSharedTesthost); @@ -983,6 +995,22 @@ public static RunConfiguration FromXml(XmlReader reader) runConfiguration.ForwardStandardOutput = bForwardStandardOutput; break; + case nameof(CreateNoNewWindow): + { + XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); + string element = reader.ReadElementContentAsString(); + + bool boolValue; + if (!bool.TryParse(element, out boolValue)) + { + throw new SettingsException(string.Format(CultureInfo.CurrentCulture, + Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, element, elementName)); + } + + runConfiguration.CreateNoNewWindow = boolValue; + break; + } + case nameof(DisableSharedTestHost): { XmlRunSettingsUtilities.ThrowOnHasAttributes(reader); diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs index 911901b439..3d72a81da3 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TestProperty/TestProperty.cs @@ -135,7 +135,7 @@ public override int GetHashCode() /// public override bool Equals(object? obj) { - return base.Equals(obj as TestProperty); + return Equals(obj as TestProperty); } /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs index 1504dc5237..7ecfa12099 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TraitCollection.cs @@ -65,17 +65,19 @@ IEnumerator IEnumerable.GetEnumerator() private IEnumerable GetTraits() { - if (!_testObject.Properties.Contains(TraitsProperty)) + if (!_testObject.Properties.Contains(TraitsProperty, EqualityComparer.Default)) { - yield break; + return Array.Empty(); } - var traits = _testObject.GetPropertyValue(TraitsProperty, Enumerable.Empty>().ToArray()); - - foreach (var trait in traits) + var traitsKvp = _testObject.GetPropertyValue(TraitsProperty, Enumerable.Empty>().ToArray()); + var traits = new Trait[traitsKvp.Length]; + for (int i = 0; i < traits.Length; i++) { - yield return new Trait(trait); + traits[i] = new Trait(traitsKvp[i]); } + + return traits; } private void Add(IEnumerable traits, IEnumerable newTraits) diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs index aefba93a27..4992e32033 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/AssemblyLoadWorker.cs @@ -267,6 +267,7 @@ private static string GetArchitectureForSource(string imagePath) const int imageFileMachineArm = 0x01c0; // ARM Little-Endian const int imageFileMachineThumb = 0x01c2; // ARM Thumb/Thumb-2 Little-Endian const int imageFileMachineArmnt = 0x01c4; // ARM Thumb-2 Little-Endian + const int imageFileMachineArm64 = 0xAA64; // ARM64 Little-Endian try { @@ -333,6 +334,10 @@ private static string GetArchitectureForSource(string imagePath) case imageFileMachineArmnt: archType = "ARM"; break; + + case imageFileMachineArm64: + archType = "ARM64"; + break; } } else diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs index ef595cffb5..57f2bbb958 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Security.Cryptography; using Microsoft.VisualStudio.TestPlatform.CoreUtilities; @@ -27,7 +28,8 @@ public static Guid GuidFromString(string data) // Any algorithm or logic change must require a sign off from feature owners of above // Also, TPV2 and TPV1 must use same Algorithm until the time TPV1 is completely deleted to be on-par // If LUT or .Net core scenario uses TPV2 to discover, but if it uses TPV1 in Devenv, then there will be testcase matching issues - byte[] hash = Sha1Helper.ComputeSha1(System.Text.Encoding.Unicode.GetBytes(data)); + using HashAlgorithm provider = SHA1.Create(); + byte[] hash = provider.ComputeHash(System.Text.Encoding.Unicode.GetBytes(data)); // Guid is always 16 bytes TPDebug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes"); diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs deleted file mode 100644 index 7b2a249fa3..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Security.Cryptography; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - -/// -/// Used to calculate SHA1 hash. -/// -/// https://tools.ietf.org/html/rfc3174 -/// -internal static class Sha1Helper -{ - public static byte[] ComputeSha1(byte[] message) - { - using HashAlgorithm provider = SHA1.Create(); - byte[] hash = provider.ComputeHash(message); - - return hash; - } - - /// - /// SHA-1 Implementation as in https://tools.ietf.org/html/rfc3174 - /// - /// - /// This implementation only works with messages with a length - /// that is a multiple of the size of 8-bits. - /// - internal class Sha1Implementation - { - /* - * Many of the variable, function and parameter names in this code - * were used because those were the names used in the publication. - * - * For more information please refer to https://tools.ietf.org/html/rfc3174. - */ - - private const int BlockBits = 512; - private const int DigestBits = 160; - private const int BlockBytes = BlockBits / 8; - private const int DigestBytes = DigestBits / 8; - - /// - /// A sequence of logical functions to be used in SHA-1. - /// Each f(t), 0 <= t <= 79, operates on three 32-bit words B, C, D and produces a 32-bit word as output. - /// - /// Function index. 0 <= t <= 79 - /// Word B - /// Word C - /// Word D - /// - /// f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19) - /// f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39) - /// f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59) - /// f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79) - /// - private static uint F(int t, uint b, uint c, uint d) - { - return t switch - { - >= 0 and <= 19 => b & c | ~b & d, - >= 20 and <= 39 or >= 60 and <= 79 => b ^ c ^ d, - _ => t is >= 40 and <= 59 - ? b & c | b & d | c & d - : throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t)) - }; - } - - /// - /// Returns a constant word K(t) which is used in the SHA-1. - /// - /// Word index. - /// - /// K(t) = 0x5A827999 ( 0 <= t <= 19) - /// K(t) = 0x6ED9EBA1 (20 <= t <= 39) - /// K(t) = 0x8F1BBCDC (40 <= t <= 59) - /// K(t) = 0xCA62C1D6 (60 <= t <= 79) - /// - private static uint K(int t) - { - return t switch - { - >= 0 and <= 19 => 0x5A827999u, - >= 20 and <= 39 => 0x6ED9EBA1u, - >= 40 and <= 59 => 0x8F1BBCDCu, - _ => t is >= 60 and <= 79 - ? 0xCA62C1D6u - : throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t)) - }; - } - - /// - /// The circular left shift operation. - /// - /// An uint word. - /// 0 <= n < 32 - /// S^n(X) = (X << n) OR (X >> 32-n) - private static uint S(uint x, byte n) - { - return n > 32 ? throw new ArgumentOutOfRangeException(nameof(n)) : (x << n) | (x >> (32 - n)); - } - - /// - /// Ensures that given bytes are in big endian notation. - /// - /// An array of bytes - private static void EnsureBigEndian(ref byte[] array) - { - ValidateArg.NotNull(array, nameof(array)); - - if (BitConverter.IsLittleEndian) - { - Array.Reverse(array); - } - } - - private readonly uint[] _h = new uint[5]; - - private void Reset() - { - // as defined in https://tools.ietf.org/html/rfc3174#section-6.1 - _h[0] = 0x67452301u; - _h[1] = 0xEFCDAB89u; - _h[2] = 0x98BADCFEu; - _h[3] = 0x10325476u; - _h[4] = 0xC3D2E1F0u; - } - - public byte[] ComputeHash(byte[] message) - { - ValidateArg.NotNull(message, nameof(message)); - - Reset(); - PadMessage(ref message); - - var messageCount = message.Length / BlockBytes; - for (var i = 0; i < messageCount; ++i) - { - ProcessBlock(message, i * BlockBytes, BlockBytes); - } - - var digest = new byte[DigestBytes]; - for (int t = 0; t < _h.Length; t++) - { - var hi = BitConverter.GetBytes(_h[t]); - EnsureBigEndian(ref hi); - - Buffer.BlockCopy(hi, 0, digest, t * hi.Length, hi.Length); - } - - return digest; - } - - private static void PadMessage(ref byte[] message) - { - var length = message.Length; - var paddingBytes = BlockBytes - (length % BlockBytes); - - // 64bit uint message size will be appended to end of the padding, making sure we have space for it. - if (paddingBytes <= 8) - paddingBytes += BlockBytes; - - var padding = new byte[paddingBytes]; - padding[0] = 0b10000000; - - var messageBits = (ulong)message.Length << 3; - var messageSize = BitConverter.GetBytes(messageBits); - EnsureBigEndian(ref messageSize); - - Buffer.BlockCopy(messageSize, 0, padding, padding.Length - messageSize.Length, messageSize.Length); - - Array.Resize(ref message, message.Length + padding.Length); - Buffer.BlockCopy(padding, 0, message, length, padding.Length); - } - - private void ProcessBlock(byte[] message, int start, int length) - { - if (start + length > message.Length) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - if (length != BlockBytes) - { - throw new ArgumentException($"Invalid block size. Actual: {length}, Expected: {BlockBytes}", nameof(length)); - } - - var w = new uint[80]; - - // Get W(0) .. W(15) - for (int t = 0; t <= 15; t++) - { - var wordBytes = new byte[sizeof(uint)]; - Buffer.BlockCopy(message, start + (t * sizeof(uint)), wordBytes, 0, sizeof(uint)); - EnsureBigEndian(ref wordBytes); - - w[t] = BitConverter.ToUInt32(wordBytes, 0); - } - - // Calculate W(16) .. W(79) - for (int t = 16; t <= 79; t++) - { - w[t] = S(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); - } - - uint a = _h[0], - b = _h[1], - c = _h[2], - d = _h[3], - e = _h[4]; - - for (int t = 0; t < 80; t++) - { - var temp = S(a, 5) + F(t, b, c, d) + e + w[t] + K(t); - e = d; - d = c; - c = S(b, 30); - b = a; - a = temp; - } - - _h[0] += a; - _h[1] += b; - _h[2] += c; - _h[3] += d; - _h[4] += e; - } - } -} diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs index 0824fd314c..1e7f0e5cf8 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs @@ -24,6 +24,20 @@ public interface IProcessHelper /// The process created. object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack); + /// + /// Launches the process with the given arguments. + /// + /// The full file name of the process. + /// The command-line arguments. + /// The working directory for this process. + /// Environment variables to set while bootstrapping the process. + /// Call back for to read error stream data + /// Call back for on process exit + /// Call back for on process output + /// When true, prevents the process from creating a new console window. + /// The process created. + object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack, bool createNoNewWindow); + /// /// Gets the current process file path. /// diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformArchitecture.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformArchitecture.cs index 9c656677de..2e7c12a266 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformArchitecture.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformArchitecture.cs @@ -15,4 +15,5 @@ public enum PlatformArchitecture S390x, Ppc64le, RiscV64, + LoongArch64, } diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj b/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj index 5aae32c1cb..7bc1805000 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj @@ -4,22 +4,12 @@ Microsoft.TestPlatform.PlatformAbstractions Microsoft.TestPlatform.PlatformAbstractions - net7.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum);netstandard2.0;net6.0 + $(TestHostMinimumTargetFrameworks);$(ExtensionTargetFrameworks) false $(NoWarn);NU1605 - - - - - - - - - - - + @@ -32,11 +22,11 @@ - - - - - + + + + + diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/PublicAPI.Shipped.txt index 60e7d5e4a9..aaf05ab202 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/PublicAPI.Shipped.txt @@ -53,6 +53,7 @@ Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelp Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.GetProcessName(int processId) -> string! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.GetTestEngineDirectory() -> string? Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.SetExitCallback(int processId, System.Action? callbackAction) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.TerminateProcess(object? process) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper.TryGetExitCode(object? process, out int exitCode) -> bool @@ -119,6 +120,6 @@ Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ThreadApartmentStateNot static Microsoft.VisualStudio.TestPlatform.ObjectModel.PlatformEqtTrace.ErrorOnInitialization.get -> string? static Microsoft.VisualStudio.TestPlatform.ObjectModel.PlatformEqtTrace.ErrorOnInitialization.set -> void static Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyExtensions.GetAssemblyLocation(this System.Reflection.Assembly! assembly) -> string! -virtual Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.AssemblyResolveEventHandler.Invoke(object? sender, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.AssemblyResolveEventArgs? args) -> System.Reflection.Assembly? virtual Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyResolver.Dispose(bool disposing) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture.RiscV64 = 6 -> Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture.LoongArch64 = 7 -> Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformArchitecture diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net462/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net462/PublicAPI.Shipped.txt index c8d1f7b662..4d0f290f95 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net462/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net462/PublicAPI.Shipped.txt @@ -15,6 +15,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Rolling Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyResolver.~PlatformAssemblyResolver() -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(System.Action? action, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformApartmentState apartmentState, bool waitForCompletion) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.SetExitCallback(int processId, System.Action? callbackAction) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessStartInfoExtensions override Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Dispose(bool disposing) -> void diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net6.0_and_net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 86% rename from src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net6.0_and_net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Shipped.txt index d17adb2a82..b989cb7068 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net6.0_and_net7.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -7,6 +7,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Rolling Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyResolver.~PlatformAssemblyResolver() -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(System.Action? action, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformApartmentState apartmentState, bool waitForCompletion) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.SetExitCallback(int processId, System.Action? callbackAction) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessStartInfoExtensions override Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Dispose(bool disposing) -> void diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..ab058de62d --- /dev/null +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt index d17adb2a82..b989cb7068 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt @@ -7,6 +7,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Rolling Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyResolver.~PlatformAssemblyResolver() -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(System.Action? action, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformApartmentState apartmentState, bool waitForCompletion) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.SetExitCallback(int processId, System.Action? callbackAction) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessStartInfoExtensions override Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Dispose(bool disposing) -> void diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt index d17adb2a82..b989cb7068 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -7,6 +7,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Rolling Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformAssemblyResolver.~PlatformAssemblyResolver() -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(System.Action? action, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformApartmentState apartmentState, bool waitForCompletion) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.SetExitCallback(int processId, System.Action? callbackAction) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessStartInfoExtensions override Microsoft.VisualStudio.TestPlatform.ObjectModel.RollingFileTraceListener.Dispose(bool disposing) -> void diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/uap10.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/uap10.0/PublicAPI.Shipped.txt index d396faaefd..903eb9d22e 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/uap10.0/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/PublicAPI/uap10.0/PublicAPI.Shipped.txt @@ -4,6 +4,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.PlatformEqtTrace.WriteLine(Micro Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(System.Action? action, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformApartmentState apartmentState, bool waitForCompletion) -> void Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.GetProcessById(int processId) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack) -> object! +Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.LaunchProcess(string! processPath, string? arguments, string? workingDirectory, System.Collections.Generic.IDictionary? envVariables, System.Action? errorCallback, System.Action? exitCallBack, System.Action? outputCallBack, bool createNoNewWindow) -> object! Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.ProcessHelper.SetExitCallback(int parentProcessId, System.Action? callbackAction) -> void static Microsoft.VisualStudio.TestPlatform.ObjectModel.PlatformEqtTrace.LogFile.get -> string? static Microsoft.VisualStudio.TestPlatform.ObjectModel.PlatformEqtTrace.LogFile.set -> void diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs index b378c5a923..456be7b4fd 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs @@ -52,6 +52,10 @@ internal ProcessHelper(IEnvironment environment) /// public object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack) + => LaunchProcess(processPath, arguments, workingDirectory, envVariables, errorCallback, exitCallBack, outputCallBack, createNoNewWindow: true); + + /// + public object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack, bool createNoNewWindow) { if (!File.Exists(processPath)) { @@ -77,7 +81,7 @@ public object LaunchProcess(string processPath, string? arguments, string? worki void InitializeAndStart() { process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; + process.StartInfo.CreateNoWindow = createNoNewWindow; process.StartInfo.WorkingDirectory = workingDirectory; process.StartInfo.FileName = processPath; @@ -266,6 +270,7 @@ public bool TryGetExitCode(object? process, out int exitCode) } catch (InvalidOperationException) { + // Process may have already exited — exit code unavailable. } exitCode = 0; @@ -301,6 +306,7 @@ public void TerminateProcess(object? process) } catch (InvalidOperationException) { + // Process may have already exited — exit code unavailable. } } diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/net462/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/net462/System/ProcessHelper.cs index cc65454857..ffb9c38bfa 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/net462/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/net462/System/ProcessHelper.cs @@ -1,19 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if NETFRAMEWORK || NETSTANDARD2_0 - using System; using System.ComponentModel; using System.Diagnostics; using System.IO; - +# if NETCOREAPP || NETSTANDARD2_0_OR_GREATER +using System.Runtime.InteropServices; +#endif using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; public partial class ProcessHelper : IProcessHelper { +#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER private PlatformArchitecture? _currentProcessArchitecture; /// @@ -29,27 +30,35 @@ public nint GetProcessHandle(int processId) => /// public PlatformArchitecture GetCurrentProcessArchitecture() { + // If we already cached the current process architecture, no need to figure it out again. + if (_currentProcessArchitecture is not null) + { + return _currentProcessArchitecture.Value; + } + + // When this is current process, we can just check if IntPointer size to get if we are 64-bit or 32-bit. + // When it is 32-bit we can just return, if it is 64-bit we need to clarify if x64 or arm64. + if (IntPtr.Size == 4) + { + _currentProcessArchitecture = PlatformArchitecture.X86; + return _currentProcessArchitecture.Value; + } + _currentProcessArchitecture ??= GetProcessArchitecture(_currentProcess.Id); return _currentProcessArchitecture.Value; } +#endif public PlatformArchitecture GetProcessArchitecture(int processId) { - if (_currentProcess.Id == processId) +#if NETCOREAPP || NETSTANDARD2_0_OR_GREATER + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // If we already cached the current process architecture, no need to figure it out again. - if (_currentProcessArchitecture is not null) - { - return _currentProcessArchitecture.Value; - } - - // When this is current process, we can just check if IntPointer size to get if we are 64-bit or 32-bit. - // When it is 32-bit we can just return, if it is 64-bit we need to clarify if x64 or arm64. - if (IntPtr.Size == 4) - { - return PlatformArchitecture.X86; - } + // No implementation for this for cross platform, and we cannot move this to platform specific file. + // Usages are only from hang dumper. + throw new NotImplementedException(); } +#endif // If the current process is 64-bit, or this is any remote process, we need to query it via native api. var process = processId == _currentProcess.Id ? _currentProcess : Process.GetProcessById(processId); @@ -72,7 +81,7 @@ public PlatformArchitecture GetProcessArchitecture(int processId) if (processMachine == NativeMethods.IMAGE_FILE_MACHINE_UNKNOWN && nativeMachine == NativeMethods.IMAGE_FILE_MACHINE_ARM64) { // To distinguish between ARM64 and x64 emulated on ARM64 we check the PE header of the current running executable. - if (IsArm64Executable(process.MainModule.FileName)) + if (IsArm64Executable(process.MainModule!.FileName)) { return PlatformArchitecture.ARM64; } @@ -169,5 +178,3 @@ private static bool IsArm64Executable(string path) return magic is 0x010B or 0x020B && machine == NativeMethods.IMAGE_FILE_MACHINE_ARM64; } } - -#endif diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs index 31f5fde389..c558510e22 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/PlatformEnvironment.cs @@ -28,6 +28,7 @@ public PlatformArchitecture Architecture // preview 6 or later, so use the numerical value for now. // case System.Runtime.InteropServices.Architecture.S390x: (Architecture)5 => PlatformArchitecture.S390x, + (Architecture)6 => PlatformArchitecture.LoongArch64, (Architecture)8 => PlatformArchitecture.Ppc64le, (Architecture)9 => PlatformArchitecture.RiscV64, _ => throw new NotSupportedException(), diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/ProcessHelper.cs index 24c07fa5ed..5663a2e431 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/netcore/System/ProcessHelper.cs @@ -41,17 +41,12 @@ public PlatformArchitecture GetCurrentProcessArchitecture() // preview 6 or later, so use the numerical value for now. // case System.Runtime.InteropServices.Architecture.S390x: (Architecture)5 => PlatformArchitecture.S390x, + (Architecture)6 => PlatformArchitecture.LoongArch64, (Architecture)8 => PlatformArchitecture.Ppc64le, (Architecture)9 => PlatformArchitecture.RiscV64, _ => throw new NotSupportedException(), }; } - - public PlatformArchitecture GetProcessArchitecture(int processId) - { - // Return the same as the current process. - return GetCurrentProcessArchitecture(); - } } #endif diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs index 21b343dad7..87a84e36d7 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs @@ -67,6 +67,7 @@ public class DefaultTestHostManager : ITestRuntimeProvider2 private StringBuilder? _testHostProcessStdOut; private IMessageLogger? _messageLogger; private bool _captureOutput; + private bool _createNoNewWindow; private bool _hostExitedEventRaised; private TestHostManagerCallbacks? _testHostManagerCallbacks; @@ -194,7 +195,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( if (!_fileHelper.Exists(testhostProcessPath)) { // We assume that we could not find testhost.exe in the root folder so we are going to lookup in the - // TestHostNetFramework folder (assuming we are currently running under netcoreapp3.1) or in dotnet SDK + // TestHostNetFramework folder (assuming we are currently running under .NET) or in dotnet SDK // context. testHostProcessName = Path.Combine("TestHostNetFramework", originalTestHostProcessName); testhostProcessPath = Path.Combine(currentWorkingDirectory, "..", testHostProcessName); @@ -293,6 +294,7 @@ private static string GetTestHostName(Architecture architecture, Framework targe PlatformArchitecture.S390x => Architecture.S390x, PlatformArchitecture.Ppc64le => Architecture.Ppc64le, PlatformArchitecture.RiscV64 => Architecture.RiscV64, + PlatformArchitecture.LoongArch64 => Architecture.LoongArch64, _ => throw new NotSupportedException(), }; @@ -374,6 +376,7 @@ public void Initialize(IMessageLogger? logger, string runsettingsXml) _messageLogger = logger; _captureOutput = runConfiguration.CaptureStandardOutput; + _createNoNewWindow = runConfiguration.CreateNoNewWindow; var forwardOutput = runConfiguration.ForwardStandardOutput; _testHostManagerCallbacks = new TestHostManagerCallbacks(forwardOutput, logger); _architecture = runConfiguration.TargetPlatform; @@ -536,7 +539,7 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke if (_customTestHostLauncher == null || (_customTestHostLauncher.IsDebug && _customTestHostLauncher is ITestHostLauncher2)) { - EqtTrace.Verbose("DefaultTestHostManager: Starting process '{0}' with command line '{1}'", testHostStartInfo.FileName, testHostStartInfo.Arguments); + EqtTrace.Verbose("DefaultTestHostManager: Starting process '{0}' with command line '{1}', CreateNoWindow={2}", testHostStartInfo.FileName, testHostStartInfo.Arguments, _createNoNewWindow); cancellationToken.ThrowIfCancellationRequested(); var outputCallback = _captureOutput ? OutputReceivedCallback : null; _testHostProcess = _processHelper.LaunchProcess( @@ -546,7 +549,8 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke testHostStartInfo.EnvironmentVariables, ErrorReceivedCallback, ExitCallBack, - outputCallback) as Process; + outputCallback, + _createNoNewWindow) as Process; } else { diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 70c9481a8f..562f40f9f8 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -75,6 +75,7 @@ public class DotnetTestHostManager : ITestRuntimeProvider2 private bool _isVersionCheckRequired = true; private string? _dotnetHostPath; private bool _captureOutput; + private bool _createNoNewWindow; private protected TestHostManagerCallbacks? _testHostManagerCallbacks; /// @@ -192,6 +193,7 @@ public void Initialize(IMessageLogger? logger, string runsettingsXml) _hostExitedEventRaised = false; var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettingsXml); _captureOutput = runConfiguration.CaptureStandardOutput; + _createNoNewWindow = runConfiguration.CreateNoNewWindow; var forwardOutput = runConfiguration.ForwardStandardOutput; _testHostManagerCallbacks = new TestHostManagerCallbacks(forwardOutput, logger); @@ -231,6 +233,29 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( EqtTrace.Verbose($"DotnetTestHostmanager.GetTestHostProcessStartInfo: Platform environment '{_platformEnvironment.Architecture}' target architecture '{_architecture}' framework '{_targetFramework}' OS '{_platformEnvironment.OperatingSystem}'"); var startInfo = new TestProcessStartInfo(); + startInfo.EnvironmentVariables = environmentVariables ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + + string? dotnetRootPath = _environmentVariableHelper.GetEnvironmentVariable("VSTEST_DOTNET_ROOT_PATH"); + string? dotnetRootArchitecture = _environmentVariableHelper.GetEnvironmentVariable("VSTEST_DOTNET_ROOT_ARCHITECTURE"); + + if (!StringUtilities.IsNullOrWhiteSpace(dotnetRootPath)) + { + if (StringUtils.IsNullOrWhiteSpace(dotnetRootArchitecture)) + { + throw new InvalidOperationException("'VSTEST_DOTNET_ROOT_PATH' and 'VSTEST_DOTNET_ROOT_ARCHITECTURE' must be both always set. If you are seeing this error, this is a bug in dotnet SDK that sets those variables."); + } + + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_PATH={dotnetRootPath}"); + EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: VSTEST_DOTNET_ROOT_ARCHITECTURE={dotnetRootArchitecture}"); + + if (!FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS)) + { + // Set DOTNET_ROOT_ for any run, so it gets propagated to testhost and its child processes, like dotnet run does it. This allows executables that start under testhost to find the path to dotnet + // from which we called dotnet test. Before this change we only expected testhost.exe to be in this situation, but with xunit v3 running separate exe under testhost, the need for setting architecture + // specific DOTNET_ROOT increases and makes this necessary for users to have good experience. + SetDotnetRootForArchitecture(startInfo, dotnetRootPath!, dotnetRootArchitecture); + } + } // .NET core host manager is not a shared host. It will expect a single test source to be provided. // TODO: Throw an exception when we get 0 or more than 1 source, that explains what happened, instead of .Single throwing a generic exception? @@ -306,8 +331,8 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( testHostPath = GetTestHostPath(runtimeConfigDevPath, depsFilePath, sourceDirectory); if (!testHostPath.IsNullOrWhiteSpace() && testHostPath.IndexOf("microsoft.testplatform.testhost", StringComparison.OrdinalIgnoreCase) >= 0) { - // testhost.dll is present in path {testHostNugetRoot}\lib\netcoreapp3.1\testhost.dll - // testhost.(x86).exe is present in location {testHostNugetRoot}\build\netcoreapp3.1\{x86/x64}\{testhost.x86.exe/testhost.exe} + // testhost.dll is present in path {testHostNugetRoot}\lib\net8.0\testhost.dll + // testhost.(x86).exe is present in location {testHostNugetRoot}\build\net8.0\{x86/x64}\{testhost.x86.exe/testhost.exe} var folderName = _architecture is Architecture.X64 or Architecture.Default or Architecture.AnyCPU ? Architecture.X64.ToString().ToLowerInvariant() : _architecture.ToString().ToLowerInvariant(); @@ -321,7 +346,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( testHostExeNugetPath = Path.Combine(testHostNugetRoot.FullName, "build", "net9.0", folderName, exeName); } #else - var testHostExeNugetPath = Path.Combine(testHostNugetRoot.FullName, "build", "netcoreapp3.1", folderName, exeName); + var testHostExeNugetPath = Path.Combine(testHostNugetRoot.FullName, "build", "net8.0", folderName, exeName); #endif if (_fileHelper.Exists(testHostExeNugetPath)) @@ -506,30 +531,79 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( // G:\nuget-package-path\microsoft.testplatform.testhost\version\**\testhost.dll // G:\tmp\netcore-test\bin\Debug\netcoreapp1.0\netcore-test.dll startInfo.Arguments = args; - startInfo.EnvironmentVariables = environmentVariables ?? new Dictionary(); // If we're running using custom apphost we need to set DOTNET_ROOT/DOTNET_ROOT(x86) // We're setting it inside SDK to support private install scenario. // i.e. I've got only private install and no global installation, in this case apphost needs to use env var to locate runtime. if (testHostExeFound) { - string prefix = "VSTEST_WINAPPHOST_"; - string dotnetRootEnvName = $"{prefix}DOTNET_ROOT(x86)"; - var dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName); - if (dotnetRoot is null) + if (!StringUtilities.IsNullOrWhiteSpace(dotnetRootPath)) { - dotnetRootEnvName = $"{prefix}DOTNET_ROOT"; - dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName); - } - - if (dotnetRoot != null) - { - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{dotnetRootEnvName}' in env variables, value '{dotnetRoot}', forwarding to '{dotnetRootEnvName.Replace(prefix, string.Empty)}'"); - startInfo.EnvironmentVariables.Add(dotnetRootEnvName.Replace(prefix, string.Empty), dotnetRoot); - } - else - { - EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables"); + // The parent process is passing to us the path in which the dotnet.exe is and is passing the architecture of the dotnet.exe, + // so if the child process (testhost) is the same architecture it can pick up that dotnet.exe location and run. This is to allow + // local installations of dotnet/sdk to work with testhost. + // + // There are 2 complications in this process: + // 1) There are differences between how .NET Apphosts are handling DOTNET_ROOT, versions pre-net6 are only looking at + // DOTNET_ROOT(x86) and then DOTNET_ROOT. This makes is really easy to set DOTNET_ROOT to point at x64 dotnet installation + // and have that picked up by x86 testhost and fail. + // Unfortunately vstest.console has to support both new (17.14+) testhosts that are built against net8, and old (pre 17.14) + // testhosts that are built using netcoreapp3.1 apphost, and so their approach to resolving DOTNET_ROOT differ. + // + // /!\ The apphost version does not align with the targeted framework (tfm), an older testhost is built against netcoreapp3.1 + // but can be used to run net8 tests. The only way to tell is the version of the testhost. + // + // netcoreapp3.1 hosts only support DOTNET_ROOT and DOTNET_ROOT(x86) env variables. + // net8 hosts, support also DOTNET_ROOT_ variables, which is what we should prefer to set the location of dotnet + // in a more architecture specific way. + // + // 2) The surrounding environment might already have the environment variables set, most likely by setting DOTNET_ROOT, which is + // a universal way of setting where the dotnet is, that works across all different architectures of the .NET apphost. + // By setting our (hopefully more specific variable) we might overwrite what user specified, and in case of DOTNET_ROOT it is probably + // preferable when we can set the DOTNET_ROOT_ variable. + var testhostDllPath = Path.ChangeExtension(startInfo.FileName, ".dll"); + // This file check is for unit tests, we expect the file to always be there. Otherwise testhost.exe would not be able to run. + var testhostVersionInfo = _fileHelper.Exists(testhostDllPath) ? FileVersionInfo.GetVersionInfo(testhostDllPath) : null; + if (testhostVersionInfo != null && testhostVersionInfo.ProductMajorPart >= 17 && testhostVersionInfo.ProductMinorPart >= 14) + { + // This is a new testhost that builds at least against net8 we should set the architecture specific DOTNET_ROOT_. + // + // We ship just testhost.exe and testhost.x86.exe if the architecture is different we won't find the testhost*.exe and + // won't reach this code, but let's write this in a generic way anyway, to avoid breaking if we add more variants of testhost*.exe. + // + // If the feature flag is set, revert to previous behavior of setting DOTNET_ROOT_ only on Windows after we found testhost.exe. + if (FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_DOTNET_ROOT_ON_NONWINDOWS)) + { + SetDotnetRootForArchitecture(startInfo, dotnetRootPath!, dotnetRootArchitecture!); + } + } + else + { + // This is an old testhost that built against netcoreapp3.1, it does not understand architecture specific DOTNET_ROOT_, we have to set it more carefully + // to avoid setting DOTNET_ROOT that points to x64 but is picked up by x86 host. + // + // Also avoid setting it if we are already getting it from the surrounding environment. + var architectureFromEnv = (Architecture)Enum.Parse(typeof(Architecture), dotnetRootArchitecture!, ignoreCase: true); + if (architectureFromEnv == _architecture) + { + if (_architecture == Architecture.X86) + { + const string dotnetRootX86 = "DOTNET_ROOT(x86)"; + if (StringUtils.IsNullOrWhiteSpace(_environmentVariableHelper.GetEnvironmentVariable(dotnetRootX86))) + { + startInfo.EnvironmentVariables.Add(dotnetRootX86, dotnetRootPath); + } + } + else + { + const string dotnetRoot = "DOTNET_ROOT"; + if (StringUtils.IsNullOrWhiteSpace(_environmentVariableHelper.GetEnvironmentVariable(dotnetRoot))) + { + startInfo.EnvironmentVariables.Add(dotnetRoot, dotnetRootPath); + } + } + } + } } } @@ -576,6 +650,8 @@ PlatformArchitecture TranslateToPlatformArchitecture(Architecture targetArchitec return PlatformArchitecture.Ppc64le; case Architecture.RiscV64: return PlatformArchitecture.RiscV64; + case Architecture.LoongArch64: + return PlatformArchitecture.LoongArch64; case Architecture.AnyCPU: case Architecture.Default: default: @@ -595,6 +671,7 @@ static bool IsSameArchitecture(Architecture targetArchitecture, PlatformArchitec Architecture.S390x => platformAchitecture == PlatformArchitecture.S390x, Architecture.Ppc64le => platformAchitecture == PlatformArchitecture.Ppc64le, Architecture.RiscV64 => platformAchitecture == PlatformArchitecture.RiscV64, + Architecture.LoongArch64 => platformAchitecture == PlatformArchitecture.LoongArch64, _ => throw new TestPlatformException($"Invalid target architecture '{targetArchitecture}'"), }; @@ -636,6 +713,26 @@ bool IsNativeModule(string modulePath) } } + private void SetDotnetRootForArchitecture(TestProcessStartInfo startInfo, string dotnetRootPath, string dotnetRootArchitecture) + { + var environmentVariableName = $"DOTNET_ROOT_{dotnetRootArchitecture.ToUpperInvariant()}"; + + var existingDotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(environmentVariableName); + if (!StringUtilities.IsNullOrWhiteSpace(existingDotnetRoot)) + { + EqtTrace.Verbose($"DotnetTestHostManager.SetDotnetRootForArchitecture: The variable {environmentVariableName} is already set in the surrounding environment, don't add it to testhost start info, because we want to keep what user provided externally."); + } + else + { + startInfo.EnvironmentVariables ??= new Dictionary(); + + // Set the architecture specific variable to the environment of the process so it is picked up. + startInfo.EnvironmentVariables.Add(environmentVariableName, dotnetRootPath); + + EqtTrace.Verbose($"DotnetTestHostManager.SetDotnetRootForArchitecture: Adding {environmentVariableName}={dotnetRootPath} to testhost start info."); + } + } + /// public IEnumerable GetTestPlatformExtensions(IEnumerable sources, IEnumerable extensions) { @@ -741,9 +838,18 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke || (_customTestHostLauncher.IsDebug && _customTestHostLauncher is ITestHostLauncher2)) { - EqtTrace.Verbose("DotnetTestHostManager: Starting process '{0}' with command line '{1}'", testHostStartInfo.FileName, testHostStartInfo.Arguments); + if (EqtTrace.IsVerboseEnabled) + { + var dotnetEnvVars = testHostStartInfo.EnvironmentVariables? + .Where(kvp => kvp.Key.StartsWith("DOTNET_", StringComparison.OrdinalIgnoreCase)) + .Select(kvp => $"{kvp.Key}={kvp.Value}") + .ToArray() ?? Array.Empty(); + + EqtTrace.Verbose($"DotnetTestHostManager: Starting process '{0}' with command line '{1}' and DOTNET environment: {string.Join(", ", dotnetEnvVars)} ", testHostStartInfo.FileName, testHostStartInfo.Arguments); + } cancellationToken.ThrowIfCancellationRequested(); + EqtTrace.Verbose("DotnetTestHostManager: Launching testhost with CreateNoWindow={0}", _createNoNewWindow); var outputCallback = _captureOutput ? OutputReceivedCallback : null; _testHostProcess = _processHelper.LaunchProcess( @@ -753,7 +859,8 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke testHostStartInfo.EnvironmentVariables, ErrorReceivedCallback, ExitCallBack, - outputCallback) as Process; + outputCallback, + _createNoNewWindow) as Process; } else { diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj index 8191a31a24..3bd5ea9246 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj +++ b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj @@ -3,11 +3,14 @@ Microsoft.TestPlatform.TestHostRuntimeProvider - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum) + + $(NetFrameworkRunnerTargetFramework);$(ExtensionTargetFrameworks) false true - + @@ -26,7 +29,6 @@ - diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net462/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net462/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Extensions.HtmlLogger/PublicAPI/net462/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net462/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net9.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net462/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net9.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net9.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net462/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net9.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs index b7c7fc0b66..0a51a550de 100644 --- a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs +++ b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs @@ -21,9 +21,9 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum try { - while (true) + while (index < args.Length) { - // skip whitespace + // Skip whitespace. while (char.IsWhiteSpace(args[index])) { index++; @@ -33,42 +33,65 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum if (args[index] == '#') { index++; - while (args[index] != '\n') + while (index < args.Length && args[index] != '\n') { index++; } + + // We are done processing comment move to next statement. continue; } - // do one argument + // Read argument until next whitespace (not in quotes). do { if (args[index] == '\\') { - int cSlashes = 1; + // Move to next char. index++; - while (index == args.Length && args[index] == '\\') - { - cSlashes++; - } - if (index == args.Length || args[index] != '"') + // If this was the last char then output the slash. + if (index == args.Length) { - currentArg.Append('\\', cSlashes); + currentArg.Append('\\'); + + index++; + continue; } else { - currentArg.Append('\\', (cSlashes >> 1)); - if (0 != (cSlashes & 1)) + // If the char after '\' is also a '\', output the second '\' and skip over to the next char. + if (args[index] == '\\') + { + currentArg.Append('\\'); + + // We processed the escaped \, move to next char. + index++; + continue; + } + + // If the char after '\' is a '"', output '"' and skip over to the next char. + if (index <= args.Length && args[index] == '"') { currentArg.Append('"'); + + // We processed the escaped " move to next char. + index++; + continue; } - else + + // If the char after '\' is anything else, output the slash. And continue processing the next char. + if (index <= args.Length) { - inQuotes = !inQuotes; + currentArg.Append('\\'); + + // Don't skip to the next char. We outputted the \ because it was not escaping \ or ". Let the next character to be processed by the loop. + // index++; + continue; } } } + // Unescaped quote enters and leaves quoted mode. else if (args[index] == '"') { inQuotes = !inQuotes; @@ -76,6 +99,7 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum } else { + // Collect all other characters. currentArg.Append(args[index]); index++; } diff --git a/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs b/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs index 86a694fa1a..7825d9adb8 100644 --- a/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs +++ b/src/Microsoft.TestPlatform.Utilities/InferRunSettingsHelper.cs @@ -718,4 +718,84 @@ private static bool IsFrameworkIncompatible(Framework sourceFramework, Framework return !sourceFramework.Name.Equals(Framework.DefaultFramework.Name, StringComparison.OrdinalIgnoreCase) && !sourceFramework.Name.Equals(targetFramework.Name, StringComparison.OrdinalIgnoreCase); } + + public static bool UpdateCollectCoverageSettings(XmlDocument xmlDocument) + { + var root = xmlDocument.DocumentElement; + + var dataCollectorNodes = root?.SelectNodes("DataCollectionRunSettings/DataCollectors/DataCollector"); + if (dataCollectorNodes == null) + { + return false; + } + foreach (XmlNode dataCollectorNode in dataCollectorNodes) + { + var dataCollectorFound = false; + foreach (XmlAttribute attribute in dataCollectorNode.Attributes!) + { + if (attribute.Name.Equals("friendlyName", StringComparison.OrdinalIgnoreCase) && + attribute.Value.Equals("Code Coverage", StringComparison.OrdinalIgnoreCase)) + { + dataCollectorFound = true; + break; + } + + if (attribute.Name.Equals("uri", StringComparison.OrdinalIgnoreCase) && + attribute.Value.Equals(CodeCoverageCollectorUri, StringComparison.OrdinalIgnoreCase)) + { + dataCollectorFound = true; + break; + } + + if (attribute.Name.Equals("assemblyQualifiedName", StringComparison.OrdinalIgnoreCase) && + attribute.Value.IndexOf("Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector", StringComparison.OrdinalIgnoreCase) >= 0) + { + dataCollectorFound = true; + break; + } + } + + if (dataCollectorFound) + { + var coverageCollectorNode = dataCollectorNode; + // Code coverage settings are present, we should update them. + + var dynamicNativeInstrumentationNode = coverageCollectorNode.SelectSingleNode("Configuration/CodeCoverage/EnableDynamicNativeInstrumentation"); + + if (dynamicNativeInstrumentationNode == null) + { + // EnableDynamicNativeInstrumentation is not set explicitly, we should set it. Whole tree might not exist. + + var currentNode = coverageCollectorNode; + var paths = "Configuration/CodeCoverage/EnableDynamicNativeInstrumentation".Split('/'); + foreach (var nodeName in paths) + { + var found = false; + foreach (XmlNode childNode in currentNode.ChildNodes) + { + if (childNode.Name == nodeName) + { + currentNode = childNode; + found = true; + break; + } + } + + if (!found) + { + var newNode = xmlDocument.CreateElement(nodeName); + currentNode.AppendChild(newNode); + currentNode = newNode; + } + } + + currentNode.InnerXml = "False"; + + return true; + } + } + } + + return false; + } } diff --git a/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj index 71ae94e3ed..8fd6b0b3cd 100644 --- a/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj +++ b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj @@ -3,15 +3,9 @@ Microsoft.TestPlatform.Utilities - net7.0;netstandard2.0;$(NetFrameworkMinimum) + $(NetFrameworkMinimum);$(ExtensionTargetFrameworks) false - - - - - - diff --git a/src/Microsoft.TestPlatform.Utilities/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Utilities/PublicAPI/PublicAPI.Shipped.txt index cb20924b23..9639b6660b 100644 --- a/src/Microsoft.TestPlatform.Utilities/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.Utilities/PublicAPI/PublicAPI.Shipped.txt @@ -39,4 +39,6 @@ static Microsoft.VisualStudio.TestPlatform.Utilities.MSTestSettingsUtilities.IsL static Microsoft.VisualStudio.TestPlatform.Utilities.ParallelRunSettingsUtilities.UpdateRunSettingsWithParallelSettingIfNotConfigured(System.Xml.XPath.XPathNavigator! navigator) -> void static Microsoft.VisualStudio.TestPlatform.Utilities.StringExtensions.Tokenize(this string? input, char separator, char escape) -> System.Collections.Generic.IEnumerable! static Microsoft.VisualStudio.TestPlatform.Utilities.InferRunSettingsHelper.UpdateBatchSize(System.Xml.XmlDocument! runSettingsDocument, long batchSizeValue) -> void +static Microsoft.VisualStudio.TestPlatform.Utilities.InferRunSettingsHelper.UpdateCollectCoverageSettings(System.Xml.XmlDocument! xmlDocument) -> bool + diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Utilities/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.Utilities/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Utilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.Utilities/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs index 69f72975a8..59d6048036 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IProcessManager.cs @@ -30,4 +30,24 @@ internal interface IProcessManager /// Raise event on process exit /// event EventHandler ProcessExited; + + /// + /// Process that we manage, or managed, useful for reporting to correlate log messages when the process no longer lives. + /// + string? ProcessName { get; } + + /// + /// Process that we manage, or managed, useful for reporting to correlate log messages when the process no longer lives. + /// + int? ProcessId { get; } + + /// + /// Error output of the process. Cleared on new process start. + /// + string? ErrorOutput { get; } + + /// + /// Exit code of the process. Cleared on new process start. + /// + int? ExitCode { get; } } diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj index a9278a3e86..cda5cf5075 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj @@ -3,7 +3,7 @@ Microsoft.TestPlatform.VsTestConsole.TranslationLayer - net7.0;net6.0;netstandard2.0;$(NetFrameworkMinimum) + $(LibraryTargetFrameworks) false @@ -34,7 +34,7 @@ - + diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.nuspec b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.nuspec index 4da3c58780..1b4c7192b8 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.nuspec +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -10,6 +11,12 @@ + + + + + + @@ -23,6 +30,7 @@ $CommonFileElements$ + @@ -30,17 +38,17 @@ - - - - + + + + @@ -154,5 +162,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/README.md b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/README.md new file mode 100644 index 0000000000..74e2184a41 --- /dev/null +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/README.md @@ -0,0 +1,31 @@ +# Microsoft.TestPlatform.TranslationLayer + +The C# SDK for the Visual Studio Test Platform protocol. Use this package to programmatically discover and execute tests from IDEs, editors, or custom tools by communicating with `vstest.console`. + +## Usage + +```xml + +``` + +## Example + +```csharp +var vstestConsolePath = ""; +var consoleWrapper = new VsTestConsoleWrapper(vstestConsolePath); + +consoleWrapper.StartSession(); +consoleWrapper.InitializeExtensions(extensionPaths); + +// Discover tests +consoleWrapper.DiscoverTests(testAssemblies, settings, discoveryHandler); + +// Run tests +consoleWrapper.RunTests(testAssemblies, settings, executionHandler); +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Translation Layer](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0008-TranslationLayer.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs index 9a7cb250f4..01b2992a0c 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleProcessManager.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Text; using System.Threading; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; @@ -53,13 +54,27 @@ internal sealed class VsTestConsoleProcessManager : IProcessManager, IDisposable private readonly bool _isNetCoreRunner; private readonly string? _dotnetExePath; private readonly ManualResetEvent _processExitedEvent = new(false); + private readonly StringBuilder _vstestConsoleError = new(); private Process? _process; + private bool _vstestConsoleStarted; private bool _vstestConsoleExited; private bool _isDisposed; internal IFileHelper FileHelper { get; set; } + /// + public string? ProcessName { get; set; } + + /// + public int? ProcessId { get; set; } + + /// + public string? ErrorOutput => _vstestConsoleError.ToString(); + + /// + public int? ExitCode { get; private set; } = -1; + /// public event EventHandler? ProcessExited; @@ -110,6 +125,11 @@ public void StartProcess(ConsoleParameters consoleParameters) throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, InternalResources.CannotFindConsoleRunner, consoleRunnerPath), consoleRunnerPath); } + ProcessName = null; + ProcessId = null; + ExitCode = null; + _vstestConsoleError.Clear(); + var arguments = string.Join(" ", BuildArguments(consoleParameters)); var info = new ProcessStartInfo(consoleRunnerPath, arguments) { @@ -136,7 +156,7 @@ public void StartProcess(ConsoleParameters consoleParameters) { // Not printing the value on purpose, env variables can contain secrets and we don't need to know the values // most of the time. - EqtTrace.Verbose("VsTestCommandLineWrapper.StartProcess: Setting environment variable: {0}", envVariable.Key); + // EqtTrace.Verbose("VsTestCommandLineWrapper.StartProcess: Setting environment variable: {0}", envVariable.Key); info.EnvironmentVariables[envVariable.Key] = envVariable.Value?.ToString(); } } @@ -145,6 +165,9 @@ public void StartProcess(ConsoleParameters consoleParameters) try { _process = Process.Start(info); + ProcessId = _process!.Id; + ProcessName = _process.ProcessName; + EqtTrace.Info($"VsTestConsoleProcessManager.StartProcess: Started process id:{_process.Id}"); // Not normally needed, but if you run multiple instances of wrapper it helps to also add {Environment.StackTrace} } catch (Win32Exception ex) { @@ -173,9 +196,10 @@ public void StartProcess(ConsoleParameters consoleParameters) public void ShutdownProcess() { // Ideally process should die by itself + EqtTrace.Info($"VsTestConsoleProcessManager.ShutDownProcess : Will terminate vstest.console process: {ProcessId}-{ProcessName}, waiting {Endsessiontimeout} milliseconds for it to exit."); if (!_processExitedEvent.WaitOne(Endsessiontimeout) && IsProcessInitialized()) { - EqtTrace.Info($"VsTestConsoleProcessManager.ShutDownProcess : Terminating vstest.console process after waiting for {Endsessiontimeout} milliseconds."); + EqtTrace.Info($"VsTestConsoleProcessManager.ShutDownProcess : Terminating vstest.console process: {ProcessId}-{ProcessName} after waiting for {Endsessiontimeout} milliseconds."); _vstestConsoleExited = true; if (_process is not null) { @@ -185,6 +209,11 @@ public void ShutdownProcess() _process.Dispose(); _process = null; } + EqtTrace.Info($"VsTestConsoleProcessManager.ShutDownProcess : Terminated vstest.console process: {ProcessId}-{ProcessName}."); + } + else + { + EqtTrace.Verbose($"VsTestConsoleProcessManager.ShutDownProcess : Process: {ProcessId}-{ProcessName} already exited, doing nothing."); } } @@ -209,6 +238,7 @@ private void Process_Exited(object? sender, EventArgs e) { _processExitedEvent.Set(); _vstestConsoleExited = true; + ExitCode = ((Process)sender!).ExitCode; ProcessExited?.Invoke(sender, e); } } @@ -218,6 +248,7 @@ private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) if (e.Data != null) { EqtTrace.Error(e.Data); + _vstestConsoleError.AppendLine(e.Data); } } diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs index e72355cd5b..4c623ed472 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs @@ -1197,7 +1197,7 @@ private void SendMessageAndListenAndReportTestResults( EqtTrace.Error("Aborting Test Run Operation: {0}", exception); eventHandler.HandleLogMessage( TestMessageLevel.Error, - TranslationLayerResources.AbortedTestsRun); + TranslationLayerResources.AbortedTestsRun + " " + exception.ToString()); var completeArgs = new TestRunCompleteEventArgs( null, false, true, exception, null, null, TimeSpan.Zero); eventHandler.HandleTestRunComplete(completeArgs, null, null, null); @@ -1282,7 +1282,7 @@ private async Task SendMessageAndListenAndReportTestResultsAsync( EqtTrace.Error("Aborting Test Run Operation: {0}", exception); eventHandler.HandleLogMessage( TestMessageLevel.Error, - TranslationLayerResources.AbortedTestsRun); + TranslationLayerResources.AbortedTestsRun + " " + exception.ToString()); var completeArgs = new TestRunCompleteEventArgs( null, false, true, exception, null, null, TimeSpan.Zero); eventHandler.HandleTestRunComplete(completeArgs, null, null, null); diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs index e3b99521c7..0a309942e0 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs @@ -135,6 +135,22 @@ internal VsTestConsoleWrapper( _vstestConsoleProcessManager.ProcessExited += (sender, args) => _requestSender.OnProcessExited(); _sessionStarted = false; + + // TODO: this is writing into the same file in integration tests (there is just 1 eqTrace for whole process) + // figure out how to make it useful. The logs helped a bit in debugging, but not by much. + //if (_consoleParameters.TraceLevel == TraceLevel.Verbose && !string.IsNullOrWhiteSpace(_consoleParameters.LogFilePath)) + //{ + // var logFilePath = Path.ChangeExtension( + // _consoleParameters.LogFilePath, + // string.Format( + // CultureInfo.InvariantCulture, + // "translationLayer.{0}_{1}{2}", + // DateTime.Now.ToString("yy-MM-dd_HH-mm-ss_fffff", CultureInfo.CurrentCulture), + // new PlatformEnvironment().GetCurrentManagedThreadId(), + // Path.GetExtension(_consoleParameters.LogFilePath)) + // ); + // EqtTrace.InitializeTrace(logFilePath, PlatformTraceLevel.Verbose); + //} } @@ -635,11 +651,13 @@ public void AbortTestRun() /// public void EndSession() { - EqtTrace.Info("VsTestConsoleWrapper.EndSession: Ending VsTestConsoleWrapper session"); + EqtTrace.Info($"VsTestConsoleWrapper.EndSession: Ending VsTestConsoleWrapper session - process id:{_vstestConsoleProcessManager.ProcessId}"); _requestSender.EndSession(); _requestSender.Close(); + EqtTrace.Info("VsTestConsoleWrapper.EndSession: Ended VsTestConsoleWrapper session"); + // If vstest.console is still hanging around, it should be explicitly killed. _vstestConsoleProcessManager.ShutdownProcess(); @@ -1200,16 +1218,53 @@ private bool WaitForConnection() var timeout = EnvironmentHelper.GetConnectionTimeout(); if (!_requestSender.WaitForRequestHandlerConnection(timeout * 1000)) { - var processName = _processHelper.GetCurrentProcessFileName(); - throw new TransationLayerException( - string.Format( - CultureInfo.CurrentCulture, - CommunicationUtilitiesResources.ConnectionTimeoutErrorMessage, - processName, - CoreUtilitiesConstants.VstestConsoleProcessName, - timeout, - EnvironmentHelper.VstestConnectionTimeout) - ); + var currentProcessName = _processHelper.GetCurrentProcessFileName(); + var childProcessName = _vstestConsoleProcessManager.ProcessName; + var childProcessId = _vstestConsoleProcessManager.ProcessId; + var childProcessExitCode = _vstestConsoleProcessManager.ExitCode; + var childProcessErrorOutput = _vstestConsoleProcessManager.ErrorOutput; + + if (childProcessId == null) + { + // Process failed to start, likely due to antivirus or other startup issues. Recommend checking machine for issues that may prevent process from starting. + throw new TransationLayerException( + string.Format( + CultureInfo.CurrentCulture, + CommunicationUtilitiesResources.ConnectionTimeoutProcessDidNotStartErrorMessage, + currentProcessName, + CoreUtilitiesConstants.VstestConsoleProcessName, + timeout)); + } + else if (childProcessExitCode == null) + { + // Process is still alive but failed to connect within the timeout, likely due to machine slowness. Recommend increasing timeout. + throw new TransationLayerException( + string.Format( + CultureInfo.CurrentCulture, + CommunicationUtilitiesResources.ConnectionTimeoutWithDetailsErrorMessage, + currentProcessName, + CoreUtilitiesConstants.VstestConsoleProcessName, + timeout, + childProcessId, + childProcessName, + EnvironmentHelper.VstestConnectionTimeout)); + } + else + { + // Process started and exited within the timeout, likely due to startup issues or incompatible environment. Recommend checking the error output for more details. + throw new TransationLayerException( + string.Format( + CultureInfo.CurrentCulture, + CommunicationUtilitiesResources.ConnectionTimeoutProcessExitedErrorMessage, + currentProcessName, + CoreUtilitiesConstants.VstestConsoleProcessName, + timeout, + childProcessId, + childProcessName, + childProcessExitCode, + childProcessErrorOutput) + ); + } } _testPlatformEventSource.TranslationLayerInitializeStop(); diff --git a/src/SettingsMigrator/SettingsMigrator.csproj b/src/SettingsMigrator/SettingsMigrator.csproj index 23ea657fd7..e87d54f8dc 100644 --- a/src/SettingsMigrator/SettingsMigrator.csproj +++ b/src/SettingsMigrator/SettingsMigrator.csproj @@ -3,16 +3,19 @@ SettingsMigrator - net7.0;$(NetFrameworkMinimum) + + $(NetFrameworkRunnerTargetFramework) Exe AnyCPU false - true + true - + win7-x64 - + diff --git a/src/datacollector.arm64/datacollector.arm64.csproj b/src/datacollector.arm64/datacollector.arm64.csproj index bb147867ac..c2584b21dc 100644 --- a/src/datacollector.arm64/datacollector.arm64.csproj +++ b/src/datacollector.arm64/datacollector.arm64.csproj @@ -9,16 +9,17 @@ datacollector.arm64 - $(NetCoreAppMinimum) - $(TargetFrameworks);net472;$(NetFrameworkMinimum) - $(TargetFrameworks);$(NetFrameworkMinimum) - $(TargetFrameworks);net7.0;net6.0; - win10-arm64 + $(RunnerTargetFrameworks) + win10-arm64 - AnyCPU + AnyCPU Exe - + false Major false @@ -52,7 +53,7 @@ - + diff --git a/src/Microsoft.TestPlatform.Utilities/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/net10.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Utilities/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/datacollector/PublicAPI/net10.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Utilities/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/net10.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Utilities/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/datacollector/PublicAPI/net10.0/PublicAPI.Unshipped.txt diff --git a/src/Microsoft.TestPlatform.Utilities/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Utilities/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/datacollector/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.Utilities/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.Utilities/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/datacollector/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/datacollector/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/net6.0/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/net6.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/net7.0/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/net7.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net6.0/PublicAPI.Shipped.txt rename to src/datacollector/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net6.0/PublicAPI.Unshipped.txt rename to src/datacollector/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt b/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/datacollector/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 439c6d5fd8..4c0fba4965 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -20,6 +20,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/datacollector/datacollector.csproj b/src/datacollector/datacollector.csproj index 6c7facedc0..94ba8f6058 100644 --- a/src/datacollector/datacollector.csproj +++ b/src/datacollector/datacollector.csproj @@ -9,13 +9,14 @@ datacollector - $(NetCoreAppMinimum) - $(TargetFrameworks);net472;$(NetFrameworkMinimum) - $(TargetFrameworks);$(NetFrameworkMinimum) - $(TargetFrameworks);net7.0;net6.0 - AnyCPU + $(RunnerTargetFrameworks) + AnyCPU Exe - + false Major false @@ -23,7 +24,7 @@ $(MSBuildWarningsAsMessages);MSB3276 - + win7-x64 @@ -39,7 +40,7 @@ - + diff --git a/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.csproj b/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.csproj index 961ffc151c..11f305b215 100644 --- a/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.csproj +++ b/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.csproj @@ -1,6 +1,10 @@  - $(NetFrameworkMinimum) + + $(TestHostMinimumTargetFrameworks);$(ExtensionTargetFrameworks) @@ -19,30 +23,15 @@ - - - - - - - - - - - + - - - - - diff --git a/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.nuspec b/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.nuspec index 530ba92536..c49f6e982f 100644 --- a/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.nuspec +++ b/src/package/Microsoft.CodeCoverage/Microsoft.CodeCoverage.nuspec @@ -2,15 +2,17 @@ $CommonMetadataElements$ + README.md - + $CommonFileElements$ + @@ -18,67 +20,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/package/Microsoft.CodeCoverage/README.md b/src/package/Microsoft.CodeCoverage/README.md new file mode 100644 index 0000000000..e92dbf1d6c --- /dev/null +++ b/src/package/Microsoft.CodeCoverage/README.md @@ -0,0 +1,23 @@ +# Microsoft.CodeCoverage + +Code coverage infrastructure for the Visual Studio Test Platform. This package enables collecting code coverage data from `vstest.console.exe` and `dotnet test`. + +## Usage + +This package is typically referenced indirectly through `Microsoft.NET.Test.Sdk`. For standalone usage: + +```xml + +``` + +Collect code coverage during a test run: + +```sh +dotnet test --collect "Code Coverage" +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Code Coverage for .NET Core](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0021-CodeCoverageForNetCore.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.csproj b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.csproj index 1764ba0ef3..6c3cb6a586 100644 --- a/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.csproj +++ b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.csproj @@ -29,6 +29,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.nuspec b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.nuspec index e95212b77a..c320b05fb2 100644 --- a/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.nuspec +++ b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.nuspec @@ -2,30 +2,53 @@ $CommonMetadataElements$ + README.md - + + + + + + $CommonFileElements$ + - + - - + + + - + + + + + + + + + + + + + + + + diff --git a/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.targets b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.targets new file mode 100644 index 0000000000..d2d47da66e --- /dev/null +++ b/src/package/Microsoft.NET.Test.Sdk/Microsoft.NET.Test.Sdk.targets @@ -0,0 +1,6 @@ + + + + + diff --git a/src/package/Microsoft.NET.Test.Sdk/README.md b/src/package/Microsoft.NET.Test.Sdk/README.md new file mode 100644 index 0000000000..1a4a85524f --- /dev/null +++ b/src/package/Microsoft.NET.Test.Sdk/README.md @@ -0,0 +1,33 @@ +# Microsoft.NET.Test.Sdk + +The MSBuild targets and properties for building .NET test projects. This package is required for any .NET test project to integrate with the Visual Studio Test Platform. + +## Usage + +Add this package to your test project: + +```xml + +``` + +This package works alongside a test framework adapter (e.g., MSTest, xUnit, NUnit) to enable test discovery and execution via `dotnet test` or Visual Studio Test Explorer. + +## Example + +```xml + + + net8.0 + + + + + + +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform SDK](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0005-Test-Platform-SDK.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets b/src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets index 7769237ada..575fd6be00 100644 --- a/src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets +++ b/src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets @@ -15,13 +15,22 @@ - Exe + Exe $(MSBuildThisFileDirectory)Microsoft.NET.Test.Sdk.Program$(DefaultLanguageSourceExtension) + + + false true diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj index b9f8595348..f3ad7825d0 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj @@ -1,12 +1,12 @@ - $(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48 + $(NetSDKTargetFramework);$(TestHostAllTargetFrameworks) Microsoft.TestPlatform.CLI.nuspec - Microsoft.TestPlatform.CLI.sourcebuild.nuspec - Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec + Microsoft.TestPlatform.CLI.sourcebuild.nuspec + Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec true false @@ -23,7 +23,6 @@ - @@ -32,41 +31,60 @@ None $(NoWarn);NU5100 + + $(NoWarn);NU1510 - NU1702 + $(MSBuildWarningsAsMessages);NU1702 - - - - - - + + - + + + + + + + + + + + + + + - - - + + + + - - - + + + @@ -76,18 +94,18 @@ - + - + diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec index e5d37dcb48..4e946dd75e 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -9,412 +10,420 @@ $CommonFileElements$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec index 163cd961fd..ec3767a9cb 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -9,106 +10,73 @@ $CommonFileElements$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec index e53eaba922..3d3555cd06 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.sourcebuild.product.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -9,6 +10,7 @@ $CommonFileElements$ + @@ -32,7 +34,6 @@ - @@ -45,6 +46,8 @@ + + @@ -57,14 +60,19 @@ - - - + + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.CLI/README.md b/src/package/Microsoft.TestPlatform.CLI/README.md new file mode 100644 index 0000000000..0b6f9c0c4a --- /dev/null +++ b/src/package/Microsoft.TestPlatform.CLI/README.md @@ -0,0 +1,18 @@ +# Microsoft.TestPlatform.CLI + +The cross-platform command-line interface for the Visual Studio Test Platform. This package provides `vstest.console.dll` and associated tooling for discovering and executing tests from the command line. + +## Usage + +This package is typically consumed by the .NET SDK and is not directly referenced in most test projects. For direct usage: + +```xml + +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform Architecture](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0001-Test-Platform-Architecture.md) +- [Packaging](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0014-Packaging.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.nuspec b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.nuspec index 0eed862a9a..538a2fc3b1 100644 --- a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.nuspec +++ b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.nuspec @@ -2,6 +2,7 @@ $CommonMetadataElements$ + README.md @@ -9,6 +10,7 @@ $CommonFileElements$ + diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md b/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md new file mode 100644 index 0000000000..93e32842a1 --- /dev/null +++ b/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md @@ -0,0 +1,16 @@ +# Microsoft.TestPlatform.Internal.Uwp + +Internal test platform libraries for the UWP (Universal Windows Platform) test runner. This package provides the test platform binaries needed to run tests in UWP applications. + +> **Note:** This is an internal package. Most test projects should reference `Microsoft.NET.Test.Sdk` instead. + +## Usage + +```xml + +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj index 116098d402..f17f680575 100644 --- a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj +++ b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj @@ -1,6 +1,7 @@ - $(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48;netstandard2.0 + + $(TestHostAllTargetFrameworks);netstandard2.0 @@ -11,11 +12,11 @@ Sometimes NU1702 is not suppressed correctly, so force reducing severity of the warning. See https://github.com/NuGet/Home/issues/9147 --> - NU1702 + $(MSBuildWarningsAsMessages);NU1702 - + true Microsoft.TestPlatform.Portable.nuspec $(OutputPath) @@ -38,18 +39,20 @@ - + - - - - - - - + + + + + + + @@ -57,25 +60,34 @@ + + + + + + - - - - - + + + + + + + + - - - - - - - + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec index ceaecfc3ac..88ed905b1f 100644 --- a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec +++ b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.nuspec @@ -2,55 +2,63 @@ $CommonMetadataElements$ + README.md $CommonFileElements$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -62,6 +70,9 @@ + + + @@ -72,6 +83,8 @@ + + @@ -83,555 +96,563 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.Portable/README.md b/src/package/Microsoft.TestPlatform.Portable/README.md new file mode 100644 index 0000000000..9d408c1670 --- /dev/null +++ b/src/package/Microsoft.TestPlatform.Portable/README.md @@ -0,0 +1,17 @@ +# Microsoft.TestPlatform.Portable + +A portable subset of binaries for the Visual Studio Test Platform (vstest). This package contains the test platform toolset for cross-platform test execution scenarios, including `vstest.console`, test hosts, and data collectors. + +## Usage + +```xml + +``` + + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform Architecture](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0001-Test-Platform-Architecture.md) +- [Packaging](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0014-Packaging.md) +- [License](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj index 356823b26f..085d532b35 100644 --- a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj +++ b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj @@ -1,11 +1,11 @@ - netcoreapp3.1;net462 + $(TestHostMinimumTargetFrameworks) - - true + + true Microsoft.TestPlatform.TestHost.nuspec $(OutputPath) Microsoft.TestPlatform.TestHost @@ -48,7 +48,10 @@ - + + PreserveNewest + + PreserveNewest diff --git a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.nuspec b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.nuspec index 2b676ebbdb..e87b81d083 100644 --- a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.nuspec +++ b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.nuspec @@ -2,9 +2,10 @@ $CommonMetadataElements$ + README.md - + @@ -15,26 +16,27 @@ $CommonFileElements$ + - + - - - + + + - - + + - - + + - - + + - - + + - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.targets b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.targets new file mode 100644 index 0000000000..02bd7df7a8 --- /dev/null +++ b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.targets @@ -0,0 +1,13 @@ + + + + <_MSTestEnableParentProcessQuery Condition="'$(UseUwpTools)'=='true'">false + <_MSTestEnableParentProcessQuery Condition="'$(_MSTestEnableParentProcessQuery)'==''">true + + + + + diff --git a/src/package/Microsoft.TestPlatform.TestHost/README.md b/src/package/Microsoft.TestPlatform.TestHost/README.md new file mode 100644 index 0000000000..fbfd408b87 --- /dev/null +++ b/src/package/Microsoft.TestPlatform.TestHost/README.md @@ -0,0 +1,17 @@ +# Microsoft.TestPlatform.TestHost + +The test host process for the Visual Studio Test Platform. This package hosts the test execution engine and communicates with the test runner to discover and execute tests in the target process. + +## Usage + +This package is typically referenced indirectly through `Microsoft.NET.Test.Sdk`. Direct references are only needed in advanced hosting scenarios. + +```xml + +``` + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform Architecture](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0001-Test-Platform-Architecture.md) +- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj index b886bb0e37..6739984bbd 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj @@ -1,6 +1,7 @@ - + - $(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48 + + $(TestHostAllTargetFrameworks) @@ -10,11 +11,11 @@ Sometimes NU1702 is not suppressed correctly, so force reducing severity of the warning. See https://github.com/NuGet/Home/issues/9147 --> - NU1702;NETSDK1023 + $(MSBuildWarningsAsMessages);NU1702;NETSDK1023 - + true Microsoft.TestPlatform.nuspec $(OutputPath) @@ -24,12 +25,6 @@ This package contains the full set of binaries for the Visual Studio Test Platform (vstest). It provides a modern, cross platform testing engine that powers the testing on .NET Core as well. It integrates with popular test frameworks like MSTest(v1 and v2), xUnit and Nunit with support for extensibility. - - The package supports running Coded UI tests. - While running Coded UI tests, you must ensure that the package version matches the major version of Visual Studio used to build the test binaries. - For example, if your Coded UI test project was built using Visual Studio 2019 (version 16.x), you must use test platform version 16.x. - Coded UI test is deprecated (https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-preview#test-tools) and - Visual Studio 2019 (Test Platform version 16.x) will be the last version with Coded UI test functionality. LICENSE_VS.txt @@ -41,31 +36,46 @@ - + + - - - + - - - - + + + + + + + - + - - - - - + + + + + + + + + @@ -77,39 +87,33 @@ + - - - - + - + - + - - + - - - - + @@ -117,23 +121,19 @@ - - - - diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec index 569b7be7d9..a74915a0b9 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.nuspec @@ -2,125 +2,91 @@ $CommonMetadataElements$ + README.md $CommonFileElements$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -132,6 +98,8 @@ + + @@ -143,6 +111,8 @@ + + @@ -154,384 +124,294 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -542,108 +422,110 @@ - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - - - - - + + + + + + - - - + + + diff --git a/src/package/Microsoft.TestPlatform/README.md b/src/package/Microsoft.TestPlatform/README.md new file mode 100644 index 0000000000..af62594116 --- /dev/null +++ b/src/package/Microsoft.TestPlatform/README.md @@ -0,0 +1,17 @@ +# Microsoft.TestPlatform + +The full set of binaries for the Visual Studio Test Platform (vstest). This package contains the complete test platform including `vstest.console`, test hosts, data collectors, and extensions for both .NET and .NET Framework. + +## Usage + +```xml + +``` + + +## Links + +- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) +- [Test Platform Architecture](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0001-Test-Platform-Architecture.md) +- [Packaging](https://github.com/microsoft/vstest/blob/main/docs/RFCs/0014-Packaging.md) +- [License](https://github.com/microsoft/vstest/blob/main/LICENSE) diff --git a/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/License.rtf b/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/License.rtf index 6785a881eb..c56b303fd6 100644 --- a/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/License.rtf +++ b/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/License.rtf @@ -1,666 +1,666 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff43\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;} -{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;} -{\f11\fbidi \fmodern\fcharset128\fprq1{\*\panose 02020609040205080304}MS Mincho{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}SimSun{\*\falt \'cb\'ce\'cc\'e5};} -{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\f43\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Tahoma{\*\falt ?l?r ???};} -{\f44\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0603020202020204}Trebuchet MS{\*\falt Arial};}{\f46\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@SimSun;}{\f47\fbidi \fmodern\fcharset128\fprq1{\*\panose 02020609040205080304}@MS Mincho;} -{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 00000000000000000000}Cambria;}{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f48\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\f49\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f51\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f52\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f53\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\f54\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f55\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f56\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f68\fbidi \fmodern\fcharset238\fprq1 Courier New CE;} -{\f69\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}{\f71\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f72\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f73\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);} -{\f74\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}{\f75\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f76\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);} -{\f160\fbidi \fmodern\fcharset0\fprq1 MS Mincho Western{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f158\fbidi \fmodern\fcharset238\fprq1 MS Mincho CE{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} -{\f159\fbidi \fmodern\fcharset204\fprq1 MS Mincho Cyr{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f161\fbidi \fmodern\fcharset161\fprq1 MS Mincho Greek{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} -{\f162\fbidi \fmodern\fcharset162\fprq1 MS Mincho Tur{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f165\fbidi \fmodern\fcharset186\fprq1 MS Mincho Baltic{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} -{\f180\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt \'cb\'ce\'cc\'e5};}{\f418\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f419\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f421\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;} -{\f422\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f423\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f424\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\f425\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\f426\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\f478\fbidi \fswiss\fcharset238\fprq2 Tahoma CE{\*\falt ?l?r ???};}{\f479\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr{\*\falt ?l?r ???};} -{\f481\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek{\*\falt ?l?r ???};}{\f482\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur{\*\falt ?l?r ???};}{\f483\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew){\*\falt ?l?r ???};} -{\f484\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic){\*\falt ?l?r ???};}{\f485\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic{\*\falt ?l?r ???};}{\f486\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese){\*\falt ?l?r ???};} -{\f487\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai){\*\falt ?l?r ???};}{\f488\fbidi \fswiss\fcharset238\fprq2 Trebuchet MS CE{\*\falt Arial};}{\f489\fbidi \fswiss\fcharset204\fprq2 Trebuchet MS Cyr{\*\falt Arial};} -{\f491\fbidi \fswiss\fcharset161\fprq2 Trebuchet MS Greek{\*\falt Arial};}{\f492\fbidi \fswiss\fcharset162\fprq2 Trebuchet MS Tur{\*\falt Arial};}{\f495\fbidi \fswiss\fcharset186\fprq2 Trebuchet MS Baltic{\*\falt Arial};} -{\f510\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\f520\fbidi \fmodern\fcharset0\fprq1 @MS Mincho Western;}{\f518\fbidi \fmodern\fcharset238\fprq1 @MS Mincho CE;}{\f519\fbidi \fmodern\fcharset204\fprq1 @MS Mincho Cyr;} -{\f521\fbidi \fmodern\fcharset161\fprq1 @MS Mincho Greek;}{\f522\fbidi \fmodern\fcharset162\fprq1 @MS Mincho Tur;}{\f525\fbidi \fmodern\fcharset186\fprq1 @MS Mincho Baltic;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;} -{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}{\fhimajor\f31536\fbidi \froman\fcharset163\fprq2 Cambria (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} -{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\cfollowedhyperlink\ctint255\cshade255\red128\green0\blue128;\red230\green230\blue230;}{\*\defchp -\f31506\fs22 }{\*\defpap \ql \fi-360\li360\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 -\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \sautoupd \sqformat \spriority0 \styrsid4934124 Normal;}{\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext1 \slink15 \sqformat \styrsid4934124 heading 1;}{\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0 \rtlch\fcs1 -\ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext2 \slink16 \sunhideused \sqformat \styrsid4934124 heading 2;}{\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar -\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext3 \slink17 \sunhideused \sqformat \styrsid4934124 heading 3;}{\s4\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 -\af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext4 \slink18 \sunhideused \sqformat \styrsid4934124 heading 4;}{\s5\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar -\tx1792\jclisttab\tx2155\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl4\outlinelevel4\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext5 \slink19 \sunhideused \sqformat \styrsid4934124 heading 5;}{\s6\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl5\outlinelevel5\adjustright\rin0\lin2149\itap0 \rtlch\fcs1 -\af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext6 \slink20 \sunhideused \sqformat \styrsid4934124 heading 6;}{\s7\ql \fi-357\li2506\ri0\sb120\sa120\widctlpar -\jclisttab\tx2509\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl6\outlinelevel6\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext7 \slink21 \sunhideused \sqformat \styrsid4934124 heading 7;}{\s8\ql \fi-357\li2863\ri0\sb120\sa120\widctlpar\jclisttab\tx2866\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl7\outlinelevel7\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 -\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext8 \slink22 \sunhideused \sqformat \styrsid4934124 heading 8;}{\s9\ql \fi-358\li3221\ri0\sb120\sa120\widctlpar -\jclisttab\tx3223\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl8\outlinelevel8\adjustright\rin0\lin3221\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext9 \slink23 \sunhideused \sqformat \styrsid4934124 heading 9;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv -\ql \fi-360\li360\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused -Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 \sbasedon10 \slink1 \slocked \styrsid4934124 Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 -\sbasedon10 \slink2 \slocked \ssemihidden \styrsid4934124 Heading 2 Char;}{\*\cs17 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink3 \slocked \ssemihidden \styrsid4934124 Heading 3 Char;}{\*\cs18 \additive \rtlch\fcs1 -\af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink4 \slocked \ssemihidden \styrsid4934124 Heading 4 Char;}{\*\cs19 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink5 \slocked \ssemihidden \styrsid4934124 Heading 5 Char;}{\* -\cs20 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink6 \slocked \ssemihidden \styrsid4934124 Heading 6 Char;}{\*\cs21 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 -\sbasedon10 \slink7 \slocked \ssemihidden \styrsid4934124 Heading 7 Char;}{\*\cs22 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink8 \slocked \ssemihidden \styrsid4934124 Heading 8 Char;}{\*\cs23 -\additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink9 \slocked \ssemihidden \styrsid4934124 Heading 9 Char;}{\*\cs24 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \f0\ul\cf2 \sbasedon10 \sunhideused \styrsid4934124 -Hyperlink;}{\s25\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext25 \styrsid4934124 Body 1;}{\s26\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext26 \styrsid4934124 Bullet 2;}{\s27\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar -\jclisttab\tx1080\wrapdefault\aspalpha\aspnum\faauto\ls3\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext27 \slink48 \styrsid4934124 Bullet 3;}{\s28\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 -\b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \styrsid4934124 Heading EULA;}{\s29\ql \li0\ri0\sb120\sa120\widctlpar\brdrb\brdrs\brdrw10\brsp20 -\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 \b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \styrsid4934124 -Heading Software Title;}{\s30\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 -\b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext30 \styrsid4934124 Preamble;}{\s31\ql \li0\ri0\sb120\sa120\widctlpar\brdrt\brdrs\brdrw10\brsp20 -\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon30 \snext31 \styrsid4934124 -Preamble Border Above;}{\s32\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\snext0 \styrsid4934124 Body 0 Bold;}{\s33\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \styrsid4934124 Body 0;}{\s34\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs16\alang1025 \ltrch\fcs0 -\fs16\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext34 \slink35 \ssemihidden \sunhideused \styrsid11950712 Balloon Text;}{\*\cs35 \additive \rtlch\fcs1 \af43\afs16 \ltrch\fcs0 -\fs16\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink34 \slocked \ssemihidden \styrsid11950712 Balloon Text Char;}{\s36\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin357\itap0 -\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext36 \styrsid11950712 Bullet 1;}{\s37\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar -\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext37 \styrsid11950712 Bullet 4;}{\s38\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar\jclisttab\tx1795\wrapdefault\aspalpha\aspnum\faauto\ls6\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext38 \styrsid11950712 Bullet 5;}{\s39\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar -\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls7\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon3 \snext39 \slink50 \styrsid11950712 Heading 3 Bold;}{\s40\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon37 \snext40 \styrsid11950712 Bullet 4 Underline;}{\*\cs41 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 -\sbasedon10 \styrsid11950712 Body 2 Char;}{\*\cs42 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 \styrsid11950712 Body 3 Char;}{\*\cs43 \additive \rtlch\fcs1 \af0\afs16 \ltrch\fcs0 \fs16 -\sbasedon10 \ssemihidden \sunhideused \styrsid8850722 annotation reference;}{\s44\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs20\alang1025 \ltrch\fcs0 -\fs20\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext44 \slink45 \sunhideused \styrsid8850722 annotation text;}{\*\cs45 \additive \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\f43\hich\af43\dbch\af11 -\sbasedon10 \slink44 \slocked \styrsid8850722 Comment Text Char;}{\s46\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs20\alang1025 \ltrch\fcs0 -\b\fs20\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon44 \snext44 \slink47 \ssemihidden \sunhideused \styrsid8850722 annotation subject;}{\*\cs47 \additive \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 -\b\fs20\loch\f43\hich\af43\dbch\af11 \sbasedon45 \slink46 \slocked \ssemihidden \styrsid8850722 Comment Subject Char;}{\*\cs48 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink27 \slocked \styrsid2434661 -Bullet 3 Char1;}{\s49\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon37 \snext49 \spriority0 \styrsid3941498 Bullet 4 Underlined;}{\*\cs50 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\fs19\loch\f43\hich\af43\dbch\af11 -\sbasedon10 \slink39 \slocked \styrsid3941498 Heading 3 Bold Char;}{\s51\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs21\alang1025 \ltrch\fcs0 -\f37\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext51 \slink52 \ssemihidden \sunhideused \styrsid3941498 Plain Text;}{\*\cs52 \additive \rtlch\fcs1 \af0\afs21 \ltrch\fcs0 \f37\fs21 -\sbasedon10 \slink51 \slocked \ssemihidden \styrsid3941498 Plain Text Char;}{\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext53 \slink54 \sunhideused \styrsid11152386 header;}{\*\cs54 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 -\sbasedon10 \slink53 \slocked \styrsid11152386 Header Char;}{\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext55 \slink56 \sunhideused \styrsid11152386 footer;}{\*\cs56 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 -\sbasedon10 \slink55 \slocked \styrsid11152386 Footer Char;}{\*\cs57 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \ssemihidden \sunhideused \styrsid13192943 FollowedHyperlink;}{\*\cs58 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 -\cf15\chshdng0\chcfpat0\chcbpat20 \sbasedon10 \ssemihidden \sunhideused \styrsid12217836 Unresolved Mention;}}{\*\listtable{\list\listtemplateid-899113366{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\cf0\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 -\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 -\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 -\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 -\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 -\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 -\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid113718381}{\list\listtemplateid1122370636\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-357\li720\jclisttab\tx723\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1083\jclisttab\tx1083\lin1083 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li1803\jclisttab\tx1803\lin1803 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2523\jclisttab\tx2523\lin2523 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3243\jclisttab\tx3243\lin3243 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li3963\jclisttab\tx3963\lin3963 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li4683\jclisttab\tx4683\lin4683 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5403\jclisttab\tx5403\lin5403 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6123\jclisttab\tx6123\lin6123 }{\listname ;}\listid152650329}{\list\listtemplateid-355573436{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat2 -\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li2247\jclisttab\tx2610\lin2247 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 -\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 -\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 -\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 -\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 -\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 -\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid398096909}{\list\listtemplateid1928476992{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s39\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 } -{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 -\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 -\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 -\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 -\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 -\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid398796681}{\list\listtemplateid789093748\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid-317712510\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s26\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid477573462}{\list\listtemplateid830884688\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3957\lin3957 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4677\lin4677 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5397\lin5397 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li6117\lin6117 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6837\lin6837 }{\listname ;}\listid545946042}{\list\listtemplateid-603941590{\listlevel\levelnfc0\levelnfcn0 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4 -\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li-1797\jclisttab\tx-1797\lin-1797 } -{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-357\li-10173 -\jclisttab\tx-9810\lin-10173 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\strike0\f3\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 -\fi-358\li-10265\jclisttab\tx-10263\lin-10265 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 -\b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \fi-357\li-9908\jclisttab\tx-9545\lin-9908 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers -\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-9551\jclisttab\tx-9548\lin-9551 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-9194\jclisttab\tx-9191\lin-9194 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-8837\jclisttab\tx-8834\lin-8837 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li-8479\jclisttab\tx-8477\lin-8479 }{\listname ;}\listid562301967}{\list\listtemplateid-170633512 -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\cf0\fbias0\hres0\chhres0 \s1\fi-357\li357 -\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s2 -\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 -\b\i0\f43\fs20\fbias0\hres0\chhres0 \s3\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 -\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \s4\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \s5\fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s6\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s7\fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel -\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s8\fi-357\li2863\jclisttab\tx2866\lin2863 -}{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s9\fi-358\li3221 -\jclisttab\tx3223\lin3221 }{\listname ;}\listid752163927}{\list\listtemplateid1725578678{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 -\ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\cf0\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;} -\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel -\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 -}{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863 -\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 -\fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid1107626792}{\list\listtemplateid-41362566\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s37\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;} -\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160 -\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel -\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1559511898}{\list\listtemplateid-743794326\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\leveltemplateid2033377338\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s27\fi-357\li1077\jclisttab\tx1080\lin1077 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1567649130}{\list\listtemplateid419070574\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4680\lin4680 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers -;}\f2\fbias0\hres0\chhres0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li6840\lin6840 }{\listname ;}\listid1589268858}{\list\listtemplateid1363474438\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid-1175557160\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s38\fi-357\li1792\jclisttab\tx1795\lin1792 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 -\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1848404271}{\list\listtemplateid-761117952\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2157\lin2157 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4317\lin4317 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5037\lin5037 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5757\lin5757 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6477\lin6477 }{\listname ;}\listid1870291363}{\list\listtemplateid1186249844\listhybrid{\listlevel\levelnfc23 -\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid1637229796\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s36\fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc23\levelnfcn23 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid2054619191}{\list\listtemplateid-1344757370{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 -\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 -\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 -\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 -\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 -\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 -\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid2057971432}{\list\listtemplateid-569628034{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fbias0\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;} -\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2157\lin2157 } -{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listname ;}\listid2106000387}}{\*\listoverridetable{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1} -{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat -\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls1}{\listoverride\listid477573462\listoverridecount0\ls2}{\listoverride\listid1567649130\listoverridecount0\ls3}{\listoverride\listid2054619191 -\listoverridecount0\ls4}{\listoverride\listid1559511898\listoverridecount0\ls5}{\listoverride\listid1848404271\listoverridecount0\ls6}{\listoverride\listid398796681\listoverridecount0\ls7}{\listoverride\listid545946042\listoverridecount0\ls8} -{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel -\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls9}{\listoverride\listid1870291363 -\listoverridecount0\ls10}{\listoverride\listid152650329\listoverridecount0\ls11}{\listoverride\listid1589268858\listoverridecount0\ls12}{\listoverride\listid2057971432\listoverridecount0\ls13}{\listoverride\listid398096909\listoverridecount0\ls14} -{\listoverride\listid752163927\listoverridecount0\ls15}{\listoverride\listid1107626792\listoverridecount0\ls16}{\listoverride\listid113718381\listoverridecount0\ls17}{\listoverride\listid2106000387\listoverridecount0\ls18}{\listoverride\listid398796681 -\listoverridecount0\ls19}{\listoverride\listid562301967\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat0} -{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls20} -{\listoverride\listid562301967\listoverridecount0\ls21}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel -\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat -\levelstartat1}\ls22}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1} -{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls23} -{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel -\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls24}{\listoverride\listid752163927 -\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel -\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls25}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat -\levelstartat3}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel -\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls26}{\listoverride\listid752163927\listoverridecount0\ls27}{\listoverride\listid752163927\listoverridecount0\ls28} -{\listoverride\listid752163927\listoverridecount0\ls29}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid79668\rsid150779\rsid211660\rsid213676\rsid263352\rsid270541 -\rsid342430\rsid483838\rsid489647\rsid528777\rsid554910\rsid608234\rsid686391\rsid736743\rsid792304\rsid922358\rsid947296\rsid999719\rsid1145719\rsid1335391\rsid1527700\rsid1714580\rsid1799620\rsid2040850\rsid2062659\rsid2322952\rsid2365705\rsid2434661 -\rsid2633486\rsid2635842\rsid2695079\rsid2708164\rsid2902063\rsid2965976\rsid2973522\rsid3041209\rsid3042054\rsid3171745\rsid3285590\rsid3355994\rsid3418540\rsid3571087\rsid3611845\rsid3623294\rsid3634687\rsid3636551\rsid3875660\rsid3882158\rsid3882522 -\rsid3882949\rsid3941498\rsid4080070\rsid4149814\rsid4402402\rsid4409825\rsid4536802\rsid4537652\rsid4609004\rsid4611858\rsid4731914\rsid4742223\rsid4748609\rsid4801980\rsid4805534\rsid4805706\rsid4858977\rsid4868258\rsid4929965\rsid4934124\rsid5113462 -\rsid5262441\rsid5309509\rsid5318439\rsid5465657\rsid5467606\rsid5471954\rsid5720387\rsid5780125\rsid6124814\rsid6171721\rsid6292707\rsid6318271\rsid6364904\rsid6424248\rsid6496414\rsid6561381\rsid6584761\rsid6620178\rsid6643866\rsid6644215\rsid6755756 -\rsid6761489\rsid6828031\rsid6833860\rsid7040710\rsid7080991\rsid7091446\rsid7099326\rsid7109146\rsid7150192\rsid7344474\rsid7420369\rsid7432529\rsid7503579\rsid7698999\rsid7756319\rsid7879410\rsid8007569\rsid8205106\rsid8332882\rsid8334492\rsid8416259 -\rsid8455816\rsid8460809\rsid8528894\rsid8586851\rsid8662808\rsid8729106\rsid8850722\rsid8921755\rsid8979707\rsid9004944\rsid9066668\rsid9072635\rsid9135771\rsid9176743\rsid9306427\rsid9380011\rsid9399500\rsid9448986\rsid9465849\rsid9516204\rsid9518548 -\rsid9577151\rsid9584906\rsid9709666\rsid9722926\rsid9728818\rsid9765890\rsid9782115\rsid9835407\rsid9860928\rsid9902756\rsid10428435\rsid10576842\rsid10688326\rsid10956334\rsid10962326\rsid11143314\rsid11152386\rsid11207705\rsid11303858\rsid11408012 -\rsid11423848\rsid11496807\rsid11541309\rsid11626503\rsid11677882\rsid11690930\rsid11760915\rsid11930791\rsid11950712\rsid12217836\rsid12536975\rsid12545879\rsid12584315\rsid12605359\rsid12799626\rsid12868905\rsid12992444\rsid13004280\rsid13056010 -\rsid13066823\rsid13192943\rsid13198603\rsid13378284\rsid13456345\rsid13513072\rsid13662583\rsid13776901\rsid13967657\rsid14108197\rsid14179805\rsid14293912\rsid14373468\rsid14418632\rsid14426867\rsid14435085\rsid14507627\rsid14684443\rsid14685080 -\rsid14707821\rsid14712272\rsid14898254\rsid14958727\rsid15158534\rsid15278441\rsid15287965\rsid15364209\rsid15405862\rsid15494051\rsid15539022\rsid15602361\rsid15602734\rsid15749471\rsid15804309\rsid15809401\rsid15870741\rsid15949319\rsid16020560 -\rsid16065250\rsid16073823\rsid16084478\rsid16334972\rsid16346709\rsid16388644\rsid16405449\rsid16537650\rsid16544777\rsid16653828\rsid16715114}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440 -\mintLim0\mnaryLim1}{\info{\creatim\yr2018\mo5\dy23\hr14\min17}{\revtim\yr2018\mo5\dy23\hr14\min17}{\version1}{\edmins0}{\nofpages3}{\nofwords1423}{\nofchars8113}{\nofcharsws9517}{\vern59}}{\*\userprops {\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb859 -95028c_Enabled}\proptype30{\staticval True}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SiteId}\proptype30{\staticval 72f988bf-86f1-41af-91ab-2d7cd011db47}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Owner}\proptype30 -{\staticval jagarg@microsoft.com}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SetDate}\proptype30{\staticval 2018-05-23T08:47:46.8790302Z}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Name}\proptype30{\staticval General} -{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Application}\proptype30{\staticval Microsoft Azure Information Protection}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Extended_MSFT_Method}\proptype30{\staticval Automatic} -{\propname Sensitivity}\proptype30{\staticval General}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen -\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 -\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\rempersonalinfo\allowfieldendsel -\wrppunct\asianbrkrule\rsidroot4934124\newtblstyruls\nogrowautofit\remdttm\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal -\nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 -\ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsep -\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsepc -\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsep -\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsepc -\par }}\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\headerl \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 -\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\headerr \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\footerl \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\footerr \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\headerf \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\footerf \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 -\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}} -{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8 -\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar -\s28\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 -\b\fs28\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 MICROSOFT SOFTWARE LICENSE TERMS -\par }\pard\plain \ltrpar\s29\ql \li0\ri0\sb120\sa120\nowidctlpar\brdrb\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 -\b\fs28\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 MICROSOFT }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid2973522 \hich\af43\dbch\af13\loch\f43 VISUAL STUDIO }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid3042054 \hich\af43\dbch\af13\loch\f43 TEST PLATFORM}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid4934124\charrsid342430 -\par }\pard\plain \ltrpar\s30\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 -\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid342430 \hich\af43\dbch\af13\loch\f43 -These license terms are an agreement between Microsoft Corporation (or based on where you live, one of }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 its -\hich\af43\dbch\af13\loch\f43 affiliates) and you. They apply to the software named above. The terms also apply to any Microsoft}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 - services or updates for the software, except to the extent those have }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 additional}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 -\b0\fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 terms.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 -\par }\pard\plain \ltrpar\s31\ql \li0\ri0\sb120\sa120\nowidctlpar\brdrt\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 -\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -If you comply with these license terms, you have the rights below. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 1.\tab}}\pard\plain \ltrpar\s1\ql \fi-360\li360\ri0\sb120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid15287965 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 -\ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 IN\hich\af43\dbch\af13\loch\f43 STALLATION AND USE RIGHTS. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4731914 \hich\af43\dbch\af13\loch\f43 -You may install and use any number of copies of the software.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid14179805\charrsid10576842 \hich\af43\dbch\af0\loch\f43 2.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid5318439 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid14179805\charrsid10576842 T}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\insrsid13378284\charrsid10576842 ERMS FOR SPECIFIC}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid14179805\charrsid10576842 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid15405862\charrsid10576842 COMPONENTS. -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\loch\af43\hich\af43\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls1\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid8332882 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \fs20\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 Third Party Components.\~ }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 -The software may include third party components with separate legal notices or governed by other agreements, as may be described in the ThirdPartyNotices file(s) accompanying the software.\~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\b0\fs20\dbch\af11\insrsid8332882\charrsid8332882 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9066668\charrsid8332882 \hich\af43\dbch\af0\loch\f43 3.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls1\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid8332882 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 -\ltrch\fcs0 \fs20\insrsid9066668\charrsid8332882 DATA. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid2708164\charrsid8332882 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid2708164\charrsid10576842 \hich\af43\dbch\af0\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid2708164 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \fs20\insrsid2708164\charrsid10576842 Data Collection. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 The software may collect information abou -t you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentati -on. There are also s}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid9066668\charrsid10576842 ome features in the software that may enable you }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid16020560\charrsid10576842 and Microsoft }{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid9066668\charrsid10576842 to collect data from users of your applications.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 If you use these features}{\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10688326\charrsid10576842 ,}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 you must comply with applicable law, including providing appropriate notices to users of your applications}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid6761489\charrsid10576842 together with }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid608234\charrsid10576842 a copy of }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\b0\fs20\insrsid6761489\charrsid10576842 Microsoft\rquote s privacy statement}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 . }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid6761489\charrsid10576842 -Our privacy statement is located}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 at}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid3171745\charrsid10576842 }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab0\af43\afs20 -\ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid270541 HYPERLINK "https://go.microsoft.com/fwlink/?LinkID=824704" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid12217836\charrsid10576842 -https://go.microsoft.com/fwlink/?LinkID=824704}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 . }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\b0\fs20\insrsid7109146\charrsid10576842 You can learn more about data collection and use in the help documentation and our privacy statement. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 -Your use of the software operates as your consent to these practices. -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid213676\charrsid10576842 \hich\af43\dbch\af0\loch\f43 b.\tab}}\pard \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid213676 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid213676\charrsid10576842 Processing of Personal Data. }{\rtlch\fcs1 \ab0\af43\afs20 -\ltrch\fcs0 \b0\fs20\insrsid213676\charrsid10576842 To the extent Microsoft is a processor or subprocessor -of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at }{\field\fldedit{\*\fldinst { -\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid270541 HYPERLINK "http://go.microsoft.com/?linkid=9840733" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid12217836\charrsid10576842 -http://go.microsoft.com/?linkid=9840733}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid213676\charrsid10576842 .}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\insrsid213676\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 4.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid15804309 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 -\ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Scope of License}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 -\b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 - The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more right\hich\af43\dbch\af13\loch\f43 -s despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. }{\rtlch\fcs1 \ab0\af43\afs20 -\ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 For more information, see }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\dbch\af13\insrsid270541 -\hich\af43\dbch\af13\loch\f43 HYPER\hich\af43\dbch\af13\loch\f43 LINK "http://www.microsoft.com/licensing/userights" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\dbch\af13\insrsid554910\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 www.microsoft.com/licensing/userights}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 . }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 You may not}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 - -\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid4934124\charrsid342430 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s26\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid4934124 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid342430 \hich\af43\dbch\af13\loch\f43 work around any technical limitations in the software}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid4934124\charrsid342430 -\hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid342430 -\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid4934124\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 reverse engineer, decompile or disassemble the software, }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -or attempt to do so, }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 except and only to the extent }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid554910\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 required by third party licensing terms governing use of certain open-source components that may be included with the software}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid4934124\charrsid10576842 -\hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}\pard \ltrpar\s26\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid2434661 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -remove, minimize, block or modify any notices of Microsoft or its suppliers in the software; -\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}\hich\af43\dbch\af13\loch\f43 use \hich\af43\dbch\af13\loch\f43 -the software in any way that is against the law; or -\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}\hich\af43\dbch\af13\loch\f43 share, publish}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 , rent, or lease}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 - the software, or provide the software as a stand-alone }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid5780125 \hich\af43\dbch\af13\loch\f43 offering}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 for others to use. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 5.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid13594873 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 -\ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Export Restrictions}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 -\b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -You must comply with all domestic and international export laws and r\hich\af43\dbch\af13\loch\f43 -egulations that apply to the software, which include restrictions on destinations, end users and end use. For further information on export restrictions, visit (aka.ms/exporting).}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid10956334\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 6.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 SUPPORT SERVICES.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \ab0\af43\afs20 -\ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Because this software is \'93\loch\f43 \hich\f43 as is,\'94\loch\f43 we may not provide supp\hich\af43\dbch\af13\loch\f43 ort services for it.}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 7.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid4934124 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Entire Agreement.} -{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 - This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 8.\tab}}\pard \ltrpar\s1\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid528777 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Applicable Law}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid528777\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\b0\fs20\insrsid528777\charrsid10576842 If you acquired the s -oftware in the United States, Washington law applies to interpretation of and claims for breach of this agreement, and the laws of the state where you live apply to all other claims. If you acquired the software in any other country, its laws apply.}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid528777\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 9.\tab}}\pard \ltrpar\s1\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid11541309 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 CONSUMER RIGHTS; REGIONAL VARIATIONS. }{\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 -This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state or country. Separate and apart from your relationship with Microsoft, you may also have rights - with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or ma -ndatory country law applies, then the following provisions apply to you: -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 a.\tab}}\pard\plain \ltrpar -\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls18\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 -\b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 Australia. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 -You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights. -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 b.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 Canada. }{\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 If you acquired this software in -Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninsta -lling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid11626503 \hich\af43\dbch\af0\loch\f43 c.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid11626503 Germany and Austria}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid11626503 . -\par }\pard\plain \ltrpar\ql \li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 (i)}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\insrsid10956334\charrsid10576842 \tab }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 Warranty}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 -\hich\af43\dbch\af11\loch\f43 . The properly licensed software will perform substantially as described in any Microsoft \hich\af43\dbch\af11\loch\f43 -materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software. -\par }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 (ii)}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 \tab }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 Limitation of Liability}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 -. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as wel\hich\af43\dbch\af11\loch\f43 l as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law. -\par }\pard\plain \ltrpar\s1\ql \li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 -\b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 -Subject to the foregoing clause (ii), Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obli -gations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other - cases of slight negligence, Microsoft will not be liable for slight negligence. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 10.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid4934124 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -Disclaimer of Warranty.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 \hich\f43 THE SOFTWARE IS LICENSED \'93\loch\f43 \hich\f43 AS-IS.\'94\loch\f43 YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. TO THE EXTENT PERMITTE -\hich\af43\dbch\af13\loch\f43 D UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 11.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -Limitation on and Exclusion of Damages. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid12584315\charrsid10576842 YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP T -O U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. -\par }\pard\plain \ltrpar\s25\ql \li357\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0\pararsid12584315 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, breach of warranty, guarantee or condition, strict liabi -\hich\af43\dbch\af13\loch\f43 l\hich\af43\dbch\af13\loch\f43 ity, negligence, or other tort to the extent permitted by applicable law. -\par }\pard\plain \ltrpar\ql \li360\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0\pararsid12584315 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not \hich\af43\dbch\af13\loch\f43 -allow the exclusion or limitation of incidental, consequential or other damages. -\par }\pard \ltrpar\ql \li0\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin0\itap0\pararsid12584315 {\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French. -\par \hich\af43\dbch\af13\loch\f43 \hich\f43 Remarque : Ce logiciel \'e9\loch\f43 \hich\f43 tant distribu\'e9\hich\af43\dbch\af13\loch\f43 \hich\f43 au Qu\'e9\loch\f43 \hich\f43 -bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7\loch\f43 ais. -\par \hich\af43\dbch\af13\loch\f43 \hich\f43 EXON\'c9\loch\f43 RATION DE GARANTIE.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Le logiciel vis\'e9\loch\f43 \hich\f43 par une licence est offert \'ab\loch\f43 \hich\f43 tel quel \'bb\loch\f43 . Toute utilisation de ce logici}{\rtlch\fcs1 -\af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid342430 \hich\af43\dbch\af13\loch\f43 \hich\f43 el est \'e0\loch\f43 \hich\f43 votre seule risque et p\'e9\loch\f43 ril. Microsoft\hich\af43\dbch\af13\loch\f43 n\hich\f43 \rquote \loch\f43 -\hich\f43 accorde aucune autre garantie expresse. Vous pouvez b\'e9\loch\f43 \hich\f43 n\'e9\loch\f43 -ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites \hich\af43\dbch\af13\loch\f43 d -\hich\af43\dbch\af13\loch\f43 \hich\f43 e qualit\'e9\loch\f43 marchande, d\hich\f43 \rquote \loch\f43 \hich\f43 ad\'e9\loch\f43 \hich\f43 quation \'e0\loch\f43 un usage particulier et d\hich\f43 \rquote \loch\f43 \hich\f43 absence de contrefa\'e7 -\loch\f43 on sont exclues.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 -\par }{\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 LIMITATION DES DOMMAGES-INT\'c9\loch\f43 \hich\f43 R\'ca\loch\f43 \hich\f43 TS ET EXCLUSION DE RESPONSABILIT\'c9\loch\f43 - POUR LES DOMMAGES.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 -Vous pouvez}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 -\hich\af43\dbch\af13\loch\f43 obtenir de Microsoft et de ses fournisseurs une indemnisation \hich\af43\dbch\af13\loch\f43 \hich\f43 en cas de dommages directs uniquement \'e0\loch\f43 \hich\f43 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9\loch\f43 -\hich\f43 tendre \'e0\loch\f43 \hich\f43 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9\loch\f43 \hich\f43 ciaux, indirects ou accessoires et pertes de b\'e9}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 -\fs20\dbch\af13\insrsid12584315\charrsid342430 \hich\af43\dbch\af13\loch\f43 \hich\f43 n\'e9\loch\f43 fices. -\par \hich\af43\dbch\af13\loch\f43 Cette limitation concerne: -\par }\pard \ltrpar\ql \fi-363\li363\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin363\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \loch\af43\dbch\af13\hich\f43 \'b7\~\~\~\~\tab -\loch\f43 tout \hich\af43\dbch\af13\loch\f43 \hich\f43 ce qui est reli\'e9\loch\f43 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et -\par \loch\af43\dbch\af13\hich\f43 \'b7\~\~\~\~\~\loch\f43 \hich\f43 les r\'e9\loch\f43 \hich\f43 clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9\loch\f43 stric\hich\af43\dbch\af13\loch\f43 \hich\f43 te, de n\'e9 -\loch\f43 gligence ou d\hich\f43 \rquote \loch\f43 \hich\f43 une autre faute dans la limite autoris\'e9\loch\f43 e par la loi en vigueur. -\par }\pard \ltrpar\ql \li0\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin0\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Elle s\hich\f43 \rquote -\loch\f43 \hich\f43 applique \'e9\loch\f43 \hich\f43 galement, m\'ea\loch\f43 \hich\f43 me si Microsoft connaissait ou devrait conna\'ee\loch\f43 tre l\hich\f43 \rquote \'e9\loch\f43 \hich\f43 ventualit\'e9\loch\f43 d\hich\f43 \rquote \loch\f43 -un tel dommage. Si votre pays n\hich\f43 \rquote \loch\f43 autorise pas l\hich\f43 \rquote \loch\f43 exclusion ou la limitation d\hich\af43\dbch\af13\loch\f43 \hich\f43 e responsabilit\'e9\loch\f43 - pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\hich\f43 \rquote \loch\f43 exclusion ci-dessus ne s\hich\f43 \rquote \loch\f43 \hich\f43 appliquera pas \'e0\loch\f43 \hich\f43 votre \'e9 -\loch\f43 gard. -\par }{\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 EFFET JURIDIQUE.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{ -\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Le pr\'e9\loch\f43 \hich\f43 sent contrat d\'e9\loch\f43 crit certains droits juridiques. Vous\hich\af43\dbch\af13\loch\f43 - pourriez avoir d\hich\f43 \rquote \loch\f43 \hich\f43 autres droits pr\'e9\loch\f43 \hich\f43 vus par les lois de votre pays. Le pr\'e9\loch\f43 \hich\f43 sent contrat ne modifie pas les droits que vous conf\'e8\loch\f43 -rent les lois de votre pays si celles-ci ne le permettent pas.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid342430 -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid9860928\charrsid342430 -\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a -9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad -5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 -b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 -0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 -a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f -c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 -0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 -a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b -4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b -4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f -7468656d65312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87 -615b8116d8a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad -79482a9c0498f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b -5d8a314d3c94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab -999fb7b4717509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9 -699640f6719e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd586 -8b37a088d1e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d6 -0cf03ac1a5193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f -9e7ef3f2d117d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be -15c308d3f28acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a9979 -3849c26ae66252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d -32a423279a668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2a -f074481847bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86 -e877f0034e16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb -44f95d843b5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a -6409fb44d08741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c -3d9058edf2c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db0256 -5e85f3b9660d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276 -b9f7dec44b7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8 -c33585b5fb9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e -51440ca2e0088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95 -b21be5ceaf8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff -6dce591a26ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec6 -9ffb9e65d028d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239 -b75a5bb1e6345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a449 -59d366ad93b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e8 -2db8df9f30254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468 -656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4 -350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d2624 -52282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe5141 -73d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000 -0000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000 -000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019 -0200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b00001600000000 -000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027 -00000000000000000000000000a00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d0100009b0a00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 4; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 7; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority59 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing; -\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1; -\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading; -\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph; -\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1; -\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1; -\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2; -\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2; -\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3; -\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3; -\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3; -\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4; -\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; -\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; -\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; -\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; -\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; -\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; -\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; -\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; -\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; -\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; -\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; -\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 -4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 -d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e500000000000000000000000020cc -88b972f2d301feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 -000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff43\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;} +{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;} +{\f11\fbidi \fmodern\fcharset128\fprq1{\*\panose 02020609040205080304}MS Mincho{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}SimSun{\*\falt \'cb\'ce\'cc\'e5};} +{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\f43\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Tahoma{\*\falt ?l?r ???};} +{\f44\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0603020202020204}Trebuchet MS{\*\falt Arial};}{\f46\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}@SimSun;}{\f47\fbidi \fmodern\fcharset128\fprq1{\*\panose 02020609040205080304}@MS Mincho;} +{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 00000000000000000000}Cambria;}{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f48\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\f49\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f51\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f52\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f53\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f54\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f55\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f56\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f68\fbidi \fmodern\fcharset238\fprq1 Courier New CE;} +{\f69\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;}{\f71\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f72\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f73\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);} +{\f74\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);}{\f75\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f76\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);} +{\f160\fbidi \fmodern\fcharset0\fprq1 MS Mincho Western{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f158\fbidi \fmodern\fcharset238\fprq1 MS Mincho CE{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} +{\f159\fbidi \fmodern\fcharset204\fprq1 MS Mincho Cyr{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f161\fbidi \fmodern\fcharset161\fprq1 MS Mincho Greek{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} +{\f162\fbidi \fmodern\fcharset162\fprq1 MS Mincho Tur{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f165\fbidi \fmodern\fcharset186\fprq1 MS Mincho Baltic{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};} +{\f180\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt \'cb\'ce\'cc\'e5};}{\f418\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f419\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f421\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;} +{\f422\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f423\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f424\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\f425\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} +{\f426\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\f478\fbidi \fswiss\fcharset238\fprq2 Tahoma CE{\*\falt ?l?r ???};}{\f479\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr{\*\falt ?l?r ???};} +{\f481\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek{\*\falt ?l?r ???};}{\f482\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur{\*\falt ?l?r ???};}{\f483\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew){\*\falt ?l?r ???};} +{\f484\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic){\*\falt ?l?r ???};}{\f485\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic{\*\falt ?l?r ???};}{\f486\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese){\*\falt ?l?r ???};} +{\f487\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai){\*\falt ?l?r ???};}{\f488\fbidi \fswiss\fcharset238\fprq2 Trebuchet MS CE{\*\falt Arial};}{\f489\fbidi \fswiss\fcharset204\fprq2 Trebuchet MS Cyr{\*\falt Arial};} +{\f491\fbidi \fswiss\fcharset161\fprq2 Trebuchet MS Greek{\*\falt Arial};}{\f492\fbidi \fswiss\fcharset162\fprq2 Trebuchet MS Tur{\*\falt Arial};}{\f495\fbidi \fswiss\fcharset186\fprq2 Trebuchet MS Baltic{\*\falt Arial};} +{\f510\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\f520\fbidi \fmodern\fcharset0\fprq1 @MS Mincho Western;}{\f518\fbidi \fmodern\fcharset238\fprq1 @MS Mincho CE;}{\f519\fbidi \fmodern\fcharset204\fprq1 @MS Mincho Cyr;} +{\f521\fbidi \fmodern\fcharset161\fprq1 @MS Mincho Greek;}{\f522\fbidi \fmodern\fcharset162\fprq1 @MS Mincho Tur;}{\f525\fbidi \fmodern\fcharset186\fprq1 @MS Mincho Baltic;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;} +{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}{\fhimajor\f31536\fbidi \froman\fcharset163\fprq2 Cambria (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} +{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} +{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; +\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\cfollowedhyperlink\ctint255\cshade255\red128\green0\blue128;\red230\green230\blue230;}{\*\defchp +\f31506\fs22 }{\*\defpap \ql \fi-360\li360\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \sautoupd \sqformat \spriority0 \styrsid4934124 Normal;}{\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext1 \slink15 \sqformat \styrsid4934124 heading 1;}{\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0 \rtlch\fcs1 +\ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext2 \slink16 \sunhideused \sqformat \styrsid4934124 heading 2;}{\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext3 \slink17 \sunhideused \sqformat \styrsid4934124 heading 3;}{\s4\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext4 \slink18 \sunhideused \sqformat \styrsid4934124 heading 4;}{\s5\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar +\tx1792\jclisttab\tx2155\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl4\outlinelevel4\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext5 \slink19 \sunhideused \sqformat \styrsid4934124 heading 5;}{\s6\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl5\outlinelevel5\adjustright\rin0\lin2149\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext6 \slink20 \sunhideused \sqformat \styrsid4934124 heading 6;}{\s7\ql \fi-357\li2506\ri0\sb120\sa120\widctlpar +\jclisttab\tx2509\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl6\outlinelevel6\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext7 \slink21 \sunhideused \sqformat \styrsid4934124 heading 7;}{\s8\ql \fi-357\li2863\ri0\sb120\sa120\widctlpar\jclisttab\tx2866\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl7\outlinelevel7\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext8 \slink22 \sunhideused \sqformat \styrsid4934124 heading 8;}{\s9\ql \fi-358\li3221\ri0\sb120\sa120\widctlpar +\jclisttab\tx3223\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl8\outlinelevel8\adjustright\rin0\lin3221\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext9 \slink23 \sunhideused \sqformat \styrsid4934124 heading 9;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv +\ql \fi-360\li360\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused +Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 \sbasedon10 \slink1 \slocked \styrsid4934124 Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 +\sbasedon10 \slink2 \slocked \ssemihidden \styrsid4934124 Heading 2 Char;}{\*\cs17 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink3 \slocked \ssemihidden \styrsid4934124 Heading 3 Char;}{\*\cs18 \additive \rtlch\fcs1 +\af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink4 \slocked \ssemihidden \styrsid4934124 Heading 4 Char;}{\*\cs19 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink5 \slocked \ssemihidden \styrsid4934124 Heading 5 Char;}{\* +\cs20 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink6 \slocked \ssemihidden \styrsid4934124 Heading 6 Char;}{\*\cs21 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 +\sbasedon10 \slink7 \slocked \ssemihidden \styrsid4934124 Heading 7 Char;}{\*\cs22 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink8 \slocked \ssemihidden \styrsid4934124 Heading 8 Char;}{\*\cs23 +\additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink9 \slocked \ssemihidden \styrsid4934124 Heading 9 Char;}{\*\cs24 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \f0\ul\cf2 \sbasedon10 \sunhideused \styrsid4934124 +Hyperlink;}{\s25\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext25 \styrsid4934124 Body 1;}{\s26\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext26 \styrsid4934124 Bullet 2;}{\s27\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\jclisttab\tx1080\wrapdefault\aspalpha\aspnum\faauto\ls3\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext27 \slink48 \styrsid4934124 Bullet 3;}{\s28\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 +\b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \styrsid4934124 Heading EULA;}{\s29\ql \li0\ri0\sb120\sa120\widctlpar\brdrb\brdrs\brdrw10\brsp20 +\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 \b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \styrsid4934124 +Heading Software Title;}{\s30\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext30 \styrsid4934124 Preamble;}{\s31\ql \li0\ri0\sb120\sa120\widctlpar\brdrt\brdrs\brdrw10\brsp20 +\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon30 \snext31 \styrsid4934124 +Preamble Border Above;}{\s32\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\snext0 \styrsid4934124 Body 0 Bold;}{\s33\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \styrsid4934124 Body 0;}{\s34\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs16\alang1025 \ltrch\fcs0 +\fs16\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext34 \slink35 \ssemihidden \sunhideused \styrsid11950712 Balloon Text;}{\*\cs35 \additive \rtlch\fcs1 \af43\afs16 \ltrch\fcs0 +\fs16\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink34 \slocked \ssemihidden \styrsid11950712 Balloon Text Char;}{\s36\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin357\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext36 \styrsid11950712 Bullet 1;}{\s37\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext37 \styrsid11950712 Bullet 4;}{\s38\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar\jclisttab\tx1795\wrapdefault\aspalpha\aspnum\faauto\ls6\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext38 \styrsid11950712 Bullet 5;}{\s39\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls7\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon3 \snext39 \slink50 \styrsid11950712 Heading 3 Bold;}{\s40\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon37 \snext40 \styrsid11950712 Bullet 4 Underline;}{\*\cs41 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 +\sbasedon10 \styrsid11950712 Body 2 Char;}{\*\cs42 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 \styrsid11950712 Body 3 Char;}{\*\cs43 \additive \rtlch\fcs1 \af0\afs16 \ltrch\fcs0 \fs16 +\sbasedon10 \ssemihidden \sunhideused \styrsid8850722 annotation reference;}{\s44\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs20\alang1025 \ltrch\fcs0 +\fs20\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext44 \slink45 \sunhideused \styrsid8850722 annotation text;}{\*\cs45 \additive \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\f43\hich\af43\dbch\af11 +\sbasedon10 \slink44 \slocked \styrsid8850722 Comment Text Char;}{\s46\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs20\alang1025 \ltrch\fcs0 +\b\fs20\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon44 \snext44 \slink47 \ssemihidden \sunhideused \styrsid8850722 annotation subject;}{\*\cs47 \additive \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 +\b\fs20\loch\f43\hich\af43\dbch\af11 \sbasedon45 \slink46 \slocked \ssemihidden \styrsid8850722 Comment Subject Char;}{\*\cs48 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 \sbasedon10 \slink27 \slocked \styrsid2434661 +Bullet 3 Char1;}{\s49\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon37 \snext49 \spriority0 \styrsid3941498 Bullet 4 Underlined;}{\*\cs50 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\fs19\loch\f43\hich\af43\dbch\af11 +\sbasedon10 \slink39 \slocked \styrsid3941498 Heading 3 Bold Char;}{\s51\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs21\alang1025 \ltrch\fcs0 +\f37\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext51 \slink52 \ssemihidden \sunhideused \styrsid3941498 Plain Text;}{\*\cs52 \additive \rtlch\fcs1 \af0\afs21 \ltrch\fcs0 \f37\fs21 +\sbasedon10 \slink51 \slocked \ssemihidden \styrsid3941498 Plain Text Char;}{\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext53 \slink54 \sunhideused \styrsid11152386 header;}{\*\cs54 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 +\sbasedon10 \slink53 \slocked \styrsid11152386 Header Char;}{\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext55 \slink56 \sunhideused \styrsid11152386 footer;}{\*\cs56 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \fs19\loch\f43\hich\af43\dbch\af11 +\sbasedon10 \slink55 \slocked \styrsid11152386 Footer Char;}{\*\cs57 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \ssemihidden \sunhideused \styrsid13192943 FollowedHyperlink;}{\*\cs58 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 +\cf15\chshdng0\chcfpat0\chcbpat20 \sbasedon10 \ssemihidden \sunhideused \styrsid12217836 Unresolved Mention;}}{\*\listtable{\list\listtemplateid-899113366{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\cf0\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 +\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 +\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 +\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 +\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid113718381}{\list\listtemplateid1122370636\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-357\li720\jclisttab\tx723\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1083\jclisttab\tx1083\lin1083 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li1803\jclisttab\tx1803\lin1803 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2523\jclisttab\tx2523\lin2523 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3243\jclisttab\tx3243\lin3243 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li3963\jclisttab\tx3963\lin3963 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li4683\jclisttab\tx4683\lin4683 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5403\jclisttab\tx5403\lin5403 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6123\jclisttab\tx6123\lin6123 }{\listname ;}\listid152650329}{\list\listtemplateid-355573436{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat2 +\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li2247\jclisttab\tx2610\lin2247 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 +\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 +\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 +\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 +\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid398096909}{\list\listtemplateid1928476992{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s39\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 } +{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 +\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 +\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 +\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid398796681}{\list\listtemplateid789093748\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid-317712510\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s26\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid477573462}{\list\listtemplateid830884688\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3957\lin3957 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4677\lin4677 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5397\lin5397 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li6117\lin6117 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6837\lin6837 }{\listname ;}\listid545946042}{\list\listtemplateid-603941590{\listlevel\levelnfc0\levelnfcn0 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4 +\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li-1797\jclisttab\tx-1797\lin-1797 } +{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-357\li-10173 +\jclisttab\tx-9810\lin-10173 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\strike0\f3\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 +\fi-358\li-10265\jclisttab\tx-10263\lin-10265 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \fi-357\li-9908\jclisttab\tx-9545\lin-9908 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers +\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-9551\jclisttab\tx-9548\lin-9551 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-9194\jclisttab\tx-9191\lin-9194 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li-8837\jclisttab\tx-8834\lin-8837 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li-8479\jclisttab\tx-8477\lin-8479 }{\listname ;}\listid562301967}{\list\listtemplateid-170633512 +{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\cf0\fbias0\hres0\chhres0 \s1\fi-357\li357 +\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s2 +\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 +\b\i0\f43\fs20\fbias0\hres0\chhres0 \s3\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \s4\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\animtext0\striked0\fbias0\hres0\chhres0 \s5\fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s6\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s7\fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel +\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s8\fi-357\li2863\jclisttab\tx2866\lin2863 +}{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s9\fi-358\li3221 +\jclisttab\tx3223\lin3221 }{\listname ;}\listid752163927}{\list\listtemplateid1725578678{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 +\ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\cf0\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;} +\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel +\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 +}{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863 +\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 +\fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid1107626792}{\list\listtemplateid-41362566\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s37\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;} +\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160 +\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;} +\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1559511898}{\list\listtemplateid-743794326\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid2033377338\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s27\fi-357\li1077\jclisttab\tx1080\lin1077 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1567649130}{\list\listtemplateid419070574\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4680\lin4680 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers +;}\f2\fbias0\hres0\chhres0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} +\f10\fbias0\hres0\chhres0 \fi-360\li6840\lin6840 }{\listname ;}\listid1589268858}{\list\listtemplateid1363474438\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid-1175557160\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s38\fi-357\li1792\jclisttab\tx1795\lin1792 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1848404271}{\list\listtemplateid-761117952\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2157\lin2157 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4317\lin4317 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5037\lin5037 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5757\lin5757 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6477\lin6477 }{\listname ;}\listid1870291363}{\list\listtemplateid1186249844\listhybrid{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid1637229796\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s36\fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid2054619191}{\list\listtemplateid-1344757370{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0\afs20 \ltrch\fcs0 \b\i0\fs20\fbias0\hres0\chhres0 \fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1 +\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792 +\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 +\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 +\ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid2057971432}{\list\listtemplateid-569628034{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fbias0\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;} +\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 +\hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2157\lin2157 } +{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listname ;}\listid2106000387}}{\*\listoverridetable{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1} +{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls1}{\listoverride\listid477573462\listoverridecount0\ls2}{\listoverride\listid1567649130\listoverridecount0\ls3}{\listoverride\listid2054619191 +\listoverridecount0\ls4}{\listoverride\listid1559511898\listoverridecount0\ls5}{\listoverride\listid1848404271\listoverridecount0\ls6}{\listoverride\listid398796681\listoverridecount0\ls7}{\listoverride\listid545946042\listoverridecount0\ls8} +{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls9}{\listoverride\listid1870291363 +\listoverridecount0\ls10}{\listoverride\listid152650329\listoverridecount0\ls11}{\listoverride\listid1589268858\listoverridecount0\ls12}{\listoverride\listid2057971432\listoverridecount0\ls13}{\listoverride\listid398096909\listoverridecount0\ls14} +{\listoverride\listid752163927\listoverridecount0\ls15}{\listoverride\listid1107626792\listoverridecount0\ls16}{\listoverride\listid113718381\listoverridecount0\ls17}{\listoverride\listid2106000387\listoverridecount0\ls18}{\listoverride\listid398796681 +\listoverridecount0\ls19}{\listoverride\listid562301967\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat0} +{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls20} +{\listoverride\listid562301967\listoverridecount0\ls21}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}\ls22}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1} +{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls23} +{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls24}{\listoverride\listid752163927 +\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls25}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat +\levelstartat3}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls26}{\listoverride\listid752163927\listoverridecount0\ls27}{\listoverride\listid752163927\listoverridecount0\ls28} +{\listoverride\listid752163927\listoverridecount0\ls29}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid79668\rsid150779\rsid211660\rsid213676\rsid263352\rsid270541 +\rsid342430\rsid483838\rsid489647\rsid528777\rsid554910\rsid608234\rsid686391\rsid736743\rsid792304\rsid922358\rsid947296\rsid999719\rsid1145719\rsid1335391\rsid1527700\rsid1714580\rsid1799620\rsid2040850\rsid2062659\rsid2322952\rsid2365705\rsid2434661 +\rsid2633486\rsid2635842\rsid2695079\rsid2708164\rsid2902063\rsid2965976\rsid2973522\rsid3041209\rsid3042054\rsid3171745\rsid3285590\rsid3355994\rsid3418540\rsid3571087\rsid3611845\rsid3623294\rsid3634687\rsid3636551\rsid3875660\rsid3882158\rsid3882522 +\rsid3882949\rsid3941498\rsid4080070\rsid4149814\rsid4402402\rsid4409825\rsid4536802\rsid4537652\rsid4609004\rsid4611858\rsid4731914\rsid4742223\rsid4748609\rsid4801980\rsid4805534\rsid4805706\rsid4858977\rsid4868258\rsid4929965\rsid4934124\rsid5113462 +\rsid5262441\rsid5309509\rsid5318439\rsid5465657\rsid5467606\rsid5471954\rsid5720387\rsid5780125\rsid6124814\rsid6171721\rsid6292707\rsid6318271\rsid6364904\rsid6424248\rsid6496414\rsid6561381\rsid6584761\rsid6620178\rsid6643866\rsid6644215\rsid6755756 +\rsid6761489\rsid6828031\rsid6833860\rsid7040710\rsid7080991\rsid7091446\rsid7099326\rsid7109146\rsid7150192\rsid7344474\rsid7420369\rsid7432529\rsid7503579\rsid7698999\rsid7756319\rsid7879410\rsid8007569\rsid8205106\rsid8332882\rsid8334492\rsid8416259 +\rsid8455816\rsid8460809\rsid8528894\rsid8586851\rsid8662808\rsid8729106\rsid8850722\rsid8921755\rsid8979707\rsid9004944\rsid9066668\rsid9072635\rsid9135771\rsid9176743\rsid9306427\rsid9380011\rsid9399500\rsid9448986\rsid9465849\rsid9516204\rsid9518548 +\rsid9577151\rsid9584906\rsid9709666\rsid9722926\rsid9728818\rsid9765890\rsid9782115\rsid9835407\rsid9860928\rsid9902756\rsid10428435\rsid10576842\rsid10688326\rsid10956334\rsid10962326\rsid11143314\rsid11152386\rsid11207705\rsid11303858\rsid11408012 +\rsid11423848\rsid11496807\rsid11541309\rsid11626503\rsid11677882\rsid11690930\rsid11760915\rsid11930791\rsid11950712\rsid12217836\rsid12536975\rsid12545879\rsid12584315\rsid12605359\rsid12799626\rsid12868905\rsid12992444\rsid13004280\rsid13056010 +\rsid13066823\rsid13192943\rsid13198603\rsid13378284\rsid13456345\rsid13513072\rsid13662583\rsid13776901\rsid13967657\rsid14108197\rsid14179805\rsid14293912\rsid14373468\rsid14418632\rsid14426867\rsid14435085\rsid14507627\rsid14684443\rsid14685080 +\rsid14707821\rsid14712272\rsid14898254\rsid14958727\rsid15158534\rsid15278441\rsid15287965\rsid15364209\rsid15405862\rsid15494051\rsid15539022\rsid15602361\rsid15602734\rsid15749471\rsid15804309\rsid15809401\rsid15870741\rsid15949319\rsid16020560 +\rsid16065250\rsid16073823\rsid16084478\rsid16334972\rsid16346709\rsid16388644\rsid16405449\rsid16537650\rsid16544777\rsid16653828\rsid16715114}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440 +\mintLim0\mnaryLim1}{\info{\creatim\yr2018\mo5\dy23\hr14\min17}{\revtim\yr2018\mo5\dy23\hr14\min17}{\version1}{\edmins0}{\nofpages3}{\nofwords1423}{\nofchars8113}{\nofcharsws9517}{\vern59}}{\*\userprops {\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb859 +95028c_Enabled}\proptype30{\staticval True}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SiteId}\proptype30{\staticval 72f988bf-86f1-41af-91ab-2d7cd011db47}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Owner}\proptype30 +{\staticval jagarg@microsoft.com}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SetDate}\proptype30{\staticval 2018-05-23T08:47:46.8790302Z}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Name}\proptype30{\staticval General} +{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Application}\proptype30{\staticval Microsoft Azure Information Protection}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Extended_MSFT_Method}\proptype30{\staticval Automatic} +{\propname Sensitivity}\proptype30{\staticval General}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen +\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\rempersonalinfo\allowfieldendsel +\wrppunct\asianbrkrule\rsidroot4934124\newtblstyruls\nogrowautofit\remdttm\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal +\nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 +\ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsep +\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsepc +\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsep +\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid11152386 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid270541 \chftnsepc +\par }}\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\headerl \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\headerr \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\footerl \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\footerr \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\headerf \ltrpar \pard\plain \ltrpar\s53\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\footerf \ltrpar \pard\plain \ltrpar\s55\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11152386 +\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}} +{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8 +\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar +\s28\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 +\b\fs28\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 MICROSOFT SOFTWARE LICENSE TERMS +\par }\pard\plain \ltrpar\s29\ql \li0\ri0\sb120\sa120\nowidctlpar\brdrb\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 +\b\fs28\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 MICROSOFT }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid2973522 \hich\af43\dbch\af13\loch\f43 VISUAL STUDIO }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid3042054 \hich\af43\dbch\af13\loch\f43 TEST PLATFORM}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid4934124\charrsid342430 +\par }\pard\plain \ltrpar\s30\ql \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid342430 \hich\af43\dbch\af13\loch\f43 +These license terms are an agreement between Microsoft Corporation (or based on where you live, one of }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 its +\hich\af43\dbch\af13\loch\f43 affiliates) and you. They apply to the software named above. The terms also apply to any Microsoft}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 + services or updates for the software, except to the extent those have }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 additional}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 +\b0\fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 terms.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 +\par }\pard\plain \ltrpar\s31\ql \li0\ri0\sb120\sa120\nowidctlpar\brdrt\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid4934124 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +If you comply with these license terms, you have the rights below. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 1.\tab}}\pard\plain \ltrpar\s1\ql \fi-360\li360\ri0\sb120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid15287965 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 +\ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 IN\hich\af43\dbch\af13\loch\f43 STALLATION AND USE RIGHTS. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4731914 \hich\af43\dbch\af13\loch\f43 +You may install and use any number of copies of the software.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid14179805\charrsid10576842 \hich\af43\dbch\af0\loch\f43 2.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid5318439 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid14179805\charrsid10576842 T}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\insrsid13378284\charrsid10576842 ERMS FOR SPECIFIC}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid14179805\charrsid10576842 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid15405862\charrsid10576842 COMPONENTS. +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\loch\af43\hich\af43\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls1\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid8332882 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \fs20\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 Third Party Components.\~ }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af11\insrsid4609004\charrsid8332882 \hich\af43\dbch\af11\loch\f43 +The software may include third party components with separate legal notices or governed by other agreements, as may be described in the ThirdPartyNotices file(s) accompanying the software.\~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b0\fs20\dbch\af11\insrsid8332882\charrsid8332882 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9066668\charrsid8332882 \hich\af43\dbch\af0\loch\f43 3.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls1\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid8332882 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 +\ltrch\fcs0 \fs20\insrsid9066668\charrsid8332882 DATA. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid2708164\charrsid8332882 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid2708164\charrsid10576842 \hich\af43\dbch\af0\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid2708164 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \fs20\insrsid2708164\charrsid10576842 Data Collection. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 The software may collect information abou +t you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentati +on. There are also s}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid9066668\charrsid10576842 ome features in the software that may enable you }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid16020560\charrsid10576842 and Microsoft }{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\cf1\insrsid9066668\charrsid10576842 to collect data from users of your applications.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 If you use these features}{\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10688326\charrsid10576842 ,}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 you must comply with applicable law, including providing appropriate notices to users of your applications}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid6761489\charrsid10576842 together with }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid608234\charrsid10576842 a copy of }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b0\fs20\insrsid6761489\charrsid10576842 Microsoft\rquote s privacy statement}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 . }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid6761489\charrsid10576842 +Our privacy statement is located}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 at}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid3171745\charrsid10576842 }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab0\af43\afs20 +\ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid270541 HYPERLINK "https://go.microsoft.com/fwlink/?LinkID=824704" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid12217836\charrsid10576842 +https://go.microsoft.com/fwlink/?LinkID=824704}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 . }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b0\fs20\insrsid7109146\charrsid10576842 You can learn more about data collection and use in the help documentation and our privacy statement. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid9066668\charrsid10576842 +Your use of the software operates as your consent to these practices. +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid213676\charrsid10576842 \hich\af43\dbch\af0\loch\f43 b.\tab}}\pard \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls15\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid213676 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid213676\charrsid10576842 Processing of Personal Data. }{\rtlch\fcs1 \ab0\af43\afs20 +\ltrch\fcs0 \b0\fs20\insrsid213676\charrsid10576842 To the extent Microsoft is a processor or subprocessor +of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at }{\field\fldedit{\*\fldinst { +\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid270541 HYPERLINK "http://go.microsoft.com/?linkid=9840733" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\insrsid12217836\charrsid10576842 +http://go.microsoft.com/?linkid=9840733}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid213676\charrsid10576842 .}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\insrsid213676\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 4.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid15804309 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 +\ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Scope of License}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 +\b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 + The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more right\hich\af43\dbch\af13\loch\f43 +s despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. }{\rtlch\fcs1 \ab0\af43\afs20 +\ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 For more information, see }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\dbch\af13\insrsid270541 +\hich\af43\dbch\af13\loch\f43 HYPER\hich\af43\dbch\af13\loch\f43 LINK "http://www.microsoft.com/licensing/userights" }}{\fldrslt {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \cs24\b0\fs20\ul\cf2\dbch\af13\insrsid554910\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 www.microsoft.com/licensing/userights}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid9860928\sftnbj {\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 . }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 You may not}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 + +\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid4934124\charrsid342430 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s26\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid4934124 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid342430 \hich\af43\dbch\af13\loch\f43 work around any technical limitations in the software}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid4934124\charrsid342430 +\hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid342430 +\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid4934124\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 reverse engineer, decompile or disassemble the software, }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +or attempt to do so, }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 except and only to the extent }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid554910\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 required by third party licensing terms governing use of certain open-source components that may be included with the software}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid4934124\charrsid10576842 +\hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}}\pard \ltrpar\s26\ql \fi-363\li720\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid2434661 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +remove, minimize, block or modify any notices of Microsoft or its suppliers in the software; +\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}\hich\af43\dbch\af13\loch\f43 use \hich\af43\dbch\af13\loch\f43 +the software in any way that is against the law; or +\par {\listtext\pard\plain\ltrpar \s26 \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\loch\af3\hich\af3\dbch\af13\insrsid2434661\charrsid10576842 \loch\af3\dbch\af13\hich\f3 \'b7\tab}\hich\af43\dbch\af13\loch\f43 share, publish}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 , rent, or lease}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 \hich\af43\dbch\af13\loch\f43 + the software, or provide the software as a stand-alone }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid5780125 \hich\af43\dbch\af13\loch\f43 offering}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid2434661\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 for others to use. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 5.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid13594873 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 +\ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Export Restrictions}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 +\b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid554910\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +You must comply with all domestic and international export laws and r\hich\af43\dbch\af13\loch\f43 +egulations that apply to the software, which include restrictions on destinations, end users and end use. For further information on export restrictions, visit (aka.ms/exporting).}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid10956334\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 6.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 SUPPORT SERVICES.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \ab0\af43\afs20 +\ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Because this software is \'93\loch\f43 \hich\f43 as is,\'94\loch\f43 we may not provide supp\hich\af43\dbch\af13\loch\f43 ort services for it.}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 7.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid4934124 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Entire Agreement.} +{\rtlch\fcs1 \ab0\af43\afs20 \ltrch\fcs0 \b0\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 + This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 8.\tab}}\pard \ltrpar\s1\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid528777 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Applicable Law}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 .}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid528777\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b0\fs20\insrsid528777\charrsid10576842 If you acquired the s +oftware in the United States, Washington law applies to interpretation of and claims for breach of this agreement, and the laws of the state where you live apply to all other claims. If you acquired the software in any other country, its laws apply.}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid528777\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 9.\tab}}\pard \ltrpar\s1\ql \fi-360\li360\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin360\itap0\pararsid11541309 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 CONSUMER RIGHTS; REGIONAL VARIATIONS. }{\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 +This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state or country. Separate and apart from your relationship with Microsoft, you may also have rights + with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or ma +ndatory country law applies, then the following provisions apply to you: +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 a.\tab}}\pard\plain \ltrpar +\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls18\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 Australia. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 +You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights. +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af0\loch\f43 b.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 Canada. }{\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 If you acquired this software in +Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninsta +lling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af0\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10956334\charrsid11626503 \hich\af43\dbch\af0\loch\f43 c.\tab}}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid11626503 Germany and Austria}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid11626503 . +\par }\pard\plain \ltrpar\ql \li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 (i)}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\insrsid10956334\charrsid10576842 \tab }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 Warranty}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 +\hich\af43\dbch\af11\loch\f43 . The properly licensed software will perform substantially as described in any Microsoft \hich\af43\dbch\af11\loch\f43 +materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software. +\par }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 (ii)}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 \tab }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b\fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 Limitation of Liability}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid10956334\charrsid10576842 \hich\af43\dbch\af11\loch\f43 +. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as wel\hich\af43\dbch\af11\loch\f43 l as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law. +\par }\pard\plain \ltrpar\s1\ql \li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin717\itap0\pararsid10956334 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\f43\fs19\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \b0\fs20\insrsid10956334\charrsid10576842 +Subject to the foregoing clause (ii), Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obli +gations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other + cases of slight negligence, Microsoft will not be liable for slight negligence. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 10.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\nowidctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid4934124 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +Disclaimer of Warranty.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 \hich\f43 THE SOFTWARE IS LICENSED \'93\loch\f43 \hich\f43 AS-IS.\'94\loch\f43 YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. TO THE EXTENT PERMITTE +\hich\af43\dbch\af13\loch\f43 D UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\caps\fs20\loch\af43\hich\af43\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 11.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \caps\fs20\dbch\af13\insrsid4934124\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +Limitation on and Exclusion of Damages. }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid12584315\charrsid10576842 YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP T +O U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. +\par }\pard\plain \ltrpar\s25\ql \li357\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0\pararsid12584315 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, breach of warranty, guarantee or condition, strict liabi +\hich\af43\dbch\af13\loch\f43 l\hich\af43\dbch\af13\loch\f43 ity, negligence, or other tort to the extent permitted by applicable law. +\par }\pard\plain \ltrpar\ql \li360\ri0\sb120\sa120\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0\pararsid12584315 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not \hich\af43\dbch\af13\loch\f43 +allow the exclusion or limitation of incidental, consequential or other damages. +\par }\pard \ltrpar\ql \li0\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin0\itap0\pararsid12584315 {\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French. +\par \hich\af43\dbch\af13\loch\f43 \hich\f43 Remarque : Ce logiciel \'e9\loch\f43 \hich\f43 tant distribu\'e9\hich\af43\dbch\af13\loch\f43 \hich\f43 au Qu\'e9\loch\f43 \hich\f43 +bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7\loch\f43 ais. +\par \hich\af43\dbch\af13\loch\f43 \hich\f43 EXON\'c9\loch\f43 RATION DE GARANTIE.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Le logiciel vis\'e9\loch\f43 \hich\f43 par une licence est offert \'ab\loch\f43 \hich\f43 tel quel \'bb\loch\f43 . Toute utilisation de ce logici}{\rtlch\fcs1 +\af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid342430 \hich\af43\dbch\af13\loch\f43 \hich\f43 el est \'e0\loch\f43 \hich\f43 votre seule risque et p\'e9\loch\f43 ril. Microsoft\hich\af43\dbch\af13\loch\f43 n\hich\f43 \rquote \loch\f43 +\hich\f43 accorde aucune autre garantie expresse. Vous pouvez b\'e9\loch\f43 \hich\f43 n\'e9\loch\f43 +ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites \hich\af43\dbch\af13\loch\f43 d +\hich\af43\dbch\af13\loch\f43 \hich\f43 e qualit\'e9\loch\f43 marchande, d\hich\f43 \rquote \loch\f43 \hich\f43 ad\'e9\loch\f43 \hich\f43 quation \'e0\loch\f43 un usage particulier et d\hich\f43 \rquote \loch\f43 \hich\f43 absence de contrefa\'e7 +\loch\f43 on sont exclues.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 +\par }{\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 LIMITATION DES DOMMAGES-INT\'c9\loch\f43 \hich\f43 R\'ca\loch\f43 \hich\f43 TS ET EXCLUSION DE RESPONSABILIT\'c9\loch\f43 + POUR LES DOMMAGES.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 +Vous pouvez}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 +\hich\af43\dbch\af13\loch\f43 obtenir de Microsoft et de ses fournisseurs une indemnisation \hich\af43\dbch\af13\loch\f43 \hich\f43 en cas de dommages directs uniquement \'e0\loch\f43 \hich\f43 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9\loch\f43 +\hich\f43 tendre \'e0\loch\f43 \hich\f43 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9\loch\f43 \hich\f43 ciaux, indirects ou accessoires et pertes de b\'e9}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\fs20\dbch\af13\insrsid12584315\charrsid342430 \hich\af43\dbch\af13\loch\f43 \hich\f43 n\'e9\loch\f43 fices. +\par \hich\af43\dbch\af13\loch\f43 Cette limitation concerne: +\par }\pard \ltrpar\ql \fi-363\li363\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin363\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \loch\af43\dbch\af13\hich\f43 \'b7\~\~\~\~\tab +\loch\f43 tout \hich\af43\dbch\af13\loch\f43 \hich\f43 ce qui est reli\'e9\loch\f43 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et +\par \loch\af43\dbch\af13\hich\f43 \'b7\~\~\~\~\~\loch\f43 \hich\f43 les r\'e9\loch\f43 \hich\f43 clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9\loch\f43 stric\hich\af43\dbch\af13\loch\f43 \hich\f43 te, de n\'e9 +\loch\f43 gligence ou d\hich\f43 \rquote \loch\f43 \hich\f43 une autre faute dans la limite autoris\'e9\loch\f43 e par la loi en vigueur. +\par }\pard \ltrpar\ql \li0\ri0\sb40\sa40\widctlpar\wrapdefault\faauto\adjustright\rin0\lin0\itap0\pararsid12584315 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 Elle s\hich\f43 \rquote +\loch\f43 \hich\f43 applique \'e9\loch\f43 \hich\f43 galement, m\'ea\loch\f43 \hich\f43 me si Microsoft connaissait ou devrait conna\'ee\loch\f43 tre l\hich\f43 \rquote \'e9\loch\f43 \hich\f43 ventualit\'e9\loch\f43 d\hich\f43 \rquote \loch\f43 +un tel dommage. Si votre pays n\hich\f43 \rquote \loch\f43 autorise pas l\hich\f43 \rquote \loch\f43 exclusion ou la limitation d\hich\af43\dbch\af13\loch\f43 \hich\f43 e responsabilit\'e9\loch\f43 + pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\hich\f43 \rquote \loch\f43 exclusion ci-dessus ne s\hich\f43 \rquote \loch\f43 \hich\f43 appliquera pas \'e0\loch\f43 \hich\f43 votre \'e9 +\loch\f43 gard. +\par }{\rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 EFFET JURIDIQUE.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\cf1\lang1036\langfe1033\langnp1036\insrsid12584315\charrsid10576842 \~}{ +\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid10576842 \hich\af43\dbch\af13\loch\f43 \hich\f43 Le pr\'e9\loch\f43 \hich\f43 sent contrat d\'e9\loch\f43 crit certains droits juridiques. Vous\hich\af43\dbch\af13\loch\f43 + pourriez avoir d\hich\f43 \rquote \loch\f43 \hich\f43 autres droits pr\'e9\loch\f43 \hich\f43 vus par les lois de votre pays. Le pr\'e9\loch\f43 \hich\f43 sent contrat ne modifie pas les droits que vous conf\'e8\loch\f43 +rent les lois de votre pays si celles-ci ne le permettent pas.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af13\insrsid12584315\charrsid342430 +\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\insrsid9860928\charrsid342430 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f +7468656d65312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87 +615b8116d8a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad +79482a9c0498f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b +5d8a314d3c94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab +999fb7b4717509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9 +699640f6719e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd586 +8b37a088d1e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d6 +0cf03ac1a5193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f +9e7ef3f2d117d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be +15c308d3f28acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a9979 +3849c26ae66252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d +32a423279a668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2a +f074481847bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86 +e877f0034e16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb +44f95d843b5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a +6409fb44d08741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c +3d9058edf2c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db0256 +5e85f3b9660d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276 +b9f7dec44b7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8 +c33585b5fb9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e +51440ca2e0088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95 +b21be5ceaf8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff +6dce591a26ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec6 +9ffb9e65d028d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239 +b75a5bb1e6345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a449 +59d366ad93b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e8 +2db8df9f30254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468 +656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4 +350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d2624 +52282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe5141 +73d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000 +0000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000 +000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019 +0200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b00001600000000 +000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027 +00000000000000000000000000a00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d0100009b0a00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority59 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing; +\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1; +\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading; +\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph; +\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1; +\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2; +\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3; +\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4; +\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e500000000000000000000000020cc +88b972f2d301feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.csproj b/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.csproj index c2fb1120e9..d6488a1b3f 100644 --- a/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.csproj +++ b/src/package/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI/Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.csproj @@ -3,7 +3,7 @@ Library true - $(NetFrameworkMinimum) + $(NetFrameworkRunnerTargetFramework) None @@ -17,6 +17,7 @@ false false false + false TestPlatform @@ -29,15 +30,12 @@ [installDir]\Common7\IDE\Extensions\TestPlatform $(ArtifactsBinDir)Microsoft.TestPlatform.CLI\$(Configuration)\$(NetFrameworkMinimum)\ - $(ArtifactsBinDir)Microsoft.TestPlatform\$(Configuration)\net472\ + $(ArtifactsBinDir)Microsoft.TestPlatform\$(Configuration)\$(NetFrameworkRunnerTargetFramework)\ $(TestPlatformBinFolder)Microsoft.Internal.Dia\ - $(TestPlatformBinFolder)Microsoft.VisualStudio.CUIT\ $(TestPlatformBinFolder)Microsoft.CodeCoverage.IO\ $(TestPlatformBinFolder)Microsoft.VisualStudio.QualityTools\ $(TestPlatformBinFolder)Microsoft.Extensions.DependencyModel\ $(TestPlatformBinFolder)Microsoft.Extensions.FileSystemGlobbing\ - $(TestPlatformBinFolder)Microsoft.VSSDK.BuildTools\ - $(TestPlatformBinFolder)Microsoft.VisualStudio.QualityTools.DataCollectors\ $(TestPlatformBinFolder)Microsoft.Internal.TestPlatform.Extensions\ @@ -84,12 +82,80 @@ + + + + + + + + + + + + + + + + Extensions + + + + + + Extensions + + + Extensions\cs + + + Extensions\de + + + Extensions\es + + + Extensions\fr + + + Extensions\it + + + Extensions\ja + + + Extensions\ko + + + Extensions\pl + + + Extensions\pt-BR + + + Extensions\ru + + + Extensions\tr + + + Extensions\zh-Hans + + + Extensions\zh-Hant + + + + Extensions\dump + + + @@ -99,6 +165,7 @@ + @@ -106,6 +173,7 @@ + @@ -113,6 +181,7 @@ + @@ -138,25 +207,6 @@ - - - - CUITPlugins - - - CUITPlugins - - - - - - - - - - - - @@ -214,84 +264,16 @@ Extensions - + Extensions - - - - - Extensions - - - - - - Extensions - - - Extensions\cs - - - Extensions\de - - - Extensions\es - - - Extensions\fr - - - Extensions\it - - - Extensions\ja - - - Extensions\ko - - - Extensions\pl - - - Extensions\pt-BR - - - Extensions\ru - - - Extensions\tr - - - Extensions\zh-Hans - - - Extensions\zh-Hant - - - - - - - - Extensions\V1\x64 - - - Extensions\V1\x86 - - - Extensions\V1 - - - - - - + + @@ -318,7 +300,7 @@ - + false diff --git a/src/package/ThirdPartyNotices.txt b/src/package/ThirdPartyNotices.txt index 5d98c81cc4..bb349995be 100644 --- a/src/package/ThirdPartyNotices.txt +++ b/src/package/ThirdPartyNotices.txt @@ -8,7 +8,7 @@ and the licenses under which Microsoft received such components are set forth be informational purposes only. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. -1. Newtonsoft version 13.0.1 (https://github.com/JamesNK/Newtonsoft.Json) +1. Newtonsoft version 13.0.3 (https://github.com/JamesNK/Newtonsoft.Json) 2. Mono.Cecil version 0.11.3 (https://github.com/jbevain/cecil) 3. Nuget.Client version 6.8.0.117 \(https://github.com/NuGet/NuGet.Client) diff --git a/src/testhost.arm64/app.manifest b/src/testhost.arm64/app.manifest index 161dc3ad9e..83296df511 100644 --- a/src/testhost.arm64/app.manifest +++ b/src/testhost.arm64/app.manifest @@ -3,6 +3,12 @@ + + + + true + + diff --git a/src/testhost.arm64/testhost.arm64.csproj b/src/testhost.arm64/testhost.arm64.csproj index 095fe3ca5d..218480d5d7 100644 --- a/src/testhost.arm64/testhost.arm64.csproj +++ b/src/testhost.arm64/testhost.arm64.csproj @@ -1,30 +1,35 @@ + None $(MSBuildWarningsAsMessages);MSB3276 + testhost.arm64 - net7.0;$(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48 + $(TestHostAllTargetFrameworks) Exe false app.manifest - + + win10-arm64 false $(AssemblyName.Replace('.arm64', '')).$(TargetFramework).arm64 + + @@ -35,24 +40,22 @@ true - - - - - - + + + + diff --git a/src/testhost.x86/DefaultEngineInvoker.cs b/src/testhost.x86/DefaultEngineInvoker.cs index b28332e8ad..95e65cba7f 100644 --- a/src/testhost.x86/DefaultEngineInvoker.cs +++ b/src/testhost.x86/DefaultEngineInvoker.cs @@ -60,6 +60,12 @@ internal class DefaultEngineInvoker : private const string RemotePath = "--remote-path"; + private static readonly bool EnableParentProcessQuery = + AppContext.TryGetSwitch( + switchName: "MSTest.EnableParentProcessQuery", + isEnabled: out bool value) + ? value : true; + private readonly ITestRequestHandler _requestHandler; private readonly IDataCollectionTestCaseEventSender _dataCollectionTestCaseEventSender; @@ -213,6 +219,12 @@ private void SetParentProcessExitCallback(IDictionary argsDicti throw new ArgumentException($"Argument {ParentProcessIdArgument} was not specified."); } + if (!EnableParentProcessQuery) + { + EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: Skipping querying parent process with id: '{0}'", parentProcessId); + return; + } + EqtTrace.Info("DefaultEngineInvoker.SetParentProcessExitCallback: Monitoring parent process with id: '{0}'", parentProcessId); if (parentProcessId == -1) diff --git a/src/testhost.x86/TestHostTraceListener.cs b/src/testhost.x86/TestHostTraceListener.cs index 0832b07d6d..d5dd7b78de 100644 --- a/src/testhost.x86/TestHostTraceListener.cs +++ b/src/testhost.x86/TestHostTraceListener.cs @@ -49,7 +49,8 @@ private static DebugAssertException GetException(string? message) var debugMethodFound = false; var frameCount = 0; MethodBase? method = null; - foreach (var f in stack.GetFrames()) + var frames = stack.GetFrames(); + foreach (var f in frames) { var m = f?.GetMethod(); var declaringType = m?.DeclaringType; @@ -65,7 +66,7 @@ private static DebugAssertException GetException(string? message) } } - var stackTrace = string.Join(Environment.NewLine, stack.ToString().Split(Environment.NewLine).TakeLast(frameCount)); + var stackTrace = new StackTrace(frames.TakeLast(frameCount)).ToString(); var methodName = method != null ? $"{method.DeclaringType?.Name}.{method.Name}" : ""; var wholeMessage = $"Method {methodName} failed with '{message}', and was translated to {typeof(DebugAssertException).FullName} to avoid terminating the process hosting the test."; diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index 2e2ac5c43c..9b5cdfe2c7 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -18,41 +18,44 @@ - + - - + + + - - + + - - + + - - + + + - - + + + - - - + + - - + + + diff --git a/src/testhost.x86/app.manifest b/src/testhost.x86/app.manifest index af789108ba..06e64089d4 100644 --- a/src/testhost.x86/app.manifest +++ b/src/testhost.x86/app.manifest @@ -3,6 +3,12 @@ + + + + true + + diff --git a/src/testhost.x86/testhost.x86.csproj b/src/testhost.x86/testhost.x86.csproj index 1a7de790bc..50245b8159 100644 --- a/src/testhost.x86/testhost.x86.csproj +++ b/src/testhost.x86/testhost.x86.csproj @@ -1,19 +1,21 @@ + None $(MSBuildWarningsAsMessages);MSB3276 + testhost.x86 - net7.0;$(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48 + $(TestHostAllTargetFrameworks) AnyCPU - win7-x86 - true + win-x86 + true Exe false app.manifest @@ -25,13 +27,16 @@ NETSDK1201 $(NoWarn);NETSDK1201 - + + false $(AssemblyName.Replace('.x86', '')).$(TargetFramework).x86 + + @@ -42,19 +47,16 @@ true - - - - - - + + + diff --git a/src/testhost/app.manifest b/src/testhost/app.manifest index af789108ba..06e64089d4 100644 --- a/src/testhost/app.manifest +++ b/src/testhost/app.manifest @@ -3,6 +3,12 @@ + + + + true + + diff --git a/src/testhost/testhost.csproj b/src/testhost/testhost.csproj index f3f4b9b979..05f048d7a1 100644 --- a/src/testhost/testhost.csproj +++ b/src/testhost/testhost.csproj @@ -1,30 +1,35 @@ + None $(MSBuildWarningsAsMessages);MSB3276 + testhost - net7.0;$(NetCoreAppMinimum);$(NetFrameworkMinimum);net47;net471;net472;net48 + $(TestHostAllTargetFrameworks) Exe false app.manifest - + + win7-x64 false $(AssemblyName).$(TargetFramework) + + @@ -35,24 +40,22 @@ true - - - - - - + + + + diff --git a/src/vstest.console.arm64/vstest.console.arm64.csproj b/src/vstest.console.arm64/vstest.console.arm64.csproj index 2cdfac3fc2..dd04b9ba0c 100644 --- a/src/vstest.console.arm64/vstest.console.arm64.csproj +++ b/src/vstest.console.arm64/vstest.console.arm64.csproj @@ -3,16 +3,16 @@ vstest.console.arm64 - net7.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(RunnerTargetFrameworks) Exe false Major false - win10-arm64 + win10-arm64 - AnyCPU + AnyCPU ..\vstest.console\app.manifest Microsoft.VisualStudio.TestPlatform.CommandLine true @@ -29,7 +29,7 @@ $(MSBuildWarningsAsMessages);MSB3276; - + diff --git a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs index 8a1e47df5f..816855d099 100644 --- a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs +++ b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs @@ -209,6 +209,7 @@ public Architecture GetArchitectureForSource(string imagePath) const int imageFileMachineArm = 0x01c0; // ARM Little-Endian const int imageFileMachineThumb = 0x01c2; // ARM Thumb/Thumb-2 Little-Endian const int imageFileMachineArmnt = 0x01c4; // ARM Thumb-2 Little-Endian + const int imageFileMachineArm64 = 0xAA64; // ARM64 Little-Endian try { @@ -276,6 +277,10 @@ public Architecture GetArchitectureForSource(string imagePath) case imageFileMachineArmnt: archType = Architecture.ARM; break; + + case imageFileMachineArm64: + archType = Architecture.ARM64; + break; } } else diff --git a/src/vstest.console/Friends.cs b/src/vstest.console/Friends.cs index d1585a0dc4..0ece628948 100644 --- a/src/vstest.console/Friends.cs +++ b/src/vstest.console/Friends.cs @@ -17,5 +17,6 @@ [assembly: InternalsVisibleTo("vstest.ProgrammerTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("TestPlatform.Playground, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Acceptance.IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Library.IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #endregion diff --git a/src/vstest.console/InProcessVsTestConsoleWrapper.cs b/src/vstest.console/InProcessVsTestConsoleWrapper.cs index 5dbdd0ea61..5c9236d358 100644 --- a/src/vstest.console/InProcessVsTestConsoleWrapper.cs +++ b/src/vstest.console/InProcessVsTestConsoleWrapper.cs @@ -93,7 +93,7 @@ internal InProcessVsTestConsoleWrapper( // Fill the parameters. consoleParameters.ParentProcessId = -#if NET7_0_OR_GREATER +#if NET6_0_OR_GREATER Environment.ProcessId; #else System.Diagnostics.Process.GetCurrentProcess().Id; diff --git a/src/vstest.console/Internal/FilePatternParser.cs b/src/vstest.console/Internal/FilePatternParser.cs index 804739ca7e..9dfcd08678 100644 --- a/src/vstest.console/Internal/FilePatternParser.cs +++ b/src/vstest.console/Internal/FilePatternParser.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.FileSystemGlobbing.Abstractions; @@ -96,7 +97,24 @@ private Tuple SplitFilePatternOnWildCard(string filePattern) { // Split the pattern based on first wild card character found. var splitOnWildCardIndex = filePattern.IndexOfAny(_wildCardCharacters); - var directorySeparatorIndex = filePattern.Substring(0, splitOnWildCardIndex).LastIndexOf(Path.DirectorySeparatorChar); + var pathBeforeWildCard = filePattern.Substring(0, splitOnWildCardIndex); + + // Find the last directory separator before the wildcard + // On Windows, we need to check both \ and / as both are valid + // On Unix-like systems, only / is the directory separator + int directorySeparatorIndex; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On Windows, check both separators and take the last one found + directorySeparatorIndex = Math.Max( + pathBeforeWildCard.LastIndexOf(Path.DirectorySeparatorChar), + pathBeforeWildCard.LastIndexOf(Path.AltDirectorySeparatorChar)); + } + else + { + // On Unix-like systems, only use the forward slash + directorySeparatorIndex = pathBeforeWildCard.LastIndexOf(Path.DirectorySeparatorChar); + } string searchDir = filePattern.Substring(0, directorySeparatorIndex); string pattern = filePattern.Substring(directorySeparatorIndex + 1); diff --git a/src/vstest.console/Internal/ISystemTimersTimer.cs b/src/vstest.console/Internal/ISystemTimersTimer.cs new file mode 100644 index 0000000000..2e07e2111c --- /dev/null +++ b/src/vstest.console/Internal/ISystemTimersTimer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Timers; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + +internal interface ISystemTimersTimer : IDisposable +{ + event ElapsedEventHandler? Elapsed; + + void Start(); + void Stop(); +} diff --git a/src/vstest.console/Internal/ProgressIndicator.cs b/src/vstest.console/Internal/ProgressIndicator.cs index 5b7fec2d48..dadfaf2846 100644 --- a/src/vstest.console/Internal/ProgressIndicator.cs +++ b/src/vstest.console/Internal/ProgressIndicator.cs @@ -6,8 +6,6 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; -using Timer = System.Timers.Timer; - namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; /// @@ -16,10 +14,11 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; internal sealed class ProgressIndicator : IProgressIndicator, IDisposable { private readonly object _syncObject = new(); - private int _dotCounter; - private Timer? _timer; + private readonly ISystemTimersTimer _timer; private readonly string _testRunProgressString; + private int _dotCounter; + /// /// Used to output to the console /// @@ -35,8 +34,9 @@ internal sealed class ProgressIndicator : IProgressIndicator, IDisposable /// public bool IsRunning { get; private set; } - public ProgressIndicator(IOutput output, IConsoleHelper consoleHelper) + public ProgressIndicator(IOutput output, IConsoleHelper consoleHelper, ISystemTimersTimer? timer = null) { + _timer = timer ?? new SystemTimersTimer(1000); ConsoleOutput = output; ConsoleHelper = consoleHelper; _testRunProgressString = string.Format(CultureInfo.CurrentCulture, "{0}...", Resources.Resources.ProgressIndicatorString); @@ -47,12 +47,8 @@ public void Start() { lock (_syncObject) { - if (_timer == null) - { - _timer = new Timer(1000); - _timer.Elapsed += Timer_Elapsed; - _timer.Start(); - } + _timer.Elapsed += Timer_Elapsed; + _timer.Start(); // Print the string based on the previous state, that is dotCounter // This is required for smooth transition diff --git a/src/vstest.console/Internal/SystemTimersTimer.cs b/src/vstest.console/Internal/SystemTimersTimer.cs new file mode 100644 index 0000000000..66c239bf10 --- /dev/null +++ b/src/vstest.console/Internal/SystemTimersTimer.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Timer = System.Timers.Timer; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + +internal sealed class SystemTimersTimer : ISystemTimersTimer +{ + private readonly Timer _timer; + public SystemTimersTimer(int interval) + { + _timer = new Timer(interval); + } + public event System.Timers.ElapsedEventHandler? Elapsed + { + add => _timer.Elapsed += value; + remove => _timer.Elapsed -= value; + } + public void Start() => _timer.Start(); + public void Stop() => _timer.Stop(); + public void Dispose() => _timer.Dispose(); +} diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index 6afdcd808e..57342e1c06 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -25,6 +25,12 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; +static file class BlameParameterNames +{ + public static HashSet CrashDumpKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) { "CollectAlways", "DumpType" }; + public static HashSet HangDumpKeys { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) { "TestTimeout", "HangDumpType" }; +} + internal class EnableBlameArgumentProcessor : IArgumentProcessor { /// @@ -208,7 +214,7 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, bool mon if (enableCrashDump) { var dumpParameters = collectDumpParameters - ?.Where(p => new[] { "CollectAlways", "DumpType" }.Contains(p.Key, StringComparer.OrdinalIgnoreCase)) + ?.Where(p => BlameParameterNames.CrashDumpKeys.Contains(p.Key)) .ToDictionary(p => p.Key, p => p.Value, StringComparer.OrdinalIgnoreCase) ?? new Dictionary(); @@ -224,7 +230,7 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, bool mon if (enableHangDump) { var hangDumpParameters = collectDumpParameters - ?.Where(p => new[] { "TestTimeout", "HangDumpType" }.Contains(p.Key, StringComparer.OrdinalIgnoreCase)) + ?.Where(p => BlameParameterNames.HangDumpKeys.Contains(p.Key)) .ToDictionary(p => p.Key, p => p.Value, StringComparer.OrdinalIgnoreCase) ?? new Dictionary(); diff --git a/src/vstest.console/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessor.cs b/src/vstest.console/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessor.cs index 71db32aa9e..686c3a44a5 100644 --- a/src/vstest.console/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessor.cs +++ b/src/vstest.console/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessor.cs @@ -5,6 +5,7 @@ using System.Linq; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + internal class ShowDeprecateDotnetVStestMessageArgumentProcessor : IArgumentProcessor { public const string CommandName = "/ShowDeprecateDotnetVSTestMessage"; diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net10.0/PublicAPI.Shipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net7.0/PublicAPI.Shipped.txt rename to src/vstest.console/PublicAPI/net10.0/PublicAPI.Shipped.txt diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net10.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/PublicAPI/net7.0/PublicAPI.Unshipped.txt rename to src/vstest.console/PublicAPI/net10.0/PublicAPI.Unshipped.txt diff --git a/src/vstest.console/PublicAPI/net462/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net462/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net462/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net462/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net462/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/net462/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net48/PublicAPI.Shipped.txt similarity index 100% rename from src/datacollector/PublicAPI/net462/PublicAPI.Shipped.txt rename to src/vstest.console/PublicAPI/net48/PublicAPI.Shipped.txt diff --git a/src/datacollector/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net48/PublicAPI.Unshipped.txt similarity index 100% rename from src/datacollector/PublicAPI/net462/PublicAPI.Unshipped.txt rename to src/vstest.console/PublicAPI/net48/PublicAPI.Unshipped.txt diff --git a/src/vstest.console/PublicAPI/net6.0/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net6.0/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net6.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net6.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/PublicAPI/net7.0/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net7.0/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net7.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net7.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/datacollector/PublicAPI/net472/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/net8.0/PublicAPI.Shipped.txt similarity index 100% rename from src/datacollector/PublicAPI/net472/PublicAPI.Shipped.txt rename to src/vstest.console/PublicAPI/net8.0/PublicAPI.Shipped.txt diff --git a/src/datacollector/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/datacollector/PublicAPI/net472/PublicAPI.Unshipped.txt rename to src/vstest.console/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt b/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110..0000000000 --- a/src/vstest.console/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index f93857b671..cbc665aec0 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -845,6 +845,11 @@ private bool UpdateRunSettingsIfRequired( settingsUpdated |= AddOrUpdateBuiltInLoggers(document, runConfiguration, loggerRunSettings); settingsUpdated |= AddOrUpdateBatchSize(document, runConfiguration, isDiscovery); + if (!FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_DYNAMICNATIVE_CODECOVERAGE_DEFAULT_SETTING)) + { + settingsUpdated |= UpdateCollectCoverageSettings(document, runConfiguration); + } + updatedRunSettingsXml = navigator.OuterXml; return settingsUpdated; @@ -883,6 +888,8 @@ static Architecture TranslateToArchitecture(PlatformArchitecture targetArchitect return Architecture.Ppc64le; case PlatformArchitecture.RiscV64: return Architecture.RiscV64; + case PlatformArchitecture.LoongArch64: + return Architecture.LoongArch64; default: EqtTrace.Error($"TestRequestManager.TranslateToArchitecture: Unhandled architecture '{targetArchitecture}'."); break; @@ -1246,6 +1253,11 @@ private static bool UpdateMSBuildLoggerIfExists( return false; } + internal static bool UpdateCollectCoverageSettings(XmlDocument xmlDocument, RunConfiguration _) + { + return InferRunSettingsHelper.UpdateCollectCoverageSettings(xmlDocument); + } + private void RunTests( IRequestData requestData, TestRunCriteria testRunCriteria, @@ -1580,6 +1592,7 @@ internal static class KnownPlatformSourceFilter // NUnit "NUnit3.TestAdapter.dll", + "testcentric.engine.metadata.dll", // XUnit "xunit.runner.visualstudio.testadapter.dll", @@ -1600,6 +1613,7 @@ internal static class KnownPlatformSourceFilter "Microsoft.Testing.Extensions.HangDump.resources.dll", "Microsoft.Testing.Extensions.HotReload.dll", "Microsoft.Testing.Extensions.HotReload.resources.dll", + "Microsoft.Testing.Extensions.MSBuild.dll", "Microsoft.Testing.Extensions.Retry.dll", "Microsoft.Testing.Extensions.Retry.resources.dll", "Microsoft.Testing.Extensions.Telemetry.dll", @@ -1625,6 +1639,12 @@ internal static class KnownPlatformSourceFilter "Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll", "Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll", "Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.resources.dll", + + // For MSTest v4 + "MSTest.TestAdapter.dll", + "MSTest.TestFramework.dll", + "MSTest.TestFramework.Extensions.dll", + "MSTestAdapter.PlatformServices.dll", }, StringComparer.OrdinalIgnoreCase); diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index f133e90359..d968aed3c5 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -14,11 +14,7 @@ - - - - - + @@ -29,6 +25,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vstest.console/app.manifest b/src/vstest.console/app.manifest index af789108ba..06e64089d4 100644 --- a/src/vstest.console/app.manifest +++ b/src/vstest.console/app.manifest @@ -3,6 +3,12 @@ + + + + true + + diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index 34b9f565b5..262e80a41a 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -5,13 +5,17 @@ vstest.console - net7.0;$(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(RunnerTargetFrameworks) Exe - + false Major false - AnyCPU + AnyCPU app.manifest Microsoft.VisualStudio.TestPlatform.CommandLine true @@ -24,10 +28,10 @@ $(MSBuildWarningsAsMessages);MSB3276 - + win7-x64 - + diff --git a/temp/testhost/testhost-10.0.runtimeconfig.json b/temp/testhost/testhost-10.0.runtimeconfig.json new file mode 100644 index 0000000000..e1d32eb6cd --- /dev/null +++ b/temp/testhost/testhost-10.0.runtimeconfig.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0-preview.0" + } + } +} diff --git a/temp/testhost/testhost-11.0.runtimeconfig.json b/temp/testhost/testhost-11.0.runtimeconfig.json new file mode 100644 index 0000000000..7c01deab38 --- /dev/null +++ b/temp/testhost/testhost-11.0.runtimeconfig.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "tfm": "net11.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "11.0.0-preview.0" + } + } +} diff --git a/temp/testhost/testhost.deps.json b/temp/testhost/testhost.deps.json index 2563b5da42..8d751253a8 100644 --- a/temp/testhost/testhost.deps.json +++ b/temp/testhost/testhost.deps.json @@ -81,7 +81,7 @@ "runtime": { "Newtonsoft.Json.dll": { "assemblyVersion": "13.0.0.0", - "fileVersion": "13.0.1.25517" + "fileVersion": "13.0.3.25517" } } } diff --git a/test.cmd b/test.cmd index db9e8e061d..c879b9d2c3 100644 --- a/test.cmd +++ b/test.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\Build.ps1""" -test %*" +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\Build.ps1""" -test %*" exit /b %ErrorLevel% diff --git a/test.sh b/test.sh index 637dec3600..7f61c8be87 100755 --- a/test.sh +++ b/test.sh @@ -13,4 +13,7 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -"$scriptroot/eng/common/build.sh" --test $@ +if [[ -z "${DOTNET_ROOT:-}" && -d "$scriptroot/.dotnet" ]]; then + export DOTNET_ROOT="$scriptroot/.dotnet" +fi +"$scriptroot/eng/common/build.sh" --test "$@" diff --git a/test/.editorconfig b/test/.editorconfig index c6e43695db..f44194a73b 100644 --- a/test/.editorconfig +++ b/test/.editorconfig @@ -32,4 +32,7 @@ dotnet_diagnostic.CA1051.severity = silent # Disabled on tests as it does not ma # CA1710: Identifiers should have correct suffix dotnet_diagnostic.CA1710.severity = warning +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + #### C# Naming styles #### diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/CollectorNameValueConfigurationManagerTests.cs b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/CollectorNameValueConfigurationManagerTests.cs index 4dcd550c11..3c63ef3158 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/CollectorNameValueConfigurationManagerTests.cs +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/CollectorNameValueConfigurationManagerTests.cs @@ -41,13 +41,13 @@ public void ConstructorShouldNotInitializeNameValuePairIfEmptyXmlElementIsPassed } var configManager = new CollectorNameValueConfigurationManager(xmlDocument.DocumentElement); - Assert.AreEqual(0, configManager.NameValuePairs.Count); + Assert.IsEmpty(configManager.NameValuePairs); } [TestMethod] public void ConstructorShouldNotInitializeNameValuePairNullIsPassed() { var configManager = new CollectorNameValueConfigurationManager(null); - Assert.AreEqual(0, configManager.NameValuePairs.Count); + Assert.IsEmpty(configManager.NameValuePairs); } } diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogContainerTests.cs b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogContainerTests.cs index 72727cbb4a..0e77ce605a 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogContainerTests.cs +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogContainerTests.cs @@ -60,7 +60,7 @@ public void OnEventLogEntryWrittenShouldAddLogs() _eventLogContainer.OnEventLogEntryWritten(_eventLog, _entryWrittenEventArgs); var newCount = _eventLogContainer.EventLogEntries.Count; - Assert.IsTrue(newCount > 0); + Assert.IsGreaterThan(0, newCount); } [TestMethod] diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogDataCollectorTests.cs b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogDataCollectorTests.cs index 43923342e1..fda7431395 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogDataCollectorTests.cs +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogDataCollectorTests.cs @@ -59,7 +59,7 @@ public void EventLoggerLogsErrorForInvalidEventSources() [TestMethod] public void InitializeShouldThrowExceptionIfEventsIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _eventLogDataCollector.Initialize( null, null!, @@ -71,7 +71,7 @@ public void InitializeShouldThrowExceptionIfEventsIsNull() [TestMethod] public void InitializeShouldThrowExceptionIfCollectionSinkIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _eventLogDataCollector.Initialize( null, _mockDataCollectionEvents.Object, @@ -83,7 +83,7 @@ public void InitializeShouldThrowExceptionIfCollectionSinkIsNull() [TestMethod] public void InitializeShouldThrowExceptionIfLoggerIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _eventLogDataCollector.Initialize( null, _mockDataCollectionEvents.Object, @@ -220,21 +220,21 @@ public void InitializeShouldSubscribeToDataCollectionEvents() public void TestSessionStartEventShouldCreateEventLogContainer() { var eventLogDataCollector = new EventLogDataCollector(); - Assert.AreEqual(0, eventLogDataCollector.ContextMap.Count); + Assert.IsEmpty(eventLogDataCollector.ContextMap); eventLogDataCollector.Initialize(null, _mockDataCollectionEvents.Object, _mockDataCollectionSink, _mockDataCollectionLogger.Object, _dataCollectionEnvironmentContext); _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs()); - Assert.AreEqual(1, eventLogDataCollector.ContextMap.Count); + Assert.HasCount(1, eventLogDataCollector.ContextMap); } [TestMethod] public void TestCaseStartEventShouldCreateEventLogContainer() { var eventLogDataCollector = new EventLogDataCollector(); - Assert.AreEqual(0, eventLogDataCollector.ContextMap.Count); + Assert.IsEmpty(eventLogDataCollector.ContextMap); eventLogDataCollector.Initialize(null, _mockDataCollectionEvents.Object, _mockDataCollectionSink, _mockDataCollectionLogger.Object, _dataCollectionEnvironmentContext); _mockDataCollectionEvents.Raise(x => x.TestCaseStart += null, new TestCaseStartEventArgs(new DataCollectionContext(new SessionId(Guid.NewGuid()), new TestExecId(Guid.NewGuid())), new TestCase())); - Assert.AreEqual(1, eventLogDataCollector.ContextMap.Count); + Assert.HasCount(1, eventLogDataCollector.ContextMap); } [TestMethod] @@ -269,7 +269,7 @@ public void TestCaseEndEventShouldThrowIfTestCaseStartIsNotInvoked() var tc = new TestCase(); var context = new DataCollectionContext(new SessionId(Guid.NewGuid()), new TestExecId(Guid.NewGuid())); - Assert.ThrowsException(() => _mockDataCollectionEvents.Raise(x => x.TestCaseEnd += null, new TestCaseEndEventArgs(context, tc, TestOutcome.Passed))); + Assert.ThrowsExactly(() => _mockDataCollectionEvents.Raise(x => x.TestCaseEnd += null, new TestCaseEndEventArgs(context, tc, TestOutcome.Passed))); } public void SessionEndEventShouldThrowIfSessionStartEventtIsNotInvoked() @@ -278,7 +278,7 @@ public void SessionEndEventShouldThrowIfSessionStartEventtIsNotInvoked() eventLogDataCollector.Initialize(null, _mockDataCollectionEvents.Object, _mockDataCollectionSink, _mockDataCollectionLogger.Object, _dataCollectionEnvironmentContext); var tc = new TestCase(); - Assert.ThrowsException(() => _mockDataCollectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(_dataCollectionEnvironmentContext.SessionDataCollectionContext))); + Assert.ThrowsExactly(() => _mockDataCollectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(_dataCollectionEnvironmentContext.SessionDataCollectionContext))); } [TestMethod] @@ -322,7 +322,7 @@ public void WriteEventLogsShouldThrowExceptionIfThrownByFileHelper() XmlDocument expectedXmlDoc = new(); expectedXmlDoc.LoadXml(configurationString); _mockFileHelper.Setup(x => x.Exists(It.IsAny())).Throws(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => _eventLogDataCollector.WriteEventLogs( new List(), 20, diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogSessionContextTests.cs b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogSessionContextTests.cs index cb5e7dd4dd..82d693be93 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogSessionContextTests.cs +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogSessionContextTests.cs @@ -29,7 +29,7 @@ public EventLogSessionContextTests() public void CreateEventLogContainerStartIndexMapShouldCreateStartIndexMap() { _eventLogSessionContext = new EventLogSessionContext(_eventLogContainersMap); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerStartIndexMap["LogName"] == 2); + Assert.AreEqual(2, _eventLogSessionContext.EventLogContainerStartIndexMap["LogName"]); } [TestMethod] @@ -37,7 +37,7 @@ public void CreateEventLogContainerEndIndexMapShouldCreateEndIndexMap() { _eventLogSessionContext = new EventLogSessionContext(_eventLogContainersMap); _eventLogSessionContext.CreateEventLogContainerEndIndexMap(); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerEndIndexMap["LogName"] == 1); + Assert.AreEqual(1, _eventLogSessionContext.EventLogContainerEndIndexMap["LogName"]); } [TestMethod] @@ -47,8 +47,8 @@ public void CreateEventLogContainerShouldNotAddIndexEntriesIfEventLogContainerMa _eventLogSessionContext.CreateEventLogContainerStartIndexMap(); _eventLogSessionContext.CreateEventLogContainerEndIndexMap(); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerStartIndexMap.Count == 0); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerEndIndexMap.Count == 0); + Assert.IsEmpty(_eventLogSessionContext.EventLogContainerStartIndexMap); + Assert.IsEmpty(_eventLogSessionContext.EventLogContainerEndIndexMap); } [TestMethod] @@ -62,8 +62,8 @@ public void CreateEventLogContainerShouldCreateNegativeEndIndexIfLogEntriesAreEm _eventLogSessionContext.CreateEventLogContainerStartIndexMap(); _eventLogSessionContext.CreateEventLogContainerEndIndexMap(); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerStartIndexMap["DummyEventLog"] == 0); - Assert.IsTrue(_eventLogSessionContext.EventLogContainerEndIndexMap["DummyEventLog"] == -1); + Assert.AreEqual(0, _eventLogSessionContext.EventLogContainerStartIndexMap["DummyEventLog"]); + Assert.AreEqual(-1, _eventLogSessionContext.EventLogContainerEndIndexMap["DummyEventLog"]); } } diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogXmlWriterTests.cs b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogXmlWriterTests.cs index a9bd34e617..6b44270e73 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogXmlWriterTests.cs +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/EventLogXmlWriterTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Xml.Linq; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -33,19 +32,43 @@ public void WriteEventLogEntriesToXmlFileShouldWriteToXmlFile() [TestMethod] public void WriteEventLogEntriesToXmlFileShouldWriteLogEntryIfPresent() { - var eventLog = new EventLog("Application"); - var eventLogEntry = eventLog.Entries[eventLog.Entries.Count - 1]; + // Get any available event log entry. The Application log may be empty on + // clean/quiet machines, so fall back to the System log which always has + // entries on a running Windows machine. + var eventLogEntry = GetLastEventLogEntry("Application") ?? GetLastEventLogEntry("System"); + Assert.IsNotNull(eventLogEntry, "No event log entries found in Application or System logs."); var eventLogEntries = new List { eventLogEntry }; var mockFileHelper = new Mock(); EventLogXmlWriter.WriteEventLogEntriesToXmlFile(FileName, eventLogEntries, mockFileHelper.Object); - // Serialize the message in case it contains any special character such as <, >, &, which the XML writer would escape - // because otherwise the raw message and the message used to call WriteAllTextToFile won't match. E.g. - // api-version=2020-07-01&format=json in raw message, becomes - // api-version=2020-07-01&format=json in the xml file. - var serializedMessage = new XElement("t", eventLogEntry.Message).LastNode!.ToString(); + // Escape XML special characters (&, <, >) the same way XmlWriter does for + // text content so we can match the DataSet-serialized XML. We intentionally + // avoid XElement for this because XElement normalises lone \n to \r\n, which + // does not match the output of DataSet.WriteXml and causes false negatives. + var escapedMessage = eventLogEntry.Message + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">"); - mockFileHelper.Verify(x => x.WriteAllTextToFile(FileName, It.Is(str => str.Contains(serializedMessage)))); + mockFileHelper.Verify(x => x.WriteAllTextToFile(FileName, It.Is(str => str.Contains(escapedMessage)))); + } + + private static EventLogEntry? GetLastEventLogEntry(string logName) + { + try + { + var eventLog = new EventLog(logName); + if (eventLog.Entries.Count > 0) + { + return eventLog.Entries[eventLog.Entries.Count - 1]; + } + } + catch + { + // Log may be inaccessible; return null so callers can try another log. + } + + return null; } } diff --git a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests.csproj b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests.csproj index eea478833c..d8d3e66ebf 100644 --- a/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests.csproj +++ b/test/DataCollectors/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests/Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests.csproj @@ -5,6 +5,7 @@ true false + Exe @@ -18,7 +19,8 @@ - + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 6f5b8d7bf3..f98e859cc3 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,5 +3,21 @@ true + + $(NoWarn);MSTEST0001 + true + + true + Recommended + + + + diff --git a/test/Intent.Primitives/Intent.Primitives.csproj b/test/Intent.Primitives/Intent.Primitives.csproj index e251c464ae..5529e52d99 100644 --- a/test/Intent.Primitives/Intent.Primitives.csproj +++ b/test/Intent.Primitives/Intent.Primitives.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/test/Intent/Intent.csproj b/test/Intent/Intent.csproj index 45d0d9ea94..77abf82a4b 100644 --- a/test/Intent/Intent.csproj +++ b/test/Intent/Intent.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable enable diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings new file mode 100644 index 0000000000..823b5bb2d5 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings @@ -0,0 +1,5 @@ + + + true + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs index ad0641ea10..0f296846c5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.TestPlatform.AcceptanceTests; @@ -17,7 +18,8 @@ public void PassingNoArgumentsToVsTestConsoleShouldPrintHelpMessage(RunnerInfo r { SetTestEnvironment(_testEnvironment, runnerInfo); - InvokeVsTest(null); + // Don't add --diag, it changes the output and prevents help from showing. + InvokeVsTest(null, collectDiagnostics: false); //Check for help usage, description and arguments text. StdOutputContains("Usage: vstest.console.exe"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs index b36ace80bc..6416f01716 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs @@ -19,30 +19,27 @@ namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] -// this whole thing is complicated and depends on versions of OS and the target runtime -// keeping this for later [TestCategory("Windows-Review")] public class BlameDataCollectorTests : AcceptanceTestBase { - public const string NETCOREANDFX = "net462;net472;netcoreapp3.1"; - public const string NET60 = "net6.0"; + public const string NETCOREANDFX = "net462;net472;net8.0"; + public const string NET80 = "net8.0"; private readonly string _procDumpPath; public BlameDataCollectorTests() { - _procDumpPath = Path.Combine(_testEnvironment.PackageDirectory, @"procdump\0.0.1\bin"); + _procDumpPath = Path.Combine(_testEnvironment.LocalPackageDirectory, @"procdump\0.0.1\bin"); var procDumpExePath = Path.Combine(_procDumpPath, "procdump.exe"); if (!File.Exists(procDumpExePath)) { throw new InvalidOperationException($"Procdump path {procDumpExePath} does not exist. " + "It is possible that antivirus deleted it from your nuget cache. " - + "Delete the whole procdump folder in your nuget cache, and run build, or restore"); + + "Delete the whole procdump folder in your nuget cache, and run tests again."); } } [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void BlameDataCollectorShouldGiveCorrectTestCaseName(RunnerInfo runnerInfo) { @@ -58,17 +55,16 @@ public void BlameDataCollectorShouldGiveCorrectTestCaseName(RunnerInfo runnerInf } [TestMethod] - [Ignore] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [NetFullTargetFrameworkDataSource(useCoreRunner: false)] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void BlameDataCollectorShouldOutputDumpFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("SimpleTestProject3.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); - arguments = string.Concat(arguments, $" /Blame:CollectDump"); + arguments = string.Concat(arguments, $" /Blame:CollectDump;DumpType=mini"); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); arguments = string.Concat(arguments, " /testcasefilter:ExitWithStackoverFlow"); @@ -84,15 +80,14 @@ public void BlameDataCollectorShouldOutputDumpFile(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void BlameDataCollectorShouldNotOutputDumpFileWhenNoCrashOccurs(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); - arguments = string.Concat(arguments, $" /Blame:CollectDump"); + arguments = string.Concat(arguments, $" /Blame:CollectDump;DumpType=mini"); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); arguments = string.Concat(arguments, " /testcasefilter:PassingTest"); @@ -103,20 +98,20 @@ public void BlameDataCollectorShouldNotOutputDumpFileWhenNoCrashOccurs(RunnerInf InvokeVsTest(arguments, env); - Assert.IsFalse(StdOut.Contains(".dmp"), "it should not collect a dump, because nothing crashed"); + Assert.DoesNotContain(".dmp", StdOut, "it should not collect a dump, because nothing crashed"); } [TestMethod] [TestCategory("Windows-Review")] + // This tests .net runner and .net framework runner, together with .net framework testhost. [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] public void BlameDataCollectorShouldOutputDumpFileWhenNoCrashOccursButCollectAlwaysIsEnabled(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); - arguments = string.Concat(arguments, $" /Blame:CollectDump;CollectAlways=True"); + arguments = string.Concat(arguments, $" /Blame:CollectDump;DumpType=mini;CollectAlways=True"); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); arguments = string.Concat(arguments, " /testcasefilter:PassingTest"); @@ -127,20 +122,19 @@ public void BlameDataCollectorShouldOutputDumpFileWhenNoCrashOccursButCollectAlw InvokeVsTest(arguments, env); - StringAssert.Matches(StdOut, new Regex("\\.dmp"), "it should collect dump, even if nothing crashed"); + Assert.MatchesRegex(new Regex("\\.dmp"), StdOut, "it should collect dump, even if nothing crashed"); } [TestMethod] - [NetCoreRunner("net462;net472;netcoreapp3.1;net6.0")] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner("net462;net472;netcoreapp3.1;net6.0")] + [NetCoreRunner("net48;net10.0")] public void HangDumpOnTimeout(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("timeout.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectHangDump;HangDumpType=full;TestTimeout=3s"" /Diag:{TempDirectory.Path}/log.txt"); + // Don't reduce this, 10s is about the safe minimum to not have flakiness. + arguments = string.Concat(arguments, $@" /Blame:""CollectHangDump;HangDumpType=mini;TestTimeout=10s"" /Diag:{TempDirectory.Path}/log.txt"); var env = new Dictionary { @@ -153,10 +147,8 @@ public void HangDumpOnTimeout(RunnerInfo runnerInfo) } [TestMethod] - // net6.0 does not support dump on exit - [NetCoreRunner("net462;net472;netcoreapp3.1")] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner("net462;net472;netcoreapp3.1")] + // .NET testhost does not support dump on exit + [NetFullTargetFrameworkDataSource] public void CrashDumpWhenThereIsNoTimeout(RunnerInfo runnerInfo) { @@ -164,7 +156,7 @@ public void CrashDumpWhenThereIsNoTimeout(RunnerInfo runnerInfo) var assemblyPaths = GetAssetFullPath("timeout.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=full;CollectAlways=true;CollectHangDump"""); + arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=mini;CollectAlways=true;CollectHangDump;HangDumpType=mini"""); var env = new Dictionary { @@ -177,10 +169,8 @@ public void CrashDumpWhenThereIsNoTimeout(RunnerInfo runnerInfo) } [TestMethod] - // net6.0 does not support dump on exit - [NetCoreRunner("net462;net472;netcoreapp3.1")] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner("net462;net472;netcoreapp3.1")] + // .NET tfms do not support dump on exit, but runner does + [NetFullTargetFrameworkDataSource] public void CrashDumpOnExit(RunnerInfo runnerInfo) { @@ -188,7 +178,7 @@ public void CrashDumpOnExit(RunnerInfo runnerInfo) var assemblyPaths = GetAssetFullPath("timeout.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=full;CollectAlways=true"""); + arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=mini;CollectAlways=true"""); var env = new Dictionary { @@ -201,16 +191,14 @@ public void CrashDumpOnExit(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreRunner("net462;net472;netcoreapp3.1;net6.0")] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner("net462;net472;netcoreapp3.1;net6.0")] + [NetCoreRunner("net48;net10.0")] public void CrashDumpOnStackOverflow(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("crash.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=full"""); + arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=mini"""); var env = new Dictionary { @@ -223,32 +211,29 @@ public void CrashDumpOnStackOverflow(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreRunner(NET60)] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner(NET50)] + [NetCoreRunner(NET80)] public void CrashDumpChildProcesses(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("child-crash.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=full"""); + arguments = string.Concat(arguments, $@" /Blame:""CollectDump;DumpType=mini"""); InvokeVsTest(arguments); ValidateDump(2); } [TestMethod] - [NetCoreRunner("net462;net472;netcoreapp3.1;net6.0")] - // should make no difference, keeping for easy debug - // [NetFrameworkRunner("net462;net472;netcoreapp3.1;net6.0")] + [NetCoreRunner("net48;net10.0")] public void HangDumpChildProcesses(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPaths = GetAssetFullPath("child-hang.dll"); var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); - arguments = string.Concat(arguments, $@" /Blame:""CollectHangDump;HangDumpType=full;TestTimeout=15s"""); + // Don't reduce this, 10s is about the safe minimum to not have flakiness. + arguments = string.Concat(arguments, $@" /Blame:""CollectHangDump;HangDumpType=mini;TestTimeout=10s"""); InvokeVsTest(arguments); ValidateDump(2); @@ -256,11 +241,13 @@ public void HangDumpChildProcesses(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] + [DoNotParallelize] // Installs/uninstalls procdump as machine-wide postmortem debugger via HKLM registry. [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void BlameDataCollectorAeDebuggerShouldCollectDump(RunnerInfo runnerInfo) { - if (!IsAdministrator()) + // For convenience skip locally, but never skip in CI. If this cannot pass in CI we are not testing it at all. + if (!IsCI && !IsAdministrator()) { Assert.Inconclusive("User is not administrator, cannot setup the debugger, and cannot check the functionality."); } @@ -275,7 +262,7 @@ public void BlameDataCollectorAeDebuggerShouldCollectDump(RunnerInfo runnerInfo) out string standardTestOutput, out string standardErrorTestOutput, out int _); - Assert.IsTrue(standardErrorTestOutput.Trim().Length == 0); + Assert.AreEqual(0, standardErrorTestOutput.Trim().Length); // Run test under postmortem monitoring var assemblyPaths = GetAssetFullPath("BlameUnitTestProject.dll"); @@ -289,12 +276,12 @@ public void BlameDataCollectorAeDebuggerShouldCollectDump(RunnerInfo runnerInfo) out standardTestOutput, out standardErrorTestOutput, out int _); - Assert.IsTrue(standardErrorTestOutput.Trim().Length == 0); + Assert.AreEqual(0, standardErrorTestOutput.Trim().Length); // We cannot be precise here procdump is at machine level so we can have more than one dump and not only the one for our test // We look for "at least" one dump file, is the best we can do without locking all tests. - Assert.IsTrue(Directory.GetFiles(TempDirectory.Path, "*.dmp", SearchOption.AllDirectories) - .Where(x => Path.GetFileNameWithoutExtension(x).StartsWith("testhost")).Count() > 0); + Assert.IsNotEmpty(Directory.GetFiles(TempDirectory.Path, "*.dmp", SearchOption.AllDirectories) + .Where(x => Path.GetFileNameWithoutExtension(x).StartsWith("testhost"))); } private static bool IsAdministrator() diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Build.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Build.cs index 8fab97215f..d425fd90a5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Build.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Build.cs @@ -1,369 +1,31 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text.RegularExpressions; -using System.Runtime.InteropServices; -using System.Xml.Linq; - using Microsoft.TestPlatform.TestUtilities; -using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; - namespace Microsoft.TestPlatform.Acceptance.IntegrationTests; [TestClass] -public class Build : IntegrationTestBase +public static class Build { - private static readonly string Root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..")); - private static readonly string DotnetDir = Path.GetFullPath(Path.Combine(Root, ".dotnet")); - private static readonly string Dotnet = Path.GetFullPath(Path.Combine(Root, ".dotnet", OSUtils.IsWindows ? "dotnet.exe" : "dotnet")); - [AssemblyInitialize] - public static void AssemblyInitialize(TestContext _) - { - SetDotnetEnvironment(); - BuildTestAssetsAndUnzipPackages(); - BuildTestAssetsCompatibility(); - CopyAndPatchDotnet(); - } - - private static void SetDotnetEnvironment() - { - // We need to set this to point to our dotnet, because we cannot guarantee what is installed on the machine in Program Files, - // and we install all the required SDKs and runtimes ourselves in Build.cmd. -#pragma warning disable RS0030 // Do not used banned APIs - Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); - Environment.SetEnvironmentVariable("DOTNET_ROOT", DotnetDir); - Environment.SetEnvironmentVariable("DOTNET_ROOT(x86)", Path.Combine(DotnetDir, "dotnet-sdk-x86")); - Environment.SetEnvironmentVariable("PATH", $"{DotnetDir};{Environment.GetEnvironmentVariable("PATH")}"); -#pragma warning restore RS0030 // Do not used banned APIs - } - - private static void CopyAndPatchDotnet() - { - var patchedDotnetDir = Path.GetFullPath(Path.Combine(Root, "artifacts", "tmp", ".dotnet")); - - // Copy dotnet. - DirectoryUtils.CopyDirectory(new DirectoryInfo(DotnetDir), new DirectoryInfo(patchedDotnetDir)); - - // Copy target file and build task dll into it. - var netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; - var packageName = $"Microsoft.TestPlatform.Build.{netTestSdkVersion}.nupkg"; - var packagePath = Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.PublishDirectory, packageName)); - - // e.g. artifacts\tmp\.dotnet\sdk\ - var sdkDirectory = Path.Combine(patchedDotnetDir, "sdk"); - // e.g. artifacts\tmp\.dotnet\sdk\8.0.100-preview.6.23330.14 - var dotnetSdkDirectory = Directory.GetDirectories(sdkDirectory).Single(); - DirectoryUtils.CopyDirectory(Path.Combine(packagePath, "lib", "netstandard2.0"), dotnetSdkDirectory); - DirectoryUtils.CopyDirectory(Path.Combine(packagePath, "runtimes", "any", "native"), dotnetSdkDirectory); - } - - private static void BuildTestAssetsCompatibility() - { - var testAssetsDir = Path.GetFullPath(Path.Combine(Root, "test", "TestAssets")); - var nugetCache = Path.GetFullPath(Path.Combine(Root, ".packages")); - - var generated = Path.GetFullPath(Path.Combine(Root, "artifacts", "tmp", "GeneratedTestAssets")); - var generatedSln = Path.Combine(generated, "CompatibilityTestAssets.sln"); - - var dependenciesPath = Path.Combine(Root, "eng", "Versions.props"); - var dependenciesXml = XDocument.Load(dependenciesPath); - var propsNode = dependenciesXml!.Element("Project")! - .Descendants("PropertyGroup") - .Where(p => p.Attributes().Any(a => a.Name == "Label" && a.Value == "VSTest test settings")) - .Single()!; - - var cacheId = new OrderedDictionary(); - - // Restore previous versions of TestPlatform (for vstest.console.exe), and TestPlatform.CLI (for vstest.console.dll). - // These properties are coming from TestPlatform.Dependencies.props. - var vstestConsoleVersionProperties = new[] { - "VSTestConsoleLatestVersion", - "VSTestConsoleLatestPreviewVersion", - "VSTestConsoleLatestStableVersion", - "VSTestConsoleRecentStableVersion", - "VSTestConsoleMostDownloadedVersion", - "VSTestConsolePreviousStableVersion", - "VSTestConsoleLegacyStableVersion", - }; - - var projects = new[] - { - Path.Combine(Root, "test", "TestAssets", "MSTestProject1", "MSTestProject1.csproj"), - Path.Combine(Root, "test", "TestAssets", "MSTestProject2", "MSTestProject2.csproj"), - }; - - var msTestVersionProperties = new[] { - "MSTestFrameworkLatestPreviewVersion", - "MSTestFrameworkLatestStableVersion", - "MSTestFrameworkRecentStableVersion", - "MSTestFrameworkMostDownloadedVersion", - "MSTestFrameworkPreviousStableVersion", - "MSTestFrameworkLegacyStableVersion", - }; - - var nugetFeeds = GetNugetSourceParameters(Root); - - // We use the same version properties for NET.Test.Sdk as for VSTestConsole, for now. - foreach (var sdkPropertyName in vstestConsoleVersionProperties) - { - string? netTestSdkVersion; - if (sdkPropertyName == "VSTestConsoleLatestVersion") - { - netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; - } - else - { - netTestSdkVersion = propsNode.Element(sdkPropertyName!)!.Value; - } - - if (netTestSdkVersion.IsNullOrWhiteSpace()) - { - throw new InvalidOperationException($"{nameof(netTestSdkVersion)} should contain version of the package to restore, but it is empty."); - } - - cacheId[sdkPropertyName] = netTestSdkVersion; - - var netTestSdkVersionDir = netTestSdkVersion.TrimStart('[').TrimEnd(']'); - if (Directory.Exists(Path.Combine(nugetCache, "microsoft.testplatform", netTestSdkVersionDir)) && Directory.Exists(Path.Combine(nugetCache, "microsoft.testplatform.cli", netTestSdkVersionDir))) - { - continue; - } - - // We restore this project to download TestPlatform and TestPlatform.CLI nugets, into our package cache. - // Using nuget.exe install errors out in various weird ways. - var tools = Path.Combine(Root, "test", "TestAssets", "Tools", "Tools.csproj"); - ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{tools}" -p:PackageVersion={netTestSdkVersionDir} """); - } - - foreach (var propertyName in msTestVersionProperties) - { - var mstestVersion = propsNode.Element(propertyName)!.Value; - cacheId[propertyName] = mstestVersion; - } - - cacheId["projects"] = projects; - - var cacheIdText = JsonConvert.SerializeObject(cacheId, Formatting.Indented); - - var currentCacheId = File.Exists(Path.Combine(generated, "checksum.json")) ? File.ReadAllText(Path.Combine(generated, "checksum.json")) : null; - - var rebuild = true; - if (cacheIdText == currentCacheId) - { - // Cache is up-to-date, just rebuilding solution. - ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{generatedSln}" """); - ExecuteApplication2(Dotnet, $"build {generatedSln} --no-restore --configuration {IntegrationTestEnvironment.BuildConfiguration} -v:minimal"); - rebuild = false; - } - - if (rebuild) - { - if (Directory.Exists(generated)) - { - Directory.Delete(generated, recursive: true); - } - - Directory.CreateDirectory(generated); - - // Fix repo root, we are 1 level deeper than in the test/TestAssets location. - var buildPropsContent = File.ReadAllText(Path.Combine(testAssetsDir, "Directory.Build.props")); - buildPropsContent = Regex.Replace(buildPropsContent, "", "$(MSBuildThisFileDirectory)../../../"); - File.WriteAllText(Path.Combine(generated, "Directory.Build.props"), buildPropsContent); - - File.Copy(Path.Combine(testAssetsDir, "Directory.Build.targets"), Path.Combine(generated, "Directory.Build.targets")); - - ExecuteApplication2(Dotnet, $"""new sln --name CompatibilityTestAssets --output "{generated}"""); - - var projectsToAdd = new List(); - foreach (var project in projects) - { - var projectName = Path.GetFileName(project); - var projectBaseName = Path.GetFileNameWithoutExtension(projectName); - var projectDir = Path.GetDirectoryName(project)!; - var projectItems = Directory.GetFiles(projectDir, "*", SearchOption.AllDirectories).Where(p => - !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") - && !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") - // Is a file, and not a directory. - && File.Exists(p)).ToList(); - - foreach (var sdkPropertyName in vstestConsoleVersionProperties) - { - string netTestSdkVersion; - if (sdkPropertyName == "VSTestConsoleLatestVersion") - { - netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; - } - else - { - netTestSdkVersion = propsNode.Element(sdkPropertyName!)!.Value; - } - - var dirNetTestSdkVersion = netTestSdkVersion.TrimStart('[').TrimEnd(']'); - var dirNetTestSdkPropertyName = sdkPropertyName - .Replace("Framework", "") - .Replace("Version", "") - .Replace("VSTestConsole", "NETTestSdk"); - - foreach (var propertyName in msTestVersionProperties) - { - var mstestVersion = propsNode.Element(propertyName)!.Value; - - var dirMSTestVersion = mstestVersion.TrimStart('[').TrimEnd(']'); - var dirMSTestPropertyName = propertyName - .Replace("Framework", "") - .Replace("Version", ""); - - // Do not make this a folder structure, it will break the relative reference to scripts\build\TestAssets.props that we have in the project, - // because the relative path will be different. - // It would be nice to use fully descriptive name but it is too long, hash the versions instead. - // $compatibilityProjectDir = "$generated/$projectBaseName--$dirNetTestSdkPropertyName-$dirNetTestSdkVersion--$dirMSTestPropertyName-$dirMSTestVersion" - - var versions = $"{dirNetTestSdkPropertyName}-{dirNetTestSdkVersion}--{dirMSTestPropertyName}-{dirMSTestVersion}"; - var hash = IntegrationTestEnvironment.GetPathHash(versions); - var projectShortName = $"{projectBaseName}--{hash}"; - var compatibilityProjectDir = Path.Combine(generated, projectShortName); - - Directory.CreateDirectory(compatibilityProjectDir); - - foreach (var projectItem in projectItems) - { - var relativePath = projectItem.Replace(projectDir, "").TrimStart(Path.DirectorySeparatorChar); - var fullPath = Path.Combine(compatibilityProjectDir, relativePath); - File.Copy(projectItem, fullPath); - } - - var compatibilityCsproj = Directory.GetFiles(compatibilityProjectDir, "*.csproj", SearchOption.AllDirectories); - - if (!compatibilityCsproj.Any()) - { - throw new InvalidOperationException($"No .csproj file was found in directory {compatibilityProjectDir}."); - } - - if (compatibilityCsproj.Length > 1) - { - throw new InvalidOperationException($"More than 1 .csproj file was found in directory {compatibilityProjectDir}."); - } - - var csproj = compatibilityCsproj.Single(); - - var content = File.ReadAllText(csproj); - // We replace the content rather than using MSBuild properties, because that allows us to create a solution with - // many versions of the package, and let msbuild figure out how to correctly restore and build in parallel. If we did use - // MSBuild properties we would have to build each combination one by one in sequence. - var newContent = content - .Replace("$(MSTestTestFrameworkVersion)", mstestVersion) - .Replace("$(MSTestTestAdapterVersion)", mstestVersion) - .Replace("$(PackageVersion)", netTestSdkVersion); - File.WriteAllText(csproj, newContent); - - var uniqueCsprojName = Path.Combine(compatibilityProjectDir, $"{projectShortName}.csproj"); - File.Move(csproj, uniqueCsprojName); - projectsToAdd.Add(uniqueCsprojName); - } - } - } - - ExecuteApplication2(Dotnet, $"""sln {generatedSln} add "{string.Join("\" \"", projectsToAdd)}" """); - - ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{generatedSln}" """); - ExecuteApplication2(Dotnet, $"""build --no-restore --configuration {IntegrationTestEnvironment.BuildConfiguration} "{generatedSln}" """); - - File.WriteAllText(Path.Combine(generated, "checksum.json"), cacheIdText); - } - } - - private static string GetNugetSourceParameters(string root) - { - string nugetConfigPath = Path.Combine(root, "NuGet.config"); - var nugetConfig = XDocument.Load(nugetConfigPath); - - var feeds = nugetConfig! - .Element("configuration")! - .Element("packageSources")! - .Descendants("add") - .Where(p => p.Attributes().Any(a => a.Name == "key")) - .SelectMany(p => p.Attributes()) - .Where(a => a.Name == "value") - .Select(a => a.Value) - .ToList(); - - if (feeds.Count == 0) - { - throw new InvalidOperationException($"No feeds were loaded from '{nugetConfigPath}'."); - } - - // --source "value1" --source "value2", including quotes - var parameters = $"""--source "{string.Join("\" --source \"", feeds)}" """; - - return parameters; - } - - protected static void ExecuteApplication2(string path, string? args, - Dictionary? environmentVariables = null, string? workingDirectory = null) + public static void AssemblyInitialize(TestContext testContext) { - - ExecuteApplication(path, args, out string stdOut, out string stdError, out int exitCode, environmentVariables, workingDirectory); - if (exitCode != 0) - { - throw new InvalidOperationException( - $""" - Executing '{path} {args}' failed. - STDOUT: {stdOut}, - STDERR: {stdError} - """); - } + // Increase the ThreadPool minimum threads to avoid starvation during parallel test + // execution. Each test class spawns vstest.console and testhost processes — the async + // process management and I/O redirection callbacks need ThreadPool threads to complete + // promptly. + var additionalThreadsCount = System.Environment.ProcessorCount * 4; + System.Threading.ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); + System.Threading.ThreadPool.SetMinThreads(workerThreads + additionalThreadsCount, completionPortThreads + additionalThreadsCount); + + IntegrationTestBuild.BuildTestAssetsForIntegrationTests(testContext); } - private static void BuildTestAssetsAndUnzipPackages() + [AssemblyCleanup] + public static void AssemblyCleanup() { - var testAssets = Path.GetFullPath(Path.Combine(Root, "test", "TestAssets", "TestAssets.sln")); - - var nugetCache = Path.GetFullPath(Path.Combine(Root, ".packages")); - var nugetFeeds = GetNugetSourceParameters(Root); - var netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; - - ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" -p:PackageVersion={netTestSdkVersion} "{testAssets}" """); - ExecuteApplication2(Dotnet, $"""build "{testAssets}" --configuration {IntegrationTestEnvironment.BuildConfiguration} --no-restore"""); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Build special project written in IL. - // This project is used on Windows only Tests. On non-Windows the build fails with: "IlasmToolPath must be set in order to build ilproj's outside of Windows.". - var cilProject = Path.Combine(Root, "test", "TestAssets", "CILProject", "CILProject.proj"); - var binPath = Path.Combine(Root, "artifacts", "bin", "TestAssets", "CILProject", IntegrationTestEnvironment.BuildConfiguration, "net462"); - ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{cilProject}" """); - ExecuteApplication2(Dotnet, $"""build "{cilProject}" --configuration {IntegrationTestEnvironment.BuildConfiguration} --no-restore --output {binPath}"""); - } - - var packagesToExtract = new[] - { - $"Microsoft.TestPlatform.{netTestSdkVersion}.nupkg", - $"Microsoft.TestPlatform.CLI.{netTestSdkVersion}.nupkg", - $"Microsoft.TestPlatform.Build.{netTestSdkVersion}.nupkg", - $"Microsoft.CodeCoverage.{netTestSdkVersion}.nupkg", - $"Microsoft.TestPlatform.Portable.{netTestSdkVersion}.nupkg", - }; - - foreach (var packageName in packagesToExtract) - { - var packagePath = Path.Combine(IntegrationTestEnvironment.LocalPackageSource, packageName); - var unzipPath = Path.Combine(IntegrationTestEnvironment.PublishDirectory, packageName); - if (Directory.Exists(unzipPath)) - { - Directory.Delete(unzipPath, recursive: true); - } - - ZipFile.ExtractToDirectory(packagePath, unzipPath); - } + IntegrationTestBuild.CleanupTestAssets(); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CUITTest.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CUITTest.cs deleted file mode 100644 index 96f8da27c6..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CUITTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.TestPlatform.TestUtilities; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.TestPlatform.AcceptanceTests; - -[TestClass] -[TestCategory("Windows-Review")] -public class CuitTest : AcceptanceTestBase -{ - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - public void CuitRunAllTests(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - CuitRunAll(runnerInfo); - } - - private void CuitRunAll(RunnerInfo runnerInfo) - { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("CUIT tests are not supported with .NET Core runner."); - return; - } - - var assemblyAbsolutePath = _testEnvironment.GetTestAsset("CUITTestProject.dll", "net462"); - var arguments = PrepareArguments(assemblyAbsolutePath, string.Empty, string.Empty, FrameworkArgValue, resultsDirectory: TempDirectory.Path); - arguments += " -- RunConfiguration.TargetPlatform=x86"; - - InvokeVsTest(arguments); - ValidateSummaryStatus(1, 0, 0); - } -} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs index 9bad647034..e996c95a06 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs @@ -310,7 +310,7 @@ private static void AssertSkippedMethod(CoverageReport coverageReport) Assert.IsNotNull(module); var coverage = double.Parse(module.BlockCoverage, CultureInfo.InvariantCulture); - Assert.IsTrue(coverage > ExpectedMinimalModuleCoverage); + Assert.IsGreaterThan(ExpectedMinimalModuleCoverage, coverage); var testSignFunction = module.SkippedFunctions.FirstOrDefault(f => f.Name.Equals("TestSign()")); Assert.IsNotNull(testSignFunction); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs new file mode 100644 index 0000000000..bd59455e93 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Linq; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +[TestClass] +public class CreateNoNewWindowTests : AcceptanceTestBase +{ + [TestMethod] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + public void WhenCreateNoNewWindowIsFalse_DiagShowsCreateNoWindowFalse(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); + + var runsettingsXml = "" + + "false" + + ""; + var runsettingsPath = GetRunsettingsFilePath(runsettingsXml); + var diagLogPath = Path.Combine(TempDirectory.Path, "logs", "log.txt"); + + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); + arguments += $" /settings:{runsettingsPath} /Diag:{diagLogPath}"; + + InvokeVsTest(arguments); + + var diagLogs = GetDiagLogContents(); + Assert.Contains("CreateNoWindow=False", diagLogs, + "Expected 'CreateNoWindow=False' in diag logs when CreateNoNewWindow is set to false."); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + public void WhenCreateNoNewWindowIsTrue_DiagShowsCreateNoWindowTrue(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); + + var runsettingsXml = "" + + "true" + + ""; + var runsettingsPath = GetRunsettingsFilePath(runsettingsXml); + var diagLogPath = Path.Combine(TempDirectory.Path, "logs", "log.txt"); + + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); + arguments += $" /settings:{runsettingsPath} /Diag:{diagLogPath}"; + + InvokeVsTest(arguments); + + var diagLogs = GetDiagLogContents(); + Assert.Contains("CreateNoWindow=True", diagLogs, + "Expected 'CreateNoWindow=True' in diag logs when CreateNoNewWindow is set to true."); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + public void WhenCreateNoNewWindowIsNotSet_DefaultIsTrue(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); + var diagLogPath = Path.Combine(TempDirectory.Path, "logs", "log.txt"); + + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, string.Empty, runnerInfo.InIsolationValue); + arguments += $" /Diag:{diagLogPath}"; + + InvokeVsTest(arguments); + + var diagLogs = GetDiagLogContents(); + Assert.Contains("CreateNoWindow=True", diagLogs, + "Expected 'CreateNoWindow=True' in diag logs when CreateNoNewWindow is not set (default)."); + } + + private string GetRunsettingsFilePath(string runsettingsXml) + { + var runsettingsPath = Path.Combine(TempDirectory.Path, "test.runsettings"); + File.WriteAllText(runsettingsPath, runsettingsXml); + return runsettingsPath; + } + + private string GetDiagLogContents() + { + var logsDir = Path.Combine(TempDirectory.Path, "logs"); + if (!Directory.Exists(logsDir)) + { + return string.Empty; + } + + var logFiles = Directory.GetFiles(logsDir, "*.txt"); + + return string.Join("\n", logFiles.Select(File.ReadAllText)); + } +} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs index 9698a81144..d355b8f63b 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs @@ -29,7 +29,7 @@ public void ExecuteTestsWithDataCollection(RunnerInfo runnerInfo) var assemblyPaths = GetAssetFullPath("SimpleTestProject2.dll"); string runSettings = GetRunsettingsFilePath(TempDirectory.Path); string diagFileName = Path.Combine(TempDirectory.Path, "diaglog.txt"); - var extensionsPath = Path.GetDirectoryName(GetTestDllForFramework("OutOfProcDataCollector.dll", runnerInfo.RunnerFramework)); + var extensionsPath = Path.GetDirectoryName(GetTestDllForFramework("OutOfProcDataCollector.dll", "netstandard2.0")); var arguments = PrepareArguments(assemblyPaths, null, runSettings, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); arguments = string.Concat(arguments, $" /Diag:{diagFileName}", $" /TestAdapterPath:{extensionsPath}"); @@ -53,7 +53,7 @@ public void ExecuteTestsWithDataCollectionUsingCollectArgument(RunnerInfo runner var assemblyPaths = GetAssetFullPath("SimpleTestProject2.dll"); string diagFileName = Path.Combine(TempDirectory.Path, "diaglog.txt"); - var extensionsPath = Path.GetDirectoryName(GetTestDllForFramework("OutOfProcDataCollector.dll", runnerInfo.RunnerFramework)); + var extensionsPath = Path.GetDirectoryName(GetTestDllForFramework("OutOfProcDataCollector.dll", "netstandard2.0")); var arguments = PrepareArguments(assemblyPaths, null, null, FrameworkArgValue, runnerInfo.InIsolationValue, TempDirectory.Path); arguments = string.Concat(arguments, $" /Diag:{diagFileName}", $" /Collect:SampleDataCollector", $" /TestAdapterPath:{extensionsPath}"); @@ -140,7 +140,7 @@ public void DataCollectorAttachmentProcessor(RunnerInfo runnerInfo) while (!streamReader.EndOfStream) { string? line = streamReader.ReadLine(); - Assert.IsTrue(line!.StartsWith("SessionEnded_Handler_")); + Assert.StartsWith("SessionEnded_Handler_", line!); fileContent.Add(line); } } @@ -152,8 +152,8 @@ public void DataCollectorAttachmentProcessor(RunnerInfo runnerInfo) foreach (var dataCollectorLogFile in dataCollectorsLogs) { string dataCollectorLog = File.ReadAllText(dataCollectorLogFile); - Assert.IsTrue(dataCollectorLog.Contains("MetadataReaderExtensionsHelper: Valid extension found: extension type 'DataCollector' identifier 'my://sample/datacollector' implementation 'AttachmentProcessorDataCollector.SampleDataCollectorV1' version '1'")); - Assert.IsTrue(dataCollectorLog.Contains("MetadataReaderExtensionsHelper: Valid extension found: extension type 'DataCollector' identifier 'my://sample/datacollector' implementation 'AttachmentProcessorDataCollector.SampleDataCollectorV2' version '2'")); + Assert.Contains("MetadataReaderExtensionsHelper: Valid extension found: extension type 'DataCollector' identifier 'my://sample/datacollector' implementation 'AttachmentProcessorDataCollector.SampleDataCollectorV1' version '1'", dataCollectorLog); + Assert.Contains("MetadataReaderExtensionsHelper: Valid extension found: extension type 'DataCollector' identifier 'my://sample/datacollector' implementation 'AttachmentProcessorDataCollector.SampleDataCollectorV2' version '2'", dataCollectorLog); Assert.IsTrue(Regex.IsMatch(dataCollectorLog, @"GetTestExtensionFromType: Discovered multiple test extensions with identifier data 'my://sample/datacollector' and type 'AttachmentProcessorDataCollector\.SampleDataCollectorV1, AttachmentProcessorDataCollector, Version=.*, Culture=neutral, PublicKeyToken=null' inside file '.*AttachmentProcessorDataCollector\.dll'; keeping the first one 'AttachmentProcessorDataCollector\.SampleDataCollectorV2, AttachmentProcessorDataCollector, Version=.*, Culture=neutral, PublicKeyToken=null'\.")); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorAttachmentsProcessorsFactoryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorAttachmentsProcessorsFactoryTests.cs index 5a7284c7cf..91d5102608 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorAttachmentsProcessorsFactoryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorAttachmentsProcessorsFactoryTests.cs @@ -52,18 +52,18 @@ public void Create_ShouldReturnListOfAttachmentProcessors() var dataCollectorAttachmentsProcessors = _dataCollectorAttachmentsProcessorsFactory.Create(invokedDataCollectors.ToArray(), null); // assert - Assert.AreEqual(3, dataCollectorAttachmentsProcessors.Length); + Assert.HasCount(3, dataCollectorAttachmentsProcessors); - Assert.AreEqual(1, dataCollectorAttachmentsProcessors.Count(x => x.FriendlyName == "Sample")); - Assert.AreEqual(1, dataCollectorAttachmentsProcessors.Count(x => x.FriendlyName == "SampleData3")); - Assert.AreEqual(1, dataCollectorAttachmentsProcessors.Count(x => x.FriendlyName == "Code Coverage")); + Assert.ContainsSingle(dataCollectorAttachmentsProcessors.Where(x => x.FriendlyName == "Sample")); + Assert.ContainsSingle(dataCollectorAttachmentsProcessors.Where(x => x.FriendlyName == "SampleData3")); + Assert.ContainsSingle(dataCollectorAttachmentsProcessors.Where(x => x.FriendlyName == "Code Coverage")); Assert.AreEqual(typeof(DataCollectorAttachmentProcessor).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); Assert.AreEqual(typeof(DataCollectorAttachmentProcessor2).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[1].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); Assert.AreEqual(typeof(CodeCoverageDataAttachmentsHandler).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[2].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void Create_EmptyOrNullInvokedDataCollector_ShouldReturnCodeCoverageDataAttachmentsHandler(bool empty) @@ -72,7 +72,7 @@ public void Create_EmptyOrNullInvokedDataCollector_ShouldReturnCodeCoverageDataA var dataCollectorAttachmentsProcessors = _dataCollectorAttachmentsProcessorsFactory.Create(empty ? [] : null, null); //assert - Assert.AreEqual(1, dataCollectorAttachmentsProcessors.Length); + Assert.ContainsSingle(dataCollectorAttachmentsProcessors); Assert.AreEqual(typeof(CodeCoverageDataAttachmentsHandler).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); } @@ -91,7 +91,7 @@ public void Create_ShouldNotFailIfWrongDataCollectorAttachmentProcessor() var dataCollectorAttachmentsProcessors = _dataCollectorAttachmentsProcessorsFactory.Create(invokedDataCollectors.ToArray(), null); // assert - Assert.AreEqual(1, dataCollectorAttachmentsProcessors.Length); + Assert.ContainsSingle(dataCollectorAttachmentsProcessors); Assert.AreEqual(typeof(CodeCoverageDataAttachmentsHandler).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); } @@ -108,7 +108,7 @@ public void Create_ShouldAddTwoTimeCodeCoverageDataAttachmentsHandler() var dataCollectorAttachmentsProcessors = _dataCollectorAttachmentsProcessorsFactory.Create(invokedDataCollectors.ToArray(), null); // assert - Assert.AreEqual(2, dataCollectorAttachmentsProcessors.Length); + Assert.HasCount(2, dataCollectorAttachmentsProcessors); Assert.AreEqual(typeof(DataCollectorAttachmentProcessor).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); Assert.AreEqual(typeof(CodeCoverageDataAttachmentsHandler).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[1].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); } @@ -141,7 +141,7 @@ public void Create_ShouldLoadOrderingByFilePath() var dataCollectorAttachmentsProcessors = _dataCollectorAttachmentsProcessorsFactory.Create(invokedDataCollectors.ToArray(), null); // assert - Assert.AreEqual(2, dataCollectorAttachmentsProcessors.Length); + Assert.HasCount(2, dataCollectorAttachmentsProcessors); Assert.IsTrue(Regex.IsMatch(dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName!, @"AttachmentProcessorDataCollector\.SampleDataCollectorAttachmentProcessor, AttachmentProcessorDataCollector, Version=.*, Culture=neutral, PublicKeyToken=null")); Assert.AreEqual(Path.Combine(version2, Path.GetFileName(dataCollectorFilePath)), dataCollectorAttachmentsProcessors[0].DataCollectorAttachmentProcessorInstance.GetType().Assembly.Location); Assert.AreEqual(typeof(CodeCoverageDataAttachmentsHandler).AssemblyQualifiedName, dataCollectorAttachmentsProcessors[1].DataCollectorAttachmentProcessorInstance.GetType().AssemblyQualifiedName); @@ -152,7 +152,7 @@ private static string GetTestAssetsFolder() string current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; while (true) { - if (File.Exists(Path.Combine(current, "TestPlatform.sln"))) + if (File.Exists(Path.Combine(current, "TestPlatform.slnx"))) { string testAssetsPath = Path.Combine(current, @"test/TestAssets"); Assert.IsTrue(Directory.Exists(testAssetsPath), $"Directory not found '{testAssetsPath}'"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs index 82891c9c76..bef9bd5c77 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs @@ -25,7 +25,7 @@ public void RunCoverletCoverage() } // We use netcoreapp runner - // "...\vstest\tools\dotnet\dotnet.exe "...\vstest\artifacts\Debug\netcoreapp3.1\vstest.console.dll" --collect:"XPlat Code Coverage" ... + // "...\vstest\tools\dotnet\dotnet.exe "...\vstest\artifacts\Debug\net8.0\vstest.console.dll" --collect:"XPlat Code Coverage" ... _testEnvironment.RunnerFramework = CoreRunnerFramework; var resultsDir = new TempDirectory(); @@ -41,15 +41,15 @@ public void RunCoverletCoverage() // This assert check that we're sure that we've updated collector setting code base with full path, // otherwise without "custom coverlet code" inside ProxyExecutionManager coverlet dll won't be resolved inside testhost. var log = Directory.GetFiles(logPathDirectory, $"coverletcoverage.{logId}.log").Single(); - Assert.IsTrue(File.ReadAllText(log).Contains("CoverletDataCollector in-process codeBase path")); + Assert.Contains("CoverletDataCollector in-process codeBase path", File.ReadAllText(log)); // Verify out-of-proc coverlet collector load var dataCollectorLog = Directory.GetFiles(logPathDirectory, $"coverletcoverage.{logId}.datacollector*log").Single(); - Assert.IsTrue(File.ReadAllText(dataCollectorLog).Contains("[coverlet]Initializing CoverletCoverageDataCollector")); + Assert.Contains("[coverlet]Initializing CoverletCoverageDataCollector", File.ReadAllText(dataCollectorLog)); // Verify in-proc coverlet collector load var hostLog = Directory.GetFiles(logPathDirectory, $"coverletcoverage.{logId}.host*log").Single(); - Assert.IsTrue(File.ReadAllText(hostLog).Contains("[coverlet]Initialize CoverletInProcDataCollector")); + Assert.Contains("[coverlet]Initialize CoverletInProcDataCollector", File.ReadAllText(hostLog)); // Verify default coverage file is generated StdOutputContains("coverage.cobertura.xml"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs index abff214c4a..d6578bb9d8 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs @@ -25,6 +25,6 @@ public void RunningTestWithAFailingDebugAssertDoesNotCrashTheHostingProcess(Runn // this will have failed tests when our trace listener works and crash the testhost process when it does not // because crashing processes is what a failed TPDebug.Assert does by default, unless you have a debugger attached ValidateSummaryStatus(passed: 4, failed: 4, 0); - StringAssert.Contains(StdOut, "threw exception: Microsoft.VisualStudio.TestPlatform.TestHost.DebugAssertException:"); + Assert.Contains("threw exception: Microsoft.VisualStudio.TestPlatform.TestHost.DebugAssertException:", StdOut); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs index cde8909ee8..db1d2a9e00 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs @@ -67,93 +67,6 @@ public void CPPRunAllTestExecutionPlatformx64Net(RunnerInfo runnerInfo) CppRunAllTests("x64"); } - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [Ignore("After the bump of TestPlatformRemoteExternalsVersion to 17.6 it doesn't work anymore, it's a test for the legacy platform and fails for wrong Microsoft.VisualStudio.Telemetry version.")] - public void WebTestRunAllTestsWithRunSettings(RunnerInfo runnerInfo) - { - if (!IsCI) - { - Assert.Inconclusive("This works on server but not locally, because locally it grabs old dll from GAC, but has version 10.0.0 as the one in our package."); - } - - SetTestEnvironment(_testEnvironment, runnerInfo); - var runSettingsFilePath = Path.Combine(TempDirectory.Path, Guid.NewGuid() + ".runsettings"); - - //test the iterationCount setting for WebTestRunConfiguration in run settings - var runSettingsXml = $@" - - - - {FrameworkArgValue} - - "; - - CreateRunSettingsFile(runSettingsFilePath, runSettingsXml); - - //therefore, the test will run for 5 iterations resulting in web test result file size of at least 150 KB - var minWebTestResultFileSizeInKB = 150; - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("WebTests tests not supported with .NET Core runner."); - return; - } - - string assemblyRelativePath = - @"microsoft.testplatform.qtools.assets\2.0.0\contentFiles\any\any\WebTestAssets\WebTest1.webtest"; - - var assemblyAbsolutePath = Path.Combine(_testEnvironment.PackageDirectory, assemblyRelativePath); - using var resultsDirectory = TempDirectory; - var arguments = PrepareArguments( - assemblyAbsolutePath, - string.Empty, - runSettingsFilePath, FrameworkArgValue, string.Empty, resultsDirectory.Path); - - InvokeVsTest(arguments); - ValidateSummaryStatus(1, 0, 0); - - if (minWebTestResultFileSizeInKB > 0) - { - var dirInfo = new DirectoryInfo(resultsDirectory.Path); - var webtestResultFile = "WebTest1.webtestResult"; - var files = dirInfo.GetFiles(webtestResultFile, SearchOption.AllDirectories); - Assert.IsTrue(files.Length > 0, $"File {webtestResultFile} not found under results directory {resultsDirectory}"); - - var fileSizeInKB = files[0].Length / 1024; - Assert.IsTrue(fileSizeInKB > minWebTestResultFileSizeInKB, $"Size of the file {webtestResultFile} is {fileSizeInKB} KB. It is not greater than {minWebTestResultFileSizeInKB} KB indicating iterationCount in run settings not honored."); - } - } - - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [Ignore("Test expects .NETFramework,Version=v4.5.2 support but the minimum one is .NETFramework,Version=v4.6.2")] - public void CodedWebTestRunAllTests(RunnerInfo runnerInfo) - { - if (!IsCI) - { - Assert.Inconclusive("This works on server but not locally, because locally it grabs old dll from GAC, but has version 10.0.0 as the one in our package."); - } - - SetTestEnvironment(_testEnvironment, runnerInfo); - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("WebTests tests not supported with .NET Core runner."); - return; - } - - string assemblyRelativePath = @"microsoft.testplatform.qtools.assets\2.0.0\contentFiles\any\any\WebTestAssets\BingWebTest.dll"; - var assemblyAbsolutePath = Path.Combine(_testEnvironment.PackageDirectory, assemblyRelativePath); - var arguments = PrepareArguments( - assemblyAbsolutePath, - string.Empty, - string.Empty, FrameworkArgValue, resultsDirectory: TempDirectory.Path); - - InvokeVsTest(arguments); - ValidateSummaryStatus(1, 0, 0); - } - [TestMethod] [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] @@ -193,7 +106,7 @@ private void CppRunAllTests(string platform) var assemblyRelativePath = platform.Equals("x64", StringComparison.OrdinalIgnoreCase) ? string.Format(CultureInfo.CurrentCulture, assemblyRelativePathFormat, platform) : string.Format(CultureInfo.CurrentCulture, assemblyRelativePathFormat, ""); - var assemblyAbsolutePath = Path.Combine(_testEnvironment.PackageDirectory, assemblyRelativePath); + var assemblyAbsolutePath = Path.Combine(_testEnvironment.GlobalPackageDirectory, assemblyRelativePath); var arguments = PrepareArguments(assemblyAbsolutePath, string.Empty, string.Empty, FrameworkArgValue, _testEnvironment.InIsolationValue, resultsDirectory: TempDirectory.Path); InvokeVsTest(arguments); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs index ba8b103749..6565366f0e 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs @@ -16,7 +16,8 @@ public class DisableAppdomainTests : AcceptanceTestBase { [TestMethod] [TestCategory("Windows")] - [NetFullTargetFrameworkDataSource] + // Run in .NET Framework testhost, disabling appdomain will force running out of process in all cases. + [NetFullTargetFrameworkDataSource(inProcess: true)] public void DisableAppdomainTest(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -24,7 +25,7 @@ public void DisableAppdomainTest(RunnerInfo runnerInfo) var diableAppdomainTest1 = _testEnvironment.GetTestAsset("DisableAppdomainTest1.dll", Net462TargetFramework); var diableAppdomainTest2 = _testEnvironment.GetTestAsset("DisableAppdomainTest2.dll", Net462TargetFramework); - RunTests(runnerInfo, $"{diableAppdomainTest1}\" \"{diableAppdomainTest2}", 2); + RunTests($"{diableAppdomainTest1}\" \"{diableAppdomainTest2}", 2); } [TestMethod] @@ -36,17 +37,11 @@ public void NewtonSoftDependencyWithDisableAppdomainTest(RunnerInfo runnerInfo) var newtonSoftDependnecyTest = _testEnvironment.GetTestAsset("NewtonSoftDependency.dll", Net462TargetFramework); - RunTests(runnerInfo, newtonSoftDependnecyTest, 1); + RunTests(newtonSoftDependnecyTest, 1); } - private void RunTests(RunnerInfo runnerInfo, string testAssembly, int passedTestCount) + private void RunTests(string testAssembly, int passedTestCount) { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("This test is not meant for .netcore."); - return; - } - var runConfigurationDictionary = new Dictionary { { "DisableAppDomain", "true" } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs index 5fc1cd39a3..05e4040b95 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs @@ -32,8 +32,9 @@ public void DiscoverAllTests(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useVsixRunner: true)] [NetCoreTargetFrameworkDataSource] + [TestCategory("Smoke")] public void MultipleSourcesDiscoverAllTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -86,12 +87,13 @@ public void DiscoverTestsShouldShowProperWarningIfNoTestsOnTestCaseFilter(Runner arguments = string.Concat(arguments, " /logger:\"console;prefix=true\""); InvokeVsTest(arguments); - StringAssert.Contains(StdOut, "Warning: No test matches the given testcase filter `NonExistTestCaseName` in"); - StringAssert.Contains(StdOut, "SimpleTestProject2.dll"); + Assert.Contains("Warning: No test matches the given testcase filter `NonExistTestCaseName` in", StdOut); + Assert.Contains("SimpleTestProject2.dll", StdOut); ExitCodeEquals(0); } [TestMethod] + [Ignore("SCI 10.0.0.0 cannot be loaded in .NET 9 test host — covered by DtaLikeHost acceptance test instead")] public void TypesToLoadAttributeTests() { var extensionsDirectory = IntegrationTestEnvironment.ExtensionsDirectory; @@ -135,7 +137,7 @@ public void DiscoverTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(Runn arguments = string.Concat(arguments, " /logger:\"console;prefix=true\""); InvokeVsTest(arguments); - StringAssert.Contains(StdOut, $"Skipping source: {nonTestDll} (.NETStandard,Version=v2.0,"); + Assert.Contains($"Skipping source: {nonTestDll} (.NETStandard,Version=v2.0,", StdOut); ExitCodeEquals(0); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs new file mode 100644 index 0000000000..610867c3bb --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +/// +/// Reproduces the binding-redirect scenario experienced by Azure DevOps' Distributed +/// Test Agent (DTAExecutionHost) and any Visual Studio host that picks up +/// Microsoft.VisualStudio.TestPlatform.Common.dll without the in-box +/// vstest.console.exe.config binding redirects. +/// +/// The test loads Common.dll inside a net472 host that has no binding +/// redirects in its app.config and calls +/// , +/// which triggers FastFilter.Builder and forces +/// System.Collections.Immutable / System.Reflection.Metadata to load. +/// +/// It runs the scenario twice: +/// 1. Against the Microsoft.TestPlatform nupkg's +/// tools/net462/Common7/IDE/Extensions/TestPlatform/ layout (as DTA consumes it). +/// 2. Against the flat layout of the Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI +/// VSIX (as Visual Studio consumes it). +/// +/// Regression guard: if Common.dll's compiled metadata references for SCI/SRM drift +/// away from the versions we ship next to it, the test fails with the same +/// FileLoadException customers see. Both layouts must stay self-consistent. +/// +[TestClass] +public class DistributedTestAgentScenarioTests : AcceptanceTestBase +{ + [TestMethod] + [TestCategory("Windows-Review")] + public void LoadingCommonDllFromMicrosoftTestPlatformPackageWithoutBindingRedirectsDoesNotThrow() + { + // Nupkg layout: DTA-style consumption of the Microsoft.TestPlatform nupkg. + RunDtaLikeHost(toolsDirOverride: null); + } + + [TestMethod] + [TestCategory("Windows-Review")] + public void LoadingCommonDllFromCliV2VsixLayoutWithoutBindingRedirectsDoesNotThrow() + { + // VSIX layout: flat folder with Common.dll + SCI + SRM at the root, as shipped + // in Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix and consumed by + // Visual Studio. The VSIX is unzipped into PublishDirectory by Build.cs. + var extractedVsixDir = Path.Combine( + IntegrationTestEnvironment.PublishDirectory, + Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion)); + + Assert.IsTrue( + Directory.Exists(extractedVsixDir), + $"Extracted VSIX directory not found at '{extractedVsixDir}'. " + + "Build.cs is expected to unzip the V2.CLI VSIX before acceptance tests run."); + + Assert.IsTrue( + File.Exists(Path.Combine(extractedVsixDir, "Microsoft.VisualStudio.TestPlatform.Common.dll")), + $"Expected Common.dll at the root of the extracted VSIX ('{extractedVsixDir}')."); + + RunDtaLikeHost(toolsDirOverride: extractedVsixDir); + } + + private void RunDtaLikeHost(string? toolsDirOverride) + { + var projectPath = GetIsolatedTestAsset("DtaLikeHost.csproj", Net472TargetFramework); + var workingDir = Path.GetDirectoryName(projectPath)!; + + var dotnetPath = GetPatchedDotnetPath(); + + var buildArgs = + $@"build ""{projectPath}"" -c {IntegrationTestEnvironment.BuildConfiguration} " + + $@"/p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion} " + + @"/nodeReuse:false"; + + if (toolsDirOverride is not null) + { + buildArgs += $@" /p:TestPlatformToolsDirOverride=""{toolsDirOverride}"""; + } + + ExecuteApplication(dotnetPath, buildArgs, out var buildOut, out var buildErr, out var buildExit, workingDirectory: workingDir); + + Assert.AreEqual( + 0, + buildExit, + $"dotnet build of DtaLikeHost failed (exit {buildExit}).\nSTDOUT:\n{buildOut}\nSTDERR:\n{buildErr}"); + + var exePath = Path.Combine( + workingDir, + "artifacts", "bin", "TestAssets", "DtaLikeHost", + IntegrationTestEnvironment.BuildConfiguration, + Net472TargetFramework, + "DtaLikeHost.exe"); + + Assert.IsTrue(File.Exists(exePath), $"Expected DtaLikeHost.exe at '{exePath}'."); + + // With the fix in place, Common.dll's compiled metadata references for + // System.Collections.Immutable and System.Reflection.Metadata match the DLLs + // shipped next to it, so the host exe completes normally even without any + // binding redirects in its app.config. + ExecuteApplication(exePath, args: null, out var runOut, out var runErr, out var runExit); + + Assert.AreEqual( + 0, + runExit, + "DtaLikeHost.exe exited non-zero, which means Common.dll's compiled metadata " + + "references for System.Collections.Immutable / System.Reflection.Metadata do " + + "not match the versions shipped next to it. DTA-style hosts (no binding " + + "redirects) will FileLoadException on FastFilter.Builder.\n" + + $"Tools dir: {toolsDirOverride ?? ""}\n" + + $"STDOUT:\n{runOut}\nSTDERR:\n{runErr}"); + + Assert.Contains("OK - no binding exception.", runOut); + } + + private static string GetPatchedDotnetPath() + { + var executable = OSUtils.IsWindows ? "dotnet.exe" : "dotnet"; + return Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "artifacts", "tmp", ".dotnet", executable)); + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.Windows.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.Windows.cs index 64a2ff00ee..884640f7a3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.Windows.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.Windows.cs @@ -8,10 +8,13 @@ using System.IO; using System.Linq; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; +using NuGet.Versioning; + namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] @@ -20,7 +23,6 @@ public class DotnetArchitectureSwitchTestsWindowsOnly : AcceptanceTestBase { [TestMethod] [DataRow("X64", "X86")] - [Ignore] // TODO: This test does not work on server, it occasionally fails with cryptic message around not being able to load MSBuild.Tasks. // [DataRow("X86", "X64")] public void Use_EnvironmentVariables(string architectureFrom, string architectureTo) @@ -41,7 +43,6 @@ public void Use_EnvironmentVariables(string architectureFrom, string architectur var environmentVariables = new Dictionary { - ["DOTNET_MULTILEVEL_LOOKUP"] = "0", [$"DOTNET_ROOT_{architectureTo}"] = Path.GetDirectoryName(dotnetPathTo)!, ["ExpectedArchitecture"] = architectureTo }; @@ -75,7 +76,6 @@ public void TestMethod1() environmentVariables = new Dictionary { - ["DOTNET_MULTILEVEL_LOOKUP"] = "0", ["DOTNET_ROOT"] = Path.GetDirectoryName(dotnetPathTo), ["ExpectedArchitecture"] = architectureTo }; @@ -89,7 +89,6 @@ public void TestMethod1() environmentVariables = new Dictionary { - ["DOTNET_MULTILEVEL_LOOKUP"] = "0", [$"DOTNET_ROOT_{architectureTo}"] = Path.GetDirectoryName(dotnetPathTo), ["DOTNET_ROOT"] = "WE SHOULD PICK THE ABOVE ONE BEFORE FALLBACK TO DOTNET_ROOT", ["ExpectedArchitecture"] = architectureTo @@ -104,7 +103,14 @@ public void TestMethod1() } private static string GetLatestSdkVersion(string dotnetPath) - => Path.GetFileName(Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath)!, @"shared/Microsoft.NETCore.App")).OrderByDescending(x => x).First()); + { + var folders = Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath)!, @"shared/Microsoft.NETCore.App")).Select(f => new + { + FullName = f, + SemanticVersion = SemanticVersion.Parse(new DirectoryInfo(f).Name) + }).OrderByDescending(x => x.SemanticVersion).ToList(); + return Path.GetFileName(folders.First().FullName); + } } #endif diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.cs index a03c0c9776..c0504923c6 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetArchitectureSwitchTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #if !NETFRAMEWORK @@ -18,7 +18,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; // >= x64 6.0.2xx // x64 5.0.4xx for Mac // x64 3.1.4XX for Win -// Manual test './tools/.../dotnet test ./test/Microsoft.TestPlatform.AcceptanceTests/bin/Debug/netcoreapp3.1/Microsoft.TestPlatform.AcceptanceTests.dll --testcasefilter:"DotnetArchitectureSwitchTests"' +// Manual test './tools/.../dotnet test ./test/Microsoft.TestPlatform.AcceptanceTests/bin/Debug/net8.0/Microsoft.TestPlatform.AcceptanceTests.dll --testcasefilter:"DotnetArchitectureSwitchTests"' [TestClass] [Ignore("Manual tests(for now). Tests in this class need some .NET SDK global installations")] public class DotnetArchitectureSwitchTests : AcceptanceTestBase @@ -40,43 +40,38 @@ public static void ClassCleanup() } [TestMethod] + [OSCondition(OperatingSystems.Windows | OperatingSystems.OSX)] public void GlobalInstallation() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return; - } - var projectName = "ArchitectureSwitch.csproj"; var projectPath = GetProjectFullPath(projectName); var projectDirectory = Path.GetDirectoryName(projectPath); var env = new Dictionary { - { "DOTNET_ROOT", null }, - { "DOTNET_MULTILEVEL_LOOKUP", "0" } + { "DOTNET_ROOT", null } }; // Verify native architecture - ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0", out string stdOut, out _, out _, env, projectDirectory); + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net8.0", out string stdOut, out _, out _, env, projectDirectory); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App")); + Assert.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App", stdOut); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App")); + Assert.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App", stdOut); } - Assert.IsTrue(stdOut.Contains("OSArchitecture: ARM64")); - Assert.IsTrue(stdOut.Contains("ProcessArchitecture: ARM64")); + Assert.Contains("OSArchitecture: ARM64", stdOut); + Assert.Contains("ProcessArchitecture: ARM64", stdOut); // Verify switch using csproj - ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net8.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); // Verify switch using test container - var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net6.0"); + var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net8.0"); ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {buildAssemblyPath} --arch x64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); @@ -84,31 +79,26 @@ static void AssertSwitch(string output) { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Assert.IsTrue(output.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App")); + Assert.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App", output); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsTrue(output.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App")); + Assert.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App", output); } - Assert.IsTrue(output.Contains("OSArchitecture: X64")); - Assert.IsTrue(output.Contains("ProcessArchitecture: X64")); + Assert.Contains("OSArchitecture: X64", output); + Assert.Contains("ProcessArchitecture: X64", output); } } [TestMethod] [DataRow(true, false)] [DataRow(false, true)] + [OSCondition(OperatingSystems.Windows | OperatingSystems.OSX)] public void DOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootX64) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return; - } - var env = new Dictionary { - ["DOTNET_ROOT"] = null, - ["DOTNET_MULTILEVEL_LOOKUP"] = "0" + ["DOTNET_ROOT"] = null }; var projectName = "ArchitectureSwitch.csproj"; @@ -116,21 +106,20 @@ public void DOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootX6 var projectDirectory = Path.GetDirectoryName(projectPath); // Verify native architecture - ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net6.0", out string stdOut, out _, out _, env, projectDirectory); + ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework net8.0", out string stdOut, out _, out _, env, projectDirectory); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App"), "Unexpected runtime location"); + Assert.Contains("Runtime location: /usr/local/share/dotnet/shared/Microsoft.NETCore.App", stdOut, "Unexpected runtime location"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App")); + Assert.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\shared\Microsoft.NETCore.App", stdOut); } - Assert.IsTrue(stdOut.Contains("OSArchitecture: ARM64"), "Unexpected OSArchitecture"); - Assert.IsTrue(stdOut.Contains("ProcessArchitecture: ARM64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: ARM64", stdOut, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: ARM64", stdOut, "Unexpected ProcessArchitecture"); env.Clear(); env["DOTNET_ROOT"] = null; - env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; if (dotnetRoot) { env["DOTNET_ROOT"] = s_privateX64Installation; @@ -142,36 +131,31 @@ public void DOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootX6 } // Verify switch using csproj - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net6.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net8.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); // Verify switch using test container - var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net6.0"); - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net6.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); + var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net8.0"); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net8.0 --arch x64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); void AssertSwitch(string output) { Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{s_privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); - Assert.IsTrue(output.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); - Assert.IsTrue(output.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: X64", output, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: X64", output, "Unexpected ProcessArchitecture"); Assert.IsTrue(!dotnetRoot || output.Contains($"DOTNET_ROOT: {s_privateX64Installation}"), "Unexpected DOTNET_ROOT var"); Assert.IsTrue(!dotnetRootX64 || output.Contains($"DOTNET_ROOT_X64: {s_privateX64Installation}"), "Unexpected DOTNET_ROOT_X64 var"); } } [TestMethod] + [OSCondition(OperatingSystems.Windows | OperatingSystems.OSX)] public void PrivateX64BuildToGlobalArmInstallation() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return; - } - var env = new Dictionary { - ["DOTNET_ROOT"] = null, - ["DOTNET_MULTILEVEL_LOOKUP"] = "0" + ["DOTNET_ROOT"] = null }; string privateInstallationMuxer = Path.Combine(s_privateX64Installation, GetMuxerName); @@ -180,42 +164,37 @@ public void PrivateX64BuildToGlobalArmInstallation() var projectDirectory = Path.GetDirectoryName(projectPath); // Verify native architecture - ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net6.0", out string stdOut, out _, out _, env, projectDirectory); + ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net8.0", out string stdOut, out _, out _, env, projectDirectory); Assert.IsTrue(Regex.IsMatch(stdOut.Replace(@"\", "/"), $"Runtime location: .*{s_privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); - Assert.IsTrue(stdOut.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); - Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: X64", stdOut, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: X64", stdOut, "Unexpected ProcessArchitecture"); // Verify switch using csproj - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net6.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net8.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); // Verify switch using test container - var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net6.0"); - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net6.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); + var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net8.0"); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net8.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); static void AssertSwitch(string output) { Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{GetDefaultLocation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); - Assert.IsTrue(output.Contains("OSArchitecture: ARM64"), "Unexpected OSArchitecture"); - Assert.IsTrue(output.Contains("ProcessArchitecture: ARM64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: ARM64", output, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: ARM64", output, "Unexpected ProcessArchitecture"); } } [TestMethod] [DataRow(true, false)] [DataRow(false, true)] + [OSCondition(OperatingSystems.Windows | OperatingSystems.OSX)] public void PrivateX64BuildToDOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, bool dotnetRootArm64) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return; - } - var env = new Dictionary { - ["DOTNET_ROOT"] = null, - ["DOTNET_MULTILEVEL_LOOKUP"] = "0" + ["DOTNET_ROOT"] = null }; string privateInstallationMuxer = Path.Combine(s_privateX64Installation, GetMuxerName); @@ -224,14 +203,13 @@ public void PrivateX64BuildToDOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, var projectDirectory = Path.GetDirectoryName(projectPath); // Verify native architecture - ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net6.0", out string stdOut, out _, out _, env, projectDirectory); + ExecuteApplication(privateInstallationMuxer, $"test {projectPath} --framework net8.0", out string stdOut, out _, out _, env, projectDirectory); Assert.IsTrue(Regex.IsMatch(stdOut.Replace(@"\", "/"), $"Runtime location: .*{s_privateX64Installation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); - Assert.IsTrue(stdOut.Contains("OSArchitecture: X64"), "Unexpected OSArchitecture"); - Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: X64", stdOut, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: X64", stdOut, "Unexpected ProcessArchitecture"); env.Clear(); env["DOTNET_ROOT"] = null; - env["DOTNET_MULTILEVEL_LOOKUP"] = "0"; if (dotnetRoot) { env["DOTNET_ROOT"] = GetDefaultLocation; @@ -243,52 +221,47 @@ public void PrivateX64BuildToDOTNET_ROOTS_EnvironmentVariables(bool dotnetRoot, } // Verify switch using csproj - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net6.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {projectPath} --framework net8.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); // Verify switch using test container - var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net6.0"); - ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net6.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); + var buildAssemblyPath = GetTestDllForFramework("ArchitectureSwitch.dll", "net8.0"); + ExecuteApplication($"{s_privateX64Installation}/{GetMuxerName}", $"test {buildAssemblyPath} --framework net8.0 --arch arm64", out stdOut, out _, out _, env, projectDirectory); AssertSwitch(stdOut); void AssertSwitch(string output) { Assert.IsTrue(Regex.IsMatch(output.Replace(@"\", "/"), $"Runtime location: .*{GetDefaultLocation.Replace(@"\", "/")}.*shared.*Microsoft.NETCore.App"), "Unexpected runtime location"); - Assert.IsTrue(output.Contains("OSArchitecture: ARM64"), "Unexpected OSArchitecture"); - Assert.IsTrue(output.Contains("ProcessArchitecture: ARM64"), "Unexpected ProcessArchitecture"); + Assert.Contains("OSArchitecture: ARM64", output, "Unexpected OSArchitecture"); + Assert.Contains("ProcessArchitecture: ARM64", output, "Unexpected ProcessArchitecture"); Assert.IsTrue(!dotnetRoot || output.Contains($"DOTNET_ROOT: {GetDefaultLocation}"), "Unexpected DOTNET_ROOT var"); Assert.IsTrue(!dotnetRootArm64 || output.Contains($"DOTNET_ROOT_ARM64: {GetDefaultLocation}"), "Unexpected DOTNET_ROOT_ARM64 var"); } } [TestMethod] + [OSCondition(OperatingSystems.Windows | OperatingSystems.OSX)] public void SilentlyForceX64() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return; - } - var projectName = "ArchitectureSwitch.csproj"; var projectPath = GetProjectFullPath(projectName); var projectDirectory = Path.GetDirectoryName(projectPath); var env = new Dictionary { - ["DOTNET_ROOT"] = null, - ["DOTNET_MULTILEVEL_LOOKUP"] = "0" + ["DOTNET_ROOT"] = null }; ExecuteApplication(GetDefaultDotnetMuxerLocation, $"test {projectPath} --framework {GetFrameworkVersionToForceToX64}", out string stdOut, out _, out _, env, projectDirectory); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Assert.IsTrue(stdOut.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App")); + Assert.Contains("Runtime location: /usr/local/share/dotnet/x64/shared/Microsoft.NETCore.App", stdOut); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Assert.IsTrue(stdOut.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App")); + Assert.Contains($@"Runtime location: {Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64\shared\Microsoft.NETCore.App", stdOut); } - Assert.IsTrue(stdOut.Contains("OSArchitecture: X64")); - Assert.IsTrue(stdOut.Contains("ProcessArchitecture: X64")); + Assert.Contains("OSArchitecture: X64", stdOut); + Assert.Contains("ProcessArchitecture: X64", stdOut); } private static string GetMuxerName => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dotnet" : "dotnet.exe"; @@ -298,8 +271,8 @@ public void SilentlyForceX64() $@"{Environment.ExpandEnvironmentVariables("%ProgramFiles%")}\dotnet\x64"; private static string GetFrameworkVersionToForceToX64 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - "net5.0" : - "netcoreapp3.1"; + "net9.0" : + "net8.0"; private static string GetDefaultLocation => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"/usr/local/share/dotnet" : diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetHostArchitectureVerifierTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetHostArchitectureVerifierTests.cs index 3ff30740f4..b74d03f42c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetHostArchitectureVerifierTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetHostArchitectureVerifierTests.cs @@ -10,6 +10,8 @@ using Newtonsoft.Json.Linq; +using NuGet.Versioning; + namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] @@ -23,7 +25,7 @@ public class DotnetHostArchitectureVerifierTests : IntegrationTestBase // [DataRow("X86")] public void VerifyHostArchitecture(string architecture) { - _testEnvironment.RunnerFramework = "netcoreapp3.1"; + _testEnvironment.RunnerFramework = CoreRunnerFramework; string dotnetPath = GetDownloadedDotnetMuxerFromTools(architecture); var vstestConsolePath = GetDotnetRunnerPath(); var dotnetRunnerPath = TempDirectory.CreateDirectory("dotnetrunner"); @@ -38,7 +40,6 @@ public void VerifyHostArchitecture(string architecture) var environmentVariables = new Dictionary { - ["DOTNET_MULTILEVEL_LOOKUP"] = "0", ["ExpectedArchitecture"] = architecture }; @@ -67,5 +68,12 @@ public void TestMethod1() } private static string GetLatestSdkVersion(string dotnetPath) - => Path.GetFileName(Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath)!, @"shared/Microsoft.NETCore.App")).OrderByDescending(x => x).First()); + { + var folders = Directory.GetDirectories(Path.Combine(Path.GetDirectoryName(dotnetPath)!, @"shared/Microsoft.NETCore.App")).Select(f => new + { + FullName = f, + SemanticVersion = SemanticVersion.Parse(new DirectoryInfo(f).Name) + }).OrderByDescending(x => x.SemanticVersion).ToList(); + return Path.GetFileName(folders.First().FullName); + } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs index 70e18080ef..3e613a599e 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.IO; using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,21 +23,21 @@ public void MSBuildLoggerCanBeEnabledByBuildPropertyAndDoesNotEatSpecialChars(Ru { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj"); + var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj", runnerInfo.TargetFramework); // Forcing terminal logger so we can see the output when it is redirected - InvokeDotnetTest($@"{projectPath} -tl:on -nodereuse:false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}"); + InvokeDotnetTest($@"{projectPath} -tl:on -nodereuse:false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); // The output: // Determining projects to restore... // Restored C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj (in 441 ms). // SimpleTestProject -> C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\artifacts\bin\TestAssets\SimpleTestProject\Debug\net462\SimpleTestProject.dll - // SimpleTestProject -> C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\artifacts\bin\TestAssets\SimpleTestProject\Debug\netcoreapp3.1\SimpleTestProject.dll + // SimpleTestProject -> C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\artifacts\bin\TestAssets\SimpleTestProject\Debug\net8.0\SimpleTestProject.dll // C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\UnitTest1.cs(41): error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>. [C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj::TargetFramework=net462] - // C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\UnitTest1.cs(41): error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>. [C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj::TargetFramework=netcoreapp3.1] + // C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\UnitTest1.cs(41): error VSTEST1: (FailingTest) SampleUnitTestProject.UnitTest1.FailingTest() Assert.AreEqual failed. Expected:<2>. Actual:<3>. [C:\Users\nohwnd\AppData\Local\Temp\vstest\xvoVt\SimpleTestProject.csproj::TargetFramework=net8.0] StdOutputContains("TESTERROR"); StdOutputContains("FailingTest ("); - StdOutputContains("): Error Message: Assert.AreEqual failed. Expected:<ğğğ𦮙我們剛才從𓋴𓅓𓏏𓇏𓇌𓀀>. Actual:."); + StdOutputContains("Expected: \"ğğğ𦮙我們剛才從𓋴𓅓𓏏𓇏𓇌𓀀\""); StdOutputContains("at TerminalLoggerUnitTests.UnitTest1.FailingTest() in"); // We are sending those as low prio messages, they won't show up on screen but will be in binlog. //StdOutputContains("passed PassingTest"); @@ -52,8 +53,8 @@ public void MSBuildLoggerCanBeDisabledByBuildProperty(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj"); - InvokeDotnetTest($@"{projectPath} -nodereuse:false /p:VsTestUseMSBuildOutput=false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}"); + var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj", runnerInfo.TargetFramework); + InvokeDotnetTest($@"{projectPath} -nodereuse:false /p:VsTestUseMSBuildOutput=false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); // Check that we see the summary that is printed from the console logger, meaning the new output is disabled. StdOutputContains("Failed! - Failed: 1, Passed: 1, Skipped: 1, Total: 3, Duration:"); @@ -72,8 +73,8 @@ public void MSBuildLoggerCanBeDisabledByEnvironmentVariableProperty(RunnerInfo r { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj"); - InvokeDotnetTest($@"{projectPath} -nodereuse:false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", environmentVariables: new Dictionary { ["MSBUILDENSURESTDOUTFORTASKPROCESSES"] = "1" }); + var projectPath = GetIsolatedTestAsset("TerminalLoggerTestProject.csproj", runnerInfo.TargetFramework); + InvokeDotnetTest($@"{projectPath} -nodereuse:false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", environmentVariables: new Dictionary { ["MSBUILDENSURESTDOUTFORTASKPROCESSES"] = "1" }, workingDirectory: Path.GetDirectoryName(projectPath)); // Check that we see the summary that is printed from the console logger, meaning the new output is disabled. StdOutputContains("Failed! - Failed: 1, Passed: 1, Skipped: 1, Total: 3, Duration:"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs index 0332671357..f0dca3004b 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs @@ -21,12 +21,13 @@ private static string GetFinalVersion(string version) // patched dotnet is not published on non-windows systems [TestCategory("Windows-Review")] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestCategory("Smoke")] public void RunDotnetTestWithCsproj(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetIsolatedTestAsset("SimpleTestProject.csproj"); - InvokeDotnetTest($@"{projectPath} -p:VSTestUseMSBuildOutput=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}"); + var projectPath = GetIsolatedTestAsset("SimpleTestProject.csproj", runnerInfo.TargetFramework); + InvokeDotnetTest($@"{projectPath} -tl:off /p:VSTestNoLogo=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); // ensure our dev version is used StdOutputContains(GetFinalVersion(IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion)); @@ -43,7 +44,7 @@ public void RunDotnetTestWithDll(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPath = GetAssetFullPath("SimpleTestProject.dll"); - InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal"""); + InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal""", workingDirectory: Path.GetDirectoryName(assemblyPath)); // ensure our dev version is used StdOutputContains(GetFinalVersion(IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion)); @@ -59,9 +60,11 @@ public void RunDotnetTestWithCsprojPassInlineSettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - var projectPath = GetIsolatedTestAsset("ParametrizedTestProject.csproj"); - InvokeDotnetTest($@"{projectPath} --logger:""Console;Verbosity=normal"" -p:VSTestUseMSBuildOutput=false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion} -- TestRunParameters.Parameter(name =\""weburl\"", value=\""http://localhost//def\"")"); + var projectPath = GetIsolatedTestAsset("ParametrizedTestProject.csproj", runnerInfo.TargetFramework); + InvokeDotnetTest($@"{projectPath} --logger:""Console;Verbosity=normal"" -tl:off /p:VSTestNoLogo=false /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion} -- TestRunParameters.Parameter(name =\""weburl\"", value=\""http://localhost//def\"")", workingDirectory: Path.GetDirectoryName(projectPath)); + // ensure our dev version is used + StdOutputContains(GetFinalVersion(IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion)); ValidateSummaryStatus(1, 0, 0); ExitCodeEquals(0); } @@ -75,7 +78,7 @@ public void RunDotnetTestWithDllPassInlineSettings(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPath = GetAssetFullPath("ParametrizedTestProject.dll"); - InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal"" -- TestRunParameters.Parameter(name=\""weburl\"", value=\""http://localhost//def\"")"); + InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal"" -- TestRunParameters.Parameter(name=\""weburl\"", value=\""http://localhost//def\"")", workingDirectory: Path.GetDirectoryName(assemblyPath)); ValidateSummaryStatus(1, 0, 0); ExitCodeEquals(0); @@ -91,9 +94,9 @@ public void RunDotnetTestWithNativeDll(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); string assemblyRelativePath = @"microsoft.testplatform.testasset.nativecpp\2.0.0\contentFiles\any\any\x64\Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; - var assemblyAbsolutePath = Path.Combine(_testEnvironment.PackageDirectory, assemblyRelativePath); + var assemblyAbsolutePath = Path.Combine(_testEnvironment.GlobalPackageDirectory, assemblyRelativePath); - InvokeDotnetTest($@"{assemblyAbsolutePath} --logger:""Console;Verbosity=normal"" --diag:c:\temp\logscpp\"); + InvokeDotnetTest($@"{assemblyAbsolutePath} --logger:""Console;Verbosity=normal"" --diag:c:\temp\logscpp\", workingDirectory: Path.GetDirectoryName(assemblyAbsolutePath)); ValidateSummaryStatus(1, 1, 0); ExitCodeEquals(1); @@ -108,7 +111,7 @@ public void RunDotnetTestAndSeeOutputFromConsoleWriteLine(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); var assemblyPath = GetAssetFullPath("OutputtingTestProject.dll"); - InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal"" "); + InvokeDotnetTest($@"{assemblyPath} --logger:""Console;Verbosity=normal"" ", workingDirectory: Path.GetDirectoryName(assemblyPath)); StdOutputContains("MY OUTPUT FROM TEST"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs index abadb1e3e5..4eeb03dcfb 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs @@ -92,7 +92,7 @@ private void VaildateDataCollectorOutput(TempDirectory tempDirectory) .Select(d => d.FullName) .ToList(); - Assert.AreEqual(4, resultFiles.Count); + Assert.HasCount(4, resultFiles); StdOutputContains("Event Log.xml"); var fileContent1 = File.ReadAllText(resultFiles[0]); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs index 1678f6a13e..f19d76ad79 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs @@ -7,8 +7,6 @@ using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; - -using TestPlatform.TestUtilities; using System.Linq; using Microsoft.VisualStudio.TestPlatform.Common; using FluentAssertions; @@ -21,7 +19,7 @@ public class ExecutionTests : AcceptanceTestBase //TODO: It looks like the first 3 tests would be useful to multiply by all 3 test frameworks, should we make the test even more generic, or duplicate them? [TestMethod] [TestCategory("Windows-Review")] - [MSTestCompatibilityDataSource(InProcess = true)] + [MSTestCompatibilityDataSource] public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -38,8 +36,8 @@ public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] - [TestPlatformCompatibilityDataSource] - public void RunTestsFromMultipleMSTestAssemblies(RunnerInfo runnerInfo) + [TestHostCompatibilityDataSource] + public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -47,7 +45,7 @@ public void RunTestsFromMultipleMSTestAssemblies(RunnerInfo runnerInfo) InvokeVsTestForExecution(assemblyPaths, testAdapterPath: null, FrameworkArgValue, string.Empty); - ValidateSummaryStatus(passed: 2, failed: 2, skipped: 2); + ValidateSummaryStatus(2, 2, 2); ExitCodeEquals(1); // failing tests StdErrHasTestRunFailedMessageButNoOtherError(); StdOutHasNoWarnings(); @@ -55,8 +53,8 @@ public void RunTestsFromMultipleMSTestAssemblies(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] - [TestHostCompatibilityDataSource] - public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(RunnerInfo runnerInfo) + [RunnerCompatibilityDataSource] + public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -66,14 +64,14 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(Ru ValidateSummaryStatus(2, 2, 2); ExitCodeEquals(1); // failing tests - StdErrHasTestRunFailedMessageButNoOtherError(); - StdOutHasNoWarnings(); } [TestMethod] [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] - public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(RunnerInfo runnerInfo) + [TestCategory("Smoke")] + [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useVsixRunner: true)] + [NetCoreTargetFrameworkDataSource] + public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations3(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -81,8 +79,6 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(R InvokeVsTestForExecution(assemblyPaths, testAdapterPath: null, FrameworkArgValue, string.Empty); - InvokeVsTestForExecution(assemblyPaths, testAdapterPath: null, FrameworkArgValue, string.Empty); - ValidateSummaryStatus(2, 2, 2); ExitCodeEquals(1); // failing tests } @@ -184,13 +180,6 @@ public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerIn { SetTestEnvironment(_testEnvironment, runnerInfo); - if (IntegrationTestEnvironment.BuildConfiguration.Equals("release", StringComparison.OrdinalIgnoreCase)) - { - // On release, x64 builds, recursive calls may be replaced with loops (tail call optimization) - Assert.Inconclusive("On StackOverflowException testhost not exited in release configuration."); - return; - } - var diagLogFilePath = Path.Combine(TempDirectory.Path, $"std_error_log_{Guid.NewGuid()}.txt"); File.Delete(diagLogFilePath); @@ -202,7 +191,7 @@ public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerIn InvokeVsTest(arguments); var errorMessage = "Process is terminated due to StackOverflowException."; - if (runnerInfo.TargetFramework.StartsWith("netcoreapp")) + if (runnerInfo.IsNetTarget) { errorMessage = "Test host process crashed : Stack overflow."; } @@ -232,7 +221,7 @@ public void UnhandleExceptionExceptionShouldBeLoggedToDiagLogFile(RunnerInfo run InvokeVsTest(arguments); var errorFirstLine = - runnerInfo.TargetFramework.StartsWith("netcoreapp") + runnerInfo.IsNetTarget ? "Test host standard error line: Unhandled exception. System.InvalidOperationException: Operation is not valid due to the current state of the object." : "Test host standard error line: Unhandled Exception: System.InvalidOperationException: Operation is not valid due to the current state of the object."; FileAssert.Contains(diagLogFilePath, errorFirstLine); @@ -402,14 +391,14 @@ public void ExecuteTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(Runne arguments = string.Concat(arguments, " /logger:\"console;prefix=true\""); InvokeVsTest(arguments); - StringAssert.Contains(StdOut, $"Skipping source: {nonTestDll} (.NETStandard,Version=v2.0,"); + Assert.Contains($"Skipping source: {nonTestDll} (.NETStandard,Version=v2.0,", StdOut); ExitCodeEquals(1); } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource] + // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunXunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -418,7 +407,7 @@ public void RunXunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) // ! * *\*TestAdapter.dll // ! * *\obj\** // Because of this in typical run we get a lot of dlls that we are sure don't have tests, like Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - // or testhost.dll. Those dlls are built for netcoreapp3.1 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json + // or testhost.dll. Those dlls are built for net8.0 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json // or deps.json, and fails the run. SetTestEnvironment(_testEnvironment, runnerInfo); @@ -433,8 +422,8 @@ public void RunXunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource()] + // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunMstestTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -443,8 +432,10 @@ public void RunMstestTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) // ! * *\*TestAdapter.dll // ! * *\obj\** // Because of this in typical run we get a lot of dlls that we are sure don't have tests, like Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - // or testhost.dll. Those dlls are built for netcoreapp3.1 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json + // or testhost.dll. Those dlls are built for net8.0 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json // or deps.json, and fails the run. + // + // IF THIS TEST FAILS ADD THE DLL TO KnownPlatformSources. SetTestEnvironment(_testEnvironment, runnerInfo); var testAssemblyPath = _testEnvironment.GetTestAsset("SimpleTestProject.dll"); @@ -457,8 +448,8 @@ public void RunMstestTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource()] + // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunNunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -467,8 +458,10 @@ public void RunNunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) // ! * *\*TestAdapter.dll // ! * *\obj\** // Because of this in typical run we get a lot of dlls that we are sure don't have tests, like Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - // or testhost.dll. Those dlls are built for netcoreapp3.1 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json + // or testhost.dll. Those dlls are built for net8.0 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json // or deps.json, and fails the run. + // + // IF THIS TEST FAILS ADD THE DLL TO KnownPlatformSources. SetTestEnvironment(_testEnvironment, runnerInfo); var testAssemblyPath = _testEnvironment.GetTestAsset("NUTestProject.dll"); @@ -490,8 +483,10 @@ public void RunTestsWhenProvidingJustPlatformDllsFailsTheRun(RunnerInfo runnerIn // ! * *\*TestAdapter.dll // ! * *\obj\** // Because of this in typical run we get a lot of dlls that we are sure don't have tests, like Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - // or testhost.dll. Those dlls are built for netcoreapp3.1 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json + // or testhost.dll. Those dlls are built for net8.0 tfm, so theoretically they should be tests, but attempting to run them fails to find runtimeconfig.json // or deps.json, and fails the run. + // + // IF THIS TEST FAILS ADD THE DLL TO KnownPlatformSources. SetTestEnvironment(_testEnvironment, runnerInfo); var testAssemblyPath = _testEnvironment.GetTestAsset("SimpleTestProject.dll"); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Features.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Features.cs deleted file mode 100644 index e5fca8736e..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Features.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Microsoft.TestPlatform.AcceptanceTests; - -public static class Features -{ - public const string ATTACH_DEBUGGER_FLOW = nameof(ATTACH_DEBUGGER_FLOW); - public const string MSTEST_EXAMPLE_FEATURE = nameof(MSTEST_EXAMPLE_FEATURE); - public const string MULTI_TFM = nameof(MULTI_TFM); - - public static IImmutableDictionary TestPlatformFeatures { get; } = new Dictionary - { - [ATTACH_DEBUGGER_FLOW] = new(version: "v16.7.0-preview-20200519-01", issue: "https://github.com/microsoft/vstest/pull/2325"), - [MULTI_TFM] = new(version: "v17.3.0", issue: "https://github.com/microsoft/vstest/pull/3412") - }.ToImmutableDictionary(); - - public static IImmutableDictionary AdapterFeatures { get; internal set; } = new Dictionary - { - [MSTEST_EXAMPLE_FEATURE] = new("2.2.8", issue: "This feature does not actually exist."), - }.ToImmutableDictionary(); -} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestPlatformCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestPlatformCompatibilityDataSource.cs deleted file mode 100644 index be3724c6e0..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestPlatformCompatibilityDataSource.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Reflection; - -namespace Microsoft.TestPlatform.AcceptanceTests; - -/// -/// A data source that provides a huge mix of runners, hosts and mstest adapter, to add up >100 tests. -/// You can control which runner versions, host versions, and adapter versions will be used. This should be -/// used only to test the most common scenarios, or special configurations that are candidates for their own -/// specialized source. -/// -/// By default net462 and netcoreapp3.1 are used for both runner and host. (4 combinations) -/// Then run with every version of runner is added. -/// Then run with every version of test.sdk is added. -/// Then run with every combination of testhost and adapter is added. -/// And then run in process is added. -/// -/// All of those are filtered down to have no duplicates, and to pass the -/// Before and After platform version filters, and adapter filters. -/// -/// When that adds up to no configuration exception is thrown. -/// -public class TestPlatformCompatibilityDataSource : TestDataSourceAttribute -{ - private readonly CompatibilityRowsBuilder _builder; - - public TestPlatformCompatibilityDataSource( - string runnerFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string runnerVersions = AcceptanceTestBase.LATEST_TO_LEGACY, - string hostFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string hostVersions = AcceptanceTestBase.LATEST_TO_LEGACY, - string adapterVersions = AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY, - string adapters = AcceptanceTestBase.MSTEST) - { - // TODO: We actually don't generate values to use different translation layers, because we don't have a good way to do - // that right now. Translation layer is loaded directly into the acceptance test, and so we don't have easy way to substitute it. - - _builder = new CompatibilityRowsBuilder(runnerFrameworks, runnerVersions, hostFrameworks, hostVersions, adapterVersions, adapters); - // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. - } - - public bool DebugVSTestConsole { get; set; } - public bool DebugTestHost { get; set; } - public bool DebugDataCollector { get; set; } - public bool DebugStopAtEntrypoint { get; set; } - - /// - /// Add run for in-process using the selected .NET Framework runners, and and all selected adapters. - /// - public bool WithInProcess { get; set; } = true; - - public bool WithEveryVersionOfRunner { get; set; } = true; - - public bool WithEveryVersionOfHost { get; set; } = true; - - public bool WithEveryVersionOfAdapter { get; set; } = true; - - public bool WithOlderConfigurations { get; set; } = true; - - public string? BeforeRunnerFeature { get; set; } - public string? AfterRunnerFeature { get; set; } - - public string? BeforeTestHostFeature { get; set; } - public string? AfterTestHostFeature { get; set; } - - public string? BeforeAdapterFeature { get; set; } - public string? AfterAdapterFeature { get; set; } - - public override void CreateData(MethodInfo methodInfo) - { - _builder.WithEveryVersionOfRunner = WithEveryVersionOfRunner; - _builder.WithEveryVersionOfHost = WithEveryVersionOfHost; - _builder.WithEveryVersionOfAdapter = WithEveryVersionOfAdapter; - _builder.WithOlderConfigurations = WithOlderConfigurations; - _builder.WithInProcess = WithInProcess; - - _builder.BeforeRunnerFeature = BeforeRunnerFeature; - _builder.AfterRunnerFeature = AfterRunnerFeature; - - _builder.BeforeTestHostFeature = BeforeTestHostFeature; - _builder.AfterTestHostFeature = AfterTestHostFeature; - - _builder.BeforeAdapterFeature = BeforeAdapterFeature; - _builder.AfterAdapterFeature = AfterAdapterFeature; - - _builder.DebugDataCollector = DebugDataCollector; - _builder.DebugVSTestConsole = DebugVSTestConsole; - _builder.DebugTestHost = DebugTestHost; - _builder.DebugStopAtEntrypoint = DebugStopAtEntrypoint; - - var data = _builder.CreateData(); - data.ForEach(AddData); - } -} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs index 74edcf0713..2b08813c24 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs @@ -50,7 +50,7 @@ public void OnWrongFrameworkPassedTestRunShouldNotRun(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); var arguments = PrepareArguments(GetSampleTestAssembly(), string.Empty, string.Empty, string.Empty, resultsDirectory: TempDirectory.Path); - if (runnerInfo.TargetFramework.Contains("netcore")) + if (runnerInfo.IsNetTarget) { arguments = string.Concat(arguments, " ", "/Framework:Framework45"); } @@ -60,7 +60,7 @@ public void OnWrongFrameworkPassedTestRunShouldNotRun(RunnerInfo runnerInfo) } InvokeVsTest(arguments); - if (runnerInfo.TargetFramework.Contains("netcore")) + if (runnerInfo.IsNetTarget) { StdOutputContains("No test is available"); } @@ -80,17 +80,17 @@ public void RunSpecificTestsShouldWorkWithFrameworkInCompatibleWarning(RunnerInf InvokeVsTest(arguments); - // When this test runs it provides an incorrect desired framework for the run. E.g. the dll is actually netcoreapp3.1 - // but we request to run as .NET Framework 4.0. On windows this has predictable results, for netcoreapp3.1 dll we fail + // When this test runs it provides an incorrect desired framework for the run. E.g. the dll is actually net8.0 + // but we request to run as .NET Framework 4.0. On windows this has predictable results, for net8.0 dll we fail // to load it into .NET Framework testhost.exe, and fail with "No test is available". For .NET Framework dll, we // just log a warning saying that we provided .NET Framework 472 dlls (or whatever the current tfm for test dlls is), // but the settings requested .NET Framework 4.0. The test will still run because .NET Framework is compatible, and in reality // the system has .NET Framework 472 or newer installed, which runs even if we ask for .NET Framework 4.0 testhost. // - // On Linux and Mac we execute only netcoreapp3.1 tests, and even though we force .NET Framework, we end up running on mono + // On Linux and Mac we execute only net8.0 tests, and even though we force .NET Framework, we end up running on mono // which is suprisingly able to run the .NET CoreApp 3.1 dll, so we still just see a warning and 1 completed test. var isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); - if (runnerInfo.TargetFramework.Contains("netcore") && isWindows) + if (runnerInfo.TargetFramework.Contains("net8") && isWindows) { StdOutputContains("No test is available"); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs index 2b03d2126a..10b445b904 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.TestPlatform.AcceptanceTests; @@ -20,19 +21,13 @@ public void ListDiscoverersShouldShowInboxDiscoverers(RunnerInfo runnerInfo) if (IsDesktopRunner()) { - StdOutputContains("executor://codedwebtestadapter/v1"); StdOutputContains("executor://mstestadapter/v1"); - StdOutputContains("executor://webtestadapter/v1"); - StdOutputContains(".Webtest"); StdOutputContains("executor://cppunittestexecutor/v1"); } else { // There are no inbox adapters for dotnet core - StdOutputDoesNotContains("executor://codedwebtestadapter/v1"); StdOutputDoesNotContains("executor://mstestadapter/v1"); - StdOutputDoesNotContains("executor://webtestadapter/v1"); - StdOutputDoesNotContains(".Webtest"); StdOutputDoesNotContains("executor://cppunittestexecutor/v1"); } } @@ -47,18 +42,14 @@ public void ListExecutorsShouldShowInboxExecutors(RunnerInfo runnerInfo) if (IsDesktopRunner()) { - StdOutputContains("executor://CodedWebTestAdapter/v1"); StdOutputContains("executor://MSTestAdapter/v1"); - StdOutputContains("executor://WebTestAdapter/v1"); StdOutputContains("executor://CppUnitTestExecutor/v1"); StdOutputContains("executor://UAPCppExecutorIdentifier"); } else { // There are no inbox adapters for dotnet core - StdOutputDoesNotContains("executor://CodedWebTestAdapter/v1"); StdOutputDoesNotContains("executor://MSTestAdapter/v1"); - StdOutputDoesNotContains("executor://WebTestAdapter/v1"); StdOutputDoesNotContains("executor://CppUnitTestExecutor/v1"); StdOutputDoesNotContains("executor://UAPCppExecutorIdentifier"); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs index 4bf30e4da6..6ff4e474ca 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs @@ -122,7 +122,7 @@ public void TrxLoggerWithLogFilePrefixShouldGenerateMultipleTrx(RunnerInfo runne InvokeVsTest(arguments); var trxFilePaths = Directory.EnumerateFiles(TempDirectory.Path, trxFileNamePattern + "_net*.trx"); - Assert.IsTrue(trxFilePaths.Count() > 1); + Assert.IsGreaterThan(1, trxFilePaths.Count()); } [TestMethod] @@ -194,7 +194,7 @@ public void TrxLoggerResultSummaryOutcomeValueShouldNotChangeIfNoTestsExecutedAn private static void AssertExpectedHtml(XmlElement root) { XmlNodeList elementList = root.GetElementsByTagName("details"); - Assert.AreEqual(2, elementList.Count); + Assert.HasCount(2, elementList); foreach (XmlElement element in elementList) { @@ -236,7 +236,7 @@ private static void IsFileAndContentEqual(string filePath) string[] divs = ["Total tests", "Passed", "Failed", "Skipped", "Run duration", "Pass percentage", "PassingTest"]; foreach (string str in divs) { - StringAssert.Contains(filePathContent, str); + Assert.Contains(str, filePathContent); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ManagedNameTests/SpecialNameTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ManagedNameTests/SpecialNameTests.cs index 4f48256f4f..007d4fd4e3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ManagedNameTests/SpecialNameTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ManagedNameTests/SpecialNameTests.cs @@ -5,9 +5,8 @@ using System.Linq; using System.Reflection; -using Microsoft.TestPlatform.AcceptanceTests; using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; - +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.TestPlatform.Acceptance.IntegrationTests.ManagedNameTests; @@ -47,7 +46,7 @@ public void VerifyThatInvalidIdentifierNamesAreParsed() var methodInfo = ManagedNameHelper.GetMethod(assembly, typeName, methodName); ManagedNameHelper.GetManagedName(methodInfo, out var typeName2, out var methodName2); - Assert.IsTrue(method == methodInfo); + Assert.AreEqual(method, methodInfo); Assert.AreEqual(typeName, typeName2, $"Type parse roundtrip test failed: {method} ({typeName} != {typeName2})"); Assert.AreEqual(methodName, methodName2, $"Method parse roundtrip test failed: {method} ({methodName} != {methodName2})"); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Microsoft.TestPlatform.Acceptance.IntegrationTests.csproj b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Microsoft.TestPlatform.Acceptance.IntegrationTests.csproj index 0780c80452..2647da5b1c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Microsoft.TestPlatform.Acceptance.IntegrationTests.csproj +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Microsoft.TestPlatform.Acceptance.IntegrationTests.csproj @@ -4,16 +4,13 @@ true true true + $(TestRunnerAdditionalArguments) --settings "$(MSBuildThisFileDirectory)\.runsettings" - Exe - net6.0;net48 + Exe + net9.0 - - - - @@ -30,39 +27,9 @@ - + - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerfInstrumentation/PerformanceTestBase.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerfInstrumentation/PerformanceTestBase.cs index 1d3d54c649..ccea5f78b2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerfInstrumentation/PerformanceTestBase.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerfInstrumentation/PerformanceTestBase.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; +using Microsoft.TestPlatform.TestUtilities; + namespace Microsoft.TestPlatform.AcceptanceTests.Performance.PerfInstrumentation; /// diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerformanceTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerformanceTests.cs index 158dd156c7..d82084b856 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerformanceTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/PerformanceTests.cs @@ -30,7 +30,7 @@ public void ExecutionPerformanceTest(string projectName, int expectedTestCount, var actualExecutionTime = GetExecutionTime(); - actualExecutionTime.Should().BeLessOrEqualTo(thresholdInMs.Milliseconds()); + actualExecutionTime.Should().BeLessThanOrEqualTo(thresholdInMs.Milliseconds()); } [TestMethod] @@ -45,7 +45,7 @@ public void DiscoveryPerformanceTest() var actualDiscoveryTime = GetDiscoveryTime(); - actualDiscoveryTime.Should().BeLessOrEqualTo(500.Milliseconds()); + actualDiscoveryTime.Should().BeLessThanOrEqualTo(500.Milliseconds()); } [TestMethod] @@ -60,7 +60,7 @@ public void VsTestConsolePerformanceTest() var actualVsTestTime = GetVsTestTime(); - actualVsTestTime.Should().BeLessOrEqualTo(2500.Milliseconds()); + actualVsTestTime.Should().BeLessThanOrEqualTo(2500.Milliseconds()); } [TestMethod] @@ -75,7 +75,7 @@ public void TestHostPerformanceTest() var actualTestHostTime = GetTestHostTime(); - actualTestHostTime.Should().BeLessOrEqualTo(2000.Milliseconds()); + actualTestHostTime.Should().BeLessThanOrEqualTo(2000.Milliseconds()); } [TestMethod] @@ -90,7 +90,7 @@ public void MsTestV2AdapterPerformanceTest() var actualAdapterTimeTaken = GetAdapterExecutionTime("executor://mstestadapter/v2"); - actualAdapterTimeTaken.Should().BeLessOrEqualTo(1500.Milliseconds()); + actualAdapterTimeTaken.Should().BeLessThanOrEqualTo(1500.Milliseconds()); } } #endif diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV1Tests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV1Tests.cs index c8018b859c..5407ab1d94 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV1Tests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV1Tests.cs @@ -63,7 +63,7 @@ public void TestCaseSerialize() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when serializing 10k test cases"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when serializing 10k test cases"); } [TestMethod] @@ -80,7 +80,7 @@ public void TestCaseDeserialize() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when de-serializing 10k test cases"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when de-serializing 10k test cases"); } [TestMethod] @@ -96,7 +96,7 @@ public void TestResultSerialize() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when serializing 10k test results"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when serializing 10k test results"); } [TestMethod] @@ -113,7 +113,7 @@ public void TestResultDeserialize() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(3.5.Seconds(), $"when de-serializing 10k test results"); + actualDuration.Should().BeLessThanOrEqualTo(3.5.Seconds(), $"when de-serializing 10k test results"); } private static string SerializeV1(T data) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV2Tests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV2Tests.cs index aabda2b83d..9be5bae87f 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV2Tests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/ProtocolV2Tests.cs @@ -61,7 +61,7 @@ public void TestCaseSerialize2() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when serializing 10k test cases"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when serializing 10k test cases"); } [TestMethod] @@ -78,7 +78,7 @@ public void TestCaseDeserialize2() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when de-serializing 10k test cases"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when de-serializing 10k test cases"); } [TestMethod] @@ -93,7 +93,7 @@ public void TestResultSerialize2() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when serializing 10k test results"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when serializing 10k test results"); } [TestMethod] @@ -110,7 +110,7 @@ public void TestResultDeserialize2() sw.Stop(); var actualDuration = sw.Elapsed; - actualDuration.Should().BeLessOrEqualTo(2.Seconds(), $"when de-serializing 10k test results"); + actualDuration.Should().BeLessThanOrEqualTo(2.Seconds(), $"when de-serializing 10k test results"); } private static string SerializeV2(T data) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/SocketTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/SocketTests.cs index dc8f12225b..9b92290274 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/SocketTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/SocketTests.cs @@ -73,7 +73,7 @@ public void SocketThroughput2() thread.Join(); dataTransferred.Wait(); - watch.Elapsed.Should().BeLessOrEqualTo(15.Seconds()); + watch.Elapsed.Should().BeLessThanOrEqualTo(15.Seconds()); } [TestMethod] @@ -104,7 +104,7 @@ public void SocketThroughput1() watch.Stop(); clientThread.Join(); - watch.Elapsed.Should().BeLessOrEqualTo(20.Seconds()); + watch.Elapsed.Should().BeLessThanOrEqualTo(20.Seconds()); } private static void SendData(ICommunicationChannel? channel, Stopwatch watch) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/DiscoveryPerfTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/DiscoveryPerfTests.cs index 0d20a2c1f0..59ce1aa5aa 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/DiscoveryPerfTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/DiscoveryPerfTests.cs @@ -45,8 +45,14 @@ public void DiscoverTests(string projectName, double expectedNumberOfTests) var perfyTestAdapterEnv = new Dictionary { ["TEST_COUNT"] = expectedNumberOfTests.ToString(CultureInfo.InvariantCulture) }; var vstestConsoleWrapper = GetVsTestConsoleWrapper(perfyTestAdapterEnv, traceLevel: System.Diagnostics.TraceLevel.Off); var assetPath = GetPerfAssetFullPath(projectName); - vstestConsoleWrapper.DiscoverTests(assetPath, GetDefaultRunSettings(), options, discoveryEventHandler2); - vstestConsoleWrapper.EndSession(); + try + { + vstestConsoleWrapper.DiscoverTests(assetPath, GetDefaultRunSettings(), options, discoveryEventHandler2); + } + finally + { + vstestConsoleWrapper.EndSession(); + } } Assert.AreEqual(expectedNumberOfTests, discoveryEventHandler2.Metrics![TelemetryDataConstants.TotalTestsDiscovered]); PostTelemetry(discoveryEventHandler2.Metrics, perfAnalyzer, projectName); @@ -87,8 +93,14 @@ public void DiscoverTestsWithDefaultAdaptersSkipped(string projectName, double e // This tells to PerfyTestAdapter how many tests it should return, this is our overhead baseline. var perfyTestAdapterEnv = new Dictionary { ["TEST_COUNT"] = expectedNumberOfTests.ToString(CultureInfo.InvariantCulture) }; var vstestConsoleWrapper = GetVsTestConsoleWrapper(perfyTestAdapterEnv, traceLevel: System.Diagnostics.TraceLevel.Off); - vstestConsoleWrapper.DiscoverTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, discoveryEventHandler2); - vstestConsoleWrapper.EndSession(); + try + { + vstestConsoleWrapper.DiscoverTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, discoveryEventHandler2); + } + finally + { + vstestConsoleWrapper.EndSession(); + } } Assert.AreEqual(expectedNumberOfTests, discoveryEventHandler2.Metrics![TelemetryDataConstants.TotalTestsDiscovered]); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/ExecutionPerfTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/ExecutionPerfTests.cs index 953dc82302..54586ca4ae 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/ExecutionPerfTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/ExecutionPerfTests.cs @@ -45,8 +45,14 @@ public void RunTests(string projectName, double expectedNumberOfTests) // This tells to PerfyTestAdapter how many tests it should return, this is our overhead baseline. var perfyTestAdapterEnv = new Dictionary { ["TEST_COUNT"] = expectedNumberOfTests.ToString(CultureInfo.InvariantCulture) }; var vstestConsoleWrapper = GetVsTestConsoleWrapper(perfyTestAdapterEnv, traceLevel: System.Diagnostics.TraceLevel.Off); - vstestConsoleWrapper.RunTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, runEventHandler); - vstestConsoleWrapper.EndSession(); + try + { + vstestConsoleWrapper.RunTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, runEventHandler); + } + finally + { + vstestConsoleWrapper.EndSession(); + } } Assert.AreEqual(expectedNumberOfTests, runEventHandler.Metrics![TelemetryDataConstants.TotalTestsRun]); @@ -86,8 +92,14 @@ public void RunTestsWithDefaultAdaptersSkipped(string projectName, double expect // This tells to PerfyTestAdapter how many tests it should return, this is our overhead baseline. var perfyTestAdapterEnv = new Dictionary { ["TEST_COUNT"] = expectedNumberOfTests.ToString(CultureInfo.InvariantCulture) }; var vstestConsoleWrapper = GetVsTestConsoleWrapper(perfyTestAdapterEnv, traceLevel: System.Diagnostics.TraceLevel.Off); - vstestConsoleWrapper.RunTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, runEventHandler); - vstestConsoleWrapper.EndSession(); + try + { + vstestConsoleWrapper.RunTests(GetPerfAssetFullPath(projectName), GetDefaultRunSettings(), options, runEventHandler); + } + finally + { + vstestConsoleWrapper.EndSession(); + } } Assert.AreEqual(expectedNumberOfTests, runEventHandler.Metrics![TelemetryDataConstants.TotalTestsRun]); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/TelemetryPerfTestBase.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/TelemetryPerfTestBase.cs index cb4a59b220..31a6e4ad63 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/TelemetryPerfTestBase.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Performance/TranslationLayer/TelemetryPerfTestBase.cs @@ -115,7 +115,7 @@ private static string GetAdapterName(string projectName) /// public string[] GetPerfAssetFullPath(string name, string framework = "net48") { - // TODO: how was I doing it before? The build is for net48, were we running net6.0 here? + // TODO: how was I doing it before? The build is for net48, were we running net8.0 here? var dllPath = GetTestDllForFramework($"{name}.dll", framework); return !File.Exists(dllPath) ? throw new FileNotFoundException(null, dllPath) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PostProcessingTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PostProcessingTests.cs index af147a3afc..6dbc197592 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PostProcessingTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PostProcessingTests.cs @@ -39,24 +39,26 @@ public void DotnetSDKSimulation_PostProcessing() .Add(new XElement("Configuration", new XElement("MergeFile", "MergedFile.txt"))); runSettingsXml.Save(runSettings); - // Build and run tests like msbuild - Parallel.For(0, 5, i => - { - string projectFolder = Path.Combine(TempDirectory.Path, i.ToString(CultureInfo.InvariantCulture)); - ExecuteApplication(GetConsoleRunnerPath(), $"new mstest -o {projectFolder}", out string stdOut, out string stdError, out int exitCode); - Assert.AreEqual(exitCode, 0); - ExecuteApplication(GetConsoleRunnerPath(), $"build {projectFolder} -c release", out stdOut, out stdError, out exitCode); - Assert.AreEqual(exitCode, 0); + // Create and build a single test project once, then reuse it for all parallel vstest runs. + string projectFolder = Path.Combine(TempDirectory.Path, "testproject"); + ExecuteApplication(GetConsoleRunnerPath(), $"new mstest -o {projectFolder}", out string setupStdOut, out string setupStdError, out int setupExitCode); + Assert.AreEqual(0, setupExitCode); + ExecuteApplication(GetConsoleRunnerPath(), $"build {projectFolder} -c release", out setupStdOut, out setupStdError, out setupExitCode); + Assert.AreEqual(0, setupExitCode); - string testContainer = Directory.GetFiles(Path.Combine(projectFolder, "bin"), $"{i}.dll", SearchOption.AllDirectories).Single(); + string testContainer = Directory.GetFiles(Path.Combine(projectFolder, "bin"), "testproject.dll", SearchOption.AllDirectories).Single(); - ExecuteVsTestConsole($"{testContainer} --Collect:\"SampleDataCollector\" --TestAdapterPath:\"{extensionsPath}\" --ResultsDirectory:\"{Path.GetDirectoryName(testContainer)}\" --Settings:\"{runSettings}\" --ArtifactsProcessingMode-Collect --TestSessionCorrelationId:\"{correlationSessionId}\" --Diag:\"{TempDirectory.Path + '/'}\"", out stdOut, out stdError, out exitCode); - Assert.AreEqual(exitCode, 0); + // Run vstest 2 times in parallel to collect artifacts (minimum needed to verify merging) + Parallel.For(0, 2, i => + { + string resultsDirectory = Path.Combine(TempDirectory.Path, i.ToString(CultureInfo.InvariantCulture)); + ExecuteVsTestConsole($"{testContainer} --Collect:\"SampleDataCollector\" --TestAdapterPath:\"{extensionsPath}\" --ResultsDirectory:\"{resultsDirectory}\" --Settings:\"{runSettings}\" --ArtifactsProcessingMode-Collect --TestSessionCorrelationId:\"{correlationSessionId}\" --Diag:\"{TempDirectory.Path + '/'}\"", out string stdOut, out string stdError, out int exitCode); + Assert.AreEqual(0, exitCode); }); // Post process artifacts ExecuteVsTestConsole($"--ArtifactsProcessingMode-PostProcess --TestSessionCorrelationId:\"{correlationSessionId}\" --Diag:\"{TempDirectory.Path + "/mergeLog/"}\"", out string stdOut, out string stdError, out int exitCode); - Assert.AreEqual(exitCode, 0); + Assert.AreEqual(0, exitCode); using StringReader reader = new(stdOut); Assert.AreEqual(string.Empty, reader.ReadLine().Trim()); @@ -70,11 +72,11 @@ public void DotnetSDKSimulation_PostProcessing() while (!streamReader.EndOfStream) { string line = streamReader.ReadLine(); - Assert.IsTrue(line.StartsWith("SessionEnded_Handler_")); + Assert.StartsWith("SessionEnded_Handler_", line); fileContent.Add(line); } - Assert.AreEqual(5, fileContent.Distinct().Count()); + Assert.AreEqual(2, fileContent.Distinct().Count()); } private static string GetRunsettingsFilePath(string resultsDir) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs index 13208c4d69..bcd7afefe5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs @@ -3,6 +3,7 @@ using System.IO; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.TestPlatform.AcceptanceTests; @@ -22,7 +23,7 @@ public void WhenTestHostProcessExitsBecauseTheTargetedRuntimeIsNoFoundThenTheMes // Arrange SetTestEnvironment(_testEnvironment, runnerInfo); const string testAssetProjectName = "SimpleTestProjectMessedUpTargetFramework"; - var assemblyPath = GetTestDllForFramework(testAssetProjectName + ".dll", Core31TargetFramework); + var assemblyPath = GetTestDllForFramework(testAssetProjectName + ".dll", Core80TargetFramework); UpdateRuntimeConfigJsonWithInvalidFramework(assemblyPath, testAssetProjectName); // Act @@ -39,7 +40,7 @@ static void UpdateRuntimeConfigJsonWithInvalidFramework(string assemblyPath, str // that's only meant to be used by this project. var runtimeConfigJson = Path.Combine(Path.GetDirectoryName(assemblyPath)!, testAssetProjectName + ".runtimeconfig.json"); var fileContent = File.ReadAllText(runtimeConfigJson); - var updatedContent = fileContent.Replace("\"version\": \"3.1.0\"", "\"version\": \"0.0.0\""); + var updatedContent = fileContent.Replace("\"version\": \"8.0.0\"", "\"version\": \"0.0.0\""); File.WriteAllText(runtimeConfigJson, updatedContent); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Program.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Program.cs deleted file mode 100644 index 360f5003ae..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma warning disable IDE1006 // Naming Styles -namespace testhost.UnitTests; -#pragma warning restore IDE1006 // Naming Styles - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Properties/AssemblyInfo.cs index 8f2d401361..f79978e327 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Properties/AssemblyInfo.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Properties/AssemblyInfo.cs @@ -3,5 +3,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -// Enable IAP at class level with as many threads as possible based on CPU and core count. -[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)] +// Enable IAP at method level with as many threads as possible based on CPU and core count. +// Method level is safe because integration tests offload work to child processes (vstest.console, testhost). +[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/README.MD b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/README.MD new file mode 100644 index 0000000000..17801fcaa0 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/README.MD @@ -0,0 +1,2 @@ +Integration tests that run outside of the test process (the work is delegated to child process and we only check the results). +These tests run from single-tfm test project, and they themselves can call test projects with multiple TFMs. diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..f8fa04de95 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RegressionBugFixTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Linq; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +/// +/// Integration tests that verify specific bug fixes remain working end-to-end. +/// Each test references the GitHub issue it guards against. +/// +[TestClass] +public class RegressionBugFixTests : AcceptanceTestBase +{ + #region GH-4461: Testhost crash must not expose socket stack traces to the user + + [TestMethod] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + public void TesthostCrash_MustNotShowSocketExceptionToUser(RunnerInfo runnerInfo) + { + // GH-4461: When testhost crashes (e.g. stack overflow), the parent process + // was dumping IOException / SocketException stack traces to stderr, confusing + // developers. The fix ensures socket errors are logged internally but not + // propagated to stderr. + SetTestEnvironment(_testEnvironment, runnerInfo); + + var assemblyPaths = GetAssetFullPath("crash.dll"); + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, FrameworkArgValue, runnerInfo.InIsolationValue); + arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); + + InvokeVsTest(arguments); + + // The test run should fail because the test crashes. + ExitCodeEquals(1); + + // The key regression check: stderr must NOT contain raw socket exception details. + StdErrorDoesNotContains("System.IO.IOException"); + StdErrorDoesNotContains("SocketException"); + } + + #endregion + + #region GH-5184: Stderr from testhost must not fail the test run + + [TestMethod] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + public void StderrFromTesthost_MustNotFailTestRun(RunnerInfo runnerInfo) + { + // GH-5184: When a test writes debug output to stderr, the test run was + // incorrectly marked as failed because stderr was treated as error output. + // The fix forwards stderr as Informational, not Error. + SetTestEnvironment(_testEnvironment, runnerInfo); + + var assemblyPaths = GetAssetFullPath("StderrOutputProject.dll"); + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, FrameworkArgValue, runnerInfo.InIsolationValue); + arguments = string.Concat(arguments, $" /ResultsDirectory:{TempDirectory.Path}"); + + InvokeVsTest(arguments); + + // The test writes to stderr but passes — the run must succeed. + ExitCodeEquals(0); + ValidateSummaryStatus(passed: 1, failed: 0, skipped: 0); + } + + #endregion + + #region GH-3136: HTML logger must handle special characters in test output + + [TestMethod] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + public void HtmlLogger_SpecialCharactersInOutput_MustNotThrow(RunnerInfo runnerInfo) + { + // GH-3136: Test output containing invalid XML characters (e.g. U+FFFF) caused + // XmlException in the HTML logger. The fix sets CheckCharacters = false on the + // XmlReader so these characters are tolerated. + SetTestEnvironment(_testEnvironment, runnerInfo); + + var assemblyPaths = GetAssetFullPath("SpecialCharOutputProject.dll"); + var htmlFileName = "TestResults.html"; + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, FrameworkArgValue, runnerInfo.InIsolationValue, TempDirectory.Path); + arguments = string.Concat(arguments, $" /logger:\"html;LogFileName={htmlFileName}\""); + + InvokeVsTest(arguments); + + ExitCodeEquals(0); + ValidateSummaryStatus(passed: 1, failed: 0, skipped: 0); + + // The HTML log file must exist and not be empty — before the fix, the logger + // would throw and the file would be missing or truncated. + var htmlFiles = Directory.GetFiles(TempDirectory.Path, "*.html", SearchOption.AllDirectories); + Assert.IsNotEmpty(htmlFiles, $"Expected an HTML log file in {TempDirectory.Path}"); + Assert.IsGreaterThan(0L, new FileInfo(htmlFiles.First()).Length, "HTML log file is empty"); + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs index 3546759ab5..76db8412a2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs @@ -1,9 +1,8 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using Microsoft.TestPlatform.TestUtilities; @@ -273,189 +272,6 @@ public void TestAdapterPathFromRunSettings(RunnerInfo runnerInfo) ValidateSummaryStatus(1, 1, 1); } - #region LegacySettings Tests - - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, useCoreRunner: false)] - public void LegacySettingsWithPlatform(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - var testAssemblyPath = GetAssetFullPath("LegacySettingsUnitTestProject.dll"); - _ = Path.GetDirectoryName(testAssemblyPath); - - var runsettingsXml = @" - - true - - - - - - "; - - var runsettingsFilePath = GetRunsettingsFilePath(null, TempDirectory); - File.WriteAllText(runsettingsFilePath, runsettingsXml); - - var arguments = PrepareArguments( - testAssemblyPath, - string.Empty, - runsettingsFilePath, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - InvokeVsTest(arguments); - ValidateSummaryStatus(0, 0, 0); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, useCoreRunner: false)] - public void LegacySettingsWithScripts(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - var testAssemblyPath = GetAssetFullPath("LegacySettingsUnitTestProject.dll"); - - // Create the script files - var guid = Guid.NewGuid(); - var setupScriptName = "setupScript_" + guid + ".bat"; - var setupScriptPath = Path.Combine(TempDirectory.Path, setupScriptName); - File.WriteAllText(setupScriptPath, @"echo > %temp%\ScriptTestingFile.txt"); - - var cleanupScriptName = "cleanupScript_" + guid + ".bat"; - var cleanupScriptPath = Path.Combine(TempDirectory.Path, cleanupScriptName); - File.WriteAllText(cleanupScriptPath, @"del %temp%\ScriptTestingFile.txt"); - - var runsettingsFormat = @" - - true - - - - - "; - - // Scripts have relative paths to temp directory where the runsettings is created. - var runsettingsXml = string.Format(CultureInfo.CurrentCulture, runsettingsFormat, setupScriptName, cleanupScriptName); - var runsettingsPath = GetRunsettingsFilePath(null, TempDirectory); - File.WriteAllText(runsettingsPath, runsettingsXml); - - var arguments = PrepareArguments( - testAssemblyPath, - string.Empty, - runsettingsPath, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - arguments = string.Concat(arguments, " /testcasefilter:Name=ScriptsTest"); - InvokeVsTest(arguments); - ValidateSummaryStatus(1, 0, 0); - - // Validate cleanup script ran - var scriptPath = Path.Combine(TempDirectory.Path, "ScriptTestingFile.txt"); - Assert.IsFalse(File.Exists(scriptPath)); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, useCoreRunner: false)] - public void LegacySettingsWithDeploymentItem(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - var testAssemblyPath = GetAssetFullPath("LegacySettingsUnitTestProject.dll"); - var testAssemblyDirectory = Path.GetDirectoryName(testAssemblyPath); - Assert.IsNotNull(testAssemblyDirectory); - - var deploymentItem = Path.Combine(testAssemblyDirectory, "Deployment", "DeploymentFile.xml"); - - var runsettingsFormat = @" - - true - - - - - - - "; - - var runsettingsXml = string.Format(CultureInfo.CurrentCulture, runsettingsFormat, deploymentItem); - var runsettingsPath = GetRunsettingsFilePath(null, TempDirectory); - File.WriteAllText(runsettingsPath, runsettingsXml); - - var arguments = PrepareArguments( - testAssemblyPath, - string.Empty, - runsettingsPath, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - arguments = string.Concat(arguments, " /testcasefilter:Name=DeploymentItemTest"); - InvokeVsTest(arguments); - ValidateSummaryStatus(1, 0, 0); - } - - [TestMethod] - [TestCategory("Windows")] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, useCoreRunner: false)] - public void LegacySettingsTestTimeout(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - var testAssemblyPath = GetAssetFullPath("LegacySettingsUnitTestProject.dll"); - var runsettingsXml = @" - - true - - - - - - "; - var runsettingsPath = GetRunsettingsFilePath(null, TempDirectory); - File.WriteAllText(runsettingsPath, runsettingsXml); - var arguments = PrepareArguments(testAssemblyPath, string.Empty, runsettingsPath, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - arguments = string.Concat(arguments, " /testcasefilter:Name~TimeTest"); - - InvokeVsTest(arguments); - - ValidateSummaryStatus(1, 1, 0); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, useCoreRunner: false)] - public void LegacySettingsAssemblyResolution(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - - var testAssemblyPath = GetAssetFullPath("LegacySettingsUnitTestProject.dll"); - var runsettingsFormat = @" - true - - - - - - - - - - - - - - - "; - - var testAssemblyDirectory = Path.Combine(_testEnvironment.TestAssetsPath, "LegacySettingsUnitTestProject", "DependencyAssembly"); - var runsettingsXml = string.Format(CultureInfo.CurrentCulture, runsettingsFormat, testAssemblyDirectory); - var runsettingsPath = GetRunsettingsFilePath(null, TempDirectory); - File.WriteAllText(runsettingsPath, runsettingsXml); - var arguments = PrepareArguments(testAssemblyPath, string.Empty, runsettingsPath, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - arguments = string.Concat(arguments, " /testcasefilter:Name=DependencyTest"); - - InvokeVsTest(arguments); - - ValidateSummaryStatus(1, 0, 0); - } - - #endregion - #region RunSettings With EnvironmentVariables Settings Tests [TestMethod] @@ -506,8 +322,8 @@ public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) SetTestEnvironment(_testEnvironment, runnerInfo); var projectName = "ProjectFileRunSettingsTestProject.csproj"; - var projectPath = GetIsolatedTestAsset(projectName); - InvokeDotnetTest($@"{projectPath} /p:VSTestUseMSBuildOutput=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}"); + var projectPath = GetIsolatedTestAsset(projectName, runnerInfo.TargetFramework); + InvokeDotnetTest($@"{projectPath} /p:VSTestUseMSBuildOutput=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); ValidateSummaryStatus(0, 1, 0); // make sure that we can revert the project settings back by providing a config from command line @@ -515,7 +331,7 @@ public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) // are honored by dotnet test, instead of just using the default, which would produce the same // result var settingsPath = GetProjectAssetFullPath(projectName, "inconclusive.runsettings"); - InvokeDotnetTest($@"{projectPath} --settings {settingsPath} /p:VSTestUseMSBuildOutput=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}"); + InvokeDotnetTest($@"{projectPath} --settings {settingsPath} /p:VSTestUseMSBuildOutput=false --logger:""Console;Verbosity=normal"" /p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}", workingDirectory: Path.GetDirectoryName(projectPath)); ValidateSummaryStatus(0, 0, 1); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs index fdfddcd95b..531f35c012 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs @@ -22,8 +22,8 @@ public void RunningApplicationThatIsBuiltAsSelfContainedWillNotFailToFindHostpol // see https://github.com/dotnet/runtime/issues/3569#issuecomment-595820524 and below for description of how it works SetTestEnvironment(_testEnvironment, runnerInfo); - // the app is published to win10-x64 because of the runtime identifier in the project - var assemblyPath = GetAssetFullPath($@"win10-x64{Path.DirectorySeparatorChar}SelfContainedAppTestProject.dll"); + // the app is published to win-x64 because of the runtime identifier in the project + var assemblyPath = GetAssetFullPath($@"win-x64{Path.DirectorySeparatorChar}SelfContainedAppTestProject.dll"); var arguments = PrepareArguments(assemblyPath, null, null, FrameworkArgValue, runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); InvokeVsTest(arguments); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs index 33efe78895..ec7f1ad8c3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs @@ -26,7 +26,7 @@ public void RunTestsShouldPublishMetrics(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - RunTests(runnerInfo); + RunTests(); } [TestMethod] @@ -36,17 +36,11 @@ public void DiscoverTestsShouldPublishMetrics(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - DiscoverTests(runnerInfo); + DiscoverTests(); } - private void RunTests(RunnerInfo runnerInfo) + private void RunTests() { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("Telemetry API is not supported for .NetCore runner"); - return; - } - var assemblyPaths = GetAssetFullPath("SimpleTestProject2.dll"); var env = new Dictionary @@ -60,14 +54,8 @@ private void RunTests(RunnerInfo runnerInfo) ValidateOutput("Execution", TempDirectory); } - private void DiscoverTests(RunnerInfo runnerInfo) + private void DiscoverTests() { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("Telemetry API is not supported for .NetCore runner"); - return; - } - var assemblyPaths = GetAssetFullPath("SimpleTestProject2.dll"); var env = new Dictionary @@ -85,7 +73,7 @@ private static void ValidateOutput(string command, TempDirectory tempDirectory) { if (!Directory.Exists(tempDirectory.Path)) { - Assert.Fail("Could not find the telemetry logs folder at {0}", tempDirectory.Path); + Assert.Fail($"Could not find the telemetry logs folder at {tempDirectory.Path}"); } bool isValid = false; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs index e5e451ddd4..72084bc4ea 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.IO; - using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -158,15 +156,11 @@ public void TestCaseFilterShouldWorkIfOnlyPropertyValueGivenInExpression(RunnerI /// [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + // MSTest v1 tests from dlls are only supported in .NET Framework runner, in and outside of VS + // via Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll + [NetFullTargetFrameworkDataSource(useCoreRunner: false)] public void DiscoverMstestV1TestsWithAndOperatorTrait(RunnerInfo runnerInfo) { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("Mstest v1 tests not supported with .NET Core runner."); - return; - } - SetTestEnvironment(_testEnvironment, runnerInfo); var arguments = PrepareArguments( @@ -184,36 +178,4 @@ public void DiscoverMstestV1TestsWithAndOperatorTrait(RunnerInfo runnerInfo) ValidateTestsNotDiscovered(listOfNotDiscoveredTests); } - /// - /// Discover tests using tmi adapter with test case filters. - /// - [TestMethod] - [TestCategory("Windows-Review")] - [Ignore("Temporary ignoring, because of incomplete interop work for legacy TP")] - [NetFullTargetFrameworkDataSource] - public void DiscoverTmiTestsWithOnlyPropertyValue(RunnerInfo runnerInfo) - { - if (runnerInfo.IsNetRunner) - { - Assert.Inconclusive("Tmi tests not supported with .NET Core runner."); - return; - } - - SetTestEnvironment(_testEnvironment, runnerInfo); - - string testAssemblyPath = _testEnvironment.GetTestAsset("MstestV1UnitTestProject.dll"); - var arguments = PrepareArguments( - testAssemblyPath, - GetTestAdapterPath(), - string.Empty, FrameworkArgValue, - runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); - string testSettingsPath = Path.Combine(Path.GetDirectoryName(testAssemblyPath)!, "MstestV1UnitTestProjectTestSettings.testsettings"); - arguments = string.Concat(arguments, " /listtests /TestCaseFilter:PassingTest /settings:", testSettingsPath); - - InvokeVsTest(arguments); - var listOfTests = new string[] { "MstestV1UnitTestProject.UnitTest1.PassingTest1", "MstestV1UnitTestProject.UnitTest1.PassingTest2" }; - var listOfNotDiscoveredTests = new string[] { "MstestV1UnitTestProject.UnitTest1.FailingTest1", "MstestV1UnitTestProject.UnitTest1.FailingTest2", "MstestV1UnitTestProject.UnitTest1.SkippingTest" }; - ValidateDiscoveredTests(listOfTests); - ValidateTestsNotDiscovered(listOfNotDiscoveredTests); - } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs index 5f55b852eb..712e00b6e8 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs @@ -22,6 +22,7 @@ public static void ClassInit(TestContext _) [TestMethod] [TestCategory("Windows-Review")] + [Ignore("Code Coverage is using 17.x.x dependency, will be solved in other PR. https://github.com/microsoft/vstest/issues/15223")] [NetFullTargetFrameworkDataSourceAttribute(useCoreRunner: false)] [NetCoreTargetFrameworkDataSourceAttribute(useCoreRunner: false)] public void RunMultipleTestAssembliesWithCodeCoverage(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CustomTestHostTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CustomTestHostTests.cs deleted file mode 100644 index 9996f086e0..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CustomTestHostTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; - -using FluentAssertions; - -using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; - -/// -/// The Run Tests using VsTestConsoleWrapper API's -/// -[TestClass] -public class CustomTestHostTests : AcceptanceTestBase -{ - private IVsTestConsoleWrapper? _vstestConsoleWrapper; - - [TestCleanup] - public void Cleanup() - { - _vstestConsoleWrapper?.EndSession(); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource(BeforeFeature = Features.ATTACH_DEBUGGER_FLOW)] - // This does not work with testhosts that are earlier than when the feature was introduced, - // when latest runner is used, because the latest runner does not downgrade the messages when - // older testhost launcher is used. - // [TestHostCompatibilityDataSource(BeforeFeature = Features.ATTACH_DEBUGGER_FLOW)] - public void RunTestsWithCustomTestHostLauncherLaunchesTheProcessUsingTheProvidedLauncher(RunnerInfo runnerInfo) - { - // Pins the existing functionality. - - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - - _vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - - // Act - var customTestHostLauncher = new TestHostLauncherV1(); - _vstestConsoleWrapper.RunTestsWithCustomTestHost(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler, customTestHostLauncher); - - // Assert - EnsureTestsRunWithoutErrors(runEventHandler, passed: 2, failed: 2, skipped: 2); - - // Ensure we tried to launch testhost process. - customTestHostLauncher.Should().BeAssignableTo(); - customTestHostLauncher.LaunchProcessProcessId.Should().NotBeNull("we should launch some real process and save the pid of it"); - } - - [TestMethod] - [TestCategory("Windows-Review")] - // [RunnerCompatibilityDataSource(BeforeFeature = Features.ATTACH_DEBUGGER_FLOW)] - [TestHostCompatibilityDataSource(DEFAULT_RUNNER_NETFX, DEFAULT_RUNNER_NETCORE, "LegacyStable", BeforeFeature = Features.ATTACH_DEBUGGER_FLOW, DebugVSTestConsole = true)] - [Ignore("This is not working for any testhost prior 16.7.0 where the change was introduced. The launch testhost flow was replaced with AttachDebugger in runner, and the new callback to AttachDebugger happens in testhost." - + "But any testhost prior 16.7.0 where the change was introduced does not know to call back AttachDebugger, and the call never happens.")] - // You can confirm that the functionality broke between runner and testhost, past this point by using newer runners, against older testhosts. - // [TestPlatformCompatibilityDataSource(AfterRunnerFeature = Features.ATTACH_DEBUGGER_FLOW, BeforeTestHostFeature = Features.ATTACH_DEBUGGER_FLOW)] - public void RunTestsWithCustomTestHostLauncherLaunchesTheProcessUsingTheProvidedLauncherWhenITestHostLauncher2IsProvided(RunnerInfo runnerInfo) - { - // Ensures compatibility with testhost and runners that were created before 16.3.0. It makes sure that even if user provides - // an implementation of the ITestHostLauncher2 interface, then testhost expecting ITestHostLauncher still works correctly. - - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - _vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - - // Act - var customTestHostLauncher = new TestHostLauncherV2(); - _vstestConsoleWrapper.RunTestsWithCustomTestHost(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler, customTestHostLauncher); - - // Assert - EnsureTestsRunWithoutErrors(runEventHandler, passed: 2, failed: 2, skipped: 2); - - customTestHostLauncher.Should().BeAssignableTo(); - customTestHostLauncher.LaunchProcessProcessId.Should().NotBeNull("we should launch some real process and save the pid of it"); - customTestHostLauncher.AttachDebuggerProcessId.Should().BeNull("we should not be asked to attach to a debugger, that flow is not used when vstest.console does not support it yet, even when it is given ITestHostLauncher2"); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource(AfterFeature = Features.ATTACH_DEBUGGER_FLOW)] - // [TestHostCompatibilityDataSource(AfterFeature = Features.ATTACH_DEBUGGER_FLOW)] - public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvidedLauncher(RunnerInfo runnerInfo) - { - - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - _vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - - // Act - var customTestHostLauncher = new TestHostLauncherV2(); - _vstestConsoleWrapper.RunTestsWithCustomTestHost(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler, customTestHostLauncher); - - // Assert - EnsureTestsRunWithoutErrors(runEventHandler, passed: 2, failed: 2, skipped: 2); - - customTestHostLauncher.Should().BeAssignableTo(); - customTestHostLauncher.AttachDebuggerProcessId.Should().NotBeNull("we should be asked to attach a debugger to some process and save the pid of the process"); - customTestHostLauncher.LaunchProcessProcessId.Should().BeNull("we should not be asked to launch some real process, that flow is not used when vstest.console supports it and is given ITestHostLauncher2"); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [Ignore("This is not working. The compatibility code only checks the protocol version (in handler), which is dictated by testhost. " - + "It sees 6 but does not realize that the provided CustomTesthostLauncher is not supporting the new feature, it ends up calling back to EditoAttachDebugger" + - "in translation layer, and that just silently skips the call.")] - [RunnerCompatibilityDataSource(AfterFeature = Features.ATTACH_DEBUGGER_FLOW)] - [TestHostCompatibilityDataSource(AfterFeature = Features.ATTACH_DEBUGGER_FLOW)] - public void RunTestsWithCustomTestHostLauncherUsesLaunchWhenGivenAnOutdatedITestHostLauncher(RunnerInfo runnerInfo) - { - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - _vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - - // Act - var customTestHostLauncher = new TestHostLauncherV1(); - _vstestConsoleWrapper.RunTestsWithCustomTestHost(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler, customTestHostLauncher); - - // Assert - EnsureTestsRunWithoutErrors(runEventHandler, passed: 2, failed: 2, skipped: 2); - - customTestHostLauncher.Should().NotBeAssignableTo(); - customTestHostLauncher.LaunchProcessProcessId.Should().NotBeNull("we should launch some real process and save the pid of it"); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [TestCategory("Feature")] - [RunnerCompatibilityDataSource(AfterFeature = Features.MULTI_TFM)] - public void RunAllTestsWithMixedTFMsWillProvideAdditionalInformationToTheDebugger(RunnerInfo runnerInfo) - { - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - var netFrameworkDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETFX); - var netDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETCORE); - var testHostLauncher = new TestHostLauncherV3(); - - // Act - // We have no preference around what TFM is used. It will be autodetected. - var runsettingsXml = ""; - vstestConsoleWrapper.RunTestsWithCustomTestHost(new[] { netFrameworkDll, netDll }, runsettingsXml, runEventHandler, testHostLauncher); - - // Assert - runEventHandler.Errors.Should().BeEmpty(); - testHostLauncher.AttachDebuggerInfos.Should().HaveCount(2); - var targetFrameworks = testHostLauncher.AttachDebuggerInfos.Select(i => i.TargetFramework).ToList(); - targetFrameworks.Should().OnlyContain(tfm => tfm.StartsWith(".NETFramework") || tfm.StartsWith(".NET ")); - - runEventHandler.TestResults.Should().HaveCount(6, "we run all tests from both assemblies"); - } - - [TestMethod] - [TestCategory("Windows-Review")] - [TestCategory("BackwardCompatibilityWithRunner")] - // "Just row" used here because mstest does not cooperate with older versions of vstest.console correctly, so we test with just the single version available - // before the multi tfm feature. - [RunnerCompatibilityDataSource(BeforeFeature = Features.MULTI_TFM, JustRow = 0)] - public void RunAllTestsCallsBackToTestHostLauncherV3EvenWhenRunnerDoesNotSupportMultiTfmOrTheNewAttachDebugger2MessageYet(RunnerInfo runnerInfo) - { - // Arrange - SetTestEnvironment(_testEnvironment, runnerInfo); - - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); - var runEventHandler = new RunEventHandler(); - var netFrameworkDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETFX); - var netDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETCORE); - var testHostLauncher = new TestHostLauncherV3(); - - // Act - // We have no preference around what TFM is used. It will be autodetected. - var runsettingsXml = ""; - vstestConsoleWrapper.RunTestsWithCustomTestHost(new[] { netFrameworkDll, netDll }, runsettingsXml, runEventHandler, testHostLauncher); - - // Assert - runEventHandler.Errors.Should().BeEmpty(); - testHostLauncher.AttachDebuggerInfos.Should().HaveCount(1); - var pid = testHostLauncher.AttachDebuggerInfos.Select(i => i.ProcessId).Single(); - pid.Should().NotBe(0); - - runEventHandler.TestResults.Should().HaveCount(3, "we run all tests from just one of the assemblies, because the runner does not support multi tfm"); - } - - private static void EnsureTestsRunWithoutErrors(RunEventHandler runEventHandler, int passed, int failed, int skipped) - { - runEventHandler.Errors.Should().BeEmpty(); - runEventHandler.TestResults.Should().HaveCount(passed + failed + skipped); - runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed).Should().Be(passed); - runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed).Should().Be(failed); - runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped).Should().Be(skipped); - } - - /// - /// The custom test host launcher implementing ITestHostLauncher. - /// - private class TestHostLauncherV1 : ITestHostLauncher - { - public int? LaunchProcessProcessId { get; private set; } - - /// - public bool IsDebug => true; - - /// - public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo) - { - return LaunchTestHost(defaultTestHostStartInfo, CancellationToken.None); - } - - /// - public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, CancellationToken cancellationToken) - { - var processInfo = new ProcessStartInfo( - defaultTestHostStartInfo.FileName!, - defaultTestHostStartInfo.Arguments!) - { - WorkingDirectory = defaultTestHostStartInfo.WorkingDirectory - }; - processInfo.UseShellExecute = false; - - var process = new Process { StartInfo = processInfo }; - process.Start(); - - LaunchProcessProcessId = process?.Id; - return LaunchProcessProcessId ?? -1; - } - } - - /// - /// The custom test host launcher implementing ITestHostLauncher2, and through that also ITestHostLauncher. - /// - private class TestHostLauncherV2 : TestHostLauncherV1, ITestHostLauncher2 - { - - public int? AttachDebuggerProcessId { get; private set; } - - public bool AttachDebuggerToProcess(int pid) => AttachDebuggerToProcess(pid, CancellationToken.None); - - public bool AttachDebuggerToProcess(int pid, CancellationToken cancellationToken) - { - AttachDebuggerProcessId = pid; - return true; - } - } - -#pragma warning disable CS0618 // Type or member is obsolete - private class TestHostLauncherV3 : ITestHostLauncher3 - { - public bool IsDebug => true; - - public List AttachDebuggerInfos { get; } = new(); - - public bool AttachDebuggerToProcess(AttachDebuggerInfo attachDebuggerInfo, CancellationToken cancellationToken) - { - AttachDebuggerInfos.Add(attachDebuggerInfo); - - return true; - } - - public bool AttachDebuggerToProcess(int pid) - { - return AttachDebuggerToProcess(new AttachDebuggerInfo - { - ProcessId = pid, - TargetFramework = null, - }, CancellationToken.None); - } - - public bool AttachDebuggerToProcess(int pid, CancellationToken cancellationToken) - { - return AttachDebuggerToProcess(new AttachDebuggerInfo - { - ProcessId = pid, - TargetFramework = null, - }, CancellationToken.None); - } - - public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo) - { - return -1; - } - - public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, CancellationToken cancellationToken) - { - return -1; - } - } -} -#pragma warning restore CS0618 // Type or member is obsolete - diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs new file mode 100644 index 0000000000..e738e84c5d --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Linq; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +[TestClass] +[TestCategory("Windows-Review")] +public class VideoRecorderTests : AcceptanceTestBase +{ + [Ignore("Video recording is flaky in CI — screen recorder fails to establish communication. See #15586.")] + [TestMethod] + [NetFullTargetFrameworkDataSource(useCoreRunner: false, useVsixRunner: true)] + public void VideoRecorderDataCollectorShouldRecordVideoWithRunSettings(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + var assemblyPaths = GetAssetFullPath("SimpleTestProject.dll"); + var arguments = PrepareArguments(assemblyPaths, GetTestAdapterPath(), string.Empty, FrameworkArgValue, + runnerInfo.InIsolationValue, resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, " /Collect:\"Screen and Voice Recorder\""); + + InvokeVsTest(arguments); + + // Verify video attachments were created + var resultFiles = Directory.GetFiles(TempDirectory.Path, "*.wmv", SearchOption.AllDirectories); + Assert.IsNotEmpty(resultFiles, + $"Expected video attachments (.wmv) in results directory '{TempDirectory.Path}', but found none. " + + $"All files: [{string.Join(", ", Directory.GetFiles(TempDirectory.Path, "*", SearchOption.AllDirectories).Select(Path.GetFileName))}]"); + } +} diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserEdgeCaseTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserEdgeCaseTests.cs new file mode 100644 index 0000000000..a5c7ec3055 --- /dev/null +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserEdgeCaseTests.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.ManagedNameUtilities; + +/// +/// Regression tests for ManagedNameParser type and method name parsing edge cases. +/// +[TestClass] +public class ManagedNameParserEdgeCaseTests +{ + // Regression test for #15259 — Cache AssemblyName in ManagedNameHelper + // Regression test for #15255 — Do half the work in GetManagedName + // These tests verify that managed name parsing produces correct results for various patterns. + + [TestMethod] + public void ParseManagedTypeName_NestedType_ShouldParseCorrectly() + { + ManagedNameParser.ParseManagedTypeName("Namespace.OuterClass+InnerClass", out string namespaceName, out string typeName); + + Assert.AreEqual("Namespace", namespaceName); + Assert.AreEqual("OuterClass+InnerClass", typeName); + } + + [TestMethod] + public void ParseManagedTypeName_NoNamespace_ShouldReturnEmptyNamespace() + { + ManagedNameParser.ParseManagedTypeName("GlobalClass", out string namespaceName, out string typeName); + + Assert.AreEqual(string.Empty, namespaceName); + Assert.AreEqual("GlobalClass", typeName); + } + + [TestMethod] + public void ParseManagedTypeName_DeepNamespace_ShouldParseCorrectly() + { + ManagedNameParser.ParseManagedTypeName("A.B.C.D.E.ClassName", out string namespaceName, out string typeName); + + Assert.AreEqual("A.B.C.D.E", namespaceName); + Assert.AreEqual("ClassName", typeName); + } + + [TestMethod] + public void ParseManagedMethodName_NoParameters_ShouldReturnNullParamTypes() + { + ManagedNameParser.ParseManagedMethodName("SimpleMethod", out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("SimpleMethod", methodName); + Assert.AreEqual(0, arity); + Assert.IsNull(parameterTypes); + } + + [TestMethod] + public void ParseManagedMethodName_EmptyParameters_ShouldReturnNullParamTypes() + { + ManagedNameParser.ParseManagedMethodName("Method()", out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("Method", methodName); + Assert.AreEqual(0, arity); + Assert.IsNull(parameterTypes); + } + + [TestMethod] + public void ParseManagedMethodName_MultipleParams_ShouldParseAll() + { + ManagedNameParser.ParseManagedMethodName("Method(System.Int32,System.String,System.Boolean)", + out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("Method", methodName); + Assert.AreEqual(0, arity); + Assert.IsNotNull(parameterTypes); + Assert.HasCount(3, parameterTypes!); + Assert.AreEqual("System.Int32", parameterTypes[0]); + Assert.AreEqual("System.String", parameterTypes[1]); + Assert.AreEqual("System.Boolean", parameterTypes[2]); + } + + [TestMethod] + public void ParseManagedMethodName_WithArity_ShouldParseArityCorrectly() + { + ManagedNameParser.ParseManagedMethodName("Method`3(System.Int32)", + out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("Method", methodName); + Assert.AreEqual(3, arity); + Assert.IsNotNull(parameterTypes); + } + + [TestMethod] + public void ParseManagedMethodName_WhitespaceInName_ShouldThrow() + { + Assert.ThrowsExactly( + () => ManagedNameParser.ParseManagedMethodName("Method Name", out _, out _, out _)); + } + + [TestMethod] + public void ParseManagedMethodName_NonNumericArity_ShouldThrow() + { + Assert.ThrowsExactly( + () => ManagedNameParser.ParseManagedMethodName("Method`abc", out _, out _, out _)); + } +} diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserFSharpTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserFSharpTests.cs new file mode 100644 index 0000000000..3f1f25fef5 --- /dev/null +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserFSharpTests.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.ManagedNameUtilities; + +/// +/// Regression tests for F# method name unescaping in ManagedNameParser. +/// +[TestClass] +public class ManagedNameParserFSharpTests +{ + // Regression test for #4972 — Unescaping F# method names + // F# methods wrapped in `` backticks are emitted into CIL with single quotes. + // If the method name itself contains a single quote, F# emits it as \' in CIL. + [TestMethod] + public void ParseMethodName_FSharpEscapedSingleQuote_ShouldParseCorrectly() + { + // In CIL, F# method ``don't pass`` becomes 'don\'t pass' + string managedMethodName = "'don\\'t pass'"; + ManagedNameParser.ParseManagedMethodName(managedMethodName, out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("don't pass", methodName); + Assert.AreEqual(0, arity); + Assert.IsNull(parameterTypes); + } + + // Regression test for #4972 + [TestMethod] + public void ParseMethodName_FSharpQuotedSimpleName_ShouldParseCorrectly() + { + // Simple F# method name wrapped in single quotes + string managedMethodName = "'my method'"; + ManagedNameParser.ParseManagedMethodName(managedMethodName, out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("my method", methodName); + Assert.AreEqual(0, arity); + Assert.IsNull(parameterTypes); + } + + // Regression test for #4972 + [TestMethod] + public void ParseMethodName_FSharpQuotedWithParameters_ShouldParseCorrectly() + { + // F# method name with parameters + string managedMethodName = "'my method'(System.Int32)"; + ManagedNameParser.ParseManagedMethodName(managedMethodName, out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("my method", methodName); + Assert.AreEqual(0, arity); + Assert.IsNotNull(parameterTypes); + Assert.HasCount(1, parameterTypes!); + Assert.AreEqual("System.Int32", parameterTypes[0]); + } + + // Regression test for #4972 + [TestMethod] + public void ParseMethodName_RegularMethodName_ShouldStillWork() + { + string managedMethodName = "TestMethod(System.String)"; + ManagedNameParser.ParseManagedMethodName(managedMethodName, out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("TestMethod", methodName); + Assert.AreEqual(0, arity); + Assert.IsNotNull(parameterTypes); + Assert.HasCount(1, parameterTypes!); + } + + // Regression test for #4972 + [TestMethod] + public void ParseMethodName_GenericFSharpMethod_ShouldParseCorrectly() + { + // Generic method with arity + string managedMethodName = "GenericMethod`2(System.Int32,System.String)"; + ManagedNameParser.ParseManagedMethodName(managedMethodName, out string methodName, out int arity, out string[]? parameterTypes); + + Assert.AreEqual("GenericMethod", methodName); + Assert.AreEqual(2, arity); + Assert.IsNotNull(parameterTypes); + Assert.HasCount(2, parameterTypes!); + } +} diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTests.cs index 826de8e0d1..cdb4a77f73 100644 --- a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTests.cs +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTests.cs @@ -70,12 +70,12 @@ public void ParseInvalidMethodName() return (method, arity, parameterTypes); } - Assert.ThrowsException(() => Parse(" Method"), "Whitespace is not valid in a ManagedName (pos: 0)"); - Assert.ThrowsException(() => Parse("Method( List)"), "Whitespace is not valid in a ManagedName (pos: 7)"); + Assert.ThrowsExactly(() => Parse(" Method"), "Whitespace is not valid in a ManagedName (pos: 0)"); + Assert.ThrowsExactly(() => Parse("Method( List)"), "Whitespace is not valid in a ManagedName (pos: 7)"); - Assert.ThrowsException(() => Parse("Method(List)xa"), "Unexpected characters after the end of the ManagedName (pos: 7)"); + Assert.ThrowsExactly(() => Parse("Method(List)xa"), "Unexpected characters after the end of the ManagedName (pos: 7)"); - Assert.ThrowsException(() => Parse("Method("), "ManagedName is incomplete"); - Assert.ThrowsException(() => Parse("Method`4a"), "Method arity must be numeric"); + Assert.ThrowsExactly(() => Parse("Method("), "ManagedName is incomplete"); + Assert.ThrowsExactly(() => Parse("Method`4a"), "Method arity must be numeric"); } } diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTypeNameRegressionTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTypeNameRegressionTests.cs new file mode 100644 index 0000000000..d22ff98e50 --- /dev/null +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameParserTypeNameRegressionTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.ManagedNameUtilities; + +/// +/// Regression tests for ManagedNameParser type name parsing variations. +/// +[TestClass] +public class ManagedNameParserTypeNameRegressionTests +{ + // Regression test for #15259 — Cache AssemblyName in ManagedNameHelper + // Regression test for #15255 — Do half the work in GetManagedName + // Verify type name parsing correctness for various patterns. + + [TestMethod] + public void ParseManagedTypeName_SimpleType_ShouldParse() + { + ManagedNameParser.ParseManagedTypeName("MyNamespace.MyClass", + out string namespaceName, out string typeName); + + Assert.AreEqual("MyNamespace", namespaceName); + Assert.AreEqual("MyClass", typeName); + } + + [TestMethod] + public void ParseManagedTypeName_GenericType_ShouldParseEntireTypeName() + { + ManagedNameParser.ParseManagedTypeName("MyNamespace.MyClass`2", + out string namespaceName, out string typeName); + + Assert.AreEqual("MyNamespace", namespaceName); + Assert.AreEqual("MyClass`2", typeName); + } + + [TestMethod] + public void ParseManagedTypeName_DeeplyNestedType_ShouldParseCorrectly() + { + ManagedNameParser.ParseManagedTypeName("A.B.C+D+E", + out string namespaceName, out string typeName); + + Assert.AreEqual("A.B", namespaceName); + Assert.AreEqual("C+D+E", typeName); + } + + [TestMethod] + public void ParseManagedTypeName_EmptyString_ShouldReturnAsTypeName() + { + ManagedNameParser.ParseManagedTypeName("", + out string namespaceName, out string typeName); + + Assert.AreEqual(string.Empty, namespaceName); + Assert.AreEqual("", typeName); + } +} diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Microsoft.TestPlatform.AdapterUtilities.UnitTests.csproj b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Microsoft.TestPlatform.AdapterUtilities.UnitTests.csproj index c2f98954c2..6fc895e272 100644 --- a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Microsoft.TestPlatform.AdapterUtilities.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Microsoft.TestPlatform.AdapterUtilities.UnitTests.csproj @@ -5,8 +5,8 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.AdapterUtilities.UnitTests true $(NoWarn);RS1024 diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Program.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Program.cs deleted file mode 100644 index 873e849545..0000000000 --- a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..249337437f --- /dev/null +++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/RegressionBugFixTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.Serialization; + +using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests; + +/// +/// Regression test for GH-4424 / PR #4440: +/// InvalidManagedNameException's serialization constructor must be marked [Obsolete] +/// on NET8+ (SYSLIB0051). The primary constructor (string message) must NOT be obsolete. +/// Before the fix, the serialization constructor was not marked obsolete, triggering +/// SYSLIB0051 warnings when building on NET8+. +/// +[TestClass] +public class RegressionBugFixTests +{ + public TestContext TestContext { get; set; } = null!; + + [TestMethod] + public void SerializationConstructor_OnNet8Plus_MustHaveObsoleteAttribute() + { + // GH-4424: The fix added [Obsolete] on #if NET8_0_OR_GREATER to the + // serialization constructor. If reverted, this attribute would be missing. + var serializationCtor = typeof(InvalidManagedNameException) + .GetConstructor( + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, + binder: null, + types: [typeof(SerializationInfo), typeof(StreamingContext)], + modifiers: null); + + Assert.IsNotNull(serializationCtor, + "Serialization constructor (SerializationInfo, StreamingContext) must exist."); + + var obsoleteAttr = serializationCtor.GetCustomAttributes(typeof(ObsoleteAttribute), inherit: false); + +#if NET8_0_OR_GREATER + Assert.IsNotEmpty(obsoleteAttr, + "GH-4424: On NET8+, the serialization constructor must have [Obsolete] attribute."); +#else + Assert.IsEmpty(obsoleteAttr, + "On pre-NET8, the serialization constructor must NOT have [Obsolete] attribute."); +#endif + } + + [TestMethod] + public void PrimaryConstructor_MustNotHaveObsoleteAttribute() + { + // The primary constructor (string message) must NOT be marked obsolete on any TFM. + var primaryCtor = typeof(InvalidManagedNameException) + .GetConstructor([typeof(string)]); + + Assert.IsNotNull(primaryCtor, + "Primary constructor (string) must exist."); + + var obsoleteAttr = primaryCtor.GetCustomAttributes(typeof(ObsoleteAttribute), inherit: false); + Assert.IsEmpty(obsoleteAttr, + "GH-4424: The primary constructor must NOT have [Obsolete] attribute."); + } + + [TestMethod] + public void PrimaryConstructor_MustPreserveMessage() + { + // Verify the primary constructor works correctly and preserves the message. + const string expectedMessage = "test managed name error"; + + var exception = new InvalidManagedNameException(expectedMessage); + + Assert.AreEqual(expectedMessage, exception.Message, + "GH-4424: InvalidManagedNameException must preserve the message from the primary constructor."); + } +} diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/Microsoft.TestPlatform.Build.UnitTests.csproj b/test/Microsoft.TestPlatform.Build.UnitTests/Microsoft.TestPlatform.Build.UnitTests.csproj index 1e5c859108..a1197a8308 100644 --- a/test/Microsoft.TestPlatform.Build.UnitTests/Microsoft.TestPlatform.Build.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Build.UnitTests/Microsoft.TestPlatform.Build.UnitTests.csproj @@ -6,8 +6,8 @@ - net8.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Build.UnitTests @@ -18,6 +18,8 @@ + + diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Build.UnitTests/Program.cs deleted file mode 100644 index ff1507b8ea..0000000000 --- a/test/Microsoft.TestPlatform.Build.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Build.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/TestTaskUtilsTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/TestTaskUtilsTests.cs index a8ed80b8f1..d16be041df 100644 --- a/test/Microsoft.TestPlatform.Build.UnitTests/TestTaskUtilsTests.cs +++ b/test/Microsoft.TestPlatform.Build.UnitTests/TestTaskUtilsTests.cs @@ -36,9 +36,9 @@ public void CreateArgumentShouldAddOneEntryForCLIRunSettings() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, " -- "); - StringAssert.Contains(commandline, $"\"{arg1}\""); - StringAssert.Contains(commandline, $"{arg2}"); + Assert.Contains(" -- ", commandline); + Assert.Contains($"\"{arg1}\"", commandline); + Assert.Contains($"{arg2}", commandline); } [TestMethod] @@ -58,9 +58,9 @@ public void CreateArgumentShouldAddCLIRunSettingsArgAtEnd() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, " -- "); - StringAssert.Contains(commandline, $"\"{arg1}\""); - StringAssert.Contains(commandline, $"{arg2}"); + Assert.Contains(" -- ", commandline); + Assert.Contains($"\"{arg1}\"", commandline); + Assert.Contains($"{arg2}", commandline); } [TestMethod] @@ -71,7 +71,7 @@ public void CreateArgumentShouldPassResultsDirectoryCorrectly() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, $"--resultsDirectory:\"{_vsTestTask.VSTestResultsDirectory?.ItemSpec}\""); + Assert.Contains($"--resultsDirectory:\"{_vsTestTask.VSTestResultsDirectory?.ItemSpec}\"", commandline); } [TestMethod] @@ -82,8 +82,8 @@ public void CreateArgumentShouldNotSetConsoleLoggerVerbosityIfConsoleLoggerIsGiv var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.DoesNotMatch(commandline, new Regex("(--logger:\"Console;Verbosity=normal\")")); - StringAssert.Contains(commandline, "--logger:\"Console;Verbosity=quiet\""); + Assert.DoesNotMatchRegex(new Regex("(--logger:\"Console;Verbosity=normal\")"), commandline); + Assert.Contains("--logger:\"Console;Verbosity=quiet\"", commandline); } [TestMethod] @@ -93,7 +93,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -103,7 +103,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -113,7 +113,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -123,7 +123,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -133,7 +133,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -143,7 +143,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -153,7 +153,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); + Assert.Contains("--logger:Console;Verbosity=quiet", commandline); } [TestMethod] @@ -163,7 +163,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); + Assert.Contains("--logger:Console;Verbosity=quiet", commandline); } [TestMethod] @@ -173,7 +173,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); + Assert.Contains("--logger:Console;Verbosity=minimal", commandline); } [TestMethod] @@ -183,7 +183,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); + Assert.Contains("--logger:Console;Verbosity=minimal", commandline); } [TestMethod] @@ -193,7 +193,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); + Assert.Contains("--logger:Console;Verbosity=normal", commandline); } [TestMethod] @@ -203,7 +203,7 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); + Assert.Contains("--logger:Console;Verbosity=quiet", commandline); } [TestMethod] @@ -213,7 +213,7 @@ public void CreateArgumentShouldPreserveWhiteSpaceInLogger() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:\"trx;LogFileName=foo bar.trx\""); + Assert.Contains("--logger:\"trx;LogFileName=foo bar.trx\"", commandline); } [TestMethod] @@ -226,8 +226,8 @@ public void CreateArgumentShouldAddOneCollectArgumentForEachCollect() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--collect:name1"); - StringAssert.Contains(commandline, "--collect:\"name 2\""); + Assert.Contains("--collect:name1", commandline); + Assert.Contains("--collect:\"name 2\"", commandline); } [TestMethod] @@ -237,8 +237,8 @@ public void CreateArgumentShouldAddMultipleTestAdapterPaths() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--testAdapterPath:path1"); - StringAssert.Contains(commandline, "--testAdapterPath:path2"); + Assert.Contains("--testAdapterPath:path1", commandline); + Assert.Contains("--testAdapterPath:path2", commandline); } [TestMethod] @@ -247,8 +247,8 @@ public void CreateArgumentShouldAddMultipleLoggers() _vsTestTask.VSTestLogger = ["trx;LogFileName=foo bar.trx", "console"]; var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--logger:\"trx;LogFileName=foo bar.trx\""); - StringAssert.Contains(commandline, "--logger:console"); + Assert.Contains("--logger:\"trx;LogFileName=foo bar.trx\"", commandline); + Assert.Contains("--logger:console", commandline); } [TestMethod] @@ -261,7 +261,7 @@ public void CreateArgumentShouldAddTraceCollectorDirectoryPathAsTestAdapterForCo var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); string expectedArg = $"--testAdapterPath:\"{_vsTestTask.VSTestTraceDataCollectorDirectoryPath?.ItemSpec}\""; - StringAssert.Contains(commandline, expectedArg); + Assert.Contains(expectedArg, commandline); } [TestMethod] @@ -274,7 +274,7 @@ public void CreateArgumentShouldNotAddTraceCollectorDirectoryPathAsTestAdapterFo var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); string notExpectedArg = $"--testAdapterPath:\"{_vsTestTask.VSTestTraceDataCollectorDirectoryPath?.ItemSpec}\""; - StringAssert.DoesNotMatch(commandline, new Regex(Regex.Escape(notExpectedArg))); + Assert.DoesNotMatchRegex(new Regex(Regex.Escape(notExpectedArg)), commandline); } [TestMethod] @@ -287,7 +287,7 @@ public void CreateArgumentShouldAddTraceCollectorDirectoryPathAsTestAdapterIfSet var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); string expectedArg = $"--testAdapterPath:{_vsTestTask.VSTestTraceDataCollectorDirectoryPath?.ItemSpec}"; - StringAssert.Contains(commandline, expectedArg); + Assert.Contains(expectedArg, commandline); } [TestMethod] @@ -299,7 +299,7 @@ public void CreateArgumentShouldNotAddTestAdapterPathIfVSTestTraceDataCollectorD var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.DoesNotMatch(commandline, new Regex(@"(--testAdapterPath:)")); + Assert.DoesNotMatchRegex(new Regex(@"(--testAdapterPath:)"), commandline); } [TestMethod] @@ -309,6 +309,6 @@ public void CreateArgumentShouldAddNoLogoOptionIfSpecifiedByUser() var commandline = TestTaskUtils.CreateCommandLineArguments(_vsTestTask); - StringAssert.Contains(commandline, "--nologo"); + Assert.Contains("--nologo", commandline); } } diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2DurationFormattingRegressionTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2DurationFormattingRegressionTests.cs new file mode 100644 index 0000000000..f9f4f648b6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2DurationFormattingRegressionTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.TestPlatform.Build.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Build.UnitTests; + +/// +/// Regression tests for VSTestTask2 duration formatting. +/// +[TestClass] +public class VSTestTask2DurationFormattingRegressionTests +{ + // Regression test for #4894 — Time is reported incorrectly for xunit + + [TestMethod] + public void GetFormattedDurationString_ExactlyOneMinute_ShouldFormat() + { + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromMinutes(1)); + Assert.AreEqual("1m", result); + } + + [TestMethod] + public void GetFormattedDurationString_MinutesAndSeconds_ShouldNotIncludeMilliseconds() + { + var duration = TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(30) + TimeSpan.FromMilliseconds(500); + string? result = VSTestTask2.GetFormattedDurationString(duration); + + // When minutes > 0, milliseconds should not be included + Assert.AreEqual("1m 30s", result); + } + + [TestMethod] + public void GetFormattedDurationString_HoursMinutesSeconds_ShouldNotIncludeSeconds() + { + var duration = TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30) + TimeSpan.FromSeconds(45); + string? result = VSTestTask2.GetFormattedDurationString(duration); + + // When hours > 0, seconds should not be included + Assert.AreEqual("2h 30m", result); + } + + [TestMethod] + public void GetFormattedDurationString_OnlyMilliseconds_ShouldFormat() + { + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromMilliseconds(42)); + Assert.AreEqual("42ms", result); + } + + [TestMethod] + public void GetFormattedDurationString_ExactlyOneSecond_ShouldFormat() + { + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromSeconds(1)); + Assert.AreEqual("1s", result); + } + + [TestMethod] + public void GetFormattedDurationString_FractionalMilliseconds_ShouldRoundDown() + { + // TimeSpan of 0.5ms + var duration = TimeSpan.FromTicks(5000); // 0.5ms + string? result = VSTestTask2.GetFormattedDurationString(duration); + // Milliseconds property rounds down, so 0ms = "< 1ms" + Assert.AreEqual("< 1ms", result); + } +} diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2RegressionTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2RegressionTests.cs new file mode 100644 index 0000000000..b3e85fb5b8 --- /dev/null +++ b/test/Microsoft.TestPlatform.Build.UnitTests/VSTestTask2RegressionTests.cs @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.TestPlatform.Build.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Build.UnitTests; + +/// +/// Regression tests for VSTestTask2 message parsing and formatting. +/// +[TestClass] +public class VSTestTask2RegressionTests +{ + // Regression test for #5115 — Write dll instead of target on abort, rename errors + // Regression test for #5113 — Error output as info in terminal logger + // Regression test for #5084 — Handle ANSI escape in terminal logger reporter + + [TestMethod] + public void GetFormattedDurationString_ZeroDuration_ShouldReturnNull() + { + // Regression test for #4894 — Time is reported incorrectly + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.Zero); + Assert.IsNull(result); + } + + [TestMethod] + public void GetFormattedDurationString_SubMillisecond_ShouldReturnLessThan1ms() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromTicks(1)); + Assert.AreEqual("< 1ms", result); + } + + [TestMethod] + public void GetFormattedDurationString_Milliseconds_ShouldFormatCorrectly() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromMilliseconds(500)); + Assert.AreEqual("500ms", result); + } + + [TestMethod] + public void GetFormattedDurationString_Seconds_ShouldFormatCorrectly() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromSeconds(5)); + Assert.AreEqual("5s", result); + } + + [TestMethod] + public void GetFormattedDurationString_SecondsAndMilliseconds_ShouldFormatCorrectly() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromMilliseconds(5500)); + Assert.AreEqual("5s 500ms", result); + } + + [TestMethod] + public void GetFormattedDurationString_Minutes_ShouldOmitMilliseconds() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromMinutes(2) + TimeSpan.FromSeconds(30)); + Assert.AreEqual("2m 30s", result); + } + + [TestMethod] + public void GetFormattedDurationString_Hours_ShouldOmitSecondsAndMilliseconds() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromHours(1) + TimeSpan.FromMinutes(15)); + Assert.AreEqual("1h 15m", result); + } + + [TestMethod] + public void GetFormattedDurationString_MoreThanOneDay_ShouldReturnGreaterThan1d() + { + // Regression test for #4894 + string? result = VSTestTask2.GetFormattedDurationString(TimeSpan.FromDays(2)); + Assert.AreEqual("> 1d", result); + } + + [TestMethod] + public void LogEventsFromTextOutput_NonSplitterMessage_ShouldNotThrow() + { + // Regression test for #5113 — Regular output should be forwarded without error + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + // Call protected method via helper + task.TestLogEventsFromTextOutput("Microsoft (R) Test Execution Command Line Tool", MessageImportance.High); + + // Should have logged an output message, not an error + Assert.IsEmpty(engine.Errors); + } + + [TestMethod] + public void LogEventsFromTextOutput_OutputError_ShouldLogAsInfo() + { + // Regression test for #5113 — Error output from testhost should not be logged as MSBuild error + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + task.TestLogEventsFromTextOutput("||||output-error||||Some stderr output from test", MessageImportance.High); + + // Should NOT log as error + Assert.IsEmpty(engine.Errors); + // Should log as message (info) + Assert.IsNotEmpty(engine.Messages); + + } + + [TestMethod] + public void LogEventsFromTextOutput_RunCancel_ShouldLogErrorWithTestRunCancelCode() + { + // Regression test for #5115 — Write dll instead of target on abort + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + task.TestLogEventsFromTextOutput("||||run-cancel||||Test run was canceled.", MessageImportance.High); + + Assert.HasCount(1, engine.Errors); + Assert.AreEqual("TESTRUNCANCEL", engine.Errors[0].Code); + } + + [TestMethod] + public void LogEventsFromTextOutput_RunAbort_ShouldLogErrorWithTestRunAbortCode() + { + // Regression test for #5115 — Write dll instead of target on abort + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + task.TestLogEventsFromTextOutput("||||run-abort||||Test run was aborted.", MessageImportance.High); + + Assert.HasCount(1, engine.Errors); + Assert.AreEqual("TESTRUNABORT", engine.Errors[0].Code); + } + + [TestMethod] + public void LogEventsFromTextOutput_TestFailed_ShouldLogErrorWithTestErrorCode() + { + // Regression test for #5115 + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + task.TestLogEventsFromTextOutput("||||test-failed||||Test failed: expected 1 but was 2||||TestFile.cs||||42", MessageImportance.High); + + Assert.HasCount(1, engine.Errors); + Assert.AreEqual("TESTERROR", engine.Errors[0].Code); + } + + [TestMethod] + public void LogEventsFromTextOutput_OutputWarning_ShouldLogAsWarning() + { + var task = CreateVSTestTask2(); + var engine = (RecordingBuildEngine)task.BuildEngine; + + task.TestLogEventsFromTextOutput("||||output-warning||||Some warning message", MessageImportance.High); + + Assert.HasCount(1, engine.Warnings); + } + + [TestMethod] + public void LogEventsFromTextOutput_NullOutput_ShouldNotThrow() + { + // Regression test for #5113 — LogMSBuildOutputMessage null-safe + var task = CreateVSTestTask2(); + + // Should not throw + task.TestLogEventsFromTextOutput("||||output-info||||", MessageImportance.High); + } + + private static TestableVSTestTask2 CreateVSTestTask2() + { + var engine = new RecordingBuildEngine(); + var task = new TestableVSTestTask2 + { + BuildEngine = engine, + TestFileFullPath = new TaskItem(@"C:\path\to\test.dll"), + VSTestConsolePath = new TaskItem(@"C:\path\to\vstest.console.dll"), + }; + return task; + } +} + +/// +/// Testable wrapper that exposes LogEventsFromTextOutput for testing. +/// +internal class TestableVSTestTask2 : VSTestTask2 +{ + public void TestLogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) + { + LogEventsFromTextOutput(singleLine, messageImportance); + } +} + +/// +/// A build engine that records errors, warnings, and messages for test verification. +/// +internal class RecordingBuildEngine : IBuildEngine +{ + public List Errors { get; } = new(); + public List Warnings { get; } = new(); + public List Messages { get; } = new(); + + public bool ContinueOnError => false; + public int LineNumberOfTaskNode => 0; + public int ColumnNumberOfTaskNode => 0; + public string ProjectFileOfTaskNode => string.Empty; + + public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs) => false; + + public void LogCustomEvent(CustomBuildEventArgs e) { } + + public void LogErrorEvent(BuildErrorEventArgs e) => Errors.Add(e); + + public void LogMessageEvent(BuildMessageEventArgs e) => Messages.Add(e); + + public void LogWarningEvent(BuildWarningEventArgs e) => Warnings.Add(e); +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs index 84acf7765a..fbf6fe4eee 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs @@ -42,6 +42,8 @@ public class DesignModeClientTests private readonly AutoResetEvent _completeEvent; private readonly Mock _mockPlatformEnvironment; + public TestContext TestContext { get; set; } + public DesignModeClientTests() { _mockTestRequestManager = new Mock(); @@ -108,7 +110,7 @@ public void DesignModeClientConnectShouldNotSendConnectedIfServerConnectionTimes _mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(false); _mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()).Returns(verCheck).Returns(sessionEnd); - Assert.ThrowsException(() => _designModeClient.ConnectToClientAndProcessRequests(PortNumber, _mockTestRequestManager.Object)); + Assert.ThrowsExactly(() => _designModeClient.ConnectToClientAndProcessRequests(PortNumber, _mockTestRequestManager.Object)); _mockCommunicationManager.Verify(cm => cm.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, PortNumber)), Times.Once); _mockCommunicationManager.Verify(cm => cm.WaitForServerConnection(It.IsAny()), Times.Once); @@ -198,7 +200,7 @@ public void DesignModeClientWithGetTestRunnerProcessStartInfoShouldDeserializeTe // Assert. Assert.IsNotNull(receivedTestRunPayload); Assert.IsNotNull(receivedTestRunPayload.TestCases); - Assert.AreEqual(1, receivedTestRunPayload.TestCases.Count); + Assert.HasCount(1, receivedTestRunPayload.TestCases); // Validate traits var traits = receivedTestRunPayload.TestCases.ToArray()[0].Traits; @@ -259,7 +261,7 @@ public void DesignModeClientWithRunSelectedTestCasesShouldDeserializeTestsWithTr // Assert. Assert.IsNotNull(receivedTestRunPayload); Assert.IsNotNull(receivedTestRunPayload.TestCases); - Assert.AreEqual(1, receivedTestRunPayload.TestCases.Count); + Assert.HasCount(1, receivedTestRunPayload.TestCases); // Validate traits var traits = receivedTestRunPayload.TestCases.ToArray()[0].Traits; @@ -272,7 +274,7 @@ public void DesignModeClientOnBadConnectionShouldStopServerAndThrowTimeoutExcept { _mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(false); - var ex = Assert.ThrowsException(() => _designModeClient.ConnectToClientAndProcessRequests(PortNumber, _mockTestRequestManager.Object)); + var ex = Assert.ThrowsExactly(() => _designModeClient.ConnectToClientAndProcessRequests(PortNumber, _mockTestRequestManager.Object)); Assert.AreEqual("vstest.console process failed to connect to translation layer process after 90 seconds. This may occur due to machine slowness, please set environment variable VSTEST_CONNECTION_TIMEOUT to increase timeout.", ex.Message); _mockCommunicationManager.Verify(cm => cm.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, PortNumber)), Times.Once); @@ -300,7 +302,7 @@ public void DesignModeClientLaunchCustomHostMustReturnIfAckComes() Action sendMessageAction = () => testableDesignModeClient.InvokeCustomHostLaunchAckCallback(expectedProcessId, null); _mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.CustomTestHostLaunch, It.IsAny())). - Callback(() => Task.Run(sendMessageAction)); + Callback(() => Task.Run(sendMessageAction, TestContext.CancellationToken)); var info = new TestProcessStartInfo(); var processId = testableDesignModeClient.LaunchCustomHost(info, CancellationToken.None); @@ -309,7 +311,6 @@ public void DesignModeClientLaunchCustomHostMustReturnIfAckComes() } [TestMethod] - [ExpectedException(typeof(TestPlatformException))] public void DesignModeClientLaunchCustomHostMustThrowIfInvalidAckComes() { var testableDesignModeClient = new TestableDesignModeClient(_mockCommunicationManager.Object, JsonDataSerializer.Instance, _mockPlatformEnvironment.Object); @@ -321,14 +322,13 @@ public void DesignModeClientLaunchCustomHostMustThrowIfInvalidAckComes() _mockCommunicationManager .Setup(cm => cm.SendMessage(MessageType.CustomTestHostLaunch, It.IsAny())) - .Callback(() => Task.Run(sendMessageAction)); + .Callback(() => Task.Run(sendMessageAction, TestContext.CancellationToken)); var info = new TestProcessStartInfo(); - testableDesignModeClient.LaunchCustomHost(info, CancellationToken.None); + Assert.ThrowsExactly(() => testableDesignModeClient.LaunchCustomHost(info, CancellationToken.None)); } [TestMethod] - [ExpectedException(typeof(TestPlatformException))] public void DesignModeClientLaunchCustomHostMustThrowIfCancellationOccursBeforeHostLaunch() { var testableDesignModeClient = new TestableDesignModeClient(_mockCommunicationManager.Object, JsonDataSerializer.Instance, _mockPlatformEnvironment.Object); @@ -337,7 +337,7 @@ public void DesignModeClientLaunchCustomHostMustThrowIfCancellationOccursBeforeH var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Cancel(); - testableDesignModeClient.LaunchCustomHost(info, cancellationTokenSource.Token); + Assert.ThrowsExactly(() => testableDesignModeClient.LaunchCustomHost(info, cancellationTokenSource.Token)); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs index 22c33b59bc..876669365d 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeTestHostLauncherTests.cs @@ -14,6 +14,8 @@ namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.DesignMode; [TestClass] public class DesignModeTestHostLauncherTests { + public TestContext TestContext { get; set; } + [TestMethod] public void DesignModeTestHostLauncherLaunchTestHostShouldCallDesignModeClientToLaunchCustomHost() { @@ -23,7 +25,7 @@ public void DesignModeTestHostLauncherLaunchTestHostShouldCallDesignModeClientTo var testProcessStartInfo = new TestProcessStartInfo(); - launcher.LaunchTestHost(testProcessStartInfo); + launcher.LaunchTestHost(testProcessStartInfo, TestContext.CancellationToken); mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo, It.IsAny()), Times.Once); } @@ -37,7 +39,7 @@ public void DesignModeDebugTestHostLauncherLaunchTestHostShouldCallDesignModeCli var testProcessStartInfo = new TestProcessStartInfo(); - launcher.LaunchTestHost(testProcessStartInfo); + launcher.LaunchTestHost(testProcessStartInfo, TestContext.CancellationToken); mockDesignModeClient.Verify(md => md.LaunchCustomHost(testProcessStartInfo, It.IsAny()), Times.Once); } diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs index 1a64538b9a..a7be4b884e 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Discovery/DiscoveryRequestTests.cs @@ -58,7 +58,7 @@ public void DiscoveryAsycIfDiscoveryRequestIsDisposedThrowsObjectDisposedExcepti { _discoveryRequest.Dispose(); - Assert.ThrowsException(() => _discoveryRequest.DiscoverAsync()); + Assert.ThrowsExactly(() => _discoveryRequest.DiscoverAsync()); } [TestMethod] @@ -74,26 +74,19 @@ public void DiscoverAsyncSetsDiscoveryInProgressAndCallManagerToDiscoverTests() public void DiscoveryAsyncIfDiscoverTestsThrowsExceptionSetsDiscoveryInProgressToFalseAndThrowsThatException() { _discoveryManager.Setup(dm => dm.DiscoverTests(_discoveryCriteria, _discoveryRequest as DiscoveryRequest)).Throws(new Exception("DummyException")); - try - { - _discoveryRequest.DiscoverAsync(); - } - catch (Exception ex) - { - Assert.IsTrue(ex is Exception); - Assert.AreEqual("DummyException", ex.Message); - Assert.IsFalse((_discoveryRequest as DiscoveryRequest).DiscoveryInProgress); - } + var ex = Assert.ThrowsExactly(() => _discoveryRequest.DiscoverAsync()); + Assert.AreEqual("DummyException", ex.Message); + Assert.IsFalse((_discoveryRequest as DiscoveryRequest).DiscoveryInProgress); } [TestMethod] public void AbortIfDiscoveryRequestDisposedShouldThrowObjectDisposedException() { _discoveryRequest.Dispose(); - Assert.ThrowsException(() => _discoveryRequest.Abort()); + Assert.ThrowsExactly(() => _discoveryRequest.Abort()); } - [DataTestMethod] + [TestMethod] [DynamicData(nameof(ProtocolConfigVersionProvider))] public void AbortIfDiscoveryIsinProgressShouldCallDiscoveryManagerAbort(int version) { @@ -128,7 +121,7 @@ public void AbortIfDiscoveryIsNotInProgressShouldNotCallDiscoveryManagerAbort() public void WaitForCompletionIfDiscoveryRequestDisposedShouldThrowObjectDisposedException() { _discoveryRequest.Dispose(); - Assert.ThrowsException(() => _discoveryRequest.WaitForCompletion()); + Assert.ThrowsExactly(() => _discoveryRequest.WaitForCompletion()); } [TestMethod] @@ -150,7 +143,7 @@ public void HandleDiscoveryCompleteShouldCloseDiscoveryManagerBeforeRaiseDiscove eventsHandler.HandleDiscoveryComplete(new DiscoveryCompleteEventArgs(1, false), []); - Assert.AreEqual(2, events.Count); + Assert.HasCount(2, events); Assert.AreEqual("close", events[0]); Assert.AreEqual("complete", events[1]); } diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs index d77e39fda6..75ec037bf1 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Execution/TestRunRequestTests.cs @@ -34,6 +34,8 @@ public class TestRunRequestTests private readonly Mock _mockDataSerializer; + public TestContext TestContext { get; set; } + public TestRunRequestTests() { _testRunCriteria = new TestRunCriteria(new List { "foo" }, 1); @@ -58,14 +60,14 @@ public void ExecuteAsycIfTestRunRequestIsDisposedThrowsObjectDisposedException() { _testRunRequest.Dispose(); - Assert.ThrowsException(() => _testRunRequest.ExecuteAsync()); + Assert.ThrowsExactly(() => _testRunRequest.ExecuteAsync()); } [TestMethod] public void ExecuteAsycIfStateIsNotPendingThrowsInvalidOperationException() { _testRunRequest.ExecuteAsync(); - Assert.ThrowsException(() => _testRunRequest.ExecuteAsync()); + Assert.ThrowsExactly(() => _testRunRequest.ExecuteAsync()); } [TestMethod] @@ -81,16 +83,10 @@ public void ExecuteAsyncSetsStateToInProgressAndCallManagerToStartTestRun() public void ExecuteAsyncIfStartTestRunThrowsExceptionSetsStateToPendingAndThrowsThatException() { _executionManager.Setup(em => em.StartTestRun(_testRunCriteria, _testRunRequest)).Throws(new Exception("DummyException")); - try - { - _testRunRequest.ExecuteAsync(); - } - catch (Exception ex) - { - Assert.IsTrue(ex is not null); - Assert.AreEqual("DummyException", ex.Message); - Assert.AreEqual(TestRunState.Pending, _testRunRequest.State); - } + var ex = Assert.ThrowsExactly(() => _testRunRequest.ExecuteAsync()); + Assert.IsNotNull(ex); + Assert.AreEqual("DummyException", ex.Message); + Assert.AreEqual(TestRunState.Pending, _testRunRequest.State); } [TestMethod] @@ -122,13 +118,13 @@ public void AbortIfDiscoveryIsinProgressShouldCallDiscoveryManagerAbort() public void WaitForCompletionIfTestRunRequestDisposedShouldThrowObjectDisposedException() { _testRunRequest.Dispose(); - Assert.ThrowsException(() => _testRunRequest.WaitForCompletion()); + Assert.ThrowsExactly(() => _testRunRequest.WaitForCompletion()); } [TestMethod] public void WaitForCompletionIfTestRunStatePendingShouldThrowInvalidOperationException() { - Assert.ThrowsException(() => _testRunRequest.WaitForCompletion()); + Assert.ThrowsExactly(() => _testRunRequest.WaitForCompletion()); } [TestMethod] @@ -585,7 +581,7 @@ public void HandleTestRunCompleteShouldCloseExecutionManager() _testRunRequest.HandleTestRunComplete(new TestRunCompleteEventArgs(new TestRunStatistics(1, null), false, false, null, null, null, TimeSpan.FromSeconds(0)), null, null, null); - Assert.AreEqual(2, events.Count); + Assert.HasCount(2, events); Assert.AreEqual("close", events[0]); Assert.AreEqual("complete", events[1]); } @@ -601,7 +597,9 @@ public void LaunchProcessWithDebuggerAttachedShouldNotCallCustomLauncherIfTestRu var testProcessStartInfo = new TestProcessStartInfo(); _testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); +#pragma warning disable MSTEST0049 // Moq Verify pattern - not an actual method invocation mockCustomLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Never); +#pragma warning restore MSTEST0049 } [TestMethod] @@ -617,7 +615,9 @@ public void LaunchProcessWithDebuggerAttachedShouldNotCallCustomLauncherIfLaunch var testProcessStartInfo = new TestProcessStartInfo(); _testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); +#pragma warning disable MSTEST0049 // Moq Verify pattern - not an actual method invocation mockCustomLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Never); +#pragma warning restore MSTEST0049 } [TestMethod] @@ -634,7 +634,9 @@ public void LaunchProcessWithDebuggerAttachedShouldCallCustomLauncherIfLauncherI mockCustomLauncher.Setup(ml => ml.IsDebug).Returns(true); _testRunRequest.LaunchProcessWithDebuggerAttached(testProcessStartInfo); +#pragma warning disable MSTEST0049 // Moq Verify pattern - not an actual method invocation mockCustomLauncher.Verify(ml => ml.LaunchTestHost(testProcessStartInfo), Times.Once); +#pragma warning restore MSTEST0049 } /// diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.csproj b/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.csproj index d3976dd0ef..890481f977 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Client.UnitTests/Microsoft.TestPlatform.Client.UnitTests.csproj @@ -6,17 +6,11 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Client.UnitTests - - - - - - diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Client.UnitTests/Program.cs deleted file mode 100644 index 2f6d8d1a57..0000000000 --- a/test/Microsoft.TestPlatform.Client.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs index b2a1cf8b2f..be7a99531c 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/TestPlatformTests.cs @@ -105,7 +105,7 @@ public void CreateDiscoveryRequestThrowsIfDiscoveryCriteriaIsNull() { TestPlatform tp = new(); - Assert.ThrowsException(() => tp.CreateDiscoveryRequest(_mockRequestData.Object, null!, new TestPlatformOptions(), It.IsAny>(), It.IsAny())); + Assert.ThrowsExactly(() => tp.CreateDiscoveryRequest(_mockRequestData.Object, null!, new TestPlatformOptions(), It.IsAny>(), It.IsAny())); } [TestMethod] @@ -283,7 +283,7 @@ public void CreateTestRunRequestThrowsIfTestRunCriteriaIsNull() { var tp = new TestPlatform(); - Assert.ThrowsException(() => tp.CreateTestRunRequest(_mockRequestData.Object, null!, new TestPlatformOptions(), It.IsAny>(), It.IsAny())); + Assert.ThrowsExactly(() => tp.CreateTestRunRequest(_mockRequestData.Object, null!, new TestPlatformOptions(), It.IsAny>(), It.IsAny())); } /// @@ -424,7 +424,7 @@ public void StartTestSessionShouldThrowExceptionIfTestSessionCriteriaIsNull() { var tp = new TestableTestPlatform(_testEngine.Object, _hostManager.Object); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => tp.StartTestSession( new Mock().Object, null!, diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/DataCollectorExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/DataCollectorExtensionManagerTests.cs index 4422bceb8e..5b076750f8 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/DataCollectorExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/DataCollectorExtensionManagerTests.cs @@ -22,7 +22,7 @@ public void Initialize() [TestMethod] public void CreateShouldThrowExceptionIfMessageLoggerIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { var dataCollectionExtensionManager = DataCollectorExtensionManager.Create(null!); }); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/ExtensionDecoratorTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/ExtensionDecoratorTests.cs index a44997fd80..0a4033083a 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/ExtensionDecoratorTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/ExtensionDecoratorTests.cs @@ -66,12 +66,14 @@ public void SerialTestRunDecorator_ShouldSerializeTests() Assert.AreEqual(0, Interlocked.Read(ref currentCount)); currentCount = Interlocked.Increment(ref currentCount); TestCase tc = tests!.First(); +#pragma warning disable MSTEST0049 // CancellationToken not relevant in Moq callback Task.Run(() => { Thread.Sleep(100); currentCount = Interlocked.Decrement(ref currentCount); frameworkHandle!.RecordEnd(tc, TestOutcome.Passed); }); +#pragma warning restore MSTEST0049 testCasesRan.Add(tc); }); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs index f31173f945..1c4a91bedd 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExtensionManagerTests.cs @@ -42,7 +42,7 @@ public void Cleanup() [TestMethod] public void TestExtensionManagerConstructorShouldThrowExceptionIfMessageLoggerIsNull() { - Assert.ThrowsException(() => _testExtensionManager = new DummyTestExtensionManager(_unfilteredTestExtensions, _filteredTestExtensions, null!)); + Assert.ThrowsExactly(() => _testExtensionManager = new DummyTestExtensionManager(_unfilteredTestExtensions, _filteredTestExtensions, null!)); } [TestMethod] @@ -60,7 +60,7 @@ public void TryGetTestExtensionShouldThrowExceptionWithNullUri() { _testExtensionManager = new DummyTestExtensionManager(_unfilteredTestExtensions, _filteredTestExtensions, _messageLogger); TestPluginCacheHelper.SetupMockAdditionalPathExtensions(typeof(TestExtensionManagerTests)); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { var result = _testExtensionManager.TryGetTestExtension(default(Uri)!); } diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestLoggerExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestLoggerExtensionManagerTests.cs index e6990a0641..26940069e1 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestLoggerExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestLoggerExtensionManagerTests.cs @@ -22,7 +22,7 @@ public void Initialize() [TestMethod] public void CreateShouldThrowExceptionIfMessageLoggerIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { var testLoggerExtensionManager = TestLoggerExtensionManager.Create(null!); }); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs index 30612dfc6e..295074c4c9 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginCacheTests.cs @@ -90,7 +90,7 @@ public void UpdateAdditionalExtensionsShouldOnlyAddUniqueExtensionPaths() var updatedExtensions = TestPluginCache.Instance.GetExtensionPaths(string.Empty); Assert.IsNotNull(updatedExtensions); - Assert.AreEqual(1, updatedExtensions.Count); + Assert.ContainsSingle(updatedExtensions); CollectionAssert.AreEqual(new List { additionalExtensions.First() }, updatedExtensions); } @@ -102,7 +102,7 @@ public void UpdateAdditionalExtensionsShouldUpdatePathsThatDoNotExist() var updatedExtensions = TestPluginCache.Instance.GetExtensionPaths(string.Empty); Assert.IsNotNull(updatedExtensions); - Assert.AreEqual(1, updatedExtensions.Count); + Assert.ContainsSingle(updatedExtensions); } [TestMethod] @@ -114,7 +114,7 @@ public void UpdateAdditionalExtensionsShouldUpdateUnfilteredExtensionsListWhenSk // Since the extension is unfiltered, above filter criteria doesn't filter it Assert.IsNotNull(updatedExtensions); - Assert.AreEqual(1, updatedExtensions.Count); + Assert.ContainsSingle(updatedExtensions); } [Ignore] @@ -134,7 +134,7 @@ public void ClearExtensionsShouldClearPathToExtensions() TestPluginCache.Instance.ClearExtensions(); - Assert.AreEqual(0, TestPluginCache.Instance.GetExtensionPaths(string.Empty).Count); + Assert.IsEmpty(TestPluginCache.Instance.GetExtensionPaths(string.Empty)); } #endregion @@ -249,7 +249,7 @@ public void GetDefaultResolutionPathsShouldReturnDirectoryFromDefaultExtensionsP var resolutionPaths = TestPluginCache.Instance.GetDefaultResolutionPaths(); Assert.IsNotNull(resolutionPaths); - Assert.IsTrue(resolutionPaths.Contains(Path.GetDirectoryName(defaultExtensionsFile)!)); + Assert.Contains(Path.GetDirectoryName(defaultExtensionsFile)!, resolutionPaths); } #endregion @@ -259,7 +259,7 @@ public void GetDefaultResolutionPathsShouldReturnDirectoryFromDefaultExtensionsP [TestMethod] public void GetResolutionPathsShouldThrowIfExtensionAssemblyIsNull() { - Assert.ThrowsException(() => TestPluginCache.GetResolutionPaths(null!)); + Assert.ThrowsExactly(() => TestPluginCache.GetResolutionPaths(null!)); } [TestMethod] @@ -298,7 +298,7 @@ public void GetTestExtensionsShouldReturnExtensionsInAssembly() TestPluginCache.Instance.GetTestExtensions(typeof(TestPluginCacheTests).Assembly.Location); Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); - Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers!.Count > 0); + Assert.IsNotEmpty(TestPluginCache.Instance.TestExtensions.TestDiscoverers!); } [TestMethod] @@ -335,7 +335,7 @@ public void GetTestExtensionsShouldShouldThrowIfDiscovererThrows() //TODO : make ITestDiscoverer interface and then mock it in order to make this test case pass. var extensionAssembly = typeof(TestPluginCacheTests).Assembly.Location; - Assert.ThrowsException(() => _testablePluginCache.GetTestExtensions(extensionAssembly)); + Assert.ThrowsExactly(() => _testablePluginCache.GetTestExtensions(extensionAssembly)); } #endregion @@ -352,7 +352,7 @@ public void DiscoverTestExtensionsShouldDiscoverExtensionsFromExtensionsFolder() Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); // Validate the discoverers to be absolutely certain. - Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestDiscoverers!.Count > 0); + Assert.IsNotEmpty(TestPluginCache.Instance.TestExtensions.TestDiscoverers!); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs index 15fdc0702a..ec7e42b5d5 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs @@ -18,8 +18,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MSTest.TestFramework.AssertExtensions; - namespace TestPlatform.Common.UnitTests.ExtensionFramework; [TestClass] @@ -87,7 +85,7 @@ public void GetTestExtensionsInformationShouldReturnLoggerExtensions() var pluginInformation = new TestLoggerPluginInformation(typeof(ValidLogger)); var pluginInformation2 = new TestLoggerPluginInformation(typeof(ValidLogger2)); - Assert.AreEqual(1, testExtensions.Keys.Count(k => k.Contains("csv"))); + Assert.ContainsSingle(testExtensions.Keys.Where(k => k.Contains("csv"))); Assert.IsTrue(testExtensions.ContainsKey(pluginInformation.IdentifierData!)); } @@ -101,9 +99,9 @@ public void GetTestExtensionsInformationShouldReturnDataCollectorExtensionsAndIg var pluginInformation = new DataCollectorConfig(typeof(ValidDataCollector)); - Assert.AreEqual(2, testExtensions.Keys.Count); - Assert.AreEqual(1, testExtensions.Keys.Count(k => k.Equals("datacollector://foo/bar"))); - Assert.AreEqual(1, testExtensions.Keys.Count(k => k.Equals("datacollector://foo/bar1"))); + Assert.HasCount(2, testExtensions.Keys); + Assert.ContainsSingle(testExtensions.Keys.Where(k => k.Equals("datacollector://foo/bar"))); + Assert.ContainsSingle(testExtensions.Keys.Where(k => k.Equals("datacollector://foo/bar1"))); } [TestMethod] @@ -117,7 +115,7 @@ public void GetTestExtensionsInformationShouldReturnSettingsProviderExtensions() var pluginInformation = new TestSettingsProviderPluginInformation(typeof(ValidSettingsProvider)); var pluginInformation2 = new TestSettingsProviderPluginInformation(typeof(ValidSettingsProvider2)); - Assert.IsTrue(testExtensions.Keys.Select(k => k.Contains("ValidSettingsProvider")).Count() >= 3); + Assert.IsGreaterThanOrEqualTo(3, testExtensions.Keys.Select(k => k.Contains("ValidSettingsProvider")).Count()); Assert.IsTrue(testExtensions.ContainsKey(pluginInformation.IdentifierData!)); Assert.IsTrue(testExtensions.ContainsKey(pluginInformation2.IdentifierData!)); } @@ -130,9 +128,9 @@ public void GetTestExtensionsInformationShouldNotAbortOnFaultyExtensions() typeof(TestPluginDiscovererTests).Assembly.Location, }; - var testExtensions = TestPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions); + _ = TestPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions); - Assert.That.DoesNotThrow(() => TestPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions)); + _ = TestPluginDiscoverer.GetTestExtensionsInformation(pathToExtensions); } #region Implementations diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs index 62682571f6..5cf0e10a5d 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginManagerTests.cs @@ -32,7 +32,7 @@ public void GetTestExtensionTypeShouldReturnExtensionType() [TestMethod] public void GetTestExtensionTypeShouldThrowIfTypeNotFound() { - Assert.ThrowsException(() => TestPluginManager.GetTestExtensionType("randomassemblyname.random")); + Assert.ThrowsExactly(() => TestPluginManager.GetTestExtensionType("randomassemblyname.random")); } [TestMethod] @@ -46,7 +46,7 @@ public void CreateTestExtensionShouldCreateExtensionTypeInstance() [TestMethod] public void CreateTestExtensionShouldThrowIfInstanceCannotBeCreated() { - Assert.ThrowsException(() => TestPluginManager.CreateTestExtension(typeof(AbstractDummyLogger))); + Assert.ThrowsExactly(() => TestPluginManager.CreateTestExtension(typeof(AbstractDummyLogger))); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/RunSettingsProviderExtensionsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/RunSettingsProviderExtensionsTests.cs index e6831cf97b..bc690e7297 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/RunSettingsProviderExtensionsTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/RunSettingsProviderExtensionsTests.cs @@ -34,27 +34,27 @@ public void UpdateRunSettingsShouldUpdateGivenSettingsXml() _runSettingsProvider.UpdateRunSettings(runSettingsXml); - StringAssert.Contains(_runSettingsProvider.ActiveRunSettings!.SettingsXml, runSettingsXml); + Assert.Contains(runSettingsXml, _runSettingsProvider.ActiveRunSettings!.SettingsXml!); } [TestMethod] public void UpdateRunSettingsShouldThrownExceptionIfRunSettingsProviderIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => RunSettingsProviderExtensions.UpdateRunSettings(null!, "")); } [TestMethod] public void UpdateRunSettingsShouldThrownExceptionIfSettingsXmlIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _runSettingsProvider.UpdateRunSettings(null!)); } [TestMethod] public void UpdateRunSettingsShouldThrownExceptionIfSettingsXmlIsEmptyOrWhiteSpace() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _runSettingsProvider.UpdateRunSettings(" ")); } @@ -101,41 +101,41 @@ public void AddDefaultRunSettingsShouldNotChangeSpecifiedSettings() var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsProvider.ActiveRunSettings!.SettingsXml); - Assert.AreEqual(runConfiguration.TargetPlatform, Architecture.X64); + Assert.AreEqual(Architecture.X64, runConfiguration.TargetPlatform); } [TestMethod] public void AddDefaultRunSettingsShouldThrowExceptionIfArgumentIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => RunSettingsProviderExtensions.AddDefaultRunSettings(null!)); } [TestMethod] public void UpdateRunSettingsNodeShouldThrowExceptionIfKeyIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNode(null!, "data")); } [TestMethod] public void UpdateRunSettingsNodeShouldThrowExceptionIfKeyIsEmptyOrWhiteSpace() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNode(" ", "data")); } [TestMethod] public void UpdateRunSettingsNodeShouldThrowExceptionIfDataIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNode("Key", null!)); } [TestMethod] public void UpdateRunSettingsNodeShouldThrowExceptionIfRunSettingsProviderIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => RunSettingsProviderExtensions.UpdateRunSettingsNode(null!, "Key", "data")); } @@ -212,28 +212,28 @@ public void UpdateRunSettingsNodeShouldUpdateKeyIfAlreadyPresent() [TestMethod] public void UpdateRunSettingsNodeInnerXmlShouldThrowExceptionIfKeyIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNodeInnerXml(null!, "")); } [TestMethod] public void UpdateRunSettingsNodeInnerXmlShouldThrowExceptionIfKeyIsEmptyOrWhiteSpace() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNodeInnerXml(" ", "")); } [TestMethod] public void UpdateRunSettingsNodeInnerXmlShouldThrowExceptionIfXmlIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _runSettingsProvider.UpdateRunSettingsNodeInnerXml("Key", null!)); } [TestMethod] public void UpdateRunSettingsNodeInnerXmlShouldThrowExceptionIfRunSettingsProviderIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => RunSettingsProviderExtensions.UpdateRunSettingsNodeInnerXml(null!, "Key", "")); } @@ -259,13 +259,13 @@ public void UpdateRunSettingsNodeInnerXmlShouldUpdateKeyIfAlreadyPresent() [TestMethod] public void QueryRunSettingsNodeShouldThrowIfKeyIsNull() { - Assert.ThrowsException(() => _runSettingsProvider.QueryRunSettingsNode(null!)); + Assert.ThrowsExactly(() => _runSettingsProvider.QueryRunSettingsNode(null!)); } [TestMethod] public void QueryRunSettingsNodeShouldThrowIfKeyIsEmptyOrWhiteSpace() { - Assert.ThrowsException(() => _runSettingsProvider.QueryRunSettingsNode(" ")); + Assert.ThrowsExactly(() => _runSettingsProvider.QueryRunSettingsNode(" ")); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs index 6b61aa6568..106bf816ce 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/Utilities/TestDiscovererPluginInformationTests.cs @@ -37,7 +37,7 @@ public void FileExtensionsShouldReturnEmptyListIfADiscovererSupportsNoFileExtens { _testPluginInformation = new TestDiscovererPluginInformation(typeof(DummyTestDiscovererWithNoFileExtensions)); Assert.IsNotNull(_testPluginInformation.FileExtensions); - Assert.AreEqual(0, _testPluginInformation.FileExtensions.Count); + Assert.IsEmpty(_testPluginInformation.FileExtensions); Assert.IsFalse(_testPluginInformation.IsDirectoryBased); } @@ -142,9 +142,9 @@ public void IsDirectoryBasedShouldReturnTrueIfDiscovererIsDirectoryBased() var testPluginMetada = _testPluginInformation.Metadata.ToArray(); Assert.IsNotNull(_testPluginInformation.FileExtensions); - Assert.AreEqual(0, _testPluginInformation.FileExtensions.Count); + Assert.IsEmpty(_testPluginInformation.FileExtensions); Assert.IsNotNull(testPluginMetada[0]); - Assert.AreEqual(0, ((List)testPluginMetada[0]!).Count); + Assert.IsEmpty((List)testPluginMetada[0]!); Assert.IsTrue(_testPluginInformation.IsDirectoryBased); Assert.IsTrue(bool.Parse(testPluginMetada[3]!.ToString()!)); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionEvaluationRegressionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionEvaluationRegressionTests.cs new file mode 100644 index 0000000000..d3d78060b6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionEvaluationRegressionTests.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.Common.UnitTests.Filtering; + +/// +/// Regression tests for Condition.Evaluate refactored operations. +/// +[TestClass] +public class ConditionEvaluationRegressionTests +{ + // Regression test for #15357 — Refactor Condition evaluation + // EvaluateEqualOperation and EvaluateContainsOperation were extracted as helpers. + // Verify all four operations work correctly. + + [TestMethod] + public void Evaluate_EqualOperation_SingleValueMatch_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" ? "UnitTest" : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_EqualOperation_SingleValueNoMatch_ShouldReturnFalse() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" ? "Integration" : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_EqualOperation_MultiValue_AnyMatch_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" + ? new string[] { "Integration", "UnitTest", "Smoke" } + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_EqualOperation_MultiValue_NoMatch_ShouldReturnFalse() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" + ? new string[] { "Integration", "Smoke" } + : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_NotEqualOperation_NoMatch_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.NotEqual, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" ? "Integration" : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_NotEqualOperation_Match_ShouldReturnFalse() + { + var condition = new Condition("Category", Operation.NotEqual, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" ? "UnitTest" : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_NotEqualOperation_MultiValue_AllDifferent_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.NotEqual, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" + ? new string[] { "Integration", "Smoke" } + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_NotEqualOperation_MultiValue_AnyMatch_ShouldReturnFalse() + { + var condition = new Condition("Category", Operation.NotEqual, "UnitTest"); + bool result = condition.Evaluate(name => name == "Category" + ? new string[] { "Integration", "UnitTest" } + : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_ContainsOperation_SubstringMatch_ShouldReturnTrue() + { + var condition = new Condition("FullyQualifiedName", Operation.Contains, "MyTests"); + bool result = condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.MyTests.TestMethod1" + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_ContainsOperation_NoSubstringMatch_ShouldReturnFalse() + { + var condition = new Condition("FullyQualifiedName", Operation.Contains, "MyTests"); + bool result = condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.OtherTests.TestMethod1" + : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_NotContainsOperation_NoSubstring_ShouldReturnTrue() + { + var condition = new Condition("FullyQualifiedName", Operation.NotContains, "MyTests"); + bool result = condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.OtherTests.TestMethod1" + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_NotContainsOperation_SubstringPresent_ShouldReturnFalse() + { + var condition = new Condition("FullyQualifiedName", Operation.NotContains, "MyTests"); + bool result = condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.MyTests.TestMethod1" + : null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_EqualOperation_CaseInsensitive_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.Equal, "unittest"); + bool result = condition.Evaluate(name => name == "Category" ? "UnitTest" : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_ContainsOperation_CaseInsensitive_ShouldReturnTrue() + { + var condition = new Condition("FullyQualifiedName", Operation.Contains, "mytests"); + bool result = condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.MyTests.TestMethod1" + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_NullPropertyValue_EqualShouldReturnFalse() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + bool result = condition.Evaluate(name => null); + Assert.IsFalse(result); + } + + [TestMethod] + public void Evaluate_NullPropertyValue_NotEqualShouldReturnTrue() + { + var condition = new Condition("Category", Operation.NotEqual, "UnitTest"); + bool result = condition.Evaluate(name => null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_ContainsOperation_MultiValue_AnyContains_ShouldReturnTrue() + { + var condition = new Condition("Tag", Operation.Contains, "unit"); + bool result = condition.Evaluate(name => name == "Tag" + ? new string[] { "integration", "unit-test", "smoke" } + : null); + Assert.IsTrue(result); + } + + [TestMethod] + public void Evaluate_NotContainsOperation_MultiValue_AllNotContaining_ShouldReturnTrue() + { + var condition = new Condition("Tag", Operation.NotContains, "unit"); + bool result = condition.Evaluate(name => name == "Tag" + ? new string[] { "integration", "smoke", "e2e" } + : null); + Assert.IsTrue(result); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionParsingRegressionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionParsingRegressionTests.cs new file mode 100644 index 0000000000..92c3a6565c --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionParsingRegressionTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.Common.UnitTests.Filtering; + +/// +/// Regression tests for Condition.Parse and tokenization. +/// +[TestClass] +public class ConditionParsingRegressionTests +{ + // Regression test for #15357 — Refactor Condition evaluation + // Verify that parsing and evaluation are integrated correctly. + + [TestMethod] + public void Parse_EqualCondition_ShouldEvaluateCorrectly() + { + var condition = Condition.Parse("Category=UnitTest"); + + Assert.IsTrue(condition.Evaluate(name => name == "Category" ? "UnitTest" : null)); + Assert.IsFalse(condition.Evaluate(name => name == "Category" ? "Integration" : null)); + } + + [TestMethod] + public void Parse_NotEqualCondition_ShouldEvaluateCorrectly() + { + var condition = Condition.Parse("Category!=UnitTest"); + + Assert.IsTrue(condition.Evaluate(name => name == "Category" ? "Integration" : null)); + Assert.IsFalse(condition.Evaluate(name => name == "Category" ? "UnitTest" : null)); + } + + [TestMethod] + public void Parse_ContainsCondition_ShouldEvaluateCorrectly() + { + var condition = Condition.Parse("FullyQualifiedName~MyClass"); + + Assert.IsTrue(condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.MyClass.Method" : null)); + Assert.IsFalse(condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.OtherClass.Method" : null)); + } + + [TestMethod] + public void Parse_NotContainsCondition_ShouldEvaluateCorrectly() + { + var condition = Condition.Parse("FullyQualifiedName!~MyClass"); + + Assert.IsTrue(condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.OtherClass.Method" : null)); + Assert.IsFalse(condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.MyClass.Method" : null)); + } + + [TestMethod] + public void Parse_DefaultCondition_ShouldUseContainsOnFQN() + { + // When only a value is provided (no operator), it defaults to FullyQualifiedName Contains + var condition = Condition.Parse("TestMethod"); + + Assert.IsTrue(condition.Evaluate(name => name == "FullyQualifiedName" + ? "Namespace.Class.TestMethod" : null)); + } + + [TestMethod] + public void Parse_EscapedCondition_ShouldHandleSpecialChars() + { + // Escaped = should be treated as literal + var condition = Condition.Parse(@"FullyQualifiedName~test\=method"); + + Assert.IsTrue(condition.Evaluate(name => name == "FullyQualifiedName" + ? "test=method" : null)); + } + + [TestMethod] + public void Parse_EmptyString_ShouldThrow() + { + Assert.ThrowsExactly(() => Condition.Parse("")); + } + + [TestMethod] + public void Parse_NullString_ShouldThrow() + { + Assert.ThrowsExactly(() => Condition.Parse(null)); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTests.cs index ab7d08c6fc..02c65418b3 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -17,21 +17,21 @@ public class ConditionTests public void ParseShouldThrownFormatExceptionOnNullConditionString() { string? conditionString = null; - Assert.ThrowsException(() => Condition.Parse(conditionString)); + Assert.ThrowsExactly(() => Condition.Parse(conditionString)); } [TestMethod] public void ParseShouldThrownFormatExceptionOnEmptyConditionString() { var conditionString = ""; - Assert.ThrowsException(() => Condition.Parse(conditionString)); + Assert.ThrowsExactly(() => Condition.Parse(conditionString)); } [TestMethod] public void ParseShouldThrownFormatExceptionOnIncompleteConditionString() { var conditionString = "PropertyName="; - Assert.ThrowsException(() => Condition.Parse(conditionString)); + Assert.ThrowsExactly(() => Condition.Parse(conditionString)); } [TestMethod] @@ -114,7 +114,7 @@ public void ParseStringWithSingleUnescapedBangThrowsFormatException1() { var conditionString = @"FullyQualifiedName=Test1(""!"")"; - Assert.ThrowsException(() => Condition.Parse(conditionString)); + Assert.ThrowsExactly(() => Condition.Parse(conditionString)); } [TestMethod] @@ -122,13 +122,13 @@ public void ParseStringWithSingleUnescapedBangThrowsFormatException2() { var conditionString = @"FullyQualifiedName!Test1()"; - Assert.ThrowsException(() => Condition.Parse(conditionString)); + Assert.ThrowsExactly(() => Condition.Parse(conditionString)); } [TestMethod] public void TokenizeNullThrowsArgumentNullException() { - Assert.ThrowsException(() => Condition.TokenizeFilterConditionString(null!), "str"); + Assert.ThrowsExactly(() => Condition.TokenizeFilterConditionString(null!), "str"); } [TestMethod] @@ -138,7 +138,7 @@ public void TokenizeConditionShouldHandleEscapedBang() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("=", tokens[1]); Assert.AreEqual(@"TestMethod\(""\!""\)", tokens[2]); @@ -151,7 +151,7 @@ public void TokenizeConditionShouldHandleEscapedNotEqual1() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("=", tokens[1]); Assert.AreEqual(@"TestMethod\(""\!\=""\)", tokens[2]); @@ -164,7 +164,7 @@ public void TokenizeConditionShouldHandleEscapedNotEqual2() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("!=", tokens[1]); Assert.AreEqual(@"TestMethod\(""\!\=""\)", tokens[2]); @@ -177,7 +177,7 @@ public void TokenizeConditionShouldHandleEscapedBackslash() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("=", tokens[1]); Assert.AreEqual(@"TestMethod\(""\\""\)", tokens[2]); @@ -190,7 +190,7 @@ public void TokenizeConditionShouldHandleEscapedTilde() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("~", tokens[1]); Assert.AreEqual(@"TestMethod\(""\~""\)", tokens[2]); @@ -203,7 +203,7 @@ public void TokenizeConditionShouldHandleEscapedNotTilde() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(3, tokens.Length); + Assert.HasCount(3, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("!~", tokens[1]); Assert.AreEqual(@"TestMethod\(""\!\~""\)", tokens[2]); @@ -216,7 +216,7 @@ public void TokenizeConditionShouldHandleSingleUnescapedBang() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(5, tokens.Length); + Assert.HasCount(5, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("!=", tokens[1]); Assert.AreEqual(@"TestMethod\(""", tokens[2]); @@ -231,7 +231,7 @@ public void TokenizeConditionShouldHandleSingleBangAtEnd() var tokens = Condition.TokenizeFilterConditionString(conditionString).ToArray(); - Assert.AreEqual(2, tokens.Length); + Assert.HasCount(2, tokens); Assert.AreEqual("FullyQualifiedName", tokens[0]); Assert.AreEqual("!", tokens[1]); } diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTokenizationRegressionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTokenizationRegressionTests.cs new file mode 100644 index 0000000000..4f3f7396e3 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionTokenizationRegressionTests.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.Common.UnitTests.Filtering; + +/// +/// Regression tests for Condition tokenization of filter strings. +/// +[TestClass] +public class ConditionTokenizationRegressionTests +{ + // Regression test for #15357 — Refactor Condition evaluation + // Verifying tokenization works correctly with special characters. + + [TestMethod] + public void TokenizeFilterConditionString_EqualOperator_ShouldProduceThreeTokens() + { + var tokens = Condition.TokenizeFilterConditionString("Name=Value").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("=", tokens[1]); + Assert.AreEqual("Value", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_NotEqualOperator_ShouldProduceThreeTokens() + { + var tokens = Condition.TokenizeFilterConditionString("Name!=Value").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("!=", tokens[1]); + Assert.AreEqual("Value", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_ContainsOperator_ShouldProduceThreeTokens() + { + var tokens = Condition.TokenizeFilterConditionString("Name~Value").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("~", tokens[1]); + Assert.AreEqual("Value", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_NotContainsOperator_ShouldProduceThreeTokens() + { + var tokens = Condition.TokenizeFilterConditionString("Name!~Value").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("!~", tokens[1]); + Assert.AreEqual("Value", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_EscapedEquals_ShouldNotSplitOnEscaped() + { + var tokens = Condition.TokenizeFilterConditionString(@"Name=Value\=More").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("=", tokens[1]); + Assert.AreEqual(@"Value\=More", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_EscapedBackslash_ShouldPreserve() + { + var tokens = Condition.TokenizeFilterConditionString(@"Name=Value\\End").ToArray(); + + Assert.HasCount(3, tokens); + Assert.AreEqual("Name", tokens[0]); + Assert.AreEqual("=", tokens[1]); + Assert.AreEqual(@"Value\\End", tokens[2]); + } + + [TestMethod] + public void TokenizeFilterConditionString_ValueOnly_ShouldProduceSingleToken() + { + var tokens = Condition.TokenizeFilterConditionString("JustAValue").ToArray(); + + Assert.HasCount(1, tokens); + Assert.AreEqual("JustAValue", tokens[0]); + } + + [TestMethod] + public void TokenizeFilterConditionString_Null_ShouldThrow() + { + Assert.ThrowsExactly( + () => Condition.TokenizeFilterConditionString(null!).ToArray()); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionValidForPropertiesRegressionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionValidForPropertiesRegressionTests.cs new file mode 100644 index 0000000000..7a9edf0e7b --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/ConditionValidForPropertiesRegressionTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.Common.UnitTests.Filtering; + +/// +/// Regression tests for Condition.ValidForProperties method. +/// +[TestClass] +public class ConditionValidForPropertiesRegressionTests +{ + // Regression test for #15357 — Refactor Condition evaluation + // Verify ValidForProperties correctly identifies valid properties. + + [TestMethod] + public void ValidForProperties_KnownProperty_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.Equal, "UnitTest"); + var properties = new List { "Category", "Priority", "FullyQualifiedName" }; + + bool isValid = condition.ValidForProperties(properties, null); + + Assert.IsTrue(isValid); + } + + [TestMethod] + public void ValidForProperties_UnknownProperty_ShouldReturnFalse() + { + var condition = new Condition("UnknownProp", Operation.Equal, "Value"); + var properties = new List { "Category", "Priority" }; + + bool isValid = condition.ValidForProperties(properties, null); + + Assert.IsFalse(isValid); + } + + [TestMethod] + public void ValidForProperties_CaseInsensitive_ShouldReturnTrue() + { + var condition = new Condition("category", Operation.Equal, "UnitTest"); + var properties = new List { "Category", "Priority" }; + + bool isValid = condition.ValidForProperties(properties, null); + + Assert.IsTrue(isValid); + } + + [TestMethod] + public void ValidForProperties_ContainsOnStringProperty_ShouldReturnTrue() + { + var condition = new Condition("Category", Operation.Contains, "Unit"); + + var stringProperty = TestProperty.Register( + "ContainsTest.Category", "Category", typeof(string), typeof(TestCase)); + + Func propertyProvider = name => + string.Equals(name, "Category", StringComparison.OrdinalIgnoreCase) ? stringProperty : null; + + var properties = new List { "Category" }; + bool isValid = condition.ValidForProperties(properties, propertyProvider); + + Assert.IsTrue(isValid, "Contains operation should be valid for string properties."); + } + + [TestMethod] + public void ValidForProperties_ContainsOnNonStringProperty_ShouldReturnFalse() + { + var condition = new Condition("Priority", Operation.Contains, "1"); + + var intProperty = TestProperty.Register( + "ContainsTest.Priority", "Priority", typeof(int), typeof(TestCase)); + + Func propertyProvider = name => + string.Equals(name, "Priority", StringComparison.OrdinalIgnoreCase) ? intProperty : null; + + var properties = new List { "Priority" }; + bool isValid = condition.ValidForProperties(properties, propertyProvider); + + Assert.IsFalse(isValid, "Contains operation should be invalid for non-string properties."); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FastFilterTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FastFilterTests.cs index 4608a2d494..7c1bad5344 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FastFilterTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FastFilterTests.cs @@ -22,7 +22,7 @@ public void MultipleOperatorKindsShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name=Test1&(Name=Test2|NameTest3)"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); } [TestMethod] @@ -31,7 +31,7 @@ public void MultipleOperationKindsShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name!=TestClass1&Category=Nightly"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); } [TestMethod] @@ -40,7 +40,7 @@ public void ContainsOperationShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name~TestClass1"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); } [TestMethod] @@ -49,7 +49,7 @@ public void NotContainsOperationShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name!~TestClass1"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); } [TestMethod] @@ -58,7 +58,7 @@ public void AndOperatorAndEqualsOperationShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name=Test1&Name=Test2"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); Assert.IsTrue(string.IsNullOrEmpty(filterExpressionWrapper.ParseError)); } @@ -68,7 +68,7 @@ public void OrOperatorAndNotEqualsOperationShouldNotCreateFastFilter() var filterExpressionWrapper = new FilterExpressionWrapper("Name!=Test1|Name!=Test2"); var fastFilter = filterExpressionWrapper.FastFilter; - Assert.IsTrue(fastFilter == null); + Assert.IsNull(fastFilter); Assert.IsTrue(string.IsNullOrEmpty(filterExpressionWrapper.ParseError)); } @@ -80,7 +80,7 @@ public void FastFilterWithSingleEqualsClause() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -105,8 +105,8 @@ public void ValidForPropertiesHandlesBigFilteringExpressions() string[]? invalidProperties = filterExpressionWrapper.ValidForProperties(new List() { "FullyQualifiedName" }, null); Assert.IsNotNull(invalidProperties); - Assert.AreEqual(invalidProperties.Length, 1); - Assert.AreEqual(invalidProperties[0], "Category"); + Assert.ContainsSingle(invalidProperties); + Assert.AreEqual("Category", invalidProperties[0]); } [TestMethod] @@ -131,7 +131,7 @@ public void FastFilterWithMultipleEqualsClause() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1", "test2", "test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -152,7 +152,7 @@ public void FastFilterWithMultipleEqualsClauseAndParentheses() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1", "test2", "test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -173,7 +173,7 @@ public void FastFilterWithMultipleEqualsClauseAndRegex() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1", "test2", "test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -198,7 +198,7 @@ public void FastFilterWithMultipleEqualsClauseForMultiplePropertyValues() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "unittest", "perftest" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("Category", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -221,7 +221,7 @@ public void FastFilterWithMultipleEqualsClauseAndRegexReplacement() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "testclass.test1", "testclass.test2", "testclass.test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsFalse(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -261,7 +261,7 @@ private static void CheckFastFailureWithNotEqualClause(string filterString) var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsTrue(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -280,7 +280,7 @@ public void FastFilterWithMultipleNotEqualsClause() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1", "test2", "test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsTrue(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -301,7 +301,7 @@ public void FastFilterWithMultipleNotEqualsClauseAndRegex() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "test1", "test2", "test3" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("FullyQualifiedName", fastFilter.FilterProperties.Keys.Single()); Assert.IsTrue(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -326,7 +326,7 @@ public void FastFilterWithMultipleNotEqualsClauseForMultiplePropertyValues() var expectedFilterValues = new HashSet(StringComparer.OrdinalIgnoreCase) { "unittest", "perftest" }; - Assert.IsTrue(fastFilter != null); + Assert.IsNotNull(fastFilter); Assert.AreEqual("Category", fastFilter.FilterProperties.Keys.Single()); Assert.IsTrue(fastFilter.IsFilteredOutWhenMatched); Assert.IsTrue(expectedFilterValues.SetEquals(fastFilter.FilterProperties.Values.Single())); @@ -354,15 +354,8 @@ public void FastFilterWithWithRegexParseErrorShouldNotCreateFastFilter() public void FastFilterShouldThrowExceptionForUnsupportedOperatorOperationCombination() { ImmutableHashSet.Builder filterHashSetBuilder = ImmutableHashSet.CreateBuilder(); - try - { - var filter = new FastFilter(ImmutableDictionary.CreateRange(new[] { new KeyValuePair>("dummyName", filterHashSetBuilder.ToImmutableHashSet()) }), Operation.Equal, Operator.And); - } - catch (Exception ex) - { - Assert.IsTrue(ex is ArgumentException); - Assert.AreEqual("An error occurred while creating Fast filter.", ex.Message); - } + var ex = Assert.ThrowsExactly(() => new FastFilter(ImmutableDictionary.CreateRange(new[] { new KeyValuePair>("dummyName", filterHashSetBuilder.ToImmutableHashSet()) }), Operation.Equal, Operator.And)); + Assert.AreEqual("An error occurred while creating Fast filter.", ex.Message); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FilterExpressionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FilterExpressionTests.cs index 99a5f7964e..a7d9e9c565 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FilterExpressionTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Filtering/FilterExpressionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -16,7 +16,7 @@ public class FilterExpressionTests [TestMethod] public void TokenizeNullThrowsArgumentNullException() { - Assert.ThrowsException(() => FilterExpression.TokenizeFilterExpressionString(null!), "str"); + Assert.ThrowsExactly(() => FilterExpression.TokenizeFilterExpressionString(null!), "str"); } [TestMethod] @@ -26,7 +26,7 @@ public void TokenizeFilterShouldHandleEscapedParenthesis() var tokens = FilterExpression.TokenizeFilterExpressionString(conditionString).ToArray(); - Assert.AreEqual(5, tokens.Length); + Assert.HasCount(5, tokens); Assert.AreEqual("(", tokens[0]); Assert.AreEqual(@"T1\(\) ", tokens[1]); Assert.AreEqual(@"|", tokens[2]); @@ -41,7 +41,7 @@ public void TokenizeFilterShouldHandleEmptyParenthesis() var tokens = FilterExpression.TokenizeFilterExpressionString(conditionString).ToArray(); - Assert.AreEqual(5, tokens.Length); + Assert.HasCount(5, tokens); Assert.AreEqual(" ", tokens[0]); Assert.AreEqual("(", tokens[1]); Assert.AreEqual(" ", tokens[2]); @@ -56,7 +56,7 @@ public void TokenizeFilterShouldHandleEscapedBackslash() var tokens = FilterExpression.TokenizeFilterExpressionString(conditionString).ToArray(); - Assert.AreEqual(5, tokens.Length); + Assert.HasCount(5, tokens); Assert.AreEqual("(", tokens[0]); Assert.AreEqual(@"FQN!=T1\(""\\""\) ", tokens[1]); Assert.AreEqual(@"|", tokens[2]); @@ -71,7 +71,7 @@ public void TokenizeFilterShouldHandleNestedParenthesis() var tokens = FilterExpression.TokenizeFilterExpressionString(conditionString).ToArray(); - Assert.AreEqual(11, tokens.Length); + Assert.HasCount(11, tokens); Assert.AreEqual("(", tokens[0]); Assert.AreEqual("(", tokens[1]); Assert.AreEqual(@"FQN!=T1", tokens[2]); @@ -92,7 +92,7 @@ public void TokenizeFilterShouldHandleInvalidEscapeSequence() var tokens = FilterExpression.TokenizeFilterExpressionString(conditionString).ToArray(); - Assert.AreEqual(5, tokens.Length); + Assert.HasCount(5, tokens); Assert.AreEqual("(", tokens[0]); Assert.AreEqual(@"T1\#\#", tokens[1]); Assert.AreEqual(@")", tokens[2]); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostExtensionManagerTests.cs index f693fe887a..bd717cd616 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostExtensionManagerTests.cs @@ -22,7 +22,7 @@ public void Initialize() [TestMethod] public void CreateShouldThrowExceptionIfMessageLoggerIsNull() { - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => { var testLoggerExtensionManager = TestRuntimeExtensionManager.Create(null!); }); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs index 5077e68760..4c6438fe52 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Logging/InternalTestLoggerEventsTests.cs @@ -102,13 +102,13 @@ public void RaiseTestResultShouldInvokeRegisteredEventHandlerIfTestResultEventAr [TestMethod] public void RaiseTestResultShouldThrowExceptionIfNullTestResultEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseTestResult(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseTestResult(null!)); } [TestMethod] public void RaiseTestRunMessageShouldThrowExceptioIfNullTestRunMessageEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseTestRunMessage(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseTestRunMessage(null!)); } [TestMethod] @@ -171,7 +171,7 @@ public void RaiseTestResultShouldThrowExceptionIfDisposedIsAlreadyCalled() { var loggerEvents = GetDisposedLoggerEvents(); - Assert.ThrowsException(() => loggerEvents.RaiseTestResult(new TestResultEventArgs(new TestResult(new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName"))))); + Assert.ThrowsExactly(() => loggerEvents.RaiseTestResult(new TestResultEventArgs(new TestResult(new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName"))))); } [TestMethod] @@ -179,7 +179,7 @@ public void RaiseTestRunMessageShouldThrowExceptionIfDisposeIsAlreadyCalled() { var loggerEvents = GetDisposedLoggerEvents(); - Assert.ThrowsException(() => loggerEvents.RaiseTestRunMessage(new TestRunMessageEventArgs(TestMessageLevel.Error, "This is a string."))); + Assert.ThrowsExactly(() => loggerEvents.RaiseTestRunMessage(new TestRunMessageEventArgs(TestMessageLevel.Error, "This is a string."))); } [TestMethod] @@ -187,7 +187,7 @@ public void CompleteTestRunShouldThrowExceptionIfAlreadyDisposed() { var loggerEvents = GetDisposedLoggerEvents(); - Assert.ThrowsException(() => loggerEvents.CompleteTestRun(null, true, false, null, null, null, new TimeSpan())); + Assert.ThrowsExactly(() => loggerEvents.CompleteTestRun(null, true, false, null, null, null, new TimeSpan())); } [TestMethod] @@ -195,7 +195,7 @@ public void EnableEventsShouldThrowExceptionIfAlreadyDisposed() { var loggerEvents = GetDisposedLoggerEvents(); - Assert.ThrowsException(() => loggerEvents.EnableEvents()); + Assert.ThrowsExactly(() => loggerEvents.EnableEvents()); } [TestMethod] @@ -231,7 +231,7 @@ public void TestLoggerProxySendMessageShouldNotInvokeRegisterdEventHandlerIfAlre [TestMethod] public void RaiseDiscoveryStartShouldThrowExceptionIfNullDiscoveryStartEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseDiscoveryStart(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseDiscoveryStart(null!)); } /// @@ -240,7 +240,7 @@ public void RaiseDiscoveryStartShouldThrowExceptionIfNullDiscoveryStartEventArgs [TestMethod] public void RaiseDiscoveredTestsShouldThrowExceptionIfNullDiscoveredTestsEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseDiscoveredTests(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseDiscoveredTests(null!)); } /// @@ -253,7 +253,7 @@ public void RaiseDiscoveredTestsShouldThrowExceptionIfAlreadyDisposed() List testCases = [new TestCase("This is a string.", new Uri("some://uri"), "DummySourceFileName")]; DiscoveredTestsEventArgs discoveredTestsEventArgs = new(testCases); - Assert.ThrowsException(() => loggerEvents.RaiseDiscoveredTests(discoveredTestsEventArgs)); + Assert.ThrowsExactly(() => loggerEvents.RaiseDiscoveredTests(discoveredTestsEventArgs)); } /// @@ -294,7 +294,7 @@ public void RaiseDiscoveredTestsShouldInvokeRegisteredEventHandler() [TestMethod] public void RaiseDiscoveryCompleteShouldThrowExceptionIfNullDiscoveryCompleteEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseDiscoveryComplete(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseDiscoveryComplete(null!)); } /// @@ -307,7 +307,7 @@ public void RaiseDiscoveryStartShouldThrowExceptionIfAlreadyDisposed() DiscoveryCriteria discoveryCriteria = new() { TestCaseFilter = "Name=Test1" }; DiscoveryStartEventArgs discoveryStartEventArgs = new(discoveryCriteria); - Assert.ThrowsException(() => loggerEvents.RaiseDiscoveryStart(discoveryStartEventArgs)); + Assert.ThrowsExactly(() => loggerEvents.RaiseDiscoveryStart(discoveryStartEventArgs)); } /// @@ -319,7 +319,7 @@ public void RaiseDiscoveryCompleteShouldThrowExceptionIfAlreadyDisposed() var loggerEvents = GetDisposedLoggerEvents(); DiscoveryCompleteEventArgs discoveryCompleteEventArgs = new(2, false); - Assert.ThrowsException(() => loggerEvents.RaiseDiscoveryComplete(discoveryCompleteEventArgs)); + Assert.ThrowsExactly(() => loggerEvents.RaiseDiscoveryComplete(discoveryCompleteEventArgs)); } /// @@ -392,7 +392,7 @@ public void RaiseDiscoveryCompleteShouldInvokeRegisteredEventHandler() [TestMethod] public void RaiseTestRunStartShouldThrowExceptionIfNullTestRunStartEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseTestRunStart(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseTestRunStart(null!)); } /// @@ -401,7 +401,7 @@ public void RaiseTestRunStartShouldThrowExceptionIfNullTestRunStartEventArgsIsPa [TestMethod] public void RaiseDiscoveryMessageShouldThrowExceptionIfNullTestRunMessageEventArgsIsPassed() { - Assert.ThrowsException(() => _loggerEvents.RaiseDiscoveryMessage(null!)); + Assert.ThrowsExactly(() => _loggerEvents.RaiseDiscoveryMessage(null!)); } /// @@ -414,7 +414,7 @@ public void RaiseTestRunStartShouldThrowExceptionIfAlreadyDisposed() TestRunCriteria testRunCriteria = new(new List { @"x:dummy\foo.dll" }, 10, false, string.Empty, TimeSpan.MaxValue, null, "Name=Test1", null); TestRunStartEventArgs testRunStartEventArgs = new(testRunCriteria); - Assert.ThrowsException(() => loggerEvents.RaiseTestRunStart(testRunStartEventArgs)); + Assert.ThrowsExactly(() => loggerEvents.RaiseTestRunStart(testRunStartEventArgs)); } /// @@ -427,7 +427,7 @@ public void RaiseDiscoveryMessageShouldThrowExceptionIfAlreadyDisposed() string message = "This is the test message"; TestRunMessageEventArgs testRunMessageEventArgs = new(TestMessageLevel.Informational, message); - Assert.ThrowsException(() => loggerEvents.RaiseDiscoveryMessage(testRunMessageEventArgs)); + Assert.ThrowsExactly(() => loggerEvents.RaiseDiscoveryMessage(testRunMessageEventArgs)); } /// diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj index 5a72e7dc34..85a7135592 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj @@ -6,16 +6,10 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Common.UnitTests - - - - - - diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Program.cs deleted file mode 100644 index a5c6522e97..0000000000 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.Common.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RequestDataTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RequestDataTests.cs index 3767137f3e..c9068f4e23 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/RequestDataTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RequestDataTests.cs @@ -34,19 +34,17 @@ public void RequestDataShouldReturnValidProtocolConfig() } [TestMethod] - [ExpectedException(typeof(ArgumentNullException))] public void RequestDataShouldThrowArgumentNullExpectionOnNullMetricsCollection() { var requestData = new RequestData(); - requestData.MetricsCollection = null!; + Assert.ThrowsExactly(() => requestData.MetricsCollection = null!); } [TestMethod] - [ExpectedException(typeof(ArgumentNullException))] public void RequestDataShouldThrowArgumentNullExpectionOnNullProtocolConfig() { var requestData = new RequestData(); - requestData.ProtocolConfig = null; + Assert.ThrowsExactly(() => requestData.ProtocolConfig = null); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs index 75bf473528..a548d64cf4 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs @@ -9,6 +9,7 @@ namespace TestPlatform.Common.UnitTests; [TestClass] +[DoNotParallelize] public class RunSettingsManagerTests { [TestCleanup] @@ -48,7 +49,7 @@ public void SetActiveRunSettingsShouldThrowIfRunSettingsPassedIsNull() { var instance = RunSettingsManager.Instance; - Assert.ThrowsException(() => instance.SetActiveRunSettings(null!)); + Assert.ThrowsExactly(() => instance.SetActiveRunSettings(null!)); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs index a21b3d16b4..0c3193067f 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsTests.cs @@ -30,14 +30,14 @@ public void TestCleanup() public void LoadSettingsXmlShouldThrowOnNullSettings() { var runSettings = new RunSettings(); - Assert.ThrowsException(() => runSettings.LoadSettingsXml(null!)); + Assert.ThrowsExactly(() => runSettings.LoadSettingsXml(null!)); } [TestMethod] public void LoadSettingsXmlShouldThrowOnEmptySettings() { var runSettings = new RunSettings(); - Assert.ThrowsException(() => runSettings.LoadSettingsXml(" ")); + Assert.ThrowsExactly(() => runSettings.LoadSettingsXml(" ")); } [TestMethod] @@ -54,7 +54,7 @@ public void LoadSettingsXmlShoulLoadAndInitializeSettingsXml() var expectedRunSettings = "" + Environment.NewLine + ""; - StringAssert.Contains(runSettings.SettingsXml, expectedRunSettings); + Assert.Contains(expectedRunSettings, runSettings.SettingsXml!); } [TestMethod] @@ -63,7 +63,7 @@ public void LoadSettingsXmlShouldThrowOnInvalidSettings() var runSettings = new RunSettings(); var invalidSettings = GetInvalidRunSettings(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => runSettings.LoadSettingsXml(invalidSettings), "An error occurred while loading the run settings."); } @@ -76,7 +76,7 @@ public void LoadSettingsXmlShouldThrowOnInvalidSettings() public void InitializeSettingsProvidersShouldThrowOnNullSettings() { var runSettings = new RunSettings(); - Assert.ThrowsException(() => runSettings.InitializeSettingsProviders(null!)); + Assert.ThrowsExactly(() => runSettings.InitializeSettingsProviders(null!)); } [TestMethod] @@ -100,7 +100,7 @@ public void InitializeSettingsProvidersShouldThrowIfNodeInRunSettingsDoesNotHave Action action = () => runSettings.GetSettings("OrphanNode"); - Assert.ThrowsException( + Assert.ThrowsExactly( action, "Settings Provider named '{0}' was not found. The settings can not be loaded.", "OrphanNode"); @@ -117,7 +117,7 @@ public void InitializeSettingsProvidersShouldThrowIfSettingsProviderLoadThrows() Action action = () => runSettings.GetSettings("BadSettings"); - Assert.ThrowsException( + Assert.ThrowsExactly( action, "An error occurred while initializing the settings provider named '{0}'", "BadSettings"); @@ -127,7 +127,7 @@ public void InitializeSettingsProvidersShouldThrowIfSettingsProviderLoadThrows() public void InitializeSettingsProvidersShouldThrowIfInvalidRunSettingsIsPassed() { var runSettings = new RunSettings(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => runSettings.InitializeSettingsProviders(GetInvalidRunSettings()), "An error occurred while loading the run settings."); } @@ -137,7 +137,7 @@ public void InitializeSettingsProvidersMultipleTimesShouldThrowInvalidOperationE { var runSettings = new RunSettings(); runSettings.InitializeSettingsProviders(GetEmptyRunSettings()); - Assert.ThrowsException( + Assert.ThrowsExactly( () => runSettings.InitializeSettingsProviders(GetEmptyRunSettings()), "The Run Settings have already been loaded."); } @@ -211,7 +211,7 @@ public void GetSettingsShouldThrowIfSettingsNameIsNull() { var runSettings = new RunSettings(); - Assert.ThrowsException(() => runSettings.GetSettings(null!)); + Assert.ThrowsExactly(() => runSettings.GetSettings(null!)); } [TestMethod] @@ -219,7 +219,7 @@ public void GetSettingsShouldThrowIfSettingsNameIsEmpty() { var runSettings = new RunSettings(); - Assert.ThrowsException(() => runSettings.GetSettings(" ")); + Assert.ThrowsExactly(() => runSettings.GetSettings(" ")); } // The remaining GetSettings tests are covered in the InitializeSettingsProviders tests above. diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs index bfb5225bc8..a776b5fb58 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/SettingsProvider/SettingsProviderExtensionManagerTests.cs @@ -82,7 +82,7 @@ public void CreateShouldDiscoverSettingsProviderExtensions() var extensionManager = SettingsProviderExtensionManager.Create(); Assert.IsNotNull(extensionManager.SettingsProvidersMap); - Assert.IsTrue(extensionManager.SettingsProvidersMap.Count > 0); + Assert.IsNotEmpty(extensionManager.SettingsProvidersMap); } [TestMethod] @@ -94,7 +94,7 @@ public void CreateShouldCacheDiscoveredExtensions() SettingsProviderExtensionManager.Create(); Assert.IsNotNull(extensionManager.SettingsProvidersMap); - Assert.IsTrue(extensionManager.SettingsProvidersMap.Count > 0); + Assert.IsNotEmpty(extensionManager.SettingsProvidersMap); } #endregion @@ -132,8 +132,8 @@ public void GetSettingsProviderShouldThrowIfSettingsNameIsNullOrEmpty() }; var spm = new TestableSettingsProviderManager(extensions, unfilteredExtensions, new Mock().Object); - Assert.ThrowsException(() => spm.GetSettingsProvider(null!)); - Assert.ThrowsException(() => spm.GetSettingsProvider(string.Empty)); + Assert.ThrowsExactly(() => spm.GetSettingsProvider(null!)); + Assert.ThrowsExactly(() => spm.GetSettingsProvider(string.Empty)); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/SourceNavigationParserTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/SourceNavigationParserTests.cs new file mode 100644 index 0000000000..b79dd36747 --- /dev/null +++ b/test/Microsoft.TestPlatform.Common.UnitTests/SourceNavigationParserTests.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Common.UnitTests; + +[TestClass] +public class SourceNavigationParserTests +{ + [TestMethod] + public void FindMethodLocations_ReturnsSignatureAndBodyStartLines() + { + var lines = new[] + { + "public class Foo", + "{", + " [TestMethod]", + " public void MyMethod()", + " {", + " DoStuff();", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodLocations(lines, "MyMethod"); + + Assert.ContainsSingle(result); + Assert.AreEqual(4, result[0].SignatureLine); // 1-based: " public void MyMethod()" + Assert.AreEqual(5, result[0].BodyStartLine); // 1-based: " {" + } + + [TestMethod] + public void FindMethodLocations_OverloadedMethods() + { + var lines = new[] + { + "public class Foo", + "{", + " public void OverLoaded()", + " {", + " }", + "", + " public void OverLoaded(string _)", + " {", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodLocations(lines, "OverLoaded"); + + Assert.HasCount(2, result); + Assert.AreEqual(3, result[0].SignatureLine); + Assert.AreEqual(4, result[0].BodyStartLine); + Assert.AreEqual(7, result[1].SignatureLine); + Assert.AreEqual(8, result[1].BodyStartLine); + } + + [TestMethod] + public void FindMethodBodyStartLines_SimpleMethod() + { + var lines = new[] + { + "public class Foo", + "{", + " public void MyMethod()", + " {", + " DoStuff();", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "MyMethod"); + + Assert.ContainsSingle(result); + Assert.AreEqual(4, result[0]); // 1-based: line " {" + } + + [TestMethod] + public void FindMethodBodyStartLines_MethodWithAttribute() + { + var lines = new[] + { + "public class Foo", + "{", + " [Test]", + " public void PassTestMethod1()", + " {", + " Assert.AreEqual(5, 5);", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "PassTestMethod1"); + + Assert.ContainsSingle(result); + Assert.AreEqual(5, result[0]); // 1-based: line " {" + } + + [TestMethod] + public void FindMethodBodyStartLines_BraceOnSameLineAsSignature() + { + var lines = new[] + { + "public class Foo", + "{", + " public void Inline() {", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "Inline"); + + Assert.ContainsSingle(result); + Assert.AreEqual(3, result[0]); // 1-based: brace is on same line as signature + } + + [TestMethod] + public void FindMethodBodyStartLines_OverloadedMethods() + { + var lines = new[] + { + "public class Foo", + "{", + " public void OverLoaded()", + " {", + " }", + "", + " public void OverLoaded(string _)", + " {", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "OverLoaded"); + + Assert.HasCount(2, result); + Assert.AreEqual(4, result[0]); // first overload brace + Assert.AreEqual(8, result[1]); // second overload brace + } + + [TestMethod] + public void FindMethodBodyStartLines_MethodNotFound() + { + var lines = new[] + { + "public class Foo", + "{", + " public void Other()", + " {", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "NotExist"); + + Assert.IsEmpty(result); + } + + [TestMethod] + public void FindMethodBodyStartLines_DoesNotMatchPropertyOrField() + { + var lines = new[] + { + "public class Foo", + "{", + " public string MyMethod = \"hello\";", + " public string MyMethodProp { get; set; }", + "}", + }; + + // "MyMethod" followed by ' =' should not match (no '(' after name). + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "MyMethod"); + + Assert.IsEmpty(result); + } + + [TestMethod] + public void FindMethodBodyStartLines_AsyncMethod() + { + var lines = new[] + { + "public class Foo", + "{", + " public async Task AsyncTestMethod()", + " {", + " await Task.Delay(0);", + " }", + "}", + }; + + var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "AsyncTestMethod"); + + Assert.ContainsSingle(result); + Assert.AreEqual(4, result[0]); + } + + [TestMethod] + public void FindMethodBodyStartLines_RealSimpleClassLibrary() + { + // Mimics test/TestAssets/SimpleClassLibrary/Class1.cs + var lines = new[] + { + "// Copyright header", + "", + "using System.Threading.Tasks;", + "", + "namespace SimpleClassLibrary", + "{", + " public class Class1", + " {", + " public void PassingTest()", + " {", // line 10 + " if (new System.Random().Next() == 20) { throw new System.NotImplementedException(); }", + " }", + "", + " public async Task AsyncTestMethod()", + " {", // line 15 + " await Task.Delay(0);", + " }", + "", + " public void OverLoadedMethod()", + " {", // line 20 + " }", + "", + " public void OverLoadedMethod(string _)", + " {", // line 24 + " }", + " }", + "}", + }; + + Assert.AreEqual(10, SourceNavigationParser.FindMethodBodyStartLines(lines, "PassingTest")[0]); + Assert.AreEqual(15, SourceNavigationParser.FindMethodBodyStartLines(lines, "AsyncTestMethod")[0]); + + var overloads = SourceNavigationParser.FindMethodBodyStartLines(lines, "OverLoadedMethod"); + Assert.HasCount(2, overloads); + Assert.AreEqual(20, overloads[0]); + Assert.AreEqual(24, overloads[1]); + } + + [TestMethod] + public void ContainsMethodSignature_MatchesMethodFollowedByParen() + { + Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" public void MyMethod()", "MyMethod")); + } + + [TestMethod] + public void ContainsMethodSignature_MatchesWithWhitespaceBeforeParen() + { + Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" public void MyMethod ()", "MyMethod")); + } + + [TestMethod] + public void ContainsMethodSignature_DoesNotMatchFieldAssignment() + { + Assert.IsFalse(SourceNavigationParser.ContainsMethodSignature(" public string MyMethod = \"hello\";", "MyMethod")); + } + + [TestMethod] + public void ContainsMethodSignature_DoesNotMatchSubstring() + { + // "MyMethodExtended(" should not match "MyMethod" because the next char after "MyMethod" is 'E', not '(' or whitespace. + Assert.IsFalse(SourceNavigationParser.ContainsMethodSignature(" public void MyMethodExtended()", "MyMethod")); + } + + [TestMethod] + public void ContainsMethodSignature_MatchesWhenNameAppearsMultipleTimes() + { + // First occurrence is a field, second is the method. + Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" // calls MyMethod then MyMethod()", "MyMethod")); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Telemetry/MetricsCollectionTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Telemetry/MetricsCollectionTests.cs index 2f0e02dfed..21e2c90eba 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Telemetry/MetricsCollectionTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Telemetry/MetricsCollectionTests.cs @@ -47,7 +47,7 @@ public void MetricsShouldReturnValidMetricsIfValidItemsAreThere() _metricsCollection.Add("DummyMessage", "DummyValue"); _metricsCollection.Add("DummyMessage2", "DummyValue"); - Assert.AreEqual(2, _metricsCollection.Metrics.Count); + Assert.HasCount(2, _metricsCollection.Metrics); Assert.IsTrue(_metricsCollection.Metrics.ContainsKey("DummyMessage")); Assert.IsTrue(_metricsCollection.Metrics.ContainsKey("DummyMessage2")); } @@ -55,6 +55,34 @@ public void MetricsShouldReturnValidMetricsIfValidItemsAreThere() [TestMethod] public void MetricsShouldReturnEmptyDictionaryIfMetricsIsEmpty() { - Assert.AreEqual(0, _metricsCollection.Metrics.Count); + Assert.IsEmpty(_metricsCollection.Metrics); + } + + [TestMethod] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "MSTEST0049", Justification = "CancellationToken not meaningful for concurrency stress test")] + public void AddShouldNotThrowWhenCalledConcurrently() + { + // Regression test for #15579 — concurrent Add calls on Dictionary + // caused InvalidOperationException: "Operations that change + // non-concurrent collections must have exclusive access." + var tasks = new System.Threading.Tasks.Task[10]; + for (int t = 0; t < tasks.Length; t++) + { + var threadId = t; + tasks[t] = System.Threading.Tasks.Task.Factory.StartNew(() => + { + for (int i = 0; i < 1000; i++) + { + _metricsCollection.Add($"Thread{threadId}_Metric{i}", i); + } + }); + } + + foreach (var task in tasks) + { + task.GetAwaiter().GetResult(); + } + + Assert.IsGreaterThan(0, _metricsCollection.Metrics.Count); } } diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs index 13864a9ab4..0747ee1caa 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/ExceptionUtilitiesTests.cs @@ -32,18 +32,18 @@ public void GetExceptionMessageShouldReturnExceptionMessageContainingAllExceptio var exception = new ArgumentException("Some bad stuff", innerException2); var message = ExceptionUtilities.GetExceptionMessage(exception); - StringAssert.Contains(message, exception.Message); - StringAssert.Contains(message, innerException.Message); - StringAssert.Contains(message, innerException.Message); + Assert.Contains(exception.Message, message); + Assert.Contains(innerException.Message, message); + Assert.Contains(innerException.Message, message); } [TestMethod] public void GetExceptionMessageShouldReturnExceptionMessageContainingStackTrace() { var message = ExceptionUtilities.GetExceptionMessage(GetExceptionWithStackTrace()); - StringAssert.Contains(message, "Stack trace:"); + Assert.Contains("Stack trace:", message); // this test is where it or - StringAssert.Contains(message, "ExceptionUtilitiesTests.GetExceptionWithStackTrace"); + Assert.Contains("ExceptionUtilitiesTests.GetExceptionWithStackTrace", message); } private static Exception GetExceptionWithStackTrace() diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/FakesUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/FakesUtilitiesTests.cs index a26dbdcdc1..bfe2157c3b 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/FakesUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/FakesUtilitiesTests.cs @@ -19,13 +19,13 @@ public class FakesUtilitiesTests public void FakesSettingsShouldThrowExceptionIfSourcesArePassedAsNull() { string runSettingsXml = @".netstandard,Version=5.0"; - Assert.ThrowsException(() => FakesUtilities.GenerateFakesSettingsForRunConfiguration(null!, runSettingsXml)); + Assert.ThrowsExactly(() => FakesUtilities.GenerateFakesSettingsForRunConfiguration(null!, runSettingsXml)); } [TestMethod] public void FakesSettingsShouldThrowExceptionIfRunSettingsIsPassedAsNull() { - Assert.ThrowsException(() => FakesUtilities.GenerateFakesSettingsForRunConfiguration([], null!)); + Assert.ThrowsExactly(() => FakesUtilities.GenerateFakesSettingsForRunConfiguration([], null!)); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs index f0b1133c56..7d0f03eefd 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Utilities/RunSettingsUtilitiesTests.cs @@ -23,7 +23,7 @@ public void CreateRunSettingsShouldReturnNullIfSettingsXmlIsNullorEmpty() [TestMethod] public void CreateRunSettingsShouldThrowExceptionWhenInvalidXmlStringIsPassed() { - Assert.ThrowsException(() => RunSettingsUtilities.CreateAndInitializeRunSettings("abc")); + Assert.ThrowsExactly(() => RunSettingsUtilities.CreateAndInitializeRunSettings("abc")); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests.csproj b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests.csproj index d63e9e4f6c..2e7b972557 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests.csproj @@ -6,15 +6,8 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.CommunicationUtilities.PlatformTests - - - - - - - diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Program.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Program.cs deleted file mode 100644 index b8f2141385..0000000000 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.CommunicationUtilities.PlatformTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketClientTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketClientTests.cs index e899a02567..4942ca2412 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketClientTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketClientTests.cs @@ -24,6 +24,8 @@ public class SocketClientTests : SocketTestsBase, IDisposable private TcpClient? _tcpClient; + public TestContext TestContext { get; set; } + public SocketClientTests() { _socketClient = new SocketClient(); @@ -49,8 +51,10 @@ public void SocketClientStartShouldConnectToLoopbackOnGivenPort() _socketClient.Start(connectionInfo); +#pragma warning disable MSTEST0049 // AcceptTcpClientAsync(CancellationToken) unavailable on .NET Framework var acceptClientTask = _tcpListener.AcceptTcpClientAsync(); Assert.IsTrue(acceptClientTask.Wait(Timeout)); +#pragma warning restore MSTEST0049 Assert.IsTrue(acceptClientTask.Result.Connected); } @@ -113,7 +117,7 @@ public void SocketClientStopShouldStopCommunication() // Validate that write on server side fails waitEvent.WaitOne(Timeout); - Assert.ThrowsException(() => WriteData(Client!)); + Assert.ThrowsExactly(() => WriteData(Client!)); } [TestMethod] @@ -124,7 +128,7 @@ public void SocketClientStopShouldCloseChannel() _socketClient.Stop(); waitEvent.WaitOne(Timeout); - Assert.ThrowsException(() => channel!.Send(Dummydata)); + Assert.ThrowsExactly(() => channel!.Send(Dummydata)); } protected override ICommunicationChannel? SetupChannel(out ConnectedEventArgs? connectedEvent) @@ -142,8 +146,10 @@ public void SocketClientStopShouldCloseChannel() var connectionInfo = StartLocalServer(); _socketClient.Start(connectionInfo); +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - AcceptTcpClientAsync/Wait overloads unavailable on .NET Framework var acceptClientTask = _tcpListener.AcceptTcpClientAsync(); if (acceptClientTask.Wait(TimeSpan.FromMilliseconds(1000))) +#pragma warning restore MSTEST0049 { _tcpClient = acceptClientTask.Result; waitEvent.WaitOne(1000); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketCommunicationManagerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketCommunicationManagerTests.cs index 7d352a6327..aeb0d0337e 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketCommunicationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketCommunicationManagerTests.cs @@ -5,7 +5,6 @@ using System.IO; using System.Net; using System.Net.Sockets; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,6 +15,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +// ConnectAsync(IPAddress, int, CancellationToken) and AcceptTcpClientAsync(CancellationToken) +// overloads are unavailable on .NET Framework; suppress CancellationToken usage warnings. +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' + namespace Microsoft.TestPlatform.CommunicationUtilities.PlatformTests; [TestClass] @@ -33,6 +36,8 @@ public class SocketCommunicationManagerTests : IDisposable private readonly TcpClient _tcpClient; private readonly TcpListener _tcpListener; + public TestContext TestContext { get; set; } + public SocketCommunicationManagerTests() { _communicationManager = new SocketCommunicationManager(); @@ -57,7 +62,7 @@ public async Task HostServerShouldStartServerAndReturnPortNumber() { var port = _communicationManager.HostServer(new IPEndPoint(IPAddress.Loopback, 0)).Port; - Assert.IsTrue(port > 0); + Assert.IsGreaterThan(0, port); await _tcpClient.ConnectAsync(IPAddress.Loopback, port); Assert.IsTrue(_tcpClient.Connected); } @@ -75,7 +80,8 @@ public async Task AcceptClientAsyncShouldWaitForClientConnection() clientConnected = true; waitEvent.Set(); }, - null); + null, + TestContext.CancellationToken); await _tcpClient.ConnectAsync(IPAddress.Loopback, port); Assert.IsTrue(_tcpClient.Connected); @@ -115,7 +121,7 @@ public void StopServerShouldCloseServer() _communicationManager.StopServer(); - Assert.ThrowsException(() => _tcpClient.ConnectAsync(IPAddress.Loopback, port).Wait()); + Assert.ThrowsExactly(() => _tcpClient.ConnectAsync(IPAddress.Loopback, port).Wait()); } #endregion @@ -156,20 +162,15 @@ public void WaitForServerConnectionShouldReturnFalseIfClientIsNotConnected() } [TestMethod] + [OSCondition(OperatingSystems.Windows | OperatingSystems.Linux)] public async Task StopClientShouldDisconnectClient() { - // TODO: This won't throw on MacOS? No way to try it. - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return; - } - var client = await StartServerAndWaitForConnection(); _communicationManager.StopClient(); // Attempt to write on client socket should throw since it should have disconnected. - Assert.ThrowsException(() => WriteOnSocket(client.Client)); + Assert.ThrowsExactly(() => WriteOnSocket(client.Client)); } #endregion @@ -283,8 +284,8 @@ public void SocketPollShouldNotHangServerClientCommunication() var client = new SocketCommunicationManager(); int port = server.HostServer(new IPEndPoint(IPAddress.Loopback, 0)).Port; - client.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, port)).Wait(); - server.AcceptClientAsync().Wait(); + client.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, port)).Wait(TestContext.CancellationToken); + server.AcceptClientAsync().Wait(TestContext.CancellationToken); server.WaitForClientConnection(1000); client.WaitForServerConnection(1000); @@ -296,12 +297,10 @@ public void SocketPollShouldNotHangServerClientCommunication() while (dataReceived < 2048 * 5) { dataReceived += server.ReceiveRawMessageAsync(CancellationToken.None).Result!.Length; - Task.Delay(1000).Wait(); + Task.Delay(1000, TestContext.CancellationToken).Wait(TestContext.CancellationToken); } clientThread.Join(); - - Assert.IsTrue(true); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketServerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketServerTests.cs index 1c392218f3..764d80099d 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketServerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.Platform.UnitTests/SocketServerTests.cs @@ -23,6 +23,8 @@ public class SocketServerTests : SocketTestsBase, IDisposable private readonly string _defaultConnection = IPAddress.Loopback.ToString() + ":0"; private readonly ICommunicationEndPoint _socketServer; + public TestContext TestContext { get; set; } + public SocketServerTests() { _socketServer = new SocketServer(); @@ -78,7 +80,7 @@ public void SocketServerStopShouldCloseClient() _socketServer.Stop(); waitEvent.WaitOne(); - Assert.ThrowsException(() => WriteData(_tcpClient)); + Assert.ThrowsExactly(() => WriteData(_tcpClient)); } [TestMethod] @@ -109,8 +111,8 @@ public void SocketServerStopShouldCloseChannel() _socketServer.Stop(); - waitEvent.Wait(); - Assert.ThrowsException(() => channel!.Send(Dummydata)); + waitEvent.Wait(TestContext.CancellationToken); + Assert.ThrowsExactly(() => channel!.Send(Dummydata)); } [TestMethod] @@ -170,6 +172,8 @@ public async Task SocketEndpointShouldInitializeChannelOnServerConnection() private async Task ConnectToServer(int port) { +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - ConnectAsync CancellationToken overload unavailable on .NET Framework await _tcpClient.ConnectAsync(IPAddress.Loopback, port); +#pragma warning restore MSTEST0049 } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/AssemblyInitializer.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/AssemblyInitializer.cs new file mode 100644 index 0000000000..73b6e601d9 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/AssemblyInitializer.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests; + +[TestClass] +public static class AssemblyInitializer +{ + [AssemblyInitialize] + public static void SetTimeout(TestContext testContext) + { + // Set the value to 1 to avoid big timeouts for these unit tests. If you change the value you + // must do it at the start of a doNotParallelize test, to ensure you don't clean the default, + // because after it resets to 90 seconds. + Environment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, "1"); + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs index ee056f0d9d..d33c3aaf90 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs @@ -68,22 +68,16 @@ public DataCollectionRequestHandlerTests() _mockDataCollectionManager.Setup(x => x.SessionStarted(It.IsAny())).Returns(true); } - [TestCleanup] - public void Cleanup() - { - Environment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, string.Empty); - } - [TestMethod] public void CreateInstanceShouldThrowExceptionIfInstanceCommunicationManagerIsNull() { - Assert.ThrowsException(() => DataCollectionRequestHandler.Create(null!, _mockMessageSink.Object)); + Assert.ThrowsExactly(() => DataCollectionRequestHandler.Create(null!, _mockMessageSink.Object)); } [TestMethod] public void CreateInstanceShouldThrowExceptinIfInstanceMessageSinkIsNull() { - Assert.ThrowsException(() => DataCollectionRequestHandler.Create(_mockCommunicationManager.Object, null!)); + Assert.ThrowsExactly(() => DataCollectionRequestHandler.Create(_mockCommunicationManager.Object, null!)); } [TestMethod] @@ -107,7 +101,7 @@ public void InitializeCommunicationShouldThrowExceptionIfThrownByCommunicationMa { _mockCommunicationManager.Setup(x => x.SetupClientAsync(It.IsAny())).Throws(); - Assert.ThrowsException(() => _requestHandler.InitializeCommunication(123)); + Assert.ThrowsExactly(() => _requestHandler.InitializeCommunication(123)); } [TestMethod] @@ -123,7 +117,7 @@ public void WaitForRequestSenderConnectionShouldThrowExceptionIfThrownByCommunic { _mockCommunicationManager.Setup(x => x.WaitForServerConnection(It.IsAny())).Throws(); - Assert.ThrowsException(() => _requestHandler.WaitForRequestSenderConnection(0)); + Assert.ThrowsExactly(() => _requestHandler.WaitForRequestSenderConnection(0)); } [TestMethod] @@ -142,7 +136,7 @@ public void SendDataCollectionMessageShouldThrowExceptionIfThrownByCommunication _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionMessage, It.IsAny())).Throws(); var message = new DataCollectionMessageEventArgs(TestMessageLevel.Error, "message"); - Assert.ThrowsException(() => _requestHandler.SendDataCollectionMessage(message)); + Assert.ThrowsExactly(() => _requestHandler.SendDataCollectionMessage(message)); } [TestMethod] @@ -158,7 +152,7 @@ public void CloseShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.StopClient()).Throws(); - Assert.ThrowsException(() => _requestHandler.Close()); + Assert.ThrowsExactly(() => _requestHandler.Close()); } [TestMethod] @@ -250,7 +244,7 @@ public void ProcessRequestsShouldAddSourceDirectoryToTestPluginCache() _requestHandler.ProcessRequests(); _mockFileHelper.Verify(x => x.EnumerateFiles($@"{temp}dir1", SearchOption.AllDirectories, @"Collector.dll"), Times.Once); - Assert.IsTrue(TestPluginCache.Instance.GetExtensionPaths(@"Collector.dll").Contains(Path.Combine(temp, "dir1", "abc.DataCollector.dll"))); + Assert.Contains(Path.Combine(temp, "dir1", "abc.DataCollector.dll"), TestPluginCache.Instance.GetExtensionPaths(@"Collector.dll")); } [TestMethod] @@ -258,7 +252,7 @@ public void ProcessRequestsShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.ReceiveMessage()).Throws(); - Assert.ThrowsException(() => _requestHandler.ProcessRequests()); + Assert.ThrowsExactly(() => _requestHandler.ProcessRequests()); } [TestMethod] @@ -276,8 +270,12 @@ public void ProcessRequestsShouldInitializeTestCaseEventHandlerIfTestCaseLevelEv } [TestMethod] + [DoNotParallelize] public void ProcessRequestsShouldSetDefaultTimeoutIfNoEnvVarialbeSet() { + // Make sure to use do-not parallelize because you set non-default value to the env variable. + using var _ = ResetableEnvironment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, null); + var beforeTestRunSTartPayload = new BeforeTestRunStartPayload { SettingsXml = "settingsxml", Sources = new List { "test1.dll" } }; _mockDataSerializer.Setup(x => x.DeserializePayload(It.Is(y => y.MessageType == MessageType.BeforeTestRunStart))) .Returns(beforeTestRunSTartPayload); @@ -288,10 +286,12 @@ public void ProcessRequestsShouldSetDefaultTimeoutIfNoEnvVarialbeSet() } [TestMethod] + [DoNotParallelize] public void ProcessRequestsShouldSetTimeoutBasedOnEnvVariable() { var timeout = 10; - Environment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, timeout.ToString(CultureInfo.InvariantCulture)); + // Make sure to use do-not parallelize because you set non-default value to the env variable. + using var _ = ResetableEnvironment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, timeout.ToString(CultureInfo.InvariantCulture)); var beforeTestRunSTartPayload = new BeforeTestRunStartPayload { SettingsXml = "settingsxml", Sources = new List { "test1.dll" } }; _mockDataSerializer.Setup(x => x.DeserializePayload(It.Is(y => y.MessageType == MessageType.BeforeTestRunStart))) .Returns(beforeTestRunSTartPayload); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs index 311230f716..3c16b43f17 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs @@ -55,14 +55,14 @@ public void SendAfterTestRunEndAndGetResultShouldReturnAttachments() Assert.IsNotNull(result.AttachmentSets); Assert.IsNotNull(result.AttachmentSets); Assert.IsNotNull(result.Metrics); - Assert.AreEqual(1, result.AttachmentSets.Count); - Assert.AreEqual(1, result.InvokedDataCollectors!.Count); - Assert.AreEqual(0, result.Metrics.Count); + Assert.HasCount(1, result.AttachmentSets); + Assert.HasCount(1, result.InvokedDataCollectors!); + Assert.IsEmpty(result.Metrics); Assert.IsNotNull(result.AttachmentSets[0]); Assert.AreEqual(displayName, result.AttachmentSets[0].DisplayName); Assert.AreEqual(datacollectorUri, result.AttachmentSets[0].Uri); Assert.AreEqual(attachmentUri, result.AttachmentSets[0].Attachments[0].Uri); - Assert.IsNotNull(result.InvokedDataCollectors[0]); + Assert.IsNotNull(result.InvokedDataCollectors![0]); Assert.AreEqual(datacollectorUri, result.InvokedDataCollectors[0].Uri); Assert.AreEqual(invokedDataCollector.FilePath, result.InvokedDataCollectors[0].FilePath); Assert.AreEqual(invokedDataCollector.AssemblyQualifiedName, result.InvokedDataCollectors[0].AssemblyQualifiedName); @@ -92,14 +92,14 @@ public void SendAfterTestRunEndAndGetResultShouldReturnAttachmentsAndPropagateTe Assert.IsNotNull(result.AttachmentSets); Assert.IsNotNull(result.AttachmentSets); Assert.IsNotNull(result.Metrics); - Assert.AreEqual(1, result.AttachmentSets.Count); - Assert.AreEqual(1, result.InvokedDataCollectors!.Count); - Assert.AreEqual(0, result.Metrics.Count); + Assert.HasCount(1, result.AttachmentSets); + Assert.HasCount(1, result.InvokedDataCollectors!); + Assert.IsEmpty(result.Metrics); Assert.IsNotNull(result.AttachmentSets[0]); Assert.AreEqual(displayName, result.AttachmentSets[0].DisplayName); Assert.AreEqual(datacollectorUri, result.AttachmentSets[0].Uri); Assert.AreEqual(attachmentUri, result.AttachmentSets[0].Attachments[0].Uri); - Assert.IsNotNull(result.InvokedDataCollectors[0]); + Assert.IsNotNull(result.InvokedDataCollectors![0]); Assert.AreEqual(datacollectorUri, result.InvokedDataCollectors[0].Uri); Assert.AreEqual(invokedDataCollector.FilePath, result.InvokedDataCollectors[0].FilePath); Assert.AreEqual(invokedDataCollector.AssemblyQualifiedName, result.InvokedDataCollectors[0].AssemblyQualifiedName); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index 15db3a0b47..fac6ab5cd2 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -52,7 +52,7 @@ public void InitializeShouldInitializeConnection() public void InitializeShouldThrowExceptionIfExceptionIsThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Throws(); - Assert.ThrowsException(() => _requestHandler.InitializeCommunication()); + Assert.ThrowsExactly(() => _requestHandler.InitializeCommunication()); } [TestMethod] @@ -70,7 +70,7 @@ public void WaitForRequestHandlerConnectionShouldThrowExceptionIfThrownByConnect { _mockCommunicationManager.Setup(x => x.WaitForClientConnection(It.IsAny())).Throws(); - Assert.ThrowsException(() => _requestHandler.WaitForRequestHandlerConnection(10)); + Assert.ThrowsExactly(() => _requestHandler.WaitForRequestHandlerConnection(10)); } [TestMethod] @@ -86,7 +86,7 @@ public void CloseShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.StopServer()).Throws(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => _requestHandler.Close()); } @@ -141,6 +141,6 @@ public void ProcessRequestsShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.ReceiveMessage()).Throws(); - Assert.ThrowsException(() => _requestHandler.ProcessRequests()); + Assert.ThrowsExactly(() => _requestHandler.ProcessRequests()); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs index 7d1808ecc9..5350920332 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs @@ -45,7 +45,7 @@ public void InitializeShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.SetupClientAsync(It.IsAny())).Throws(); - Assert.ThrowsException(() => _dataCollectionTestCaseEventSender.InitializeCommunication(123)); + Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.InitializeCommunication(123)); } [TestMethod] @@ -61,7 +61,7 @@ public void WaitForRequestSenderConnectionShouldThrowExceptionIfThrownByCommunic { _mockCommunicationManager.Setup(x => x.WaitForServerConnection(It.IsAny())).Throws(); - Assert.ThrowsException(() => _dataCollectionTestCaseEventSender.WaitForRequestSenderConnection(123)); + Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.WaitForRequestSenderConnection(123)); } [TestMethod] @@ -77,7 +77,7 @@ public void CloseShouldThrowExceptionIfThrownByCommunicationManager() { _mockCommunicationManager.Setup(x => x.StopClient()).Throws(); - Assert.ThrowsException(() => _dataCollectionTestCaseEventSender.Close()); + Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.Close()); } [TestMethod] @@ -97,7 +97,7 @@ public void SendTestCaseStartShouldThrowExceptionIfThrownByCommunicationManager( var testcaseStartEventArgs = new TestCaseStartEventArgs(_testCase); _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs)).Throws(); - Assert.ThrowsException(() => _dataCollectionTestCaseEventSender.SendTestCaseStart(testcaseStartEventArgs)); + Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.SendTestCaseStart(testcaseStartEventArgs)); } [TestMethod] @@ -120,6 +120,6 @@ public void SendTestCaseCompletedShouldThrowExceptionIfThrownByCommunicationMana _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestEnd, It.IsAny())).Throws(); - Assert.ThrowsException(() => _dataCollectionTestCaseEventSender.SendTestCaseEnd(testCaseEndEventArgs)); + Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.SendTestCaseEnd(testCaseEndEventArgs)); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerDiscoveryStatusRegressionTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerDiscoveryStatusRegressionTests.cs new file mode 100644 index 0000000000..cda53a41a3 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerDiscoveryStatusRegressionTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression tests for JSON serialization with discovery status information. +/// +[TestClass] +public class JsonDataSerializerDiscoveryStatusRegressionTests +{ + // Regression test for #3381 — Change serializer settings to not send empty values + // DiscoveryCompletePayload should include source status lists when serialized. + + [TestMethod] + public void SerializeDeserialize_DiscoveryCompletePayload_ShouldPreserveSourceStatus() + { + var serializer = JsonDataSerializer.Instance; + + var payload = new DiscoveryCompleteEventArgs(42, false) + { + FullyDiscoveredSources = new System.Collections.Generic.List { "a.dll", "b.dll" }, + PartiallyDiscoveredSources = new System.Collections.Generic.List { "c.dll" }, + NotDiscoveredSources = new System.Collections.Generic.List { "d.dll" }, + }; + + // Serialize + string json = serializer.SerializePayload("TestDiscovery.Completed", payload); + + // Should contain the source status data + Assert.Contains("a.dll", json); + Assert.Contains("c.dll", json); + Assert.Contains("d.dll", json); + } + + [TestMethod] + public void SerializePayload_NullSourceLists_ShouldSerializeSuccessfully() + { + var serializer = JsonDataSerializer.Instance; + + var payload = new DiscoveryCompleteEventArgs(10, false) + { + FullyDiscoveredSources = null, + PartiallyDiscoveredSources = null, + NotDiscoveredSources = null, + }; + + // Should not throw + string json = serializer.SerializePayload("TestDiscovery.Completed", payload); + Assert.IsNotNull(json); + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerPayloadRegressionTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerPayloadRegressionTests.cs new file mode 100644 index 0000000000..13c063c0fa --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/JsonDataSerializerPayloadRegressionTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression tests for JsonDataSerializer payload serialization. +/// +[TestClass] +public class JsonDataSerializerPayloadRegressionTests +{ + // Regression test for #3381 — Change serializer settings to not send empty values + [TestMethod] + public void SerializePayload_WithMessageType_ShouldContainMessageType() + { + var serializer = JsonDataSerializer.Instance; + + string json = serializer.SerializePayload("TestMessage", "TestData"); + + Assert.Contains("TestMessage", json); + Assert.Contains("TestData", json); + } + + [TestMethod] + public void SerializePayload_WithComplexObject_ShouldSerialize() + { + var serializer = JsonDataSerializer.Instance; + + var testCase = new TestCase("Ns.Class.Method", new Uri("executor://test"), "test.dll"); + string json = serializer.SerializePayload("TestDiscovery.TestFound", testCase); + + Assert.Contains("Ns.Class.Method", json); + } + + [TestMethod] + public void DeserializeMessage_ShouldReturnCorrectMessageType() + { + var serializer = JsonDataSerializer.Instance; + + var rawMessage = serializer.SerializePayload("TestExecution.Started", "payload"); + var message = serializer.DeserializeMessage(rawMessage); + + Assert.AreEqual("TestExecution.Started", message.MessageType); + } + + [TestMethod] + public void SerializePayload_WithProtocolVersion_ShouldSucceed() + { + var serializer = JsonDataSerializer.Instance; + + // Version 1 serialization + string json = serializer.SerializePayload("TestMsg", "Data", version: 1); + Assert.IsNotNull(json); + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelRegressionTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelRegressionTests.cs new file mode 100644 index 0000000000..8311ad759b --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelRegressionTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression tests for LengthPrefixCommunicationChannel with TrackableEvent integration. +/// +[TestClass] +public class LengthPrefixCommunicationChannelRegressionTests +{ + // Regression test for #4553 — 17.6.x consumes lot of CPU + // The communication channel now uses TrackableEvent for message notification + // instead of regular events, enabling subscriber-aware waiting. + + [TestMethod] + public void MessageReceived_ShouldBeTrackableEvent() + { + using var stream = new MemoryStream(); + using var channel = new LengthPrefixCommunicationChannel(stream); + + // MessageReceived should be a TrackableEvent + Assert.IsNotNull(channel.MessageReceived); + } + + [TestMethod] + public void MessageReceived_Subscribe_ShouldMakeWaitForSubscriberReturnTrue() + { + using var stream = new MemoryStream(); + using var channel = new LengthPrefixCommunicationChannel(stream); + using var cts = new CancellationTokenSource(); + + channel.MessageReceived.Subscribe((sender, args) => { }); + + bool hasSubscriber = channel.MessageReceived.WaitForSubscriber(100, cts.Token); + Assert.IsTrue(hasSubscriber, "After subscribing, WaitForSubscriber should return true."); + } + + [TestMethod] + public void MessageReceived_NoSubscriber_WaitShouldTimeout() + { + using var stream = new MemoryStream(); + using var channel = new LengthPrefixCommunicationChannel(stream); + using var cts = new CancellationTokenSource(); + + bool hasSubscriber = channel.MessageReceived.WaitForSubscriber(50, cts.Token); + Assert.IsFalse(hasSubscriber, "Without subscribers, WaitForSubscriber should timeout and return false."); + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj index 9fee3675c6..d4a43e7ce4 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Microsoft.TestPlatform.CommunicationUtilities.UnitTests.csproj @@ -6,11 +6,11 @@ - net6.0;net48 - Exe + net10.0;net9.0;net48 + Exe Microsoft.TestPlatform.CommunicationUtilities.UnitTests - + diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Program.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Program.cs deleted file mode 100644 index a4932304d9..0000000000 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..c02f62869c --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/RegressionBugFixTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression test for GH-4461: +/// After a non-timeout IOException with a SocketException inner exception, +/// the error output parameter must remain null (not the exception). +/// Previously, the code set error = ioException, which propagated +/// confusing socket errors to developers when testhost crashed. +/// +[TestClass] +public class RegressionBugFixTests +{ + public TestContext TestContext { get; set; } = null!; + + private static (TcpClient client, TcpClient serverSide, TcpListener listener) CreateConnectedTcpPair() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + + var client = new TcpClient(); + client.Connect(IPAddress.Loopback, port); + var serverSide = listener.AcceptTcpClient(); + + return (client, serverSide, listener); + } + + [TestMethod] + public async Task MessageLoopAsync_NonTimeoutSocketIOException_ErrorMustBeNull() + { + // GH-4461: When a non-timeout IOException wrapping a SocketException occurs, + // the error handler must receive null (not the exception). + // If the fix were reverted (error = ioException uncommented), capturedError would be non-null. + var (client, serverSide, listener) = CreateConnectedTcpPair(); + try + { + var channel = new Mock(); + var socketException = new SocketException((int)SocketError.ConnectionReset); + var ioException = new IOException("Connection forcibly closed", socketException); + + channel.Setup(c => c.NotifyDataAvailable(It.IsAny())) + .Throws(ioException); + + Exception? capturedError = null; + + // Write data so Poll returns true and NotifyDataAvailable is invoked. + var serverStream = serverSide.GetStream(); + await serverStream.WriteAsync(new byte[] { 0x1 }, 0, 1, TestContext.CancellationToken); + await serverStream.FlushAsync(TestContext.CancellationToken); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + + await client.MessageLoopAsync(channel.Object, error => capturedError = error, cts.Token); + + // The fix commented out `error = ioException`, so capturedError must be null. + Assert.IsNull(capturedError, + "GH-4461: Non-timeout IOException with SocketException must NOT be propagated to the error handler."); + } + finally + { + client.Dispose(); + serverSide.Dispose(); + listener.Stop(); + } + } + + [TestMethod] + public async Task MessageLoopAsync_NonIOException_ErrorMustBeSet() + { + // Contrast test: non-IOException exceptions must still be propagated. + var (client, serverSide, listener) = CreateConnectedTcpPair(); + try + { + var channel = new Mock(); + var expectedException = new InvalidOperationException("Non-IO failure"); + channel.Setup(c => c.NotifyDataAvailable(It.IsAny())) + .Throws(expectedException); + + Exception? capturedError = null; + + var serverStream = serverSide.GetStream(); + await serverStream.WriteAsync(new byte[] { 1 }, 0, 1, TestContext.CancellationToken); + await serverStream.FlushAsync(TestContext.CancellationToken); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + + await client.MessageLoopAsync(channel.Object, error => capturedError = error, cts.Token); + + Assert.AreSame(expectedException, capturedError, + "Non-IOException must still be propagated to the error handler."); + } + finally + { + client.Dispose(); + serverSide.Dispose(); + listener.Stop(); + } + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ResetableEnvironment.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ResetableEnvironment.cs new file mode 100644 index 0000000000..2287139c4d --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/ResetableEnvironment.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests; + +internal class ResetableEnvironment +{ + /// + /// Sets environment variable and resets it when returned object is disposed. Use with `using`. + /// + /// + /// + /// + public static DisposableVariable SetEnvironmentVariable(string name, string? value) + { + string? originalValue = Environment.GetEnvironmentVariable(name); + + Environment.SetEnvironmentVariable(name, value); + + return new DisposableVariable(name, originalValue); + } +} + +internal class DisposableVariable : IDisposable +{ + private readonly string _name; + private readonly string? _originalValue; + + public DisposableVariable(string name, string? originalValue) + { + _name = name; + _originalValue = originalValue; + } + + public void Dispose() + { + Environment.SetEnvironmentVariable(_name, _originalValue); + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs index 6205d5b415..f1daf13268 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs @@ -247,7 +247,7 @@ public void TestCaseObjectShouldDeserializeTraitsWithSpecialCharacters(int versi var test = Deserialize(json, version); var traits = test.Traits.ToArray(); - Assert.AreEqual(1, traits.Length); + Assert.HasCount(1, traits); Assert.AreEqual(@"SDJDDHW>,:&^%//\\\\", traits[0].Value); } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestObjectConverterTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestObjectConverterTests.cs index 90b10ef588..d1ebb2aaa7 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestObjectConverterTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestObjectConverterTests.cs @@ -91,7 +91,7 @@ public void TestObjectShouldDeserializeCustomProperties() var test = Deserialize(json); var properties = test.Properties.ToArray(); - Assert.AreEqual(2, properties.Length); + Assert.HasCount(2, properties); Assert.AreEqual(Guid.Parse("02048dfd-3da7-475d-a011-8dd1121855ec"), test.GetPropertyValue(properties.First(x => x.Label == "label1"))); Assert.AreEqual(29, test.GetPropertyValue(properties.First(x => x.Label == "label2"))); } @@ -104,7 +104,7 @@ public void TestObjectShouldDeserializeNullValueForProperty() var test = Deserialize(json); var properties = test.Properties.ToArray(); - Assert.AreEqual(1, properties.Length); + Assert.HasCount(1, properties); Assert.IsTrue(string.IsNullOrEmpty(test.GetPropertyValue(properties[0])!.ToString())); } @@ -116,7 +116,7 @@ public void TestObjectShouldDeserializeStringArrayValueForProperty() var test = Deserialize(json); var properties = test.Properties.ToArray(); - Assert.AreEqual(1, properties.Length); + Assert.HasCount(1, properties); CollectionAssert.AreEqual(new[] { "val1", "val2" }, (string[])test.GetPropertyValue(properties[0])!); } @@ -128,7 +128,7 @@ public void TestObjectShouldDeserializeDatetimeOffset() var test = Deserialize(json); var properties = test.Properties.ToArray(); - Assert.AreEqual(1, properties.Length); + Assert.HasCount(1, properties); Assert.AreEqual(DateTimeOffset.MaxValue, test.GetPropertyValue(properties[0])); } @@ -160,7 +160,7 @@ public void TestObjectSetPropertyValueShouldNotConvertIfValueMatchesPropertyData // type is object testobj.SetPropertyValue(property, false); - Assert.AreEqual(false, testobj.GetPropertyValue(property)); + Assert.IsFalse((bool)testobj.GetPropertyValue(property)!); } private static string Serialize(T data) diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs index 087c01c46e..f98cc9d60d 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs @@ -78,8 +78,8 @@ public void TestResultObjectShouldContainAllPropertiesOnDeserialization(int vers var test = Deserialize(json, version); Assert.AreEqual(TestResult.TestCase.Id, test.TestCase.Id); - Assert.AreEqual(TestResult.Attachments.Count, test.Attachments.Count); - Assert.AreEqual(TestResult.Messages.Count, test.Messages.Count); + Assert.HasCount(TestResult.Attachments.Count, test.Attachments); + Assert.HasCount(TestResult.Messages.Count, test.Messages); Assert.AreEqual(TestResult.ComputerName, test.ComputerName); Assert.AreEqual(TestResult.DisplayName, test.DisplayName); @@ -116,7 +116,7 @@ public void TestResultObjectShouldDeserializeAttachments(int version) var result = Deserialize(json, version); - Assert.AreEqual(1, result.Attachments.Count); + Assert.HasCount(1, result.Attachments); Assert.AreEqual(new Uri("http://dummyUri"), result.Attachments[0].Uri); Assert.AreEqual("sampleAttachment", result.Attachments[0].DisplayName); } @@ -147,8 +147,8 @@ public void TestResultObjectShouldDeserializeDefaultValues(int version) var result = Deserialize(json, version); - Assert.AreEqual(0, result.Attachments.Count); - Assert.AreEqual(0, result.Messages.Count); + Assert.IsEmpty(result.Attachments); + Assert.IsEmpty(result.Messages); Assert.IsNull(result.DisplayName); Assert.IsNull(result.ErrorMessage); Assert.IsNull(result.ErrorStackTrace); @@ -216,8 +216,8 @@ public void TestResultObjectShouldContainAllPropertiesOnDeserializationV2(int ve var test = Deserialize(json, version); Assert.AreEqual(TestResult.TestCase.Id, test.TestCase.Id); - Assert.AreEqual(TestResult.Attachments.Count, test.Attachments.Count); - Assert.AreEqual(TestResult.Messages.Count, test.Messages.Count); + Assert.HasCount(TestResult.Attachments.Count, test.Attachments); + Assert.HasCount(TestResult.Messages.Count, test.Messages); Assert.AreEqual(TestResult.ComputerName, test.ComputerName); Assert.AreEqual(TestResult.DisplayName, test.DisplayName); @@ -254,7 +254,7 @@ public void TestResultObjectShouldDeserializeAttachmentsV2(int version) var result = Deserialize(json, version); - Assert.AreEqual(1, result.Attachments.Count); + Assert.HasCount(1, result.Attachments); Assert.AreEqual(new Uri("http://dummyUri"), result.Attachments[0].Uri); Assert.AreEqual("sampleAttachment", result.Attachments[0].DisplayName); } @@ -284,7 +284,7 @@ public void TestResultSerializationShouldThrowWhenProvidedProtocolVersionDoesNot // only checked for version 2 var version = int.MaxValue; - Assert.ThrowsException(() => Serialize(TestResult, version)); + Assert.ThrowsExactly(() => Serialize(TestResult, version)); } #endregion diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/SocketCommunicationManagerTest.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/SocketCommunicationManagerTest.cs index c8faff459a..27e3190f54 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/SocketCommunicationManagerTest.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/SocketCommunicationManagerTest.cs @@ -5,7 +5,6 @@ namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests; -[TestClass] -public class SocketCommunicationManagerTest +public static class SocketCommunicationManagerTest { } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs index 8cdeb06831..f1df84a12d 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs @@ -43,6 +43,8 @@ public class TestRequestSenderTests private readonly ITestRequestSender _testRequestSender; private ConnectedEventArgs _connectedEventArgs; + public TestContext TestContext { get; set; } = null!; + public TestRequestSenderTests() { _connectionInfo = new TestHostConnectionInfo @@ -63,12 +65,6 @@ public TestRequestSenderTests() _testRunCriteriaWithSources = new TestRunCriteriaWithSources(new Dictionary>(), "runsettings", null, null!); } - [TestCleanup] - public void Cleanup() - { - Environment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, string.Empty); - } - [TestMethod] public void InitializeCommunicationShouldHostServerAndAcceptClient() { @@ -110,7 +106,7 @@ public void WaitForRequestHandlerConnectionWithTimeoutShouldReturnImmediatelyWhe watch.Stop(); Assert.IsFalse(connected); - Assert.IsTrue(watch.ElapsedMilliseconds < connectionTimeout); + Assert.IsLessThan(connectionTimeout, watch.ElapsedMilliseconds); } [TestMethod] @@ -125,7 +121,7 @@ public void WaitForRequestHandlerConnectionWithTimeoutShouldReturnImmediatelyIfH watch.Stop(); Assert.IsFalse(connected); - Assert.IsTrue(watch.ElapsedMilliseconds < connectionTimeout); + Assert.IsLessThan(connectionTimeout, watch.ElapsedMilliseconds); } [TestMethod] @@ -203,7 +199,7 @@ public void EndSessionShouldNotSendTestRunAbortMessageIfClientDisconnected() _mockChannel.Verify(mockChannel => mockChannel.Send(MessageType.CancelTestRun), Times.Never); } - [DataTestMethod] + [TestMethod] [DataRow("")] [DataRow(" ")] [DataRow(null)] @@ -263,7 +259,7 @@ public void CheckVersionWithTestHostShouldThrowIfTestHostVersionDoesNotMatch() SetupRaiseMessageReceivedOnCheckVersion(); SetupFakeCommunicationChannel(); - Assert.ThrowsException(() => _testRequestSender.CheckVersionWithTestHost()); + Assert.ThrowsExactly(() => _testRequestSender.CheckVersionWithTestHost()); } [TestMethod] @@ -273,17 +269,19 @@ public void CheckVersionWithTestHostShouldThrowIfUnexpectedResponseIsReceived() SetupRaiseMessageReceivedOnCheckVersion(); SetupFakeCommunicationChannel(); - Assert.ThrowsException(() => _testRequestSender.CheckVersionWithTestHost()); + Assert.ThrowsExactly(() => _testRequestSender.CheckVersionWithTestHost()); } [TestMethod] + [DoNotParallelize] public void CheckVersionWithTestHostShouldThrowIfProtocolNegotiationTimeouts() { + // Make sure to use do-not parallelize because you set non-default value to the env variable. Environment.SetEnvironmentVariable(EnvironmentHelper.VstestConnectionTimeout, "0"); SetupFakeCommunicationChannel(); - var message = Assert.ThrowsException(() => _testRequestSender.CheckVersionWithTestHost()).Message; + var message = Assert.ThrowsExactly(() => _testRequestSender.CheckVersionWithTestHost()).Message; Assert.AreEqual(message, TimoutErrorMessage); } @@ -797,8 +795,8 @@ public async Task StartTestRunWithTestsShouldNotifyExecutionCompleteIfClientDisc SetupFakeCommunicationChannel(); // Note: Even if the calls get invoked on separate threads, the request sender should send back the complete message just once. - var t1 = Task.Run(RaiseClientDisconnectedEvent); - var t2 = Task.Run(() => _testRequestSender.StartTestRun(runCriteria, _mockExecutionEventsHandler.Object)); + var t1 = Task.Run(RaiseClientDisconnectedEvent, TestContext.CancellationToken); + var t2 = Task.Run(() => _testRequestSender.StartTestRun(runCriteria, _mockExecutionEventsHandler.Object), TestContext.CancellationToken); await Task.WhenAll(t1, t2); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventRegressionTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventRegressionTests.cs new file mode 100644 index 0000000000..dae55dcd72 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventRegressionTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression tests for TrackableEvent — subscribe/notify/wait synchronization. +/// +[TestClass] +public class TrackableEventRegressionTests +{ + // Regression test for #4553 — 17.6.x consumes lot of CPU + // TrackableEvent replaces polling-based event handling with ManualResetEventSlim. + [TestMethod] + public void WaitForSubscriber_NoSubscriber_ShouldTimeout() + { + var trackableEvent = new TrackableEvent(); + using var cts = new CancellationTokenSource(); + + bool result = trackableEvent.WaitForSubscriber(50, cts.Token); + + Assert.IsFalse(result, "WaitForSubscriber should return false when no subscriber is registered."); + } + + // Regression test for #4553 + [TestMethod] + public void WaitForSubscriber_AfterSubscribe_ShouldReturnTrue() + { + var trackableEvent = new TrackableEvent(); + using var cts = new CancellationTokenSource(); + + trackableEvent.Subscribe((sender, args) => { }); + + bool result = trackableEvent.WaitForSubscriber(1000, cts.Token); + + Assert.IsTrue(result, "WaitForSubscriber should return true after a subscriber is registered."); + } + + // Regression test for #4553 + [TestMethod] + public void WaitForSubscriber_AfterUnsubscribe_ShouldTimeout() + { + var trackableEvent = new TrackableEvent(); + using var cts = new CancellationTokenSource(); + + EventHandler handler = (sender, args) => { }; + trackableEvent.Subscribe(handler); + trackableEvent.Unsubscribe(handler); + + bool result = trackableEvent.WaitForSubscriber(50, cts.Token); + + Assert.IsFalse(result, "WaitForSubscriber should return false after all subscribers are removed."); + } + + // Regression test for #4553 + [TestMethod] + public void Notify_WithSubscriber_ShouldInvokeHandler() + { + var trackableEvent = new TrackableEvent(); + bool handlerCalled = false; + var expectedArgs = new MessageReceivedEventArgs { Data = "test-data" }; + + trackableEvent.Subscribe((sender, args) => + { + handlerCalled = true; + Assert.AreEqual("test-data", args.Data); + }); + + trackableEvent.Notify(this, expectedArgs, "TestNotify"); + + Assert.IsTrue(handlerCalled, "Notify should invoke the subscribed handler."); + } + + // Regression test for #4553 + [TestMethod] + public void Notify_WithoutSubscriber_ShouldNotThrow() + { + var trackableEvent = new TrackableEvent(); + var args = new MessageReceivedEventArgs { Data = "test-data" }; + + // Should not throw even without subscribers + trackableEvent.Notify(this, args, "TestNotify"); + } + + // Regression test for #4553 + [TestMethod] + public void Subscribe_Null_ShouldNotThrow() + { + var trackableEvent = new TrackableEvent(); + + // Subscribe with null should not throw + trackableEvent.Subscribe(null); + } + + // Regression test for #4553 + [TestMethod] + public void WaitForSubscriber_CancellationRequested_ShouldReturnFalse() + { + var trackableEvent = new TrackableEvent(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + try + { + trackableEvent.WaitForSubscriber(5000, cts.Token); + // If it returns without throwing, that's also acceptable behavior + } + catch (OperationCanceledException) + { + // Expected — ManualResetEventSlim.Wait throws on cancellation + } + } +} diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventThreadSafetyRegressionTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventThreadSafetyRegressionTests.cs new file mode 100644 index 0000000000..504fcea8a5 --- /dev/null +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TrackableEventThreadSafetyRegressionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Threading; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.UnitTests; + +/// +/// Regression tests for TrackableEvent thread safety and synchronization. +/// +[TestClass] +public class TrackableEventThreadSafetyRegressionTests +{ + // Regression test for #4553 — 17.6.x consumes lot of CPU + // TrackableEvent replaced polling-based event notification with ManualResetEventSlim. + // This test verifies cross-thread subscribe-then-wait behavior. + [TestMethod] + public void WaitForSubscriber_SubscribeFromDifferentThread_ShouldSignal() + { + var trackableEvent = new TrackableEvent(); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + + // Subscribe from another thread + var subscribeThread = new Thread(() => + { + Thread.Sleep(50); + trackableEvent.Subscribe((sender, args) => { }); + }); + subscribeThread.Start(); + + bool result = trackableEvent.WaitForSubscriber(3000, cts.Token); + + Assert.IsTrue(result, "WaitForSubscriber should return true when subscription happens from another thread."); + subscribeThread.Join(); + } + + // Regression test for #4553 + [TestMethod] + public void MultipleSubscribers_AllShouldBeNotified() + { + var trackableEvent = new TrackableEvent(); + int callCount = 0; + + trackableEvent.Subscribe((sender, args) => Interlocked.Increment(ref callCount)); + trackableEvent.Subscribe((sender, args) => Interlocked.Increment(ref callCount)); + + var args = new MessageReceivedEventArgs { Data = "test" }; + trackableEvent.Notify(this, args, "MultiNotify"); + + Assert.AreEqual(2, callCount, "Both subscribers should be notified."); + } + + // Regression test for #4553 + [TestMethod] + public void UnsubscribeOne_OtherShouldStillWork() + { + var trackableEvent = new TrackableEvent(); + int callCount = 0; + using var cts = new CancellationTokenSource(); + + EventHandler handler1 = (sender, args) => Interlocked.Increment(ref callCount); + EventHandler handler2 = (sender, args) => Interlocked.Increment(ref callCount); + + trackableEvent.Subscribe(handler1); + trackableEvent.Subscribe(handler2); + trackableEvent.Unsubscribe(handler1); + + // WaitForSubscriber should still return true because handler2 is still subscribed + bool hasSubscriber = trackableEvent.WaitForSubscriber(100, cts.Token); + Assert.IsTrue(hasSubscriber); + + trackableEvent.Notify(this, new MessageReceivedEventArgs { Data = "test" }, "Partial"); + Assert.AreEqual(1, callCount, "Only handler2 should be called."); + } +} diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/CommandLineArgumentsHelperTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/CommandLineArgumentsHelperTests.cs index 1b7ec113d6..053ae7d3a2 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/CommandLineArgumentsHelperTests.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/CommandLineArgumentsHelperTests.cs @@ -29,14 +29,14 @@ public void GetArgumentsDictionaryShouldIgnoreValuesWithoutPreceedingHypen() var args = new List() { "port", "12312", "--parentprocessid", "2312", "--testsourcepath", @"C:\temp\1.dll" }; var argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary(args.ToArray()); - Assert.IsTrue(argsDictionary.Count == 2); + Assert.HasCount(2, argsDictionary); Assert.AreEqual("2312", argsDictionary["--parentprocessid"]); Assert.AreEqual(@"C:\temp\1.dll", argsDictionary["--testsourcepath"]); args = ["--port", "12312", "--parentprocessid", "2312", "testsourcepath", @"C:\temp\1.dll"]; argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary(args.ToArray()); - Assert.IsTrue(argsDictionary.Count == 2); + Assert.HasCount(2, argsDictionary); Assert.AreEqual("12312", argsDictionary["--port"]); Assert.AreEqual("2312", argsDictionary["--parentprocessid"]); } @@ -59,7 +59,7 @@ public void GetStringArgFromDictShouldReturnNullIfValueIsNotPresent() string? data = CommandLineArgumentsHelper.GetStringArgFromDict(argsDictionary, "--hello"); - Assert.IsTrue(argsDictionary.Count == 2); + Assert.HasCount(2, argsDictionary); Assert.IsNull(data); } @@ -71,7 +71,7 @@ public void GetStringArgFromDictShouldReturnEmptyStringIfKeyIsNotPresent() string? data = CommandLineArgumentsHelper.GetStringArgFromDict(argsDictionary, "--port"); - Assert.IsTrue(argsDictionary.Count == 2); + Assert.HasCount(2, argsDictionary); Assert.AreEqual(string.Empty, data); } @@ -79,10 +79,10 @@ public void GetStringArgFromDictShouldReturnEmptyStringIfKeyIsNotPresent() public void GetArgumentsDictionaryShouldReturnEmptyDictionaryIfEmptyArgIsPassed() { var argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary(null); - Assert.IsTrue(argsDictionary.Count == 0); + Assert.IsEmpty(argsDictionary); argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary([]); - Assert.IsTrue(argsDictionary.Count == 0); + Assert.IsEmpty(argsDictionary); } [TestMethod] @@ -91,7 +91,7 @@ public void GetArgumentsDictionaryShouldTreatValueAsNullIfTwoConsecutiveKeysAreP var args = new List() { "--hello", "--world" }; var argsDictionary = CommandLineArgumentsHelper.GetArgumentsDictionary(args.ToArray()); - Assert.IsTrue(argsDictionary.Count == 2); + Assert.HasCount(2, argsDictionary); Assert.IsNull(argsDictionary["--hello"]); Assert.IsNull(argsDictionary["--world"]); } diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs index ea802a56da..c1c56513c2 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Helpers/DotnetHostHelperTest.cs @@ -44,7 +44,7 @@ public void GetDotnetPathByArchitecture_SameArchitecture() Assert.AreEqual(finalMuxerPath, muxerPath); } - [DataTestMethod] + [TestMethod] [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT(x86)")] [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT")] [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, PlatformOperatingSystem.Windows, "DOTNET_ROOT(x86)", true, DotnetMuxerResolutionStrategy.DotnetRootArchitectureLess)] @@ -112,7 +112,7 @@ public void GetDotnetPathByArchitecture_EnvVars(PlatformArchitecture targetArchi Assert.AreEqual(found ? envVars[envVar] : null, muxerPath); } - [DataTestMethod] + [TestMethod] [DataRow("DOTNET_ROOT_ARM64", "DOTNET_ROOT", PlatformArchitecture.ARM64, PlatformArchitecture.X64)] [DataRow("DOTNET_ROOT(x86)", "DOTNET_ROOT", PlatformArchitecture.X86, PlatformArchitecture.X64)] [DataRow("DOTNET_ROOT_ARM64", "DOTNET_ROOT", PlatformArchitecture.ARM64, PlatformArchitecture.X64, DotnetMuxerResolutionStrategy.DotnetRootArchitecture | DotnetMuxerResolutionStrategy.DotnetRootArchitectureLess)] @@ -151,7 +151,7 @@ public void GetDotnetPathByArchitecture_EnvVars_DirectoryNotExists_TryNext( Assert.AreEqual(envVars[nextEnv], muxerPath); } - [DataTestMethod] + [TestMethod] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, true)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X86, false)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, true, DotnetMuxerResolutionStrategy.GlobalInstallationLocation)] @@ -180,7 +180,7 @@ public void GetDotnetPathByArchitecture_GlobalInstallation_Windows( Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); } - [DataTestMethod] + [TestMethod] [DataRow(true, false, false, false)] [DataRow(false, true, false, false)] [DataRow(false, false, true, false)] @@ -215,7 +215,7 @@ public void GetDotnetPathByArchitecture_GlobalInstallation_NullSubkeys( Assert.IsFalse(dotnetHostHelper.TryGetDotnetPathByArchitecture(PlatformArchitecture.X64, strategy, out string? muxerPath)); } - [DataTestMethod] + [TestMethod] [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location_arm64", true, PlatformOperatingSystem.OSX)] [DataRow(PlatformArchitecture.X64, "/etc/dotnet/install_location_x64", true, PlatformOperatingSystem.OSX)] [DataRow(PlatformArchitecture.ARM64, "/etc/dotnet/install_location", true, PlatformOperatingSystem.OSX)] @@ -261,7 +261,7 @@ public void GetDotnetPathByArchitecture_GlobalInstallation_Unix( Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); } - [DataTestMethod] + [TestMethod] [DataRow(PlatformArchitecture.X86, PlatformArchitecture.X64, "ProgramFiles(x86)", "dotnet", true)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "ProgramFiles", @"dotnet\x64", true)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "ProgramFiles", "dotnet", true)] @@ -298,7 +298,8 @@ public void GetDotnetPathByArchitecture_DefaultInstallation_Win( Assert.AreEqual(found ? dotnetMuxer : null, muxerPath); } - [DataTestMethod] +#pragma warning disable MSTEST0042 // duplicate data row - TODO: Look more into it + [TestMethod] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/local/share/dotnet", "", true, PlatformOperatingSystem.OSX)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "/usr/local/share/dotnet/x64", "", true, PlatformOperatingSystem.OSX)] [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, "/usr/local/share/dotnet", "", true, PlatformOperatingSystem.OSX)] @@ -316,6 +317,7 @@ public void GetDotnetPathByArchitecture_DefaultInstallation_Win( [DataRow(PlatformArchitecture.X64, PlatformArchitecture.ARM64, "/usr/share/dotnet/x64", "", false, PlatformOperatingSystem.Unix, DotnetMuxerResolutionStrategy.DefaultInstallationLocation)] [DataRow(PlatformArchitecture.ARM64, PlatformArchitecture.X64, "/usr/share/dotnet", "", false, PlatformOperatingSystem.Unix, DotnetMuxerResolutionStrategy.DefaultInstallationLocation)] [DataRow(PlatformArchitecture.X64, PlatformArchitecture.X64, "/usr/share/dotnet", "", false, PlatformOperatingSystem.Unix, DotnetMuxerResolutionStrategy.DefaultInstallationLocation)] +#pragma warning restore MSTEST0042 public void GetDotnetPathByArchitecture_DefaultInstallation_Unix( PlatformArchitecture targetArchitecture, PlatformArchitecture platformArchitecture, diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj index ba4499c758..c0f492aed2 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Microsoft.TestPlatform.CoreUtilities.UnitTests.csproj @@ -6,30 +6,10 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.CoreUtilities.UnitTests - - - 4.3.0 - - - 4.3.0 - - - 4.3.0 - - - 4.3.0 - - - - - - - - diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Output/OutputExtensionsTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Output/OutputExtensionsTests.cs index 59a8006b64..7c8d403833 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Output/OutputExtensionsTests.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Output/OutputExtensionsTests.cs @@ -64,7 +64,7 @@ public void OutputErrorForSimpleMessageShouldSetConsoleColorToRed() } _mockOutput.Object.Error(false, "HelloError", null); - Assert.IsTrue(_color == ConsoleColor.Red, "Console color not set."); + Assert.AreEqual(ConsoleColor.Red, _color, "Console color not set."); } [TestMethod] @@ -90,7 +90,7 @@ public void OutputWarningForSimpleMessageShouldSetConsoleColorToYellow() } _mockOutput.Object.Warning(false, "HelloWarning", null); - Assert.IsTrue(_color == ConsoleColor.Yellow); + Assert.AreEqual(ConsoleColor.Yellow, _color); } [TestMethod] @@ -116,7 +116,7 @@ public void OutputInformationForSimpleMessageShouldSetConsoleColorToGivenColor() } _mockOutput.Object.Information(false, ConsoleColor.Green, "HelloInformation", null); - Assert.IsTrue(_color == ConsoleColor.Green); + Assert.AreEqual(ConsoleColor.Green, _color); } [TestMethod] @@ -139,7 +139,7 @@ public void OutputInformationShouldNotChangeConsoleOutputColor() _mockOutput.Object.Information(false, "HelloInformation {0} {1}", "Foo", "Bar"); _mockOutput.Verify(o => o.WriteLine("HelloInformation Foo Bar", OutputLevel.Information), Times.Once()); - Assert.IsTrue(color1 == color2); + Assert.AreEqual(color2, color1); } private bool CanNotSetConsoleForegroundColor() diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Program.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Program.cs deleted file mode 100644 index f027d8d6ce..0000000000 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace TestPlatform.CoreUtilities.UnitTests; - -/// -/// Main entry point for the command line runner. -/// -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs index f9e38311d2..a9f52f2b1b 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Tracing/EqtTraceTests.cs @@ -41,7 +41,7 @@ public static void Init(TestContext _) [TestMethod] public void CheckInitializeLogFileTest() { - Assert.AreEqual(s_logFile, EqtTrace.LogFile, "Expected log file to be {0}", s_logFile); + Assert.AreEqual(s_logFile, EqtTrace.LogFile, $"Expected log file to be {s_logFile}"); } [TestMethod] @@ -52,7 +52,7 @@ public void CheckIfTraceStateIsVerboseEnabled() #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; #endif - Assert.IsTrue(EqtTrace.IsVerboseEnabled, "Expected trace state to be verbose actual state {0}", EqtTrace.IsVerboseEnabled); + Assert.IsTrue(EqtTrace.IsVerboseEnabled, $"Expected trace state to be verbose actual state {EqtTrace.IsVerboseEnabled}"); } [TestMethod] @@ -63,7 +63,7 @@ public void CheckIfTraceStateIsErrorEnabled() #else EqtTrace.TraceLevel = PlatformTraceLevel.Error; #endif - Assert.IsTrue(EqtTrace.IsErrorEnabled, "Expected trace state to be error actual state {0}", EqtTrace.IsErrorEnabled); + Assert.IsTrue(EqtTrace.IsErrorEnabled, $"Expected trace state to be error actual state {EqtTrace.IsErrorEnabled}"); } [TestMethod] @@ -74,7 +74,7 @@ public void CheckIfTraceStateIsInfoEnabled() #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; #endif - Assert.IsTrue(EqtTrace.IsInfoEnabled, "Expected trace state to be info actual state {0}", EqtTrace.IsInfoEnabled); + Assert.IsTrue(EqtTrace.IsInfoEnabled, $"Expected trace state to be info actual state {EqtTrace.IsInfoEnabled}"); } [TestMethod] @@ -85,7 +85,7 @@ public void CheckIfTraceStateIsWarningEnabled() #else EqtTrace.TraceLevel = PlatformTraceLevel.Warning; #endif - Assert.IsTrue(EqtTrace.IsWarningEnabled, "Expected trace state to be warning actual state {0}", EqtTrace.IsWarningEnabled); + Assert.IsTrue(EqtTrace.IsWarningEnabled, $"Expected trace state to be warning actual state {EqtTrace.IsWarningEnabled}"); } [TestMethod] @@ -109,7 +109,7 @@ public void TraceShouldWriteWarning() EqtTrace.TraceLevel = PlatformTraceLevel.Warning; #endif EqtTrace.Warning("Dummy Warning Message"); - Assert.IsTrue(ReadLogFile().Contains("Dummy Warning Message"), "Expected Warning message"); + Assert.Contains("Dummy Warning Message", ReadLogFile(), "Expected Warning message"); } [TestMethod] @@ -121,7 +121,7 @@ public void TraceShouldWriteVerbose() EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; #endif EqtTrace.Verbose("Dummy Verbose Message"); - Assert.IsTrue(ReadLogFile().Contains("Dummy Verbose Message"), "Expected Verbose message"); + Assert.Contains("Dummy Verbose Message", ReadLogFile(), "Expected Verbose message"); } [TestMethod] @@ -133,7 +133,7 @@ public void TraceShouldWriteInfo() EqtTrace.TraceLevel = PlatformTraceLevel.Info; #endif EqtTrace.Info("Dummy Info Message"); - Assert.IsTrue(ReadLogFile().Contains("Dummy Info Message"), "Expected Info message"); + Assert.Contains("Dummy Info Message", ReadLogFile(), "Expected Info message"); } [TestMethod] @@ -148,8 +148,8 @@ public void TraceShouldNotWriteVerboseIfTraceLevelIsInfo() EqtTrace.Verbose("Unexpected Dummy Verbose Message"); var logFileContent = ReadLogFile(); - Assert.IsFalse(logFileContent.Contains("Unexpected Dummy Verbose Message"), "Verbose message not expected"); - Assert.IsTrue(logFileContent.Contains("Dummy Info Message"), "Expected Info message"); + Assert.DoesNotContain("Unexpected Dummy Verbose Message", logFileContent, "Verbose message not expected"); + Assert.Contains("Dummy Info Message", logFileContent, "Expected Info message"); } [TestMethod] @@ -162,7 +162,7 @@ public void TraceShouldNotWriteIfDoNotInitializationIsSetToTrue() EqtTrace.TraceLevel = PlatformTraceLevel.Info; #endif EqtTrace.Info("Dummy Info Message: TraceShouldNotWriteIfDoNotInitializationIsSetToTrue"); - Assert.IsFalse(ReadLogFile().Contains("Dummy Info Message: TraceShouldNotWriteIfDoNotInitializationIsSetToTrue"), "Did not expect Dummy Info message"); + Assert.DoesNotContain("Dummy Info Message: TraceShouldNotWriteIfDoNotInitializationIsSetToTrue", ReadLogFile(), "Did not expect Dummy Info message"); } private static string ReadLogFile() diff --git a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs index b986d94fcc..3e35169852 100644 --- a/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs +++ b/test/Microsoft.TestPlatform.CoreUtilities.UnitTests/Utilities/JobQueueTests.cs @@ -17,7 +17,7 @@ public class JobQueueTests public void ConstructorThrowsWhenNullProcessHandlerIsProvided() { JobQueue? jobQueue = null; - Assert.ThrowsException(() => jobQueue = new JobQueue(null!, "dp", int.MaxValue, int.MaxValue, false, (message) => { })); + Assert.ThrowsExactly(() => jobQueue = new JobQueue(null!, "dp", int.MaxValue, int.MaxValue, false, (message) => { })); if (jobQueue != null) { @@ -29,9 +29,9 @@ public void ConstructorThrowsWhenNullProcessHandlerIsProvided() public void ThrowsWhenNullEmptyOrWhiteSpaceDisplayNameIsProvided() { JobQueue? jobQueue = null; - Assert.ThrowsException(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), null!, int.MaxValue, int.MaxValue, false, (message) => { })); - Assert.ThrowsException(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), "", int.MaxValue, int.MaxValue, false, (message) => { })); - Assert.ThrowsException(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), " ", int.MaxValue, int.MaxValue, false, (message) => { })); + Assert.ThrowsExactly(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), null!, int.MaxValue, int.MaxValue, false, (message) => { })); + Assert.ThrowsExactly(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), "", int.MaxValue, int.MaxValue, false, (message) => { })); + Assert.ThrowsExactly(() => jobQueue = new JobQueue(GetEmptyProcessHandler(), " ", int.MaxValue, int.MaxValue, false, (message) => { })); if (jobQueue != null) { @@ -86,7 +86,7 @@ public void ThrowsWhenQueuingAfterDisposed() var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); queue.Dispose(); - Assert.ThrowsException(() => queue.QueueJob("dp", 0)); + Assert.ThrowsExactly(() => queue.QueueJob("dp", 0)); } [TestMethod] @@ -95,7 +95,7 @@ public void ThrowsWhenResumingAfterDisposed() var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); queue.Dispose(); - Assert.ThrowsException(() => queue.Resume()); + Assert.ThrowsExactly(() => queue.Resume()); } [TestMethod] @@ -104,7 +104,7 @@ public void ThrowsWhenPausingAfterDisposed() var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); queue.Dispose(); - Assert.ThrowsException(() => queue.Pause()); + Assert.ThrowsExactly(() => queue.Pause()); } [TestMethod] @@ -113,7 +113,7 @@ public void ThrowsWhenFlushingAfterDisposed() var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); queue.Dispose(); - Assert.ThrowsException(() => queue.Flush()); + Assert.ThrowsExactly(() => queue.Flush()); } [TestMethod] @@ -141,12 +141,12 @@ public void OncePausedNoFurtherJobsAreProcessedUntilResumeIsCalled() // Allow other threads to execute and verify no jobs processed because the queue is paused. Thread.Sleep(0); - Assert.AreEqual(0, processedJobs.Count); + Assert.IsEmpty(processedJobs); queue.Resume(); } - Assert.AreEqual(3, processedJobs.Count); + Assert.HasCount(3, processedJobs); } [TestMethod] @@ -155,7 +155,7 @@ public void ThrowsWhenBeingDisposedWhileQueueIsPaused() using var queue = new JobQueue(GetEmptyProcessHandler(), "dp", int.MaxValue, int.MaxValue, false, (message) => { }); queue.Pause(); - Assert.ThrowsException(() => queue.Dispose()); + Assert.ThrowsExactly(() => queue.Dispose()); queue.Resume(); } @@ -360,7 +360,7 @@ public void TestLargeTestResultCanBeLoadedWithBlockingEnabled() } [TestMethod] - [Timeout(60000)] + [Timeout(60000, CooperativeCancellation = true)] public void TestDisposeUnblocksBlockedThreads() { var allowJobProcessingHandlerToProceed = new ManualResetEvent(false); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs index 504ee4a33f..1053724af5 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/FrameworkHandleTests.cs @@ -18,62 +18,83 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Adapter; public class FrameworkHandleTests { [TestMethod] - public void EnableShutdownAfterTestRunShoudBeFalseByDefault() + public void LaunchProcessWithDebuggerAttachedShouldThrowIfObjectIsDisposed() { var tec = GetTestExecutionContext(); var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null!); + frameworkHandle.Dispose(); - Assert.IsFalse(frameworkHandle.EnableShutdownAfterTestRun); + Assert.ThrowsExactly(() => frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null!, null!, null!)); } [TestMethod] - public void EnableShutdownAfterTestRunShoudBeSetAppropriately() + [Ignore("TODO: Enable method once we fix the \"IsDebug\" in TestExecutionContext")] + public void LaunchProcessWithDebuggerAttachedShouldThrowIfNotInDebugContext() { var tec = GetTestExecutionContext(); var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null!); - frameworkHandle.EnableShutdownAfterTestRun = true; - - Assert.IsTrue(frameworkHandle.EnableShutdownAfterTestRun); + var exception = Assert.ThrowsExactly(() => frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null, null, null)); + Assert.AreEqual("This operation is not allowed in the context of a non-debug run.", exception.Message); } [TestMethod] - public void LaunchProcessWithDebuggerAttachedShouldThrowIfObjectIsDisposed() + public void LaunchProcessWithDebuggerAttachedShouldCallRunEventsHandler() { var tec = GetTestExecutionContext(); - var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null!); - frameworkHandle.Dispose(); + tec.IsDebug = true; + var mockTestRunEventsHandler = new Mock(); + + var frameworkHandle = new FrameworkHandle( + null, + new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), + tec, + mockTestRunEventsHandler.Object); - Assert.ThrowsException(() => frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null!, null!, null!)); + frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null, null, null); + + mockTestRunEventsHandler.Verify(mt => + mt.LaunchProcessWithDebuggerAttached(It.IsAny()), Times.Once); } [TestMethod] - [Ignore("TODO: Enable method once we fix the \"IsDebug\" in TestExecutionContext")] - public void LaunchProcessWithDebuggerAttachedShouldThrowIfNotInDebugContext() + public void LaunchProcessWithDebuggerAttachedShouldSetCurrentDirectoryWhenWorkingDirectoryIsNull() { var tec = GetTestExecutionContext(); - var frameworkHandle = new FrameworkHandle(null, new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), tec, null!); + tec.IsDebug = true; + var mockTestRunEventsHandler = new Mock(); + TestProcessStartInfo? capturedProcessInfo = null; + + mockTestRunEventsHandler + .Setup(mt => mt.LaunchProcessWithDebuggerAttached(It.IsAny())) + .Callback(info => capturedProcessInfo = info) + .Returns(1234); - var isExceptionThrown = false; - try - { - frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null, null, null); - } - catch (InvalidOperationException exception) - { - isExceptionThrown = true; - Assert.AreEqual("This operation is not allowed in the context of a non-debug run.", exception.Message); - } - - Assert.IsTrue(isExceptionThrown); + var frameworkHandle = new FrameworkHandle( + null, + new TestRunCache(100, TimeSpan.MaxValue, (s, r, ip) => { }), + tec, + mockTestRunEventsHandler.Object); + + frameworkHandle.LaunchProcessWithDebuggerAttached("test.exe", null, null, null); + + Assert.IsNotNull(capturedProcessInfo); + Assert.AreEqual(Environment.CurrentDirectory, capturedProcessInfo.WorkingDirectory); } [TestMethod] - public void LaunchProcessWithDebuggerAttachedShouldCallRunEventsHandler() + public void LaunchProcessWithDebuggerAttachedShouldUseProvidedWorkingDirectory() { var tec = GetTestExecutionContext(); tec.IsDebug = true; var mockTestRunEventsHandler = new Mock(); + TestProcessStartInfo? capturedProcessInfo = null; + var expectedWorkingDirectory = "/custom/path"; + + mockTestRunEventsHandler + .Setup(mt => mt.LaunchProcessWithDebuggerAttached(It.IsAny())) + .Callback(info => capturedProcessInfo = info) + .Returns(1234); var frameworkHandle = new FrameworkHandle( null, @@ -81,10 +102,10 @@ public void LaunchProcessWithDebuggerAttachedShouldCallRunEventsHandler() tec, mockTestRunEventsHandler.Object); - frameworkHandle.LaunchProcessWithDebuggerAttached(null!, null, null, null); + frameworkHandle.LaunchProcessWithDebuggerAttached("test.exe", expectedWorkingDirectory, null, null); - mockTestRunEventsHandler.Verify(mt => - mt.LaunchProcessWithDebuggerAttached(It.IsAny()), Times.Once); + Assert.IsNotNull(capturedProcessInfo); + Assert.AreEqual(expectedWorkingDirectory, capturedProcessInfo.WorkingDirectory); } private static TestExecutionContext GetTestExecutionContext() diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs index be223b219a..cce3ab3ad1 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Adapter/TestExecutionRecorderTests.cs @@ -43,28 +43,28 @@ public void AttachmentsShouldReturnEmptyListByDefault() var attachments = _testRecorder.Attachments; Assert.IsNotNull(attachments); - Assert.AreEqual(0, attachments.Count); + Assert.IsEmpty(attachments); } [TestMethod] public void RecordStartShouldUpdateTestRunCache() { _testRecorder.RecordStart(_testCase); - Assert.IsTrue(_testableTestRunCache.TestStartedList.Contains(_testCase)); + Assert.Contains(_testCase, _testableTestRunCache.TestStartedList); } [TestMethod] public void RecordResultShouldUpdateTestRunCache() { _testRecorder.RecordResult(_testResult); - Assert.IsTrue(_testableTestRunCache.TestResultList.Contains(_testResult)); + Assert.Contains(_testResult, _testableTestRunCache.TestResultList); } [TestMethod] public void RecordEndShouldUpdateTestRunCache() { _testRecorder.RecordEnd(_testCase, TestOutcome.Passed); - Assert.IsTrue(_testableTestRunCache.TestCompletedList.Contains(_testCase)); + Assert.Contains(_testCase, _testableTestRunCache.TestCompletedList); } [TestMethod] @@ -193,7 +193,7 @@ public void RecordResultShouldFlushIfRecordEndWasCalledBefore() _testRecorderWithTestEventsHandler.RecordResult(_testResult); _mockTestCaseEventsHandler.Verify(x => x.SendTestCaseEnd(_testCase, TestOutcome.Passed), Times.Once); - Assert.IsTrue(_testableTestRunCache.TestResultList.Contains(_testResult)); + Assert.Contains(_testResult, _testableTestRunCache.TestResultList); } [TestMethod] @@ -205,7 +205,7 @@ public void RecordResultShouldSendTestCaseEndEventAndFlushIfRecordEndWasCalledAf _testRecorderWithTestEventsHandler.RecordEnd(_testCase, _testResult.Outcome); _mockTestCaseEventsHandler.Verify(x => x.SendTestCaseEnd(_testCase, TestOutcome.Passed), Times.Once); - Assert.IsTrue(_testableTestRunCache.TestResultList.Contains(_testResult)); + Assert.Contains(_testResult, _testableTestRunCache.TestResultList); } [TestMethod] @@ -216,7 +216,7 @@ public void RecordResultShouldSendTestCaseEndEventIfRecordEndWasNotCalled() _testRecorderWithTestEventsHandler.RecordResult(_testResult); _mockTestCaseEventsHandler.Verify(x => x.SendTestCaseEnd(_testCase, TestOutcome.Passed), Times.Once); - Assert.IsTrue(_testableTestRunCache.TestResultList.Contains(_testResult)); + Assert.Contains(_testResult, _testableTestRunCache.TestResultList); } #endregion diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/DataCollectorAttachmentProcessorAppDomainTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/DataCollectorAttachmentProcessorAppDomainTests.cs index ca3ebea968..9be5f13b50 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/DataCollectorAttachmentProcessorAppDomainTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/DataCollectorAttachmentProcessorAppDomainTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #if NETFRAMEWORK @@ -68,7 +68,7 @@ public async Task DataCollectorAttachmentProcessorAppDomain_ShouldCancel() Task runProcessing = dcap.ProcessAttachmentSetsAsync(doc.DocumentElement, attachments, new Progress((int report) => cts.Cancel()), _loggerMock.Object, cts.Token); //assert - await Assert.ThrowsExceptionAsync(async () => await runProcessing); + await Assert.ThrowsExactlyAsync(async () => await runProcessing); } [TestMethod] @@ -95,7 +95,7 @@ public async Task DataCollectorAttachmentProcessorAppDomain_ShouldReturnCorrectA Assert.AreEqual(attachmentSet.DisplayName, firstAttachmentSet.DisplayName); Assert.AreEqual(attachmentSet.Uri, firstAttachmentSet.Uri); - Assert.AreEqual(attachmentSet.Attachments.Count, attachmentsResult.Count); + Assert.HasCount(attachmentSet.Attachments.Count, attachmentsResult); Assert.AreEqual(attachmentSet.Attachments[0].Description, firstAttachmentSet.Attachments[0].Description); Assert.AreEqual(attachmentSet.Attachments[0].Uri, firstAttachmentSet.Attachments[0].Uri); Assert.AreEqual(attachmentSet.Attachments[0].Uri, firstAttachmentSet.Attachments[0].Uri); @@ -158,7 +158,7 @@ public async Task DataCollectorAttachmentProcessorAppDomain_ShouldLogCorrectly() // assert countdownEvent.Wait(new CancellationTokenSource(10000).Token); - Assert.AreEqual(3, messages.Count); + Assert.HasCount(3, messages); Assert.AreEqual(TestMessageLevel.Informational, messages[0].Item1); Assert.AreEqual("Info", messages[0].Item2); Assert.AreEqual(TestMessageLevel.Warning, messages[1].Item1); @@ -183,7 +183,7 @@ public void DataCollectorAttachmentProcessorAppDomain_ShouldReportFailureDuringE { if (messageLevel == TestMessageLevel.Error) { - Assert.IsTrue(message.Contains("System.Exception: Failed to create the extension")); + Assert.Contains("System.Exception: Failed to create the extension", message); errorReportEvent.Set(); } }); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs index 54bb5e48e6..3aa33d6b01 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs @@ -1,6 +1,8 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable MSTEST0049 // CancellationToken not applicable in test setup/Moq callbacks + using System; using System.Collections.Generic; using System.Linq; @@ -100,7 +102,7 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturnNoAttachments_IfNoA var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(0, result.Count); + Assert.IsEmpty(result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); @@ -140,8 +142,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1NotProcessedAttach var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(1, result.Count); - Assert.IsTrue(result.Contains(inputAttachments[0])); + Assert.ContainsSingle(result); + Assert.Contains(inputAttachments[0], result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); @@ -189,8 +191,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1ProcessedAttachmen var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(1, result.Count); - Assert.IsTrue(result.Contains(outputAttachments[0])); + Assert.ContainsSingle(result); + Assert.Contains(outputAttachments[0], result); _mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStart(1)); _mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStop(1)); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); @@ -237,8 +239,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_ var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(1, result.Count); - Assert.IsTrue(result.Contains(inputAttachments[0])); + Assert.ContainsSingle(result); + Assert.Contains(inputAttachments[0], result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Once); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), _cancellationTokenSource.Token)); @@ -279,8 +281,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_ var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(1, result.Count); - Assert.IsTrue(result.Contains(inputAttachments[0])); + Assert.ContainsSingle(result); + Assert.Contains(inputAttachments[0], result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); @@ -348,10 +350,10 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturnProcessedAttachment var result = await _manager.ProcessTestRunAttachmentsAsync(Constants.EmptyRunSettings, _mockRequestData.Object, inputAttachments, Array.Empty(), _cancellationTokenSource.Token); // assert - Assert.AreEqual(3, result.Count); - Assert.IsTrue(result.Contains(inputAttachments[4])); - Assert.IsTrue(result.Contains(outputAttachmentsForHandler1[0])); - Assert.IsTrue(result.Contains(outputAttachmentsForHandler2[0])); + Assert.HasCount(3, result); + Assert.Contains(inputAttachments[4], result); + Assert.Contains(outputAttachmentsForHandler1[0], result); + Assert.Contains(outputAttachmentsForHandler2[0], result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.Is>(c => c.Count == 2 && c.Contains(inputAttachments[0]) && c.Contains(inputAttachments[1])), It.IsAny>(), It.IsAny(), _cancellationTokenSource.Token)); @@ -489,8 +491,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_ // assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Count); - Assert.IsTrue(result.Contains(inputAttachments[0])); + Assert.ContainsSingle(result); + Assert.Contains(inputAttachments[0], result); _mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); _mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); _mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), _cancellationTokenSource.Token)); @@ -603,7 +605,7 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldNotFailIfRunsettingsIsNul _mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public async Task ProcessTestRunAttachmentsAsync_ShouldFlowCorrectDataCollectorConfiguration(bool withConfig) @@ -703,8 +705,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldNotConsumeAttachmentsIfPr { // assert Assert.IsTrue(firstProcessorFailed); - Assert.AreEqual(1, i1.Count); - Assert.AreEqual(3, i1.Single().Attachments.Count); + Assert.ContainsSingle(i1); + Assert.HasCount(3, i1.Single().Attachments); for (int i = 0; i < i1.Single().Attachments.Count; i++) { Assert.AreEqual(inputAttachments.Single().Attachments[i], i1.Single().Attachments[i]); @@ -761,8 +763,8 @@ public async Task ProcessTestRunAttachmentsAsync_ShouldNotConsumeAttachmentsIfAl { // assert Assert.IsTrue(firstProcessorFailed); - Assert.AreEqual(1, i1.Count); - Assert.AreEqual(3, i1.Single().Attachments.Count); + Assert.ContainsSingle(i1); + Assert.HasCount(3, i1.Single().Attachments); for (int i = 0; i < i1.Single().Attachments.Count; i++) { Assert.AreEqual(inputAttachments.Single().Attachments[i], i1.Single().Attachments[i]); @@ -834,7 +836,7 @@ private static bool VerifyProgressArgs(TestRunAttachmentsProcessingProgressEvent { Assert.AreEqual(1, args.CurrentAttachmentProcessorIndex); Assert.AreEqual(2, args.AttachmentProcessorsCount); - Assert.AreEqual(1, args.CurrentAttachmentProcessorUris.Count); + Assert.ContainsSingle(args.CurrentAttachmentProcessorUris); Assert.AreEqual(Uri1, args.CurrentAttachmentProcessorUris.First().AbsoluteUri); return progress == args.CurrentAttachmentProcessorProgress; } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorRegressionTests.cs new file mode 100644 index 0000000000..ffd5ba683a --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorRegressionTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.Client.Parallel; + +/// +/// Regression tests for DiscoveryDataAggregator source status tracking. +/// +[TestClass] +public class DiscoveryDataAggregatorRegressionTests +{ + // Regression test for #3381 — Change serializer settings to not send empty values + // DiscoveryDataAggregator tracks which sources were discovered, partially discovered, or skipped. + [TestMethod] + public void MarkSourcesWithStatus_ShouldTrackNotDiscoveredSources() + { + var aggregator = new DiscoveryDataAggregator(); + var sources = new List { "test1.dll", "test2.dll" }; + + aggregator.MarkSourcesWithStatus(sources, DiscoveryStatus.NotDiscovered); + + var result = aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered); + Assert.HasCount(2, result); + CollectionAssert.Contains(result, "test1.dll"); + CollectionAssert.Contains(result, "test2.dll"); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesWithStatus_ShouldUpgradeStatus() + { + var aggregator = new DiscoveryDataAggregator(); + aggregator.MarkSourcesWithStatus(new[] { "test1.dll" }, DiscoveryStatus.NotDiscovered); + aggregator.MarkSourcesWithStatus(new[] { "test1.dll" }, DiscoveryStatus.FullyDiscovered); + + var notDiscovered = aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered); + var fullyDiscovered = aggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered); + + Assert.IsEmpty(notDiscovered); + Assert.HasCount(1, fullyDiscovered); + Assert.AreEqual("test1.dll", fullyDiscovered[0]); + } + + // Regression test for #3381 + [TestMethod] + public void GetSourcesWithStatus_EmptyAggregator_ShouldReturnEmptyList() + { + var aggregator = new DiscoveryDataAggregator(); + + var result = aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered); + + Assert.IsEmpty(result); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesWithStatus_NullSources_ShouldNotThrow() + { + var aggregator = new DiscoveryDataAggregator(); + + // Should not throw + aggregator.MarkSourcesWithStatus(null, DiscoveryStatus.NotDiscovered); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesWithStatus_NullEntriesInSources_ShouldBeIgnored() + { + var aggregator = new DiscoveryDataAggregator(); + var sources = new List { "test1.dll", null, "test2.dll" }; + + aggregator.MarkSourcesWithStatus(sources, DiscoveryStatus.NotDiscovered); + + var result = aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered); + Assert.HasCount(2, result); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesWithStatus_MixedStatuses_ShouldFilterCorrectly() + { + var aggregator = new DiscoveryDataAggregator(); + aggregator.MarkSourcesWithStatus(new[] { "a.dll", "b.dll", "c.dll" }, DiscoveryStatus.NotDiscovered); + aggregator.MarkSourcesWithStatus(new[] { "a.dll" }, DiscoveryStatus.FullyDiscovered); + aggregator.MarkSourcesWithStatus(new[] { "b.dll" }, DiscoveryStatus.PartiallyDiscovered); + + Assert.HasCount(1, aggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); + Assert.HasCount(1, aggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); + Assert.HasCount(1, aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); + } + + // Regression test for #3381 + [TestMethod] + public void TryAggregateIsMessageSent_FirstCall_ShouldReturnTrue() + { + var aggregator = new DiscoveryDataAggregator(); + + bool first = aggregator.TryAggregateIsMessageSent(); + bool second = aggregator.TryAggregateIsMessageSent(); + + Assert.IsTrue(first, "First call should return true (first to send)."); + Assert.IsFalse(second, "Second call should return false (already sent)."); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesWithStatus_AfterMessageSent_ShouldSkipUpdate() + { + var aggregator = new DiscoveryDataAggregator(); + aggregator.MarkSourcesWithStatus(new[] { "test.dll" }, DiscoveryStatus.NotDiscovered); + + // Mark message as sent + aggregator.TryAggregateIsMessageSent(); + + // This should be skipped since message was already sent + aggregator.MarkSourcesWithStatus(new[] { "test.dll" }, DiscoveryStatus.FullyDiscovered); + + // Status should still be NotDiscovered since update was skipped + var result = aggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered); + Assert.HasCount(1, result); + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorSourceTrackingRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorSourceTrackingRegressionTests.cs new file mode 100644 index 0000000000..d920cf540e --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorSourceTrackingRegressionTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.Client.Parallel; + +/// +/// Regression tests for DiscoveryDataAggregator test case source tracking. +/// +[TestClass] +public class DiscoveryDataAggregatorSourceTrackingRegressionTests +{ + // Regression test for #3381 — Discovery source status tracking + [TestMethod] + public void MarkSourcesBasedOnDiscoveredTestCases_ShouldMarkAsPartiallyDiscovered() + { + var aggregator = new DiscoveryDataAggregator(); + + // First mark as not discovered + aggregator.MarkSourcesWithStatus(new[] { "test1.dll", "test2.dll" }, DiscoveryStatus.NotDiscovered); + + // Simulate discovering test cases from test1.dll + var testCases = new List + { + new("Test.Method1", new Uri("executor://test"), "test1.dll"), + new("Test.Method2", new Uri("executor://test"), "test1.dll"), + }; + + string? previousSource = null; + _ = aggregator.MarkSourcesBasedOnDiscoveredTestCases(previousSource, testCases); + + // test1.dll should now be partially discovered (not fully since discovery hasn't completed) + var partialSources = aggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered); + Assert.Contains("test1.dll", partialSources); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesBasedOnDiscoveredTestCases_NullTestCases_ShouldNotThrow() + { + var aggregator = new DiscoveryDataAggregator(); + + // Should not throw + aggregator.MarkSourcesBasedOnDiscoveredTestCases(null, null); + } + + // Regression test for #3381 + [TestMethod] + public void MarkSourcesBasedOnDiscoveredTestCases_EmptyTestCases_ShouldReturnPreviousSource() + { + var aggregator = new DiscoveryDataAggregator(); + aggregator.MarkSourcesWithStatus(new[] { "test1.dll" }, DiscoveryStatus.NotDiscovered); + + string? result = aggregator.MarkSourcesBasedOnDiscoveredTestCases("test1.dll", new List()); + + // Should return the previous source + Assert.AreEqual("test1.dll", result); + } + + // Regression test for #3381 + [TestMethod] + public void Aggregate_TotalTests_ShouldAccumulateCount() + { + var aggregator = new DiscoveryDataAggregator(); + + aggregator.Aggregate(new DiscoveryCompleteEventArgs(10, false)); + aggregator.Aggregate(new DiscoveryCompleteEventArgs(15, false)); + + Assert.AreEqual(25, aggregator.TotalTests); + Assert.IsFalse(aggregator.IsAborted); + } + + // Regression test for #3381 + [TestMethod] + public void Aggregate_WhenAnyAborted_ShouldBeAborted() + { + var aggregator = new DiscoveryDataAggregator(); + + aggregator.Aggregate(new DiscoveryCompleteEventArgs(10, false)); + aggregator.Aggregate(new DiscoveryCompleteEventArgs(5, true)); + + Assert.IsTrue(aggregator.IsAborted); + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorTests.cs index 9b466e9a54..25633d4687 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/DiscoveryDataAggregatorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -57,7 +57,7 @@ public void AggregateDiscoveryDataMetricsShouldAggregateMetricsCorrectly() aggregator.AggregateMetrics(null); var runMetrics = aggregator.GetMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -161,7 +161,7 @@ public void GetAggregatedDiscoveryDataMetricsShouldReturnEmptyIfMetricAggregator aggregator.AggregateMetrics(new Dictionary()); var runMetrics = aggregator.GetMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -172,7 +172,7 @@ public void GetAggregatedDiscoveryDataMetricsShouldReturnEmptyIfMetricsIsNull() aggregator.AggregateMetrics(null); var runMetrics = aggregator.GetMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -235,7 +235,7 @@ public void GetDiscoveryDataMetricsShouldNotAddNumberOfAdapterDiscoveredIfMetric Assert.IsFalse(runMetrics.TryGetValue(TelemetryDataConstants.NumberOfAdapterDiscoveredDuringDiscovery, out _)); } - [DataTestMethod] + [TestMethod] [DataRow(DiscoveryStatus.FullyDiscovered)] [DataRow(DiscoveryStatus.PartiallyDiscovered)] [DataRow(DiscoveryStatus.NotDiscovered)] @@ -248,12 +248,12 @@ public void MarkSourcesWithStatusWhenSourcesIsNullDoesNothing(DiscoveryStatus di dataAggregator.MarkSourcesWithStatus(null, discoveryStatus); // Assert - Assert.AreEqual(0, dataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered).Count); - Assert.AreEqual(0, dataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered).Count); - Assert.AreEqual(0, dataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered).Count); + Assert.IsEmpty(dataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); + Assert.IsEmpty(dataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); + Assert.IsEmpty(dataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); } - [DataTestMethod] + [TestMethod] [DataRow(DiscoveryStatus.FullyDiscovered)] [DataRow(DiscoveryStatus.PartiallyDiscovered)] [DataRow(DiscoveryStatus.NotDiscovered)] @@ -264,7 +264,7 @@ public void MarkSourcesWithStatusIgnoresNullSources(DiscoveryStatus discoverySta var sources = new[] { "a", null, "b" }; // Sanity check - Assert.AreEqual(0, dataAggregator.GetSourcesWithStatus(discoveryStatus).Count); + Assert.IsEmpty(dataAggregator.GetSourcesWithStatus(discoveryStatus)); // Act dataAggregator.MarkSourcesWithStatus(sources, discoveryStatus); @@ -273,7 +273,7 @@ public void MarkSourcesWithStatusIgnoresNullSources(DiscoveryStatus discoverySta CollectionAssert.AreEquivalent(new[] { "a", "b" }, dataAggregator.GetSourcesWithStatus(discoveryStatus)); } - [DataTestMethod] + [TestMethod] [DataRow(DiscoveryStatus.FullyDiscovered)] [DataRow(DiscoveryStatus.PartiallyDiscovered)] public void MarkSourcesWithStatusWhenSourceAddedAndStatusDifferentFromNotDiscoveredLogsWarning(DiscoveryStatus discoveryStatus) @@ -288,7 +288,7 @@ public void MarkSourcesWithStatusWhenSourceAddedAndStatusDifferentFromNotDiscove CollectionAssert.AreEquivalent(new[] { "a" }, dataAggregator.GetSourcesWithStatus(discoveryStatus)); } - [DataTestMethod] + [TestMethod] [DataRow(DiscoveryStatus.NotDiscovered)] [DataRow(DiscoveryStatus.PartiallyDiscovered)] public void MarkSourcesWithStatusWhenSourceStatusWasFullyDiscoveredAndIsDowngradedLogsWarning(DiscoveryStatus discoveryStatus) @@ -356,14 +356,14 @@ public void MarkSourcesBasedOnDiscoveredTestCasesReuseLastDiscoveredSource() CollectionAssert.AreEquivalent(new[] { "b" }, dataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); } - [DataTestMethod] + [TestMethod] [DataRow(DiscoveryStatus.FullyDiscovered)] [DataRow(DiscoveryStatus.PartiallyDiscovered)] [DataRow(DiscoveryStatus.NotDiscovered)] public void GetSourcesWithStatusWhenEmptyDictionaryReturnsEmptyList(DiscoveryStatus discoveryStatus) { var instanceSources = new DiscoveryDataAggregator().GetSourcesWithStatus(discoveryStatus); - Assert.AreEqual(0, instanceSources.Count); + Assert.IsEmpty(instanceSources); } [TestMethod] @@ -423,6 +423,6 @@ public void TryAggregateIsMessageSentOnlyReportsOnceEvenWhenRunningInParallel() System.Threading.Tasks.Parallel.For(0, 100, _ => concurrentBag.Add(dataAggregator.TryAggregateIsMessageSent())); // Assert - Assert.AreEqual(1, concurrentBag.Count(b => b)); + Assert.ContainsSingle(concurrentBag.Where(b => b)); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs index 42cd47010f..039bcbb479 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable MSTEST0049 // CancellationToken not applicable in test setup/Moq callbacks + using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + using FluentAssertions; namespace TestPlatform.CrossPlatEngine.UnitTests.Client; diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyDiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyDiscoveryManagerTests.cs index 37cfb2764d..c5cb74955a 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyDiscoveryManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyDiscoveryManagerTests.cs @@ -1,6 +1,8 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable MSTEST0049 // CancellationToken not applicable in test setup/Moq callbacks + using System; using System.Collections.Generic; using System.Threading; @@ -113,7 +115,7 @@ public void AbortShouldCallAllConcurrentManagersOnce() parallelDiscoveryManager.Abort(); - Assert.AreEqual(2, _usedMockManagers.Count, "Number of Concurrent Managers created should be equal to the number of sources that should run"); + Assert.HasCount(2, _usedMockManagers, "Number of Concurrent Managers created should be equal to the number of sources that should run"); _usedMockManagers.ForEach(dm => dm.Verify(m => m.Abort(), Times.Once)); } @@ -141,7 +143,7 @@ public void DiscoverTestsShouldProcessAllSources() } Assert.IsTrue(discoveryCompleted, "Test discovery not completed."); - Assert.AreEqual(_sources.Count, _processedSources.Count, "All Sources must be processed."); + Assert.HasCount(_sources.Count, _processedSources, "All Sources must be processed."); AssertMissingAndDuplicateSources(_processedSources); } @@ -214,7 +216,7 @@ public void DiscoveryTestsShouldStopDiscoveryIfAbortionWasRequested() }); Assert.IsTrue(_discoveryCompleted.Wait(Timeout10Seconds), "Test discovery not completed."); - Assert.AreEqual(1, _processedSources.Count, "One source should be processed."); + Assert.ContainsSingle(_processedSources, "One source should be processed."); } [TestMethod] @@ -232,7 +234,7 @@ public void DiscoveryTestsShouldStopDiscoveryIfAbortionWithEventHandlerWasReques }); Assert.IsTrue(_discoveryCompleted.Wait(Timeout10Seconds), "Test discovery not completed."); - Assert.AreEqual(1, _processedSources.Count, "One source should be processed."); + Assert.ContainsSingle(_processedSources, "One source should be processed."); } [TestMethod] @@ -252,7 +254,7 @@ public void DiscoveryTestsShouldProcessAllSourceIfOneDiscoveryManagerIsStarved() // Processed sources should be 1 since the 2nd source is never discovered Assert.IsTrue(_discoveryCompleted.Wait(Timeout10Seconds), "Test discovery not completed."); - Assert.AreEqual(1, _processedSources.Count, "All Sources must be processed."); + Assert.ContainsSingle(_processedSources, "All Sources must be processed."); } [TestMethod] @@ -324,10 +326,10 @@ public void DiscoveryTestsWithCompletionMarksAllSourcesAsFullyDiscovered() Task.Run(() => parallelDiscoveryManager.DiscoverTests(_discoveryCriteriaWith2Sources, _mockEventHandler.Object)); Assert.IsTrue(_discoveryCompleted.Wait(Timeout10Seconds), "Test discovery not completed."); - Assert.AreEqual(_sources.Count, _processedSources.Count, "All Sources must be processed."); + Assert.HasCount(_sources.Count, _processedSources, "All Sources must be processed."); CollectionAssert.AreEquivalent(_sources, _dataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); - Assert.AreEqual(0, _dataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered).Count); - Assert.AreEqual(0, _dataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered).Count); + Assert.IsEmpty(_dataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); + Assert.IsEmpty(_dataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); } private ParallelProxyDiscoveryManager SetupDiscoveryManager(Func getProxyManager, int parallelLevel, bool abortDiscovery) @@ -382,14 +384,14 @@ private void AssertMissingAndDuplicateSources(List processedSources) { if (matchFound) { - Assert.Fail("Concurrency issue detected: Source['{0}'] got processed twice", processedSrc); + Assert.Fail($"Concurrency issue detected: Source['{processedSrc}'] got processed twice"); } matchFound = true; } } - Assert.IsTrue(matchFound, "Concurrency issue detected: Source['{0}'] did NOT get processed at all", source); + Assert.IsTrue(matchFound, $"Concurrency issue detected: Source['{source}'] did NOT get processed at all"); } } @@ -401,7 +403,7 @@ private void InvokeAndVerifyInitialize(int maxParallelLevel, bool skipDefaultAda parallelDiscoveryManager.Initialize(skipDefaultAdapters); // Verify - Assert.AreEqual(0, _usedMockManagers.Count, $"No managers are pre-created until there is work for them."); + Assert.IsEmpty(_usedMockManagers, $"No managers are pre-created until there is work for them."); _usedMockManagers.ForEach(dm => dm.Verify(m => m.Initialize(skipDefaultAdapters), Times.Once)); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs index 2ac3486f2f..cab7a938ef 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable MSTEST0049 // CancellationToken not applicable in test setup/Moq callbacks + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -117,7 +119,7 @@ public void AbortShouldCallAllConcurrentManagersOnce() parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, new Mock().Object); parallelExecutionManager.Abort(It.IsAny()); - Assert.AreEqual(2, _usedMockManagers.Count, "Number of Concurrent Managers created should be equal to the amount of dlls that run"); + Assert.HasCount(2, _usedMockManagers, "Number of Concurrent Managers created should be equal to the amount of dlls that run"); _usedMockManagers.ForEach(em => em.Verify(m => m.Abort(It.IsAny()), Times.Once)); } @@ -130,7 +132,7 @@ public void CancelShouldCallAllConcurrentManagersOnce() parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, new Mock().Object); parallelExecutionManager.Cancel(It.IsAny()); - Assert.AreEqual(2, _usedMockManagers.Count, "Number of Concurrent Managers created should be equal to the amount of dlls that run"); + Assert.HasCount(2, _usedMockManagers, "Number of Concurrent Managers created should be equal to the amount of dlls that run"); _usedMockManagers.ForEach(em => em.Verify(m => m.Cancel(It.IsAny()), Times.Once)); } @@ -142,7 +144,7 @@ public void StartTestRunShouldProcessAllSources() parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, _mockEventHandler.Object); Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(_sources.Count, _processedSources.Count, "All Sources must be processed."); + Assert.HasCount(_sources.Count, _processedSources, "All Sources must be processed."); AssertMissingAndDuplicateSources(_processedSources); } @@ -156,7 +158,7 @@ public void StartTestRunShouldProcessAllTestCases() parallelExecutionManager.StartTestRun(_testRunCriteriaWithTestsFrom3Dlls, _mockEventHandler.Object); Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(_testCases.Count, _processedTestCases.Count, "All Tests must be processed."); + Assert.HasCount(_testCases.Count, _processedTestCases, "All Tests must be processed."); AssertMissingAndDuplicateTestCases(_testCases, _processedTestCases); } @@ -168,7 +170,7 @@ public void StartTestRunWithSourcesShouldNotSendCompleteUntilAllSourcesAreProces Task.Run(() => parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, _mockEventHandler.Object)); Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(_sources.Count, _processedSources.Count, "All Sources must be processed."); + Assert.HasCount(_sources.Count, _processedSources, "All Sources must be processed."); AssertMissingAndDuplicateSources(_processedSources); } @@ -236,7 +238,7 @@ public void StartTestRunWithTestsShouldNotSendCompleteUntilAllTestsAreProcessed( } Assert.IsTrue(executionCompleted, "Test run not completed."); - Assert.AreEqual(_testCases.Count, _processedTestCases.Count, "All Tests must be processed."); + Assert.HasCount(_testCases.Count, _processedTestCases, "All Tests must be processed."); AssertMissingAndDuplicateTestCases(_testCases, _processedTestCases); } @@ -252,7 +254,7 @@ public void StartTestRunShouldNotProcessAllSourcesOnExecutionCancelsForAnySource Task.Run(() => parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, _mockEventHandler.Object)); Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(1, _processedSources.Count, "Abort should stop all sources execution."); + Assert.ContainsSingle(_processedSources, "Abort should stop all sources execution."); } [TestMethod] @@ -268,7 +270,7 @@ public void StartTestRunShouldNotProcessAllSourcesOnExecutionAborted() Task.Run(() => parallelExecutionManager.StartTestRun(_testRunCriteriaWith2Sources, _mockEventHandler.Object)); Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(1, _processedSources.Count, "Abort should stop all sources execution."); + Assert.ContainsSingle(_processedSources, "Abort should stop all sources execution."); } [TestMethod] @@ -284,7 +286,7 @@ public void StartTestRunShouldProcessAllSourcesOnExecutionAbortsForAnySource() Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(2, _processedSources.Count, "Abort should stop all sources execution."); + Assert.HasCount(2, _processedSources, "Abort should stop all sources execution."); } [TestMethod] @@ -301,7 +303,7 @@ public void StartTestRunShouldProcessAllSourceIfOneDiscoveryManagerIsStarved() // Processed sources should be 1 since the 2nd source is never discovered Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); - Assert.AreEqual(1, _processedSources.Count, "All Sources must be processed."); + Assert.ContainsSingle(_processedSources, "All Sources must be processed."); } [TestMethod] @@ -392,9 +394,9 @@ public void StartTestRunShouldAggregateRunData() { Assert.AreEqual(TimeSpan.FromMilliseconds(200), completeArgs.ElapsedTimeInRunningTests, "Time should be max of all"); - Assert.AreEqual(2, completeArgs.AttachmentSets.Count, + Assert.HasCount(2, completeArgs.AttachmentSets, "All Complete Arg attachments should return"); - Assert.AreEqual(2, runAttachments.Count, "All RunContextAttachments should return"); + Assert.HasCount(2, runAttachments, "All RunContextAttachments should return"); Assert.IsTrue(completeArgs.IsAborted, "Aborted value must be OR of all values"); Assert.IsTrue(completeArgs.IsCanceled, "Canceled value must be OR of all values"); @@ -423,7 +425,7 @@ public void StartTestRunShouldAggregateRunData() Assert.IsTrue(_executionCompleted.Wait(Timeout3Seconds), "Test run not completed."); Assert.IsNull(assertException, assertException?.ToString()); - Assert.AreEqual(_sources.Count, _processedSources.Count, "All Sources must be processed."); + Assert.HasCount(_sources.Count, _processedSources, "All Sources must be processed."); AssertMissingAndDuplicateSources(_processedSources); } @@ -471,7 +473,7 @@ private void AssertMissingAndDuplicateSources(List processedSources) { if (matchFound) { - Assert.Fail("Concurrreny issue detected: Source['{0}'] got processed twice", processedSrc); + Assert.Fail($"Concurrreny issue detected: Source['{processedSrc}'] got processed twice"); } matchFound = true; @@ -505,13 +507,12 @@ private static void AssertMissingAndDuplicateTestCases(List tests, Lis if (processedTest.FullyQualifiedName.Equals(test.FullyQualifiedName)) { if (matchFound) - Assert.Fail("Concurrency issue detected: Test['{0}'] got processed twice", test.FullyQualifiedName); + Assert.Fail($"Concurrency issue detected: Test['{test.FullyQualifiedName}'] got processed twice"); matchFound = true; } } - Assert.IsTrue(matchFound, "Concurrency issue detected: Test['{0}'] did NOT get processed at all", - test.FullyQualifiedName); + Assert.IsTrue(matchFound, $"Concurrency issue detected: Test['{test.FullyQualifiedName}'] did NOT get processed at all"); } } @@ -576,7 +577,7 @@ private void InvokeAndVerifyInitialize(int parallelLevel, bool skipDefaultAdapte parallelExecutionManager.Initialize(skipDefaultAdapters); - Assert.AreEqual(0, _usedMockManagers.Count, $"No concurrent managers should be pre-created, until there is work for them"); + Assert.IsEmpty(_usedMockManagers, $"No concurrent managers should be pre-created, until there is work for them"); _usedMockManagers.ForEach(em => em.Verify(m => m.Initialize(skipDefaultAdapters), Times.Once)); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs index 61d6b8b17c..ebee92ba21 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelRunDataAggregatorTests.cs @@ -1,10 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable MSTEST0049 // CancellationToken not applicable in test setup/Moq callbacks + using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; @@ -29,10 +34,10 @@ public void ParallelRunDataAggregatorConstructorShouldInitializeAggregatorVars() Assert.IsNotNull(aggregator.RunCompleteArgsAttachments, "RunCompleteArgsAttachments list must not be null"); Assert.IsNotNull(aggregator.RunContextAttachments, "RunContextAttachments list must not be null"); - Assert.AreEqual(0, aggregator.Exceptions.Count, "Exceptions List must be initialized as empty list."); - Assert.AreEqual(0, aggregator.ExecutorUris.Count, "Exceptions List must be initialized as empty list."); - Assert.AreEqual(0, aggregator.RunCompleteArgsAttachments.Count, "RunCompleteArgsAttachments List must be initialized as empty list."); - Assert.AreEqual(0, aggregator.RunContextAttachments.Count, "RunContextAttachments List must be initialized as empty list"); + Assert.IsEmpty(aggregator.Exceptions, "Exceptions List must be initialized as empty list."); + Assert.IsEmpty(aggregator.ExecutorUris, "Exceptions List must be initialized as empty list."); + Assert.IsEmpty(aggregator.RunCompleteArgsAttachments, "RunCompleteArgsAttachments List must be initialized as empty list."); + Assert.IsEmpty(aggregator.RunContextAttachments, "RunContextAttachments List must be initialized as empty list"); Assert.IsFalse(aggregator.IsAborted, "Aborted must be false by default"); @@ -52,7 +57,7 @@ public void AggregateShouldAggregateRunCompleteAttachmentsCorrectly() aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, attachmentSet1, null, null); - Assert.AreEqual(1, aggregator.RunCompleteArgsAttachments.Count, "RunCompleteArgsAttachments List must have data."); + Assert.ContainsSingle(aggregator.RunCompleteArgsAttachments, "RunCompleteArgsAttachments List must have data."); var attachmentSet2 = new Collection { @@ -61,7 +66,7 @@ public void AggregateShouldAggregateRunCompleteAttachmentsCorrectly() aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, attachmentSet2, null, null); - Assert.AreEqual(2, aggregator.RunCompleteArgsAttachments.Count, "RunCompleteArgsAttachments List must have aggregated data."); + Assert.HasCount(2, aggregator.RunCompleteArgsAttachments, "RunCompleteArgsAttachments List must have aggregated data."); } [TestMethod] @@ -76,7 +81,7 @@ public void AggregateShouldAggregateRunContextAttachmentsCorrectly() aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, attachmentSet1, null, null, null); - Assert.AreEqual(1, aggregator.RunContextAttachments.Count, "RunContextAttachments List must have data."); + Assert.ContainsSingle(aggregator.RunContextAttachments, "RunContextAttachments List must have data."); var attachmentSet2 = new Collection { @@ -85,7 +90,7 @@ public void AggregateShouldAggregateRunContextAttachmentsCorrectly() aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, attachmentSet2, null, null, null); - Assert.AreEqual(2, aggregator.RunContextAttachments.Count, "RunContextAttachments List must have aggregated data."); + Assert.HasCount(2, aggregator.RunContextAttachments, "RunContextAttachments List must have aggregated data."); } [TestMethod] @@ -98,19 +103,19 @@ public void AggregateShouldAggregateInvokedCollectorsCorrectly() new(new Uri("datacollector://sample"),"sample", typeof(string).AssemblyQualifiedName!, typeof(string).Assembly.Location,false) }; aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null, invokedDataCollectors, null); - Assert.AreEqual(1, aggregator.InvokedDataCollectors.Count, "InvokedDataCollectors List must have data."); + Assert.ContainsSingle(aggregator.InvokedDataCollectors, "InvokedDataCollectors List must have data."); var invokedDataCollectors2 = new Collection() { new(new Uri("datacollector://sample2"),"sample2", typeof(int).AssemblyQualifiedName!, typeof(int).Assembly.Location,false) }; aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null, invokedDataCollectors2, null); - Assert.AreEqual(2, aggregator.InvokedDataCollectors.Count, "InvokedDataCollectors List must have aggregated data."); + Assert.HasCount(2, aggregator.InvokedDataCollectors, "InvokedDataCollectors List must have aggregated data."); aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null, invokedDataCollectors, null); aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null, invokedDataCollectors2, null); - Assert.AreEqual(2, aggregator.InvokedDataCollectors.Count, "InvokedDataCollectors List must have aggregated data."); + Assert.HasCount(2, aggregator.InvokedDataCollectors, "InvokedDataCollectors List must have aggregated data."); Assert.AreEqual(invokedDataCollectors[0].AssemblyQualifiedName, aggregator.InvokedDataCollectors[0].AssemblyQualifiedName); Assert.AreEqual(invokedDataCollectors[0].FilePath, aggregator.InvokedDataCollectors[0].FilePath); Assert.AreEqual(invokedDataCollectors[0].Uri, aggregator.InvokedDataCollectors[0].Uri); @@ -198,7 +203,7 @@ public void AggregateShouldAggregateExceptionsCorrectly() var aggregatedException = aggregator.GetAggregatedException() as AggregateException; Assert.IsNotNull(aggregatedException, "Aggregated exception must NOT be null"); Assert.IsNotNull(aggregatedException.InnerExceptions, "Inner exception list must NOT be null"); - Assert.AreEqual(1, aggregatedException.InnerExceptions.Count, "Inner exception list must have one element"); + Assert.ContainsSingle(aggregatedException.InnerExceptions, "Inner exception list must have one element"); Assert.AreEqual(exception1, aggregatedException.InnerExceptions[0], "Inner exception must be the one set."); var exception2 = new NotSupportedException(); @@ -208,7 +213,7 @@ public void AggregateShouldAggregateExceptionsCorrectly() aggregatedException = aggregator.GetAggregatedException() as AggregateException; Assert.IsNotNull(aggregatedException, "Aggregated exception must NOT be null"); Assert.IsNotNull(aggregatedException.InnerExceptions, "Inner exception list must NOT be null"); - Assert.AreEqual(2, aggregatedException.InnerExceptions.Count, "Inner exception list must have one element"); + Assert.HasCount(2, aggregatedException.InnerExceptions, "Inner exception list must have one element"); Assert.AreEqual(exception2, aggregatedException.InnerExceptions[1], "Inner exception must be the one set."); } @@ -219,19 +224,19 @@ public void AggregateShouldAggregateExecutorUrisCorrectly() aggregator.Aggregate(null, null, null, TimeSpan.Zero, false, false, null, null, null, null); - Assert.AreEqual(0, aggregator.ExecutorUris.Count, "ExecutorUris List must not have data."); + Assert.IsEmpty(aggregator.ExecutorUris, "ExecutorUris List must not have data."); var uri1 = "x://hello1"; aggregator.Aggregate(null, new List() { uri1 }, null, TimeSpan.Zero, false, false, null, null, null, null); - Assert.AreEqual(1, aggregator.ExecutorUris.Count, "ExecutorUris List must have data."); - Assert.IsTrue(aggregator.ExecutorUris.Contains(uri1), "ExecutorUris List must have correct data."); + Assert.ContainsSingle(aggregator.ExecutorUris, "ExecutorUris List must have data."); + Assert.Contains(uri1, aggregator.ExecutorUris, "ExecutorUris List must have correct data."); var uri2 = "x://hello2"; aggregator.Aggregate(null, new List() { uri2 }, null, TimeSpan.Zero, false, false, null, null, null, null); - Assert.AreEqual(2, aggregator.ExecutorUris.Count, "ExecutorUris List must have aggregated data."); - Assert.IsTrue(aggregator.ExecutorUris.Contains(uri2), "ExecutorUris List must have correct data."); + Assert.HasCount(2, aggregator.ExecutorUris, "ExecutorUris List must have aggregated data."); + Assert.Contains(uri2, aggregator.ExecutorUris, "ExecutorUris List must have correct data."); } [TestMethod] @@ -292,7 +297,7 @@ public void AggregateRunDataMetricsShouldAggregateMetricsCorrectly() aggregator.AggregateRunDataMetrics(null); var runMetrics = aggregator.GetAggregatedRunDataMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -379,7 +384,7 @@ public void GetAggregatedRunDataMetricsShouldReturnEmptyIfMetricAggregatorIsEmpt aggregator.AggregateRunDataMetrics(dict); var runMetrics = aggregator.GetAggregatedRunDataMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -391,7 +396,7 @@ public void GetAggregatedRunDataMetricsShouldReturnEmptyIfMetricsIsNull() aggregator.AggregateRunDataMetrics(null); var runMetrics = aggregator.GetAggregatedRunDataMetrics(); - Assert.AreEqual(0, runMetrics.Count); + Assert.IsEmpty(runMetrics); } [TestMethod] @@ -455,4 +460,66 @@ public void GetRunDataMetricsShouldNotAddNumberOfAdapterDiscoveredIfMetricsIsEmp var runMetrics = aggregator.GetAggregatedRunDataMetrics(); Assert.IsFalse(runMetrics.TryGetValue(TelemetryDataConstants.NumberOfAdapterDiscoveredDuringExecution, out _)); } + + [TestMethod] + public void AggregateAndGetAggregatedRunStatsShouldBeThreadSafe() + { + var aggregator = new ParallelRunDataAggregator(Constants.EmptyRunSettings); + + const int threadCount = 10; + const int iterationsPerThread = 100; + var barrier = new Barrier(threadCount); + + // Start threads that call Aggregate concurrently + var aggregateTasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(() => + { + barrier.SignalAndWait(); + for (int i = 0; i < iterationsPerThread; i++) + { + var stats = new Dictionary + { + { TestOutcome.Passed, 1 }, + }; + aggregator.Aggregate(new TestRunStatistics(1, stats), null, null, TimeSpan.Zero, false, false, null, null, null, null); + } + })).ToArray(); + + Task.WaitAll(aggregateTasks.ToArray()); + + var finalStats = aggregator.GetAggregatedRunStats(); + Assert.AreEqual(threadCount * iterationsPerThread, finalStats.ExecutedTests, + "All test results should be aggregated without data loss"); + Assert.AreEqual(threadCount * iterationsPerThread, finalStats.Stats![TestOutcome.Passed], + "All passed test counts should be aggregated correctly"); + } + + [TestMethod] + public void AggregateRunDataMetricsShouldBeThreadSafe() + { + var aggregator = new ParallelRunDataAggregator(Constants.EmptyRunSettings); + + const int threadCount = 10; + const int iterationsPerThread = 100; + var barrier = new Barrier(threadCount); + + var tasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(() => + { + barrier.SignalAndWait(); + for (int i = 0; i < iterationsPerThread; i++) + { + var dict = new Dictionary + { + { TelemetryDataConstants.TotalTestsRanByAdapter, 1 } + }; + aggregator.AggregateRunDataMetrics(dict); + } + })).ToArray(); + + Task.WaitAll(tasks); + + var runMetrics = aggregator.GetAggregatedRunDataMetrics(); + Assert.IsTrue(runMetrics.TryGetValue(TelemetryDataConstants.TotalTestsRanByAdapter, out var value)); + Assert.AreEqual((double)(threadCount * iterationsPerThread), Convert.ToDouble(value, CultureInfo.InvariantCulture), + "All metrics should be aggregated without lost updates"); + } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs index f9483d82bb..c3c01e6be3 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs @@ -22,7 +22,6 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client; -[TestClass] public class ProxyBaseManagerTests { private const int Clientprocessexitwait = 10 * 1000; diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs index b78ab647f8..209fc9c54e 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs @@ -446,8 +446,8 @@ public void DiscoveryTestsMarksAllSourcesAsNotDiscovered() // Assert CollectionAssert.AreEquivalent(inputSource, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered).Count); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered).Count); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); } [TestMethod] @@ -601,7 +601,7 @@ public void HandleDiscoveredTestsMarksDiscoveryStatus() }); // Assert - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered).Count); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); CollectionAssert.AreEquivalent( new List { "d" }, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); @@ -610,7 +610,7 @@ public void HandleDiscoveredTestsMarksDiscoveryStatus() _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void HandleDiscoveryCompleteWhenAbortedNoPastDiscoveryAndNoLastCunkNotifiesWithCorrectDiscovery(bool trueIsEmptyFalseIsNull) @@ -631,11 +631,11 @@ public void HandleDiscoveryCompleteWhenAbortedNoPastDiscoveryAndNoLastCunkNotifi CollectionAssert.AreEquivalent( new[] { "a", "b" }, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered).Count); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered).Count); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.FullyDiscovered)); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void HandleDiscoveryCompleteWhenAbortedPastDiscoveryAndNoLastCunkNotifiesWithCorrectDiscovery(bool trueIsEmptyFalseIsNull) @@ -664,7 +664,7 @@ public void HandleDiscoveryCompleteWhenAbortedPastDiscoveryAndNoLastCunkNotifies CollectionAssert.AreEquivalent( new[] { "b" }, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered).Count); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); } [TestMethod] @@ -724,7 +724,7 @@ public void HandleDiscoveryCompleteWhenAbortedPastDiscoveryAndLastCunkNotifiesWi CollectionAssert.AreEquivalent( new[] { "d" }, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.PartiallyDiscovered)); - Assert.AreEqual(0, _discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered).Count); + Assert.IsEmpty(_discoveryDataAggregator.GetSourcesWithStatus(DiscoveryStatus.NotDiscovered)); } private void InvokeAndVerifyDiscoverTests(bool skipDefaultAdapters) diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs index 286c193f53..7ede6551bb 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs @@ -298,7 +298,7 @@ public void SetupChannelShouldThrowExceptionIfClientConnectionTimeout() _mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny(), It.IsAny())).Returns(false); _mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(false)); - Assert.ThrowsException(() => _testExecutionManager.SetupChannel(new List { "source.dll" }, runsettings)); + Assert.ThrowsExactly(() => _testExecutionManager.SetupChannel(new List { "source.dll" }, runsettings)); } [TestMethod] @@ -309,7 +309,7 @@ public void SetupChannelShouldThrowExceptionWithOneSourceIfTestHostExitedBeforeC _mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny(), It.IsAny())).Returns(false); _mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)).Callback(() => _mockTestHostManager.Raise(t => t.HostExited += null, new HostProviderEventArgs("I crashed!"))); - Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.Resources.TestHostExitedWithError, "source.dll", "I crashed!"), Assert.ThrowsException(() => _testExecutionManager.SetupChannel(new List { "source.dll" }, runsettings)).Message); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.Resources.TestHostExitedWithError, "source.dll", "I crashed!"), Assert.ThrowsExactly(() => _testExecutionManager.SetupChannel(new List { "source.dll" }, runsettings)).Message); } [TestMethod] @@ -320,7 +320,7 @@ public void SetupChannelShouldThrowExceptionWithAllSourcesIfTestHostExitedBefore _mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny(), It.IsAny())).Returns(false); _mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)).Callback(() => _mockTestHostManager.Raise(t => t.HostExited += null, new HostProviderEventArgs("I crashed!"))); - Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.Resources.TestHostExitedWithError, string.Join("', '", ["source1.dll", "source2.dll"]), "I crashed!"), Assert.ThrowsException(() => _testExecutionManager.SetupChannel(new List { "source1.dll", "source2.dll" }, runsettings)).Message); + Assert.AreEqual(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.Resources.TestHostExitedWithError, string.Join("', '", ["source1.dll", "source2.dll"]), "I crashed!"), Assert.ThrowsExactly(() => _testExecutionManager.SetupChannel(new List { "source1.dll", "source2.dll" }, runsettings)).Message); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs index eaf3784085..8e5107b786 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerWithDataCollectionTests.cs @@ -67,7 +67,7 @@ public void InitializeShouldThrowExceptionIfThrownByDataCollectionManager() { _mockDataCollectionManager.Setup(x => x.Initialize()).Throws(); - Assert.ThrowsException(() => _proxyExecutionManager.Initialize(false)); + Assert.ThrowsExactly(() => _proxyExecutionManager.Initialize(false)); } [TestMethod] @@ -75,7 +75,7 @@ public void InitializeShouldCallAfterTestRunIfExceptionIsThrownWhileCreatingData { _mockDataCollectionManager.Setup(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new Exception("MyException")); - Assert.ThrowsException(() => _proxyExecutionManager.Initialize(false)); + Assert.ThrowsExactly(() => _proxyExecutionManager.Initialize(false)); _mockDataCollectionManager.Verify(dc => dc.BeforeTestRunStart(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockDataCollectionManager.Verify(dc => dc.AfterTestRunEnd(It.IsAny(), It.IsAny()), Times.Once); @@ -96,7 +96,7 @@ public void InitializeShouldSaveExceptionMessagesIfThrownByDataCollectionProcess proxyExecutionManager.Initialize(false); Assert.IsNotNull(proxyExecutionManager.DataCollectionRunEventsHandler.Messages); Assert.AreEqual(TestMessageLevel.Error, proxyExecutionManager.DataCollectionRunEventsHandler.Messages[0].Item1); - StringAssert.Contains(proxyExecutionManager.DataCollectionRunEventsHandler.Messages[0].Item2, "MyException"); + Assert.Contains("MyException", proxyExecutionManager.DataCollectionRunEventsHandler.Messages[0].Item2!); } [TestMethod] @@ -110,7 +110,7 @@ public void UpdateTestProcessStartInfoShouldUpdateDataCollectionPortArg() var proxyExecutionManager = new TestableProxyExecutionManagerWithDataCollection(_mockRequestSender.Object, _mockTestHostManager.Object, _mockDataCollectionManager.Object); proxyExecutionManager.UpdateTestProcessStartInfoWrapper(testProcessStartInfo); - Assert.IsTrue(testProcessStartInfo.Arguments.Contains("--datacollectionport 0")); + Assert.Contains("--datacollectionport 0", testProcessStartInfo.Arguments); } [TestMethod] @@ -130,7 +130,7 @@ public void UpdateTestProcessStartInfoShouldUpdateTelemetryOptedInArgTrueIfTelem proxyExecutionManager.UpdateTestProcessStartInfoWrapper(testProcessStartInfo); // Verify. - Assert.IsTrue(testProcessStartInfo.Arguments.Contains("--telemetryoptedin true")); + Assert.Contains("--telemetryoptedin true", testProcessStartInfo.Arguments); } [TestMethod] @@ -150,7 +150,7 @@ public void UpdateTestProcessStartInfoShouldUpdateTelemetryOptedInArgFalseIfTele proxyExecutionManager.UpdateTestProcessStartInfoWrapper(testProcessStartInfo); // Verify. - Assert.IsTrue(testProcessStartInfo.Arguments.Contains("--telemetryoptedin false")); + Assert.Contains("--telemetryoptedin false", testProcessStartInfo.Arguments); } [TestMethod] @@ -184,7 +184,7 @@ public void LaunchProcessWithDebuggerAttachedShouldUpdateEnvironmentVariables() proxyExecutionManager.LaunchProcessWithDebuggerAttached(testProcessStartInfo); // Verify. - Assert.IsTrue(launchedStartInfo != null, "Failed to get the start info"); + Assert.IsNotNull(launchedStartInfo, "Failed to get the start info"); foreach (var envVaribale in testProcessStartInfo.EnvironmentVariables) { Assert.AreEqual(envVaribale.Value, launchedStartInfo.EnvironmentVariables![envVaribale.Key], $"Expected environment variable {envVaribale.Key} : {envVaribale.Value} not found"); @@ -192,7 +192,7 @@ public void LaunchProcessWithDebuggerAttachedShouldUpdateEnvironmentVariables() mockRunEventsHandler.Verify(r => r.HandleRawMessage(raw1)); mockRunEventsHandler.Verify(r => r.HandleRawMessage(raw2)); - Assert.AreEqual(0, proxyExecutionManager.DataCollectionRunEventsHandler.RawMessages.Count); + Assert.IsEmpty(proxyExecutionManager.DataCollectionRunEventsHandler.RawMessages); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs index a38583a79e..6cb8aa5060 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -123,8 +123,8 @@ public void SetupChannelOutcomeShouldTakeTesthostSessionSupportIntoAccount( TesthostFriendlyName = testhostFriendlyName }; - Assert.IsTrue(testOperationManager.IsTesthostCompatibleWithTestSessions() == expectedCompatibilityCheckResult); - Assert.IsTrue(testOperationManager.SetupChannel([], DefaultRunSettings) == expectedSetupResult); + Assert.AreEqual(expectedCompatibilityCheckResult, testOperationManager.IsTesthostCompatibleWithTestSessions()); + Assert.AreEqual(expectedSetupResult, testOperationManager.SetupChannel([], DefaultRunSettings)); } [TestMethod] @@ -278,7 +278,7 @@ public void SetupChannelShouldThrowIfWaitForTestHostConnectionTimesOut() var operationManager = new TestableProxyOperationManager(_mockRequestData.Object, _mockRequestSender.Object, _mockTestHostManager.Object); - var message = Assert.ThrowsException(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; + var message = Assert.ThrowsExactly(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; Assert.AreEqual(message, TimoutErrorMessage); } @@ -292,7 +292,7 @@ public void SetupChannelShouldThrowTestPlatformExceptionIfRequestCancelled() var operationManager = new TestableProxyOperationManager(_mockRequestData.Object, _mockRequestSender.Object, _mockTestHostManager.Object, cancellationTokenSource); cancellationTokenSource.Cancel(); - var message = Assert.ThrowsException(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; + var message = Assert.ThrowsExactly(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; Equals("Canceling the operation as requested.", message); } @@ -302,12 +302,14 @@ public void SetupChannelShouldThrowTestPlatformExceptionIfRequestCancelledDuring SetupTestHostLaunched(true); _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(ConnectionTimeout, It.IsAny())).Returns(false); +#pragma warning disable MSTEST0049 // CancellationToken not applicable in Moq callback _mockTestHostManager.Setup(rs => rs.LaunchTestHostAsync(It.IsAny(), It.IsAny())).Callback(() => Task.Run(() => throw new OperationCanceledException())); +#pragma warning restore MSTEST0049 var cancellationTokenSource = new CancellationTokenSource(); var operationManager = new TestableProxyOperationManager(_mockRequestData.Object, _mockRequestSender.Object, _mockTestHostManager.Object, cancellationTokenSource); - var message = Assert.ThrowsException(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; + var message = Assert.ThrowsExactly(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; Equals("Canceling the operation as requested.", message); } @@ -321,7 +323,7 @@ public void SetupChannelShouldThrowTestPlatformExceptionIfRequestCancelledPostHo _mockTestHostManager.Setup(rs => rs.LaunchTestHostAsync(It.IsAny(), It.IsAny())).Callback(() => cancellationTokenSource.Cancel()); var operationManager = new TestableProxyOperationManager(_mockRequestData.Object, _mockRequestSender.Object, _mockTestHostManager.Object, cancellationTokenSource); - var message = Assert.ThrowsException(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; + var message = Assert.ThrowsExactly(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; Equals("Canceling the operation as requested.", message); } @@ -333,7 +335,7 @@ public void SetupChannelShouldThrowIfLaunchTestHostFails() var operationManager = new TestableProxyOperationManager(_mockRequestData.Object, _mockRequestSender.Object, _mockTestHostManager.Object); - var message = Assert.ThrowsException(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; + var message = Assert.ThrowsExactly(() => operationManager.SetupChannel([], DefaultRunSettings)).Message; Assert.AreEqual(message, Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources.InitializationFailed); } @@ -349,7 +351,7 @@ public void SetupChannelShouldThrowExceptionIfVersionCheckFails() { // Make the version check fail _mockRequestSender.Setup(rs => rs.CheckVersionWithTestHost()).Throws(new TestPlatformException("Version check failed")); - Assert.ThrowsException(() => _testOperationManager.SetupChannel([], DefaultRunSettings)); + Assert.ThrowsExactly(() => _testOperationManager.SetupChannel([], DefaultRunSettings)); } [TestMethod] @@ -474,7 +476,7 @@ public void UpdateTestProcessStartInfoShouldUpdateTelemetryOptedInArgTrueIfTelem testOperationManager.SetupChannel([], DefaultRunSettings); // Verify. - Assert.IsTrue(receivedTestProcessInfo.Arguments!.Contains("--telemetryoptedin true")); + Assert.Contains("--telemetryoptedin true", receivedTestProcessInfo.Arguments!); } [TestMethod] @@ -497,7 +499,7 @@ public void UpdateTestProcessStartInfoShouldUpdateTelemetryOptedInArgFalseIfTele testOperationManager.SetupChannel([], DefaultRunSettings); // Verify. - Assert.IsTrue(receivedTestProcessInfo.Arguments!.Contains("--telemetryoptedin false")); + Assert.Contains("--telemetryoptedin false", receivedTestProcessInfo.Arguments!); } [MemberNotNull(nameof(_mockProcessHelper), nameof(_mockFileHelper), nameof(_mockEnvironment), nameof(_mockRunsettingHelper), nameof(_mockWindowsRegistry), nameof(_mockEnvironmentVariableHelper))] @@ -520,9 +522,10 @@ private void SetUpMocksForDotNetTestHost() It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Callback, Action, Action, Action>( - (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback) => + It.IsAny>(), + It.IsAny())) + .Callback, Action, Action, Action, bool>( + (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback, createNoNewWindow) => { var process = Process.GetCurrentProcess(); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs index 853bfa8486..4e9ff82e57 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs @@ -398,12 +398,12 @@ public void DequeueProxyShouldSucceedIfIdentificationCriteriaAreMet() Times.Once); // First call to DequeueProxy fails because of source mismatch. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( @"C:\temp\FakeTestAsset2.dll", testSessionCriteria.RunSettings)); // Second call to DequeueProxy fails because of runsettings mismatch. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], _runSettingsOneEnvVar)); @@ -414,7 +414,7 @@ public void DequeueProxyShouldSucceedIfIdentificationCriteriaAreMet() mockProxyOperationManager.Object); // Fourth call to DequeueProxy fails because proxy became unavailable following successful deque. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], testSessionCriteria.RunSettings)); } @@ -445,7 +445,7 @@ public void DequeueProxyTwoConsecutiveTimesWithEnqueueShouldBeSuccessful() testSessionCriteria.RunSettings), mockProxyOperationManager.Object); - Assert.AreEqual(proxyManager.EnqueueProxy(mockProxyOperationManager.Object.Id), true); + Assert.IsTrue(proxyManager.EnqueueProxy(mockProxyOperationManager.Object.Id)); // Call to DequeueProxy succeeds when called with the same runsettings as before. Assert.AreEqual(proxyManager.DequeueProxy( @@ -475,7 +475,7 @@ public void DequeueProxyShouldFailIfRunSettingsMatchingFails() Times.Once); // This call to DequeueProxy fails because of runsettings mismatch. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], _runSettingsTwoEnvVars)); } @@ -501,7 +501,7 @@ public void DequeueProxyShouldFailIfRunSettingsMatchingFailsFor2EnvVariables() Times.Once); // This call to DequeueProxy fails because of runsettings mismatch. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], _runSettingsOneEnvVar)); } @@ -527,7 +527,7 @@ public void DequeueProxyShouldFailIfRunSettingsMatchingFailsForDataCollectors() Times.Once); // This call to DequeueProxy fails because of runsettings mismatch. - Assert.ThrowsException(() => proxyManager.DequeueProxy( + Assert.ThrowsExactly(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], _runSettingsTwoEnvVarsAndDataCollectors)); } @@ -543,8 +543,8 @@ public void EnqueueProxyShouldSucceedIfIdentificationCriteriaAreMet() var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); // Validate sanity checks. - Assert.ThrowsException(() => proxyManager.EnqueueProxy(-1)); - Assert.ThrowsException(() => proxyManager.EnqueueProxy(1)); + Assert.ThrowsExactly(() => proxyManager.EnqueueProxy(-1)); + Assert.ThrowsExactly(() => proxyManager.EnqueueProxy(1)); // StartSession should succeed. Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); @@ -557,7 +557,7 @@ public void EnqueueProxyShouldSucceedIfIdentificationCriteriaAreMet() Times.Once); // Call throws exception because proxy is already available. - Assert.ThrowsException(() => proxyManager.EnqueueProxy(0)); + Assert.ThrowsExactly(() => proxyManager.EnqueueProxy(0)); // Call succeeds. Assert.AreEqual(proxyManager.DequeueProxy( diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs index 465ce2effa..f481990759 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/DataCollectionTestRunEventsHandlerTests.cs @@ -123,7 +123,7 @@ public void HandleRawMessageShouldInvokeAfterTestRunEndAndReturnInvokedDataColle { var testRunCompleteArgs = o as TestRunCompletePayload; Assert.IsNotNull(testRunCompleteArgs); - Assert.AreEqual(1, testRunCompleteArgs.TestRunCompleteArgs!.InvokedDataCollectors.Count); + Assert.ContainsSingle(testRunCompleteArgs.TestRunCompleteArgs!.InvokedDataCollectors); Assert.AreEqual(invokedDataCollectors[0], testRunCompleteArgs.TestRunCompleteArgs.InvokedDataCollectors[0]); }); @@ -132,7 +132,7 @@ public void HandleRawMessageShouldInvokeAfterTestRunEndAndReturnInvokedDataColle var testRunCompleteEventArgs2 = new TestRunCompleteEventArgs(null, false, false, null, new Collection(), new Collection(), new TimeSpan()); _testRunEventHandler.HandleTestRunComplete(testRunCompleteEventArgs2, null, null, null); - Assert.AreEqual(1, testRunCompleteEventArgs2.InvokedDataCollectors.Count); + Assert.ContainsSingle(testRunCompleteEventArgs2.InvokedDataCollectors); Assert.AreEqual(invokedDataCollectors[0], testRunCompleteEventArgs2.InvokedDataCollectors[0]); _proxyDataCollectionManager.Verify( @@ -157,8 +157,8 @@ public void GetCombinedAttachmentSetsShouldReturnCombinedAttachments() var result = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(attachments1, attachments2); - Assert.AreEqual(1, result.Count); - Assert.AreEqual(2, result.First().Attachments.Count); + Assert.ContainsSingle(result); + Assert.HasCount(2, result.First().Attachments); } [TestMethod] @@ -171,8 +171,8 @@ public void GetCombinedAttachmentSetsShouldReturnFirstArgumentIfSecondArgumentIs var result = DataCollectionTestRunEventsHandler.GetCombinedAttachmentSets(attachments1, null); - Assert.AreEqual(1, result.Count); - Assert.AreEqual(1, result.First().Attachments.Count); + Assert.ContainsSingle(result); + Assert.ContainsSingle(result.First().Attachments); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionExtensionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionExtensionManagerTests.cs index 1edc315269..3b8fbf57b0 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionExtensionManagerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -65,11 +65,11 @@ public void CodeBasePathsAreDeduplicatedWithCaseIgnoring() var inProcDataCollectionExtensionManager = new TestableInProcDataCollectionExtensionManager(_settingsXml, _mockTestEventsPublisher.Object, null, testPluginCache, _mockFileHelper.Object); - Assert.AreEqual(3, inProcDataCollectionExtensionManager.CodeBasePaths.Count); // "CodeBasePaths" contains the two extensions(after removing duplicates) and the "_defaultCodebase" + Assert.HasCount(3, inProcDataCollectionExtensionManager.CodeBasePaths); // "CodeBasePaths" contains the two extensions(after removing duplicates) and the "_defaultCodebase" - Assert.IsTrue(inProcDataCollectionExtensionManager.CodeBasePaths.Contains(null)); - Assert.IsTrue(inProcDataCollectionExtensionManager.CodeBasePaths.Contains(directory1)); - Assert.IsTrue(inProcDataCollectionExtensionManager.CodeBasePaths.Contains(directory2)); + Assert.Contains((string?)null, inProcDataCollectionExtensionManager.CodeBasePaths); + Assert.Contains(directory1, inProcDataCollectionExtensionManager.CodeBasePaths); + Assert.Contains(directory2, inProcDataCollectionExtensionManager.CodeBasePaths); } @@ -79,7 +79,7 @@ public void InProcDataCollectionExtensionManagerShouldLoadsDataCollectorsFromRun var dataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.First().Value; Assert.IsTrue(_inProcDataCollectionManager.IsInProcDataCollectionEnabled, "InProcDataCollection must be enabled if runsettings contains inproc datacollectors."); - Assert.AreEqual(1, _inProcDataCollectionManager.InProcDataCollectors.Count, "One Datacollector must be registered"); + Assert.ContainsSingle(_inProcDataCollectionManager.InProcDataCollectors, "One Datacollector must be registered"); Equals(dataCollector.AssemblyQualifiedName, "TestImpactListener.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7ccb7239ffde675a"); Equals(dataCollector.CodeBase, Path.Combine(Temp, "repos", "MSTest", "src", "managed", "TestPlatform", "TestImpactListener.Tests", "bin", "Debug", "TestImpactListener.Tests.dll")); @@ -101,7 +101,7 @@ public void InProcDataCollectionExtensionManagerLoadsDataCollectorFromDefaultCod "; - _mockFileHelper.Setup(fh => fh.Exists(Path.Combine(Temp, "repos", "MSTest", "src", "managed", "TestPlatform", "TestImpactListener.Tests", "bin", "Debug", "TestImpactListener.Tests.dll"))).Returns(true); + _mockFileHelper.Setup(fh => fh.Exists(Path.Combine(new[] { Temp, "repos", "MSTest", "src", "managed", "TestPlatform", "TestImpactListener.Tests", "bin", "Debug", "TestImpactListener.Tests.dll" }))).Returns(true); _inProcDataCollectionManager = new TestableInProcDataCollectionExtensionManager(settingsXml, _mockTestEventsPublisher.Object, _defaultCodebase, _testPluginCache, _mockFileHelper.Object); var codebase = ((MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First()).CodeBase; @@ -231,11 +231,11 @@ public void TriggerSessionStartShouldBeCalledWithCorrectTestSources() var mockDataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First(); _mockTestEventsPublisher.Raise(x => x.SessionStart += null, new SessionStartEventArgs(properties)); - Assert.IsTrue((mockDataCollector.TestSessionStartCalled == 1), "TestSessionStart must be called on datacollector"); + Assert.AreEqual(1, mockDataCollector.TestSessionStartCalled, "TestSessionStart must be called on datacollector"); Assert.IsNotNull(mockDataCollector.TestSources); - Assert.IsTrue(mockDataCollector.TestSources.Contains("testsource1.dll")); - Assert.IsTrue(mockDataCollector.TestSources.Contains("testsource2.dll")); + Assert.Contains("testsource1.dll", mockDataCollector.TestSources); + Assert.Contains("testsource2.dll", mockDataCollector.TestSources); } @@ -245,10 +245,10 @@ public void TriggerSessionStartShouldCallInProcDataCollector() _mockTestEventsPublisher.Raise(x => x.SessionStart += null, new SessionStartEventArgs()); var mockDataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First(); - Assert.IsTrue((mockDataCollector.TestSessionStartCalled == 1), "TestSessionStart must be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestSessionEndCalled == 0), "TestSessionEnd must NOT be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestCaseStartCalled == 0), "TestCaseStart must NOT be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestCaseEndCalled == 0), "TestCaseEnd must NOT be called on datacollector"); + Assert.AreEqual(1, mockDataCollector.TestSessionStartCalled, "TestSessionStart must be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestSessionEndCalled, "TestSessionEnd must NOT be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestCaseStartCalled, "TestCaseStart must NOT be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestCaseEndCalled, "TestCaseEnd must NOT be called on datacollector"); } [TestMethod] @@ -257,10 +257,10 @@ public void TriggerSessionEndShouldCallInProcDataCollector() _mockTestEventsPublisher.Raise(x => x.SessionEnd += null, new SessionEndEventArgs()); var mockDataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First(); - Assert.IsTrue((mockDataCollector.TestSessionStartCalled == 0), "TestSessionEnd must NOT be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestSessionEndCalled == 1), "TestSessionStart must be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestCaseStartCalled == 0), "TestCaseStart must NOT be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestCaseEndCalled == 0), "TestCaseEnd must NOT be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestSessionStartCalled, "TestSessionEnd must NOT be called on datacollector"); + Assert.AreEqual(1, mockDataCollector.TestSessionEndCalled, "TestSessionStart must be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestCaseStartCalled, "TestCaseStart must NOT be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestCaseEndCalled, "TestCaseEnd must NOT be called on datacollector"); } [TestMethod] @@ -273,8 +273,8 @@ public void TriggerTestCaseStartShouldCallInProcDataCollector() var mockDataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First(); - Assert.IsTrue((mockDataCollector.TestCaseStartCalled == 1), "TestCaseStart must be called on datacollector"); - Assert.IsTrue((mockDataCollector.TestCaseEndCalled == 0), "TestCaseEnd must NOT be called on datacollector"); + Assert.AreEqual(1, mockDataCollector.TestCaseStartCalled, "TestCaseStart must be called on datacollector"); + Assert.AreEqual(0, mockDataCollector.TestCaseEndCalled, "TestCaseEnd must NOT be called on datacollector"); } [TestMethod] @@ -289,8 +289,8 @@ public void TriggerTestCaseEndShouldtBeCalledMultipleTimesInDataDrivenScenario() _mockTestEventsPublisher.Raise(x => x.TestCaseEnd += null, new TestCaseEndEventArgs(testCase, TestOutcome.Failed)); var mockDataCollector = (MockDataCollector)_inProcDataCollectionManager.InProcDataCollectors.Values.First(); - Assert.IsTrue((mockDataCollector.TestCaseStartCalled == 2), "TestCaseStart must only be called once"); - Assert.IsTrue((mockDataCollector.TestCaseEndCalled == 2), "TestCaseEnd must only be called once"); + Assert.AreEqual(2, mockDataCollector.TestCaseStartCalled, "TestCaseStart must only be called once"); + Assert.AreEqual(2, mockDataCollector.TestCaseEndCalled, "TestCaseEnd must only be called once"); } internal class TestableInProcDataCollectionExtensionManager : InProcDataCollectionExtensionManager diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionSinkTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionSinkTests.cs index d3b85388af..6b0f035c00 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionSinkTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/InProcDataCollectionSinkTests.cs @@ -41,7 +41,7 @@ public void SendDataShouldThrowArgumentExceptionIfKeyIsNull() { _testCase.SetPropertyValue(TestCaseProperties.Id, Guid.NewGuid()); - Assert.ThrowsException( + Assert.ThrowsExactly( () => _dataCollectionSink.SendData(_dataCollectionContext, null!, "DummyValue")); } @@ -50,7 +50,7 @@ public void SendDataShouldThrowArgumentExceptionIfValueIsNull() { _testCase.SetPropertyValue(TestCaseProperties.Id, Guid.NewGuid()); - Assert.ThrowsException( + Assert.ThrowsExactly( () => _dataCollectionSink.SendData(_dataCollectionContext, "DummyKey", null!)); } @@ -58,7 +58,7 @@ public void SendDataShouldThrowArgumentExceptionIfValueIsNull() // TODO : Currently this code hits when test case id is null for core projects. For that we don't have algorithm to generate the guid. It's not implemented exception now (Source Code : EqtHash.cs). public void SendDataShouldThrowArgumentExceptionIfTestCaseIdIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _dataCollectionSink.SendData(_dataCollectionContext, "DummyKey", "DummyValue")); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs index 6ed0c20b60..51ce13b0a8 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyDataCollectionManagerTests.cs @@ -70,7 +70,7 @@ public void InitializeShouldThrowExceptionIfConnectionTimeouts() { _mockDataCollectionRequestSender.Setup(x => x.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); - var message = Assert.ThrowsException(() => _proxyDataCollectionManager.Initialize()).Message; + var message = Assert.ThrowsExactly(() => _proxyDataCollectionManager.Initialize()).Message; Assert.AreEqual(message, TimoutErrorMessage); } @@ -178,7 +178,7 @@ public void BeforeTestRunStartShouldReturnDataCollectorParameters() x => x.SendBeforeTestRunStartAndGetResult(It.IsAny(), sourceList, false, It.IsAny()), Times.Once); Assert.IsNotNull(result); Assert.AreEqual(res.DataCollectionEventsPort, result.DataCollectionEventsPort); - Assert.AreEqual(res.EnvironmentVariables.Count, result.EnvironmentVariables!.Count); + Assert.HasCount(res.EnvironmentVariables.Count, result.EnvironmentVariables!); } [TestMethod] @@ -192,7 +192,7 @@ public void BeforeTestRunStartsShouldInvokeRunEventsHandlerIfExceptionIsThrown() var result = _proxyDataCollectionManager.BeforeTestRunStart(true, true, mockRunEventsHandler.Object); mockRunEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.IsRegex("Exception of type 'System.Exception' was thrown..*")), Times.Once); - Assert.AreEqual(0, result.EnvironmentVariables!.Count); + Assert.IsEmpty(result.EnvironmentVariables!); Assert.IsFalse(result.AreTestCaseLevelEventsRequired); Assert.AreEqual(0, result.DataCollectionEventsPort); } @@ -212,7 +212,7 @@ public void SendBeforeTestRunStartAndGetResultShouldBeInvokedWithCorrectTestSour x => x.SendBeforeTestRunStartAndGetResult(string.Empty, testSources, false, It.IsAny()), Times.Once); Assert.IsNotNull(result); Assert.AreEqual(res.DataCollectionEventsPort, result.DataCollectionEventsPort); - Assert.AreEqual(res.EnvironmentVariables.Count, result.EnvironmentVariables!.Count); + Assert.HasCount(res.EnvironmentVariables.Count, result.EnvironmentVariables!); } [TestMethod] @@ -238,8 +238,8 @@ public void AfterTestRunEndShouldReturnAttachments(bool telemetryOptedIn) var result = _proxyDataCollectionManager.AfterTestRunEnd(false, null); Assert.IsNotNull(result); - Assert.AreEqual(1, result.Attachments!.Count); - Assert.IsNotNull(result.Attachments[0]); + Assert.ContainsSingle(result.Attachments!); + Assert.IsNotNull(result.Attachments![0]); Assert.AreEqual(dispName, result.Attachments[0].DisplayName); Assert.AreEqual(uri, result.Attachments[0].Uri); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyOutOfProcDataCollectionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyOutOfProcDataCollectionManagerTests.cs index 23b888d436..456059f0c0 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyOutOfProcDataCollectionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ProxyOutOfProcDataCollectionManagerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -50,8 +50,8 @@ public void TriggerTestCaseEndShouldReturnCacheAttachmentsAndAssociateWithTestRe { _mockTestEventsPublisher.Raise(x => x.TestResult += null, new TestResultEventArgs(_testResult)); - Assert.AreEqual(1, _testResult.Attachments.Count); - Assert.IsTrue(_testResult.Attachments[0].Attachments[0].Uri.OriginalString.Contains("attachment.txt")); + Assert.ContainsSingle(_testResult.Attachments); + Assert.Contains("attachment.txt", _testResult.Attachments[0].Attachments[0].Uri.OriginalString); } [TestMethod] @@ -62,6 +62,6 @@ public void TriggerSendTestResultShouldDeleteTheAttachmentsFromCache() _testResult = new VisualStudio.TestPlatform.ObjectModel.TestResult(_testcase); _mockTestEventsPublisher.Raise(x => x.TestResult += null, new TestResultEventArgs(_testResult)); - Assert.AreEqual(0, _testResult.Attachments.Count); + Assert.IsEmpty(_testResult.Attachments); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs index 3215c024a3..bd3a86946d 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscovererEnumeratorTests.cs @@ -528,7 +528,7 @@ public void LoadTestsShouldCallIntoTheAdapterWithTheRightTestCaseSink() InvokeLoadTestWithMockSetup(); Assert.IsTrue(ManagedDllTestDiscoverer.IsManagedDiscoverTestCalled); - Assert.AreEqual(2, _discoveryResultCache.Tests.Count); + Assert.HasCount(2, _discoveryResultCache.Tests); } [TestMethod] @@ -536,7 +536,7 @@ public void LoadTestsShouldNotShowAnyWarningOnTestsDiscovered() { InvokeLoadTestWithMockSetup(); - Assert.AreEqual(2, _discoveryResultCache.Tests.Count); + Assert.HasCount(2, _discoveryResultCache.Tests); _messageLoggerMock.Verify(m => m.SendMessage(TestMessageLevel.Warning, It.IsAny()), Times.Never); } @@ -758,6 +758,7 @@ public static void Reset() } [FileExtension(".dll")] + [FileExtension(".exe")] [DefaultExecutorUri("discoverer://manageddlldiscoverer")] [Category("managed")] private class ManagedDllTestDiscoverer : DllTestDiscoverer @@ -781,6 +782,7 @@ public static void Reset() } [FileExtension(".dll")] + [FileExtension(".exe")] [DefaultExecutorUri("discoverer://nativedlldiscoverer")] [Category("native")] private class NativeDllTestDiscoverer : DllTestDiscoverer @@ -833,7 +835,7 @@ private static bool ShouldTestDiscovered(IEnumerable sources) var shouldTestDiscovered = false; foreach (var source in sources) { - if (source.Equals("native.dll") || source.Equals("managed.dll") || source.EndsWith("CrossPlatEngine.UnitTests.dll")) + if (source.Equals("native.dll") || source.Equals("managed.dll") || source.EndsWith("CrossPlatEngine.UnitTests.dll") || source.EndsWith("CrossPlatEngine.UnitTests.exe")) { shouldTestDiscovered = true; break; diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs index 86fee2b8d9..d3a70f8728 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryManagerTests.cs @@ -18,6 +18,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using FluentAssertions; using CrossPlatEngineResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources; @@ -310,7 +311,7 @@ public void DiscoveryTestsShouldSendAbortValuesCorrectlyIfAbortionHappened() _discoveryManager.Abort(mockHandler.Object); // Assert - Assert.AreEqual(true, receivedDiscoveryCompleteEventArgs!.IsAborted); + Assert.IsTrue(receivedDiscoveryCompleteEventArgs!.IsAborted); Assert.AreEqual(-1, receivedDiscoveryCompleteEventArgs.TotalCount); } #endregion diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs index c8e2a92c6d..b375d55147 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/DiscoveryResultCacheTests.cs @@ -15,13 +15,15 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Discovery; [TestClass] public class DiscoveryResultCacheTests { + public TestContext TestContext { get; set; } + [TestMethod] public void DiscoveryResultCacheConstructorShouldInitializeDiscoveredTestsList() { var cache = new DiscoveryResultCache(1000, TimeSpan.FromHours(1), (tests) => { }); Assert.IsNotNull(cache.Tests); - Assert.AreEqual(0, cache.Tests.Count); + Assert.IsEmpty(cache.Tests); } [TestMethod] @@ -40,7 +42,7 @@ public void AddTestShouldAddATestCaseToTheList() var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); cache.AddTest(testCase); - Assert.AreEqual(1, cache.Tests.Count); + Assert.ContainsSingle(cache.Tests); Assert.AreEqual(testCase, cache.Tests[0]); } @@ -67,7 +69,7 @@ public void AddTestShouldReportTestCasesIfMaxCacheSizeIsMet() cache.AddTest(testCase2); Assert.IsNotNull(reportedTestCases); - Assert.AreEqual(2, reportedTestCases.Count); + Assert.HasCount(2, reportedTestCases); CollectionAssert.AreEqual(new List { testCase1, testCase2 }, reportedTestCases.ToList()); } @@ -82,7 +84,7 @@ public void AddTestShouldResetTestListOnceCacheSizeIsMet() cache.AddTest(testCase2); Assert.IsNotNull(cache.Tests); - Assert.AreEqual(0, cache.Tests.Count); + Assert.IsEmpty(cache.Tests); // Validate that total tests is no reset though. Assert.AreEqual(2, cache.TotalDiscoveredTests); @@ -95,11 +97,11 @@ public void AddTestShouldReportTestCasesIfCacheTimeoutIsMet() var cache = new DiscoveryResultCache(100, TimeSpan.FromMilliseconds(10), (tests) => reportedTestCases = tests); var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); - Task.Delay(20).Wait(); + Task.Delay(20, TestContext.CancellationToken).Wait(TestContext.CancellationToken); cache.AddTest(testCase); Assert.IsNotNull(reportedTestCases); - Assert.AreEqual(1, reportedTestCases.Count); + Assert.ContainsSingle(reportedTestCases); Assert.AreEqual(testCase, reportedTestCases.ToArray()[0]); } @@ -110,11 +112,11 @@ public void AddTestShouldResetTestListIfCacheTimeoutIsMet() var cache = new DiscoveryResultCache(100, TimeSpan.FromMilliseconds(10), (tests) => reportedTestCases = tests); var testCase = new TestCase("A.C.M", new Uri("executor://unittest"), "A"); - Task.Delay(20).Wait(); + Task.Delay(20, TestContext.CancellationToken).Wait(TestContext.CancellationToken); cache.AddTest(testCase); Assert.IsNotNull(cache.Tests); - Assert.AreEqual(0, cache.Tests.Count); + Assert.IsEmpty(cache.Tests); // Validate that total tests is no reset though. Assert.AreEqual(1, cache.TotalDiscoveredTests); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs index aca9334005..805af71c75 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Discovery/TestCaseDiscoverySinkTests.cs @@ -35,7 +35,7 @@ public void SendTestCaseShouldSendTestCasesToCache() // Assert that the cache has the test case. Assert.IsNotNull(cache.Tests); - Assert.AreEqual(1, cache.Tests.Count); + Assert.ContainsSingle(cache.Tests); Assert.AreEqual(testCase, cache.Tests[0]); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs new file mode 100644 index 0000000000..98dfe294b0 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.EventHandlers; + +/// +/// Regression tests for NullPathConverter — should pass through all inputs unchanged. +/// +[TestClass] +public class NullPathConverterRegressionTests +{ + // Regression test for #3367 — PathConverter does not convert uris + // NullPathConverter is used when no path conversion is needed (no deployment scenario). + [TestMethod] + public void UpdatePath_ShouldReturnInputUnchanged() + { + IPathConverter converter = NullPathConverter.Instance; + + string path = @"C:\Some\Path\test.dll"; + string? result = converter.UpdatePath(path, PathConversionDirection.Receive); + + Assert.AreEqual(path, result); + } + + // Regression test for #3367 + [TestMethod] + public void UpdatePath_Null_ShouldReturnNull() + { + IPathConverter converter = NullPathConverter.Instance; + + string? result = converter.UpdatePath(null, PathConversionDirection.Send); + + Assert.IsNull(result); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestCase_ShouldNotModifyTestCase() + { + IPathConverter converter = NullPathConverter.Instance; + + var testCase = new TestCase("Test1", new Uri("executor://test"), @"C:\Path\test.dll") + { + CodeFilePath = @"C:\Path\TestClass.cs" + }; + + string originalSource = testCase.Source; + string? originalCodeFilePath = testCase.CodeFilePath; + + converter.UpdateTestCase(testCase, PathConversionDirection.Receive); + + Assert.AreEqual(originalSource, testCase.Source); + Assert.AreEqual(originalCodeFilePath, testCase.CodeFilePath); + } + + // Regression test for #3367 + [TestMethod] + public void Instance_ShouldReturnSameInstance() + { + var instance1 = NullPathConverter.Instance; + var instance2 = NullPathConverter.Instance; + + Assert.AreSame(instance1, instance2, "NullPathConverter should be a singleton."); + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterAttachmentRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterAttachmentRegressionTests.cs new file mode 100644 index 0000000000..ef52e91575 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterAttachmentRegressionTests.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.EventHandlers; + +/// +/// Regression tests for PathConverter attachment handling. +/// +[TestClass] +public class PathConverterAttachmentRegressionTests +{ + private readonly PathConverter _pathConverter; + + public PathConverterAttachmentRegressionTests() + { + _pathConverter = new PathConverter( + @"C:\Remote\Project", + @"C:\Local\Deploy", + new FileHelper()); + } + + // Regression test for #3367 — PathConverter does not convert uris + [TestMethod] + public void UpdateAttachmentSets_Collection_ShouldNotThrow() + { + var attachments = new Collection + { + new(new Uri("datacollector://Microsoft/TestPlatform/Coverage"), "Coverage") + }; + + // Should not throw + _pathConverter.UpdateAttachmentSets(attachments, PathConversionDirection.Receive); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateAttachmentSets_ICollection_ShouldNotThrow() + { + ICollection attachments = new List + { + new(new Uri("datacollector://Microsoft/TestPlatform/Coverage"), "Coverage") + }; + + // Should not throw + _pathConverter.UpdateAttachmentSets(attachments, PathConversionDirection.Receive); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateAttachmentSets_EmptyCollection_ShouldNotThrow() + { + var attachments = new Collection(); + + // Should not throw with empty collection + _pathConverter.UpdateAttachmentSets(attachments, PathConversionDirection.Receive); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestRunCompleteEventArgs_ShouldHandleEmptyAttachments() + { + var attachments = new Collection(); + var args = new TestRunCompleteEventArgs( + stats: null, + isCanceled: false, + isAborted: false, + error: null, + attachmentSets: attachments, + elapsedTime: TimeSpan.Zero); + + // Should not throw + _pathConverter.UpdateTestRunCompleteEventArgs(args, PathConversionDirection.Send); + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterDiscoveryCriteriaRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterDiscoveryCriteriaRegressionTests.cs new file mode 100644 index 0000000000..1d8d97de88 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterDiscoveryCriteriaRegressionTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.EventHandlers; + +/// +/// Regression tests for PathConverter DiscoveryCriteria and TestRunCriteria handling. +/// +[TestClass] +[TestCategory("Windows")] +public class PathConverterDiscoveryCriteriaRegressionTests +{ + private readonly PathConverter _pathConverter; + + public PathConverterDiscoveryCriteriaRegressionTests() + { + _pathConverter = new PathConverter( + @"C:\Remote\TestDir", + @"C:\Local\DeployDir", + new FileHelper()); + } + + // Regression test for #3367 — PathConverter does not convert uris + [TestMethod] + public void UpdateDiscoveryCriteria_ShouldUpdateSourcePaths() + { + var criteria = new DiscoveryCriteria( + new[] { @"C:\Remote\TestDir\test.dll" }, + frequencyOfDiscoveredTestsEvent: 100, + testSettings: null) + { + Package = @"C:\Remote\TestDir\app.msix" + }; + + _pathConverter.UpdateDiscoveryCriteria(criteria, PathConversionDirection.Receive); + + Assert.IsTrue(criteria.Package!.StartsWith(@"C:\Local\DeployDir\", StringComparison.Ordinal)); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestRunCriteriaWithSources_ShouldUpdatePackage() + { + var sourceMap = new Dictionary> + { + ["adapter1"] = new[] { @"C:\Remote\TestDir\test1.dll" } + }; + + var criteria = new CommunicationUtilities.ObjectModel.TestRunCriteriaWithSources( + sourceMap, + @"C:\Remote\TestDir\app.msix", + runSettings: null!, + testExecutionContext: null!); + + var result = _pathConverter.UpdateTestRunCriteriaWithSources(criteria, PathConversionDirection.Receive); + + Assert.IsTrue(result.Package!.StartsWith(@"C:\Local\DeployDir\", StringComparison.Ordinal)); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestRunCriteriaWithTests_ShouldUpdateTestCasePaths() + { + var tests = new List + { + new("Test1", new Uri("executor://test"), @"C:\Remote\TestDir\test.dll") + }; + + var criteria = new CommunicationUtilities.ObjectModel.TestRunCriteriaWithTests( + tests, + package: @"C:\Remote\TestDir\app.msix", + runSettings: null!, + testExecutionContext: null!); + + var result = _pathConverter.UpdateTestRunCriteriaWithTests(criteria, PathConversionDirection.Receive); + + Assert.IsTrue(result.Package!.StartsWith(@"C:\Local\DeployDir\", StringComparison.Ordinal)); + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterRegressionTests.cs new file mode 100644 index 0000000000..3347f20b4a --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/PathConverterRegressionTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests.EventHandlers; + +/// +/// Regression tests for PathConverter — path conversion for UWP deployment. +/// +[TestClass] +[TestCategory("Windows")] +public class PathConverterRegressionTests +{ + private readonly PathConverter _pathConverter; + + public PathConverterRegressionTests() + { + // Set up path converter: original (remote) path and deployment (local) path + _pathConverter = new PathConverter( + @"C:\Remote\TestProject", + @"C:\Local\Deployed", + new FileHelper()); + } + + // Regression test for #3367 — PathConverter does not convert uris + [TestMethod] + public void UpdatePath_Receive_ShouldReplaceOriginalWithDeployment() + { + string path = @"C:\Remote\TestProject\bin\test.dll"; + string result = _pathConverter.UpdatePath(path, PathConversionDirection.Receive); + + Assert.IsTrue(result.StartsWith(@"C:\Local\Deployed\", StringComparison.Ordinal), + $"Expected deployment path but got: {result}"); + } + + // Regression test for #3367 + [TestMethod] + public void UpdatePath_Send_ShouldReplaceDeploymentWithOriginal() + { + string path = @"C:\Local\Deployed\bin\test.dll"; + string result = _pathConverter.UpdatePath(path, PathConversionDirection.Send); + + Assert.IsTrue(result.StartsWith(@"C:\Remote\TestProject\", StringComparison.Ordinal), + $"Expected original path but got: {result}"); + } + + // Regression test for #3367 + [TestMethod] + public void UpdatePath_NullInput_ShouldReturnNull() + { + string? result = _pathConverter.UpdatePath(null, PathConversionDirection.Receive); + Assert.IsNull(result); + } + + // Regression test for #3367 + [TestMethod] + public void UpdatePath_PathWithoutMatchingPrefix_ShouldReturnUnchanged() + { + string path = @"D:\Other\Path\test.dll"; + string result = _pathConverter.UpdatePath(path, PathConversionDirection.Receive); + + Assert.AreEqual(path, result); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestCase_ShouldUpdateSourceAndCodeFilePath() + { + var testCase = new TestCase( + "TestNamespace.TestClass.TestMethod", + new Uri("executor://testexecutor"), + @"C:\Remote\TestProject\bin\test.dll") + { + CodeFilePath = @"C:\Remote\TestProject\src\TestClass.cs" + }; + + _pathConverter.UpdateTestCase(testCase, PathConversionDirection.Receive); + + Assert.IsTrue(testCase.Source.StartsWith(@"C:\Local\Deployed\", StringComparison.Ordinal)); + Assert.IsTrue(testCase.CodeFilePath!.StartsWith(@"C:\Local\Deployed\", StringComparison.Ordinal)); + } + + // Regression test for #3367 + [TestMethod] + public void UpdateTestCases_ShouldUpdateAllTestCases() + { + var testCases = new List + { + new("Test1", new Uri("executor://test"), @"C:\Remote\TestProject\test1.dll"), + new("Test2", new Uri("executor://test"), @"C:\Remote\TestProject\test2.dll"), + }; + + _pathConverter.UpdateTestCases(testCases, PathConversionDirection.Receive); + + foreach (var tc in testCases) + { + Assert.IsTrue(tc.Source.StartsWith(@"C:\Local\Deployed\", StringComparison.Ordinal)); + } + } + + // Regression test for #3367 + [TestMethod] + public void UpdatePaths_ShouldUpdateAllPaths() + { + var paths = new List + { + @"C:\Remote\TestProject\a.dll", + @"C:\Remote\TestProject\b.dll", + }; + + var result = _pathConverter.UpdatePaths(paths, PathConversionDirection.Receive).ToList(); + + Assert.HasCount(2, result); foreach (var p in result) + { + Assert.IsTrue(p.StartsWith(@"C:\Local\Deployed\", StringComparison.Ordinal)); + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs index f57cf09db5..f5978779e3 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs @@ -38,6 +38,8 @@ public class TestRequestHandlerTests private readonly TestHostConnectionInfo _testHostConnectionInfo; private readonly JobQueue _jobQueue; + public TestContext TestContext { get; set; } + public TestRequestHandlerTests() { _mockCommunicationClient = new Mock(); @@ -132,7 +134,7 @@ public void ProcessRequestsShouldProcessMessagesUntilSessionCompleted() var task = ProcessRequestsAsync(_mockTestHostManagerFactory.Object); SendSessionEnd(); - Assert.IsTrue(task.Wait(2000)); + Assert.IsTrue(task.Wait(2000, TestContext.CancellationToken)); } #region Version Check Protocol @@ -489,6 +491,7 @@ private void SendSessionEnd() SendMessageOnChannel(new Message { MessageType = MessageType.SessionEnd, Payload = string.Empty }); } +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - helper methods not in test context private Task ProcessRequestsAsync() { return Task.Run(() => _requestHandler.ProcessRequests(new Mock().Object)); @@ -498,6 +501,7 @@ private Task ProcessRequestsAsync(ITestHostManagerFactory testHostManagerFactory { return Task.Run(() => _requestHandler.ProcessRequests(testHostManagerFactory)); } +#pragma warning restore MSTEST0049 private string Serialize(Message message) { @@ -534,11 +538,11 @@ public TestableTestRequestHandler( private static void OnLaunchAdapterProcessWithDebuggerAttachedAckReceived(Message message) { - Assert.AreEqual(message.MessageType, MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback); + Assert.AreEqual(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, message.MessageType); } private static void OnAttachDebuggerAckRecieved(Message message) { - Assert.AreEqual(message.MessageType, MessageType.AttachDebuggerCallback); + Assert.AreEqual(MessageType.AttachDebuggerCallback, message.MessageType); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs index 4f7194fd83..b72c2abb67 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/BaseRunTestsTests.cs @@ -372,7 +372,7 @@ public void RunTestsShouldNotAddExecutorUriToExecutorUriListIfNoTestsAreRun() _runTestsInstance.RunTests(); - Assert.AreEqual(0, _runTestsInstance.GetExecutorUrisThatRanTests.Count); + Assert.IsEmpty(_runTestsInstance.GetExecutorUrisThatRanTests); } [TestMethod] @@ -546,7 +546,7 @@ public void RunTestsShouldUpdateTestResultsTestCaseSourceWithPackageIfTestSource // verify TC.Source is updated with package foreach (var tr in _receivedRunStatusArgs.NewTestResults) { - Assert.AreEqual(tr.TestCase.Source, package); + Assert.AreEqual(package, tr.TestCase.Source); } } @@ -566,7 +566,7 @@ public void RunTestsShouldUpdateActiveTestCasesSourceWithPackageIfTestSourceIsPa foreach (var tc in _receivedRunStatusArgs.ActiveTests) { - Assert.AreEqual(tc.Source, package); + Assert.AreEqual(package, tc.Source); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs index bea628a470..8fa8bb9a94 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/ExecutionManagerTests.cs @@ -22,6 +22,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using FluentAssertions; using static TestPlatform.CrossPlatEngine.UnitTests.Execution.RunTestsWithSourcesTests; @@ -87,7 +88,7 @@ public void InitializeShouldLoadAndInitializeAllExtensions() Assert.IsNotNull(TestPluginCache.Instance.TestExtensions); // Executors - Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestExecutors!.Count > 0); + Assert.IsNotEmpty(TestPluginCache.Instance.TestExtensions.TestExecutors!); var allExecutors = TestExecutorExtensionManager.Create().TestExtensions; foreach (var executor in allExecutors) @@ -96,7 +97,7 @@ public void InitializeShouldLoadAndInitializeAllExtensions() } // Settings Providers - Assert.IsTrue(TestPluginCache.Instance.TestExtensions.TestSettingsProviders!.Count > 0); + Assert.IsNotEmpty(TestPluginCache.Instance.TestExtensions.TestSettingsProviders!); var settingsProviders = SettingsProviderExtensionManager.Create().SettingsProvidersMap.Values; foreach (var provider in settingsProviders) diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs index ba5b3dd728..89a81acd47 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Execution/RunTestsWithSourcesTests.cs @@ -382,6 +382,7 @@ public void CallInvokeExecutor(LazyExtension Microsoft.TestPlatform.CrossPlatEngine.UnitTests - net6.0;net48 - Exe + net9.0;net48 + Exe @@ -18,12 +18,4 @@ - - - - - - - - diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/PostProcessing/ArtifactProcessingTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/PostProcessing/ArtifactProcessingTests.cs index 909ac63b15..b594dd6719 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/PostProcessing/ArtifactProcessingTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/PostProcessing/ArtifactProcessingTests.cs @@ -291,7 +291,7 @@ public async Task PostProcessArtifactsAsync_DeserializationException_ShouldStopP _dataSerializer.Setup(x => x.DeserializeMessage(It.IsAny())).Returns((string rawMessage) => throw new Exception("Malformed json")); // act - await Assert.ThrowsExceptionAsync(() => _artifactProcessingManager.PostProcessArtifactsAsync()); + await Assert.ThrowsExactlyAsync(() => _artifactProcessingManager.PostProcessArtifactsAsync()); // assert _fileHelperMock.Verify(x => x.DeleteDirectory(It.IsAny(), It.IsAny()), Times.Once); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Program.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Program.cs deleted file mode 100644 index 79c3743c99..0000000000 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs index 420f2ab44c..ecf3e6f3f3 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestEngineTests.cs @@ -1075,7 +1075,7 @@ public void CreatedNonParallelExecutionManagerShouldBeInitialzedWithCorrectTestS var nonParallelExecutionManager = _testEngine.CreateNonParallelExecutionManager(_mockRequestData.Object, testRunCriteria, true, runtimeProviderInfo); Assert.IsInstanceOfType(nonParallelExecutionManager, typeof(ProxyExecutionManagerWithDataCollection)); - Assert.IsTrue(((ProxyExecutionManagerWithDataCollection)nonParallelExecutionManager).ProxyDataCollectionManager.Sources.Contains("1.dll")); - Assert.IsTrue(((ProxyExecutionManagerWithDataCollection)nonParallelExecutionManager).ProxyDataCollectionManager.Sources.Contains("2.dll")); + Assert.Contains("1.dll", ((ProxyExecutionManagerWithDataCollection)nonParallelExecutionManager).ProxyDataCollectionManager.Sources); + Assert.Contains("2.dll", ((ProxyExecutionManagerWithDataCollection)nonParallelExecutionManager).ProxyDataCollectionManager.Sources); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs index c4c8293cd2..dda80177ac 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestExtensionManagerTests.cs @@ -47,6 +47,6 @@ public void ClearExtensionsShouldClearExtensionsInCache() _testExtensionManager.ClearExtensions(); - Assert.AreEqual(0, TestPluginCache.Instance.GetExtensionPaths(string.Empty).Count); + Assert.IsEmpty(TestPluginCache.Instance.GetExtensionPaths(string.Empty)); } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs index 7510928451..128616b323 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs @@ -257,7 +257,7 @@ public void HandleTestRunStatsChangeShouldNotInvokeTestRunChangedHandlerOfLogger public void AddLoggerShouldNotThrowExceptionIfUriIsNull() { var testLoggerManager = new DummyTestLoggerManager(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => testLoggerManager.InitializeLoggerByUri(null!, null)); } @@ -320,7 +320,7 @@ public void AddLoggerShouldThrowObjectDisposedExceptionAfterDisposedIsCalled() var testLoggerManager = new DummyTestLoggerManager(); testLoggerManager.Dispose(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => testLoggerManager.InitializeLoggerByUri(new Uri("some://uri"), null)); } @@ -330,7 +330,7 @@ public void EnableLoggingShouldThrowObjectDisposedExceptionAfterDisposedIsCalled { var testLoggerManager = new DummyTestLoggerManager(); testLoggerManager.Dispose(); - Assert.ThrowsException( + Assert.ThrowsExactly( () => testLoggerManager.EnableLogging()); } @@ -706,7 +706,7 @@ public void InitializeShouldNotInitializeLoggersWhenOnlyAssemblyNameIsPresent() var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); Assert.AreEqual(0, ValidLoggerWithParameters.Counter); } @@ -733,7 +733,7 @@ public void InitializeShouldNotInitializeLoggersFromAssemblyNameWhenInterfaceDoe var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); Assert.AreEqual(0, InvalidLogger.Counter); } @@ -758,7 +758,7 @@ public void InitializeShouldNotInitializeLoggersWhenAssemblyNameInvalid() "; var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); Assert.AreEqual(0, ValidLoggerWithParameters.Counter); } @@ -785,7 +785,7 @@ public void InitializeShouldNotInitializeLoggersWhenCodeBaseInvalid() var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); Assert.AreEqual(0, ValidLoggerWithParameters.Counter); } @@ -876,7 +876,7 @@ public void InitializeShouldNotConsiderLoggerAsInitializedWhenInitializationErro var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); } [TestMethod] @@ -902,7 +902,7 @@ public void InitializeShouldThrowWhenLoggerManagerAlreadyDisposed() var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); testLoggerManager.Dispose(); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); } [TestMethod] @@ -1033,9 +1033,9 @@ public void InitializeShouldPassConfigurationElementAsParameters() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); @@ -1073,9 +1073,9 @@ public void InitializeShouldSkipEmptyConfigurationValueInParameters() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(3, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.IsFalse(ValidLoggerWithParameters.Parameters.TryGetValue("Key1", out var key1Value)); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(3, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.IsFalse(ValidLoggerWithParameters.Parameters!.TryGetValue("Key1", out var key1Value)); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); @@ -1114,9 +1114,9 @@ public void InitializeShouldUseLastValueInParametersForDuplicateConfigurationVal testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value3", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value3", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); @@ -1185,9 +1185,9 @@ public void InitializeShouldInitializeFromAssemblyNameIfAllAttributesPresent() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1223,9 +1223,9 @@ public void InitializeShouldInitializeFromUriIfUriAndNamePresent() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1261,9 +1261,9 @@ public void InitializeShouldInitializeFromUriIfUnableToFromAssemblyName() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1299,9 +1299,9 @@ public void InitializeShouldInitializeFromNameIfUnableToFromUri() testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1343,10 +1343,10 @@ public void InitializeShouldInitializeLoggersWithTestRunDirectoryIfPresentInRunS testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual("DummyTestResultsFolder", ValidLoggerWithParameters.Parameters["testRunDirectory"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual("DummyTestResultsFolder", ValidLoggerWithParameters.Parameters!["testRunDirectory"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1387,10 +1387,10 @@ public void InitializeShouldInitializeLoggersWithDefaultTestRunDirectoryIfNotPre testLoggerManager.Initialize(settingsXml); Assert.AreEqual(1, ValidLoggerWithParameters.Counter); - Assert.AreEqual(4, ValidLoggerWithParameters.Parameters!.Count); // Two additional because of testRunDirectory and targetFramework - Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters["Key1"]); - Assert.AreEqual(Constants.DefaultResultsDirectory, ValidLoggerWithParameters.Parameters["testRunDirectory"]); - Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters["Key2"]); + Assert.HasCount(4, ValidLoggerWithParameters.Parameters!); // Two additional because of testRunDirectory and targetFramework + Assert.AreEqual("Value1", ValidLoggerWithParameters.Parameters!["Key1"]); + Assert.AreEqual(Constants.DefaultResultsDirectory, ValidLoggerWithParameters.Parameters!["testRunDirectory"]); + Assert.AreEqual("Value2", ValidLoggerWithParameters.Parameters!["Key2"]); mockMetricsCollection.Verify( rd => rd.Add(TelemetryDataConstants.LoggerUsed, "TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLogger2,TestPlatform.CrossPlatEngine.UnitTests.TestLoggerManagerTests+ValidLoggerWithParameters")); } @@ -1424,7 +1424,7 @@ public void InitializeShouldNotInitializeIfUnableToFromName() var testLoggerManager = new DummyTestLoggerManager(mockRequestData.Object); - Assert.ThrowsException(() => testLoggerManager.Initialize(settingsXml)); + Assert.ThrowsExactly(() => testLoggerManager.Initialize(settingsXml)); Assert.AreEqual(0, ValidLoggerWithParameters.Counter); } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Utilities/TestSourcesUtilityTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Utilities/TestSourcesUtilityTests.cs index ad0932b0f8..7af513de24 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Utilities/TestSourcesUtilityTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Utilities/TestSourcesUtilityTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -29,9 +29,9 @@ public void GetSourcesShouldAggregateSourcesIfMultiplePresentInAdapterSourceMap( var sources = TestSourcesUtility.GetSources(adapterSourceMap)!; Assert.AreEqual(5, sources.Count()); - Assert.IsTrue(sources.Contains("source1.dll")); - Assert.IsTrue(sources.Contains("source2.dll")); - Assert.IsTrue(sources.Contains("source3.dll")); + Assert.Contains("source1.dll", sources); + Assert.Contains("source2.dll", sources); + Assert.Contains("source3.dll", sources); } [TestMethod] @@ -44,8 +44,8 @@ public void GetSourcesShouldGetDistinctSourcesFromTestCases() var sources = TestSourcesUtility.GetSources(tests); Assert.AreEqual(2, sources.Count()); - Assert.IsTrue(sources.Contains("source1.dll")); - Assert.IsTrue(sources.Contains("source2.dll")); + Assert.Contains("source1.dll", sources); + Assert.Contains("source2.dll", sources); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs index e2d3e41e48..b48dd14294 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Xml; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -40,6 +41,8 @@ public class BlameCollectorTests private readonly XmlElement? _configurationElement; private readonly string _filepath; + public TestContext TestContext { get; set; } = null!; + /// /// Initializes a new instance of the class. /// @@ -78,7 +81,7 @@ public BlameCollectorTests() [TestMethod] public void InitializeShouldThrowExceptionIfDataCollectionLoggerIsNull() { - Assert.ThrowsException(() => _blameDataCollector.Initialize( + Assert.ThrowsExactly(() => _blameDataCollector.Initialize( _configurationElement, _mockDataColectionEvents.Object, _mockDataCollectionSink.Object, @@ -185,7 +188,7 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() _mockLogger.Object, _context); - hangBasedDumpcollected.Wait(1000); + hangBasedDumpcollected.Wait(1000, TestContext.CancellationToken); _mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, It.IsAny()), Times.Once); _mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); @@ -219,7 +222,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet _mockLogger.Object, _context); - hangBasedDumpcollected.Wait(1000); + hangBasedDumpcollected.Wait(1000, TestContext.CancellationToken); _mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, It.IsAny()), Times.Once); } @@ -254,7 +257,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt _mockLogger.Object, _context); - hangBasedDumpcollected.Wait(1000); + hangBasedDumpcollected.Wait(1000, TestContext.CancellationToken); _mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); _mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, It.IsAny()), Times.Once); _mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); @@ -352,8 +355,7 @@ public void EventHandlersShouldGenerateCorrectTestSequenceAndTestObjectDictionar y[blameTestObject1.Id].FullyQualifiedName == "TestProject.UnitTest.TestMethod1" && y[blameTestObject2.Id].FullyQualifiedName == "TestProject.UnitTest.TestMethod2" && y[blameTestObject1.Id].Source == "abc.dll" && y[blameTestObject2.Id].Source == "abc.dll" && y[blameTestObject1.Id].DisplayName == "TestMethod1" && y[blameTestObject2.Id].DisplayName == "TestMethod2"), - It.IsAny()), - Times.Once); + It.IsAny()), Times.Once); } /// @@ -741,6 +743,56 @@ private static XmlElement GetDumpConfigurationElement( return outernode; } + /// + /// Concurrent test case start and end events should not corrupt internal state + /// + [TestMethod] + public void ConcurrentTestCaseStartAndEndEventsShouldNotCorruptState() + { + // Initializing Blame Data Collector + _blameDataCollector.Initialize( + _configurationElement, + _mockDataColectionEvents.Object, + _mockDataCollectionSink.Object, + _mockLogger.Object, + _context); + + const int threadCount = 10; + var barrier = new Barrier(threadCount); + var tasks = new List(); + var allTestCases = new List(); + + for (int t = 0; t < threadCount; t++) + { + var task = Task.Run(() => + { + barrier.SignalAndWait(TestContext.CancellationToken); + for (int i = 0; i < 50; i++) + { + var testcase = new TestCase($"TestProject.UnitTest.TestMethod{Guid.NewGuid()}", new Uri("test:/abc"), "abc.dll"); + lock (allTestCases) + { + allTestCases.Add(testcase); + } + + _mockDataColectionEvents.Raise(x => x.TestCaseStart += null, new TestCaseStartEventArgs(testcase)); + _mockDataColectionEvents.Raise(x => x.TestCaseEnd += null, new TestCaseEndEventArgs(testcase, TestOutcome.Passed)); + } + }, TestContext.CancellationToken); + tasks.Add(task); + } + + // This must not throw due to concurrent collection modifications + Task.WaitAll(tasks.ToArray(), TestContext.CancellationToken); + + // Raise session end - all tests completed so no sequence file should be written + _mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(_dataCollectionContext)); + _mockBlameReaderWriter.Verify( + x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny()), + Times.Never, + "No sequence file should be written when all tests complete"); + } + /// /// The testable blame collector. /// diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameLoggerTests.cs index 347173a11c..ef68f6aeb3 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameLoggerTests.cs @@ -41,7 +41,7 @@ public BlameLoggerTests() [TestMethod] public void InitializeShouldThrowExceptionIfEventsIsNull() { - Assert.ThrowsException(() => _blameLogger.Initialize(null!, string.Empty)); + Assert.ThrowsExactly(() => _blameLogger.Initialize(null!, string.Empty)); } /// diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/InactivityTimerTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/InactivityTimerTests.cs index 2014f39a7d..5b863ec00a 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/InactivityTimerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/InactivityTimerTests.cs @@ -14,12 +14,14 @@ public class InactivityTimerTests private int _callBackCount; private readonly ManualResetEventSlim _timerEvent = new(); + public TestContext TestContext { get; set; } = null!; + [TestMethod] public void InactivityTimerShouldResetAndCallbackWhenResetIsCalled() { var timer = new InactivityTimer(TimerCallback); timer.ResetTimer(TimeSpan.FromMilliseconds(1)); - _timerEvent.Wait(1000); + _timerEvent.Wait(1000, TestContext.CancellationToken); Assert.AreEqual(1, _callBackCount, "Should have fired once."); } diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj index 836dd60c67..81efd08aad 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj @@ -19,8 +19,8 @@ Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests @@ -31,11 +31,4 @@ - - - - - - - diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs index 03e9c234ac..b56886b894 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs @@ -62,7 +62,7 @@ public void GetDumpFileWillThrowExceptionIfNoDumpfile() processDumpUtility.StartTriggerBasedProcessDump(processId, testResultsDirectory, false, ".NETCoreApp,Version=v5.0", false, _ => { }); - var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFiles(true, false)); + var ex = Assert.ThrowsExactly(() => processDumpUtility.GetDumpFiles(true, false)); Assert.AreEqual(ex.Message, Resources.Resources.DumpFileNotGeneratedErrorMessage); } } diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Program.cs deleted file mode 100644 index 6333b3f0bc..0000000000 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/XmlReaderWriterTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/XmlReaderWriterTests.cs index 59fe94e370..728c15c3d3 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/XmlReaderWriterTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/XmlReaderWriterTests.cs @@ -56,7 +56,7 @@ public void WriteTestSequenceShouldThrowExceptionIfFilePathIsNull() _testCaseList.Add(_blameTestObject.Id); _testObjectDictionary.Add(_blameTestObject.Id, _blameTestObject); - Assert.ThrowsException(() => _xmlReaderWriter.WriteTestSequence(_testCaseList, _testObjectDictionary, null!)); + Assert.ThrowsExactly(() => _xmlReaderWriter.WriteTestSequence(_testCaseList, _testObjectDictionary, null!)); } /// @@ -68,7 +68,7 @@ public void WriteTestSequenceShouldThrowExceptionIfFilePathIsEmpty() _testCaseList.Add(_blameTestObject.Id); _testObjectDictionary.Add(_blameTestObject.Id, _blameTestObject); - Assert.ThrowsException(() => _xmlReaderWriter.WriteTestSequence(_testCaseList, _testObjectDictionary, string.Empty)); + Assert.ThrowsExactly(() => _xmlReaderWriter.WriteTestSequence(_testCaseList, _testObjectDictionary, string.Empty)); } /// @@ -77,7 +77,7 @@ public void WriteTestSequenceShouldThrowExceptionIfFilePathIsEmpty() [TestMethod] public void ReadTestSequenceShouldThrowExceptionIfFilePathIsNull() { - Assert.ThrowsException(() => _xmlReaderWriter.ReadTestSequence(null!)); + Assert.ThrowsExactly(() => _xmlReaderWriter.ReadTestSequence(null!)); } /// @@ -88,27 +88,7 @@ public void ReadTestSequenceShouldThrowExceptionIfFileNotFound() { _mockFileHelper.Setup(m => m.Exists(It.IsAny())).Returns(false); - Assert.ThrowsException(() => _xmlReaderWriter.ReadTestSequence(string.Empty)); - } - - /// - /// The read test sequence should read file stream. - /// - [TestMethod] - public void ReadTestSequenceShouldReadFileStream() - { - // Setup - _mockFileHelper.Setup(m => m.Exists(It.IsAny())).Returns(true); - _mockFileHelper.Setup(m => m.GetStream("path.xml", FileMode.Open, FileAccess.ReadWrite)).Returns(_mockStream.Object); - - // Call to Read Test Sequence - _xmlReaderWriter.ReadTestSequence("path.xml"); - - // Verify Call to fileHelper - _mockFileHelper.Verify(x => x.GetStream("path.xml", FileMode.Open, FileAccess.ReadWrite)); - - // Verify Call to stream read - _mockStream.Verify(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())); + Assert.ThrowsExactly(() => _xmlReaderWriter.ReadTestSequence(string.Empty)); } /// @@ -131,7 +111,7 @@ public void WriteTestSequenceShouldWriteFileStream() // Assert it has some data var data = Encoding.UTF8.GetString(stream.ToArray()); - Assert.IsTrue(data.Length > 0, "Stream should have some data."); + Assert.IsGreaterThan(0, data.Length, "Stream should have some data."); } /// diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs index 821093aa91..4c3e934298 100644 --- a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/HtmlLoggerTests.cs @@ -55,7 +55,7 @@ public HtmlLoggerTests() [TestMethod] public void InitializeShouldThrowExceptionIfEventsIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _htmlLogger.Initialize(null!, _parameters)); } @@ -67,7 +67,7 @@ public void InitializeShouldInitializeAllProperties() _htmlLogger.Initialize(events.Object, testResultDir); - Assert.AreEqual(_htmlLogger.TestResultsDirPath, testResultDir); + Assert.AreEqual(testResultDir, _htmlLogger.TestResultsDirPath); Assert.IsNotNull(_htmlLogger.TestRunDetails); Assert.IsNotNull(_htmlLogger.Results); } @@ -75,26 +75,23 @@ public void InitializeShouldInitializeAllProperties() [TestMethod] public void InitializeShouldThrowExceptionIfTestRunDirectoryIsEmptyOrNull() { - Assert.ThrowsException( - () => - { - _events = new Mock(); - _parameters[DefaultLoggerParameterNames.TestRunDirectory] = null!; - _htmlLogger.Initialize(_events.Object, _parameters); - }); + _events = new Mock(); + _parameters[DefaultLoggerParameterNames.TestRunDirectory] = null!; + Assert.ThrowsExactly( + () => _htmlLogger.Initialize(_events.Object, _parameters)); } [TestMethod] public void InitializeShouldThrowExceptionIfParametersAreEmpty() { var events = new Mock(); - Assert.ThrowsException(() => _htmlLogger.Initialize(events.Object, new Dictionary())); + Assert.ThrowsExactly(() => _htmlLogger.Initialize(events.Object, new Dictionary())); } [TestMethod] public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() { - Assert.ThrowsException(() => _htmlLogger.TestMessageHandler(new object(), default!)); + Assert.ThrowsExactly(() => _htmlLogger.TestMessageHandler(new object(), default!)); } #endregion @@ -123,7 +120,7 @@ public void TestCompleteHandlerShouldThrowExceptionIfParametersAreNull() { Dictionary? parameters = null; var events = new Mock(); - Assert.ThrowsException(() => _htmlLogger.Initialize(events.Object, parameters!)); + Assert.ThrowsExactly(() => _htmlLogger.Initialize(events.Object, parameters!)); } [TestMethod] @@ -138,7 +135,7 @@ public void TestMessageHandlerShouldAddMessageInListIfItIsWarningAndError() _htmlLogger.TestMessageHandler(new object(), testRunMessageEventArgs2); var runLevelMessageErrorAndWarning = _htmlLogger.TestRunDetails!.RunLevelMessageErrorAndWarning!; - Assert.AreEqual(2, runLevelMessageErrorAndWarning.Count); + Assert.HasCount(2, runLevelMessageErrorAndWarning); Assert.AreEqual(message, runLevelMessageErrorAndWarning.First()); } @@ -299,7 +296,7 @@ public void TestResultHandlerShouldCreateOneTestEntryForEachTestCase() _htmlLogger.TestResultHandler(new object(), resultEventArg1.Object); _htmlLogger.TestResultHandler(new object(), resultEventArg2.Object); - Assert.AreEqual(2, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!.Count, "TestResultHandler is not creating test result entry for each test case"); + Assert.HasCount(2, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!, "TestResultHandler is not creating test result entry for each test case"); } [TestMethod] @@ -317,9 +314,9 @@ public void TestResultHandlerShouldCreateOneTestResultCollectionForOneSource() _htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); _htmlLogger.TestResultHandler(new object(), new Mock(result2).Object); - Assert.AreEqual(2, _htmlLogger.TestRunDetails!.ResultCollectionList!.Count); - Assert.AreEqual("abc.dll", _htmlLogger.TestRunDetails.ResultCollectionList.First().Source); - Assert.AreEqual("def.dll", _htmlLogger.TestRunDetails.ResultCollectionList.Last().Source); + Assert.HasCount(2, _htmlLogger.TestRunDetails!.ResultCollectionList!); + Assert.AreEqual("abc.dll", _htmlLogger.TestRunDetails!.ResultCollectionList!.First().Source); + Assert.AreEqual("def.dll", _htmlLogger.TestRunDetails!.ResultCollectionList!.Last().Source); } [TestMethod] @@ -330,7 +327,7 @@ public void TestResultHandlerShouldAddFailedResultToFailedResultListInTestResult _htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); - Assert.AreEqual(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().FailedResultList!.Count); + Assert.HasCount(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().FailedResultList!); } [TestMethod] @@ -348,7 +345,7 @@ public void TestResultHandlerShouldAddHierarchicalResultsForOrderedTest() _htmlLogger.TestResultHandler(new object(), new Mock(result1).Object); - Assert.AreEqual(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!.Count, "test handler is adding parent result correctly"); + Assert.HasCount(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!, "test handler is adding parent result correctly"); Assert.IsNull(_htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!.First().InnerTestResults, "test handler is adding child result correctly"); var result2 = new ObjectModel.TestResult(testCase2); @@ -362,8 +359,8 @@ public void TestResultHandlerShouldAddHierarchicalResultsForOrderedTest() _htmlLogger.TestResultHandler(new object(), new Mock(result2).Object); _htmlLogger.TestResultHandler(new object(), new Mock(result3).Object); - Assert.AreEqual(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!.Count, "test handler is adding parent result correctly"); - Assert.AreEqual(2, _htmlLogger.TestRunDetails.ResultCollectionList!.First().ResultList!.First().InnerTestResults!.Count, "test handler is adding child result correctly"); + Assert.HasCount(1, _htmlLogger.TestRunDetails!.ResultCollectionList!.First().ResultList!, "test handler is adding parent result correctly"); + Assert.HasCount(2, _htmlLogger.TestRunDetails.ResultCollectionList!.First().ResultList!.First().InnerTestResults!, "test handler is adding child result correctly"); } [TestMethod] @@ -413,7 +410,7 @@ public void TestCompleteHandlerShouldCreateCustomHtmlFileNamewithLogFileNameKey( _htmlLogger.Initialize(new Mock().Object, parameters); _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); - Assert.IsTrue(_htmlLogger.HtmlFilePath!.Contains("TestResult")); + Assert.Contains("TestResult", _htmlLogger.HtmlFilePath!); } [TestMethod] @@ -433,7 +430,7 @@ public void TestCompleteHandlerShouldCreateCustomHtmlFileNameWithLogPrefix() _htmlLogger.Initialize(new Mock().Object, parameters); _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); - Assert.IsFalse(_htmlLogger.HtmlFilePath!.Contains("__")); + Assert.DoesNotContain("__", _htmlLogger.HtmlFilePath!); } [TestMethod] @@ -453,7 +450,7 @@ public void TestCompleteHandlerShouldCreateCustomHtmlFileNameWithLogPrefixIfTarg _htmlLogger.Initialize(new Mock().Object, parameters); _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); - Assert.IsTrue(_htmlLogger.HtmlFilePath!.Contains("sample_net451")); + Assert.Contains("sample_net451", _htmlLogger.HtmlFilePath!); } [TestMethod] @@ -495,7 +492,7 @@ public void TestCompleteHandlerShouldThrowExceptionWithLogPrefixIfTargetFramewor _htmlLogger.Initialize(new Mock().Object, parameters); - Assert.ThrowsException(() => _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero))); + Assert.ThrowsExactly(() => _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero))); } [TestMethod] @@ -506,7 +503,7 @@ public void IntializeShouldThrowExceptionIfBothPrefixAndNameProvided() _parameters[HtmlLoggerConstants.LogFilePrefixKey] = "HtmlPrefix"; _parameters[DefaultLoggerParameterNames.TargetFramework] = ".NETFramework,Version=4.5.1"; - Assert.ThrowsException(() => _htmlLogger.Initialize(_events.Object, _parameters)); + Assert.ThrowsExactly(() => _htmlLogger.Initialize(_events.Object, _parameters)); } [TestMethod] @@ -574,8 +571,8 @@ public void TestCompleteHandlerShouldWriteToXmlSerializerCorrectly() _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); _mockXmlSerializer.Verify(x => x.WriteObject(It.IsAny(), It.IsAny()), Times.Once); - Assert.IsTrue(_htmlLogger.XmlFilePath!.Contains(".xml")); - Assert.IsTrue(_htmlLogger.HtmlFilePath!.Contains(".html")); + Assert.Contains(".xml", _htmlLogger.XmlFilePath!); + Assert.Contains(".html", _htmlLogger.HtmlFilePath!); } [TestMethod] @@ -591,6 +588,22 @@ public void TestCompleteHandlerShouldNotDivideByZeroWhenThereAre0TestResults() Assert.AreEqual(0, _htmlLogger.TestRunDetails.Summary.PassPercentage); } + [TestMethod] + public void XmlFilePathShouldContainFractionalSecondsForCrossProcessUniqueness() + { + _htmlLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); + + Assert.IsNotNull(_htmlLogger.XmlFilePath); + var fileName = Path.GetFileNameWithoutExtension(_htmlLogger.XmlFilePath); + + // The filename should contain a fractional-seconds timestamp: yyyyMMdd_HHmmss.fffffff + // e.g., TestResult_user_MACHINE_20260324_065512.1234567 + Assert.MatchesRegex( + @"\d{8}_\d{6}\.\d{7}$", + fileName, + $"Filename should end with yyyyMMdd_HHmmss.fffffff pattern: {fileName}"); + } + private static TestCase CreateTestCase(string testCaseName) { return new TestCase(testCaseName, new Uri("some://uri"), "DummySourceFileName"); diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj index a109012ab5..7708ce4b17 100644 --- a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests.csproj @@ -6,17 +6,10 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe - - - - - - - diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs deleted file mode 100644 index 0df49b12a6..0000000000 --- a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..de6fab8a6e --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/RegressionBugFixTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Xml; + +using Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using HtmlLoggerImpl = Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.HtmlLogger; +using IHtmlTransformer = Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.IHtmlTransformer; + +namespace Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests; + +/// +/// Regression test for GH-2483: +/// HtmlLogger.Initialize must create the test results directory if it doesn't exist. +/// Previously, Initialize would not create the directory, causing later file +/// operations to fail with DirectoryNotFoundException. +/// +[TestClass] +public class RegressionBugFixTests +{ + [TestMethod] + public void Initialize_NonExistentDirectory_MustCreateIt() + { + // GH-2483: The fix added Directory.CreateDirectory(testResultsDirPath) in Initialize. + // If the fix were reverted, the directory would not exist after Initialize. + var mockFileHelper = new Mock(); + var mockHtmlTransformer = new Mock(); + var mockXmlSerializer = new Mock(); + var htmlLogger = new HtmlLoggerImpl(mockFileHelper.Object, mockHtmlTransformer.Object, mockXmlSerializer.Object); + + var events = new Mock(); + var nonExistentDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + + try + { + // Act + htmlLogger.Initialize(events.Object, nonExistentDir); + + // Assert: directory must have been created + Assert.IsTrue(Directory.Exists(nonExistentDir), + "GH-2483: Initialize must call Directory.CreateDirectory for the test results directory."); + Assert.AreEqual(nonExistentDir, htmlLogger.TestResultsDirPath); + } + finally + { + if (Directory.Exists(nonExistentDir)) + { + Directory.Delete(nonExistentDir, recursive: true); + } + } + } + + [TestMethod] + public void Initialize_ExistingDirectory_MustNotThrow() + { + // Complementary test: Initialize with an existing directory must succeed. + var mockFileHelper = new Mock(); + var mockHtmlTransformer = new Mock(); + var mockXmlSerializer = new Mock(); + var htmlLogger = new HtmlLoggerImpl(mockFileHelper.Object, mockHtmlTransformer.Object, mockXmlSerializer.Object); + + var events = new Mock(); + var existingDir = Path.GetTempPath(); + + htmlLogger.Initialize(events.Object, existingDir); + + Assert.AreEqual(existingDir, htmlLogger.TestResultsDirPath); + } + + /// + /// Regression test for GH-3136 / PR #4576: + /// HtmlTransformer.Transform must not throw XmlException when the XML input contains + /// invalid character references like &#xFFFF;. Before the fix, the default + /// XmlReaderSettings had CheckCharacters=true, causing XmlException on such input. + /// The fix changed Transform to use XmlReader.Create with CheckCharacters=false. + /// + [TestMethod] + public void Transform_XmlWithInvalidCharacterReferences_MustNotThrowXmlException() + { + // GH-3136: DCS can serialize characters into character references that are not + // strictly valid XML (e.g. ￿). The fix ensures the XmlReader tolerates them. + var transformer = new HtmlTransformer(); + var xmlFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".xml"); + var htmlFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".html"); + + try + { + // Write XML containing an invalid character reference that would cause + // XmlException with CheckCharacters=true (the pre-fix default). + File.WriteAllText(xmlFile, + "\n" + + "test￿value"); + + // Act: must not throw XmlException. Before the fix, this line threw: + // "System.Xml.XmlException: '', hexadecimal value 0xFFFF, is an invalid character." + transformer.Transform(xmlFile, htmlFile); + + // Assert: output file must exist and be non-empty. + Assert.IsTrue(File.Exists(htmlFile), + "GH-3136: Transform must produce an output HTML file."); + Assert.IsGreaterThan(0, new FileInfo(htmlFile).Length, + "GH-3136: Output HTML file must be non-empty."); + } + finally + { + if (File.Exists(xmlFile)) + { + File.Delete(xmlFile); + } + + if (File.Exists(htmlFile)) + { + File.Delete(htmlFile); + } + } + } +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.csproj index c8b9420b40..3df4825d3e 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests.csproj @@ -6,18 +6,11 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests - - - - - - - diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Program.cs deleted file mode 100644 index f3ba5448d2..0000000000 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..47117592fe --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/RegressionBugFixTests.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; +using TrxLoggerObjectModel = Microsoft.TestPlatform.Extensions.TrxLogger.ObjectModel; + +using DefaultLoggerParameterNames = Microsoft.VisualStudio.TestPlatform.ObjectModel.DefaultLoggerParameterNames; + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests; + +/// +/// Regression tests for: +/// - GH-2319: Setting ErrorStackTrace before ErrorMessage must not crash. +/// - GH-5132: WarnOnFileOverwrite=false must suppress overwrite warning. +/// - GH-4243: TestOutcome.Error must have value 0, must serialize as "Error" (not "Min"). +/// +[TestClass] +public class RegressionBugFixTests +{ + private static readonly string DefaultTestRunDirectory = Path.GetTempPath(); + + #region GH-2319: ErrorStackTrace without ErrorMessage doesn't crash + + [TestMethod] + public void ErrorStackTrace_SetBeforeErrorMessage_MustNotThrow() + { + // GH-2319: Previously, setting ErrorStackTrace first would crash with Debug.Assert + // because _errorInfo was null. The fix uses ??= to lazily initialize. + var testResult = CreateTestResult(); + + // Act: set ErrorStackTrace FIRST, before any ErrorMessage + testResult.ErrorStackTrace = "at SomeTest.Method() in file.cs:line 42"; + + // Assert: should not throw and ErrorMessage should return empty string (not null or crash) + Assert.AreEqual("at SomeTest.Method() in file.cs:line 42", testResult.ErrorStackTrace); + Assert.AreEqual(string.Empty, testResult.ErrorMessage, + "GH-2319: ErrorMessage must return empty string when only ErrorStackTrace is set."); + } + + [TestMethod] + public void ErrorMessage_ThenErrorStackTrace_BothAccessible() + { + // Setting ErrorMessage first, then ErrorStackTrace: both must be readable. + var testResult = CreateTestResult(); + + testResult.ErrorMessage = "Assert.Fail hit"; + testResult.ErrorStackTrace = "at MyTest.Run()"; + + Assert.AreEqual("Assert.Fail hit", testResult.ErrorMessage); + Assert.AreEqual("at MyTest.Run()", testResult.ErrorStackTrace); + } + + [TestMethod] + public void ErrorMessage_NeverSet_ReturnsEmptyString() + { + // Before the fix, accessing getters on a fresh TestResult without _errorInfo + // would behave unpredictably. The fix returns string.Empty via null-coalescing. + var testResult = CreateTestResult(); + + Assert.AreEqual(string.Empty, testResult.ErrorMessage); + Assert.AreEqual(string.Empty, testResult.ErrorStackTrace); + } + + #endregion + + #region GH-5132: WarnOnFileOverwrite parameter + + [TestMethod] + public void Initialize_WarnOnFileOverwriteFalse_FieldMustBeFalse() + { + // GH-5132: When WarnOnFileOverwrite=false, the _warnOnFileOverwrite field must be false. + // If the fix were reverted (field removed), this would fail. + var logger = new TestableTrxLogger(); + var events = new Mock(); + var parameters = CreateDefaultParameters(); + parameters[TrxLoggerConstants.WarnOnFileOverwrite] = "false"; + + logger.Initialize(events.Object, parameters); + + var warnField = typeof(VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger) + .GetField("_warnOnFileOverwrite", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.IsNotNull(warnField, "_warnOnFileOverwrite field must exist."); + Assert.IsFalse((bool)warnField.GetValue(logger)!, + "GH-5132: _warnOnFileOverwrite must be false when parameter is 'false'."); + } + + [TestMethod] + public void Initialize_WarnOnFileOverwriteNotSet_DefaultsToTrue() + { + // GH-5132: When WarnOnFileOverwrite parameter is not provided, default must be true + // (preserving existing behavior for users who did not opt out). + var logger = new TestableTrxLogger(); + var events = new Mock(); + var parameters = CreateDefaultParameters(); + + logger.Initialize(events.Object, parameters); + + var warnField = typeof(VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger) + .GetField("_warnOnFileOverwrite", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.IsNotNull(warnField, "_warnOnFileOverwrite field must exist."); + Assert.IsTrue((bool)warnField.GetValue(logger)!, + "GH-5132: _warnOnFileOverwrite must default to true when parameter is not provided."); + } + + #endregion + + #region GH-4243: TestOutcome has no Min/Max aliases + + [TestMethod] + public void TestOutcome_Error_MustHaveIntValue0() + { + // GH-4243: Error must be the first enum member (value 0). + // Previously Min=Error alias made Error.ToString() return "Min". + // Box through object to avoid compile-time constant folding (MSTEST0032). + object errorEnum = TrxLoggerObjectModel.TestOutcome.Error; + Assert.AreEqual(0, (int)(TrxLoggerObjectModel.TestOutcome)errorEnum, + "GH-4243: TestOutcome.Error must have integer value 0."); + } + + [TestMethod] + public void TestOutcome_Error_MustSerializeAsError_NotMin() + { + // GH-4243: The fix removed Min=Error alias. Error.ToString() must return "Error". + // If the fix were reverted and Min=Error added back, ToString() would return "Min". + Assert.AreEqual("Error", TrxLoggerObjectModel.TestOutcome.Error.ToString(), + "GH-4243: TestOutcome.Error.ToString() must be 'Error', not 'Min'."); + } + + [TestMethod] + public void TestOutcome_NoMinOrMaxMember() + { + // GH-4243: The enum must NOT have a member named "Min" or "Max". + // Verify via reflection. + var names = Enum.GetNames(typeof(TrxLoggerObjectModel.TestOutcome)); + CollectionAssert.DoesNotContain(names, "Min", + "GH-4243: TestOutcome enum must not have a 'Min' member."); + CollectionAssert.DoesNotContain(names, "Max", + "GH-4243: TestOutcome enum must not have a 'Max' member."); + } + + #endregion + + #region Helpers + + private static TrxLoggerObjectModel.TestResult CreateTestResult() + { + return new TrxLoggerObjectModel.TestResult( + runId: Guid.NewGuid(), + testId: Guid.NewGuid(), + executionId: Guid.NewGuid(), + parentExecutionId: Guid.Empty, + resultName: "TestResult1", + computerName: Environment.MachineName, + outcome: TrxLoggerObjectModel.TestOutcome.Failed, + testType: new TrxLoggerObjectModel.TestType(Guid.NewGuid()), + testCategoryId: TrxLoggerObjectModel.TestListCategoryId.Uncategorized, + trxFileHelper: new TrxFileHelper()); + } + + private static Dictionary CreateDefaultParameters() + { + return new Dictionary + { + [DefaultLoggerParameterNames.TestRunDirectory] = DefaultTestRunDirectory, + [TrxLoggerConstants.LogFileNameKey] = "test.trx" + }; + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs index 9c1cc94e27..1847a6ae70 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerTests.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; @@ -40,6 +42,8 @@ public class TrxLoggerTests private TestableTrxLogger _testableTrxLogger; + public TestContext TestContext { get; set; } + public TrxLoggerTests() { _events = new Mock(); @@ -65,7 +69,7 @@ public void Cleanup() [TestMethod] public void InitializeShouldThrowExceptionIfEventsIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => _testableTrxLogger.Initialize(null!, _parameters)); } @@ -79,13 +83,10 @@ public void InitializeShouldNotThrowExceptionIfEventsIsNotNull() [TestMethod] public void InitializeShouldThrowExceptionIfTestRunDirectoryIsEmptyOrNull() { - Assert.ThrowsException( - () => - { - var events = new Mock(); - _parameters[DefaultLoggerParameterNames.TestRunDirectory] = null!; - _testableTrxLogger.Initialize(events.Object, _parameters); - }); + var events = new Mock(); + _parameters[DefaultLoggerParameterNames.TestRunDirectory] = null!; + Assert.ThrowsExactly( + () => _testableTrxLogger.Initialize(events.Object, _parameters)); } [TestMethod] @@ -99,13 +100,13 @@ public void InitializeShouldNotThrowExceptionIfTestRunDirectoryIsNeitherEmptyNor public void InitializeShouldThrowExceptionIfParametersAreEmpty() { var events = new Mock(); - Assert.ThrowsException(() => _testableTrxLogger.Initialize(events.Object, new Dictionary())); + Assert.ThrowsExactly(() => _testableTrxLogger.Initialize(events.Object, new Dictionary())); } [TestMethod] public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() { - Assert.ThrowsException(() => _testableTrxLogger.TestMessageHandler(new object(), default!)); + Assert.ThrowsExactly(() => _testableTrxLogger.TestMessageHandler(new object(), default!)); } [TestMethod] @@ -131,7 +132,7 @@ public void TestMessageHandlerShouldAddMessageInListIfItIsWarning() _testableTrxLogger.TestMessageHandler(new object(), trme); _testableTrxLogger.TestMessageHandler(new object(), trme); - Assert.AreEqual(2, _testableTrxLogger.GetRunLevelErrorsAndWarnings().Count); + Assert.HasCount(2, _testableTrxLogger.GetRunLevelErrorsAndWarnings()); } [TestMethod] @@ -141,7 +142,7 @@ public void TestMessageHandlerShouldAddMessageInListIfItIsError() TestRunMessageEventArgs trme = new(TestMessageLevel.Error, message); _testableTrxLogger.TestMessageHandler(new object(), trme); - Assert.AreEqual(1, _testableTrxLogger.GetRunLevelErrorsAndWarnings().Count); + Assert.HasCount(1, _testableTrxLogger.GetRunLevelErrorsAndWarnings()); } [TestMethod] @@ -533,7 +534,7 @@ public void TestRunCompleteHandlerShouldReportFailedOutcomeIfTestRunIsAborted() _testableTrxLogger.TestRunCompleteHandler(new object(), new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero)); - Assert.AreEqual(_testableTrxLogger.TestResultOutcome, TrxLoggerObjectModel.TestOutcome.Failed); + Assert.AreEqual(TrxLoggerObjectModel.TestOutcome.Failed, _testableTrxLogger.TestResultOutcome); } [TestMethod] @@ -629,6 +630,49 @@ public void DefaultTrxFileShouldCreateIfLogFileNameParameterNotPassed() Assert.IsFalse(string.IsNullOrWhiteSpace(_testableTrxLogger.TrxFile)); } + [TestMethod] + public void DefaultTrxFileNameShouldIncludeFrameworkWhenAvailable() + { + _parameters.Remove(TrxLoggerConstants.LogFileNameKey); + _parameters[DefaultLoggerParameterNames.TargetFramework] = ".NETCoreApp,Version=v10.0"; + _testableTrxLogger.Initialize(_events.Object, _parameters); + + MakeTestRunComplete(); + + var fileName = Path.GetFileName(_testableTrxLogger.TrxFile); + Assert.IsNotNull(fileName); + Assert.Contains("_net10.0", fileName, $"Expected TFM 'net10.0' in filename but got: {fileName}"); + Assert.EndsWith(".trx", fileName, $"Expected .trx extension but got: {fileName}"); + } + + [TestMethod] + public void DefaultTrxFileNameShouldWorkWithoutFramework() + { + _parameters.Remove(TrxLoggerConstants.LogFileNameKey); + _testableTrxLogger.Initialize(_events.Object, _parameters); + + MakeTestRunComplete(); + + var fileName = Path.GetFileName(_testableTrxLogger.TrxFile); + Assert.IsNotNull(fileName); + Assert.EndsWith(".trx", fileName, $"Expected .trx extension but got: {fileName}"); + } + + [TestMethod] + public void DefaultTrxFileNameShouldUseRawStringWhenFrameworkCannotBeParsed() + { + _parameters.Remove(TrxLoggerConstants.LogFileNameKey); + _parameters[DefaultLoggerParameterNames.TargetFramework] = "SomeCustomFramework"; + _testableTrxLogger.Initialize(_events.Object, _parameters); + + MakeTestRunComplete(); + + var fileName = Path.GetFileName(_testableTrxLogger.TrxFile); + Assert.IsNotNull(fileName); + Assert.Contains("_SomeCustomFramework", fileName, $"Expected raw framework string in filename but got: {fileName}"); + Assert.EndsWith(".trx", fileName, $"Expected .trx extension but got: {fileName}"); + } + [TestMethod] public void DefaultTrxFileNameVerification() { @@ -656,7 +700,7 @@ public void DefaultTrxFileShouldIterateIfLogFileNameParameterNotPassed() var files = TestMultipleTrxLoggers(); - Assert.AreEqual(MultipleLoggerInstanceCount, files.Length, "All logger instances should get different file names!"); + Assert.HasCount(MultipleLoggerInstanceCount, files, "All logger instances should get different file names!"); } [TestMethod] @@ -664,7 +708,7 @@ public void TrxFileNameShouldNotIterate() { var files = TestMultipleTrxLoggers(); - Assert.AreEqual(1, files.Length, "All logger instances should get the same file name!"); + Assert.HasCount(1, files, "All logger instances should get the same file name!"); } [TestMethod] @@ -675,7 +719,7 @@ public void TrxPrefixFileNameShouldIterate() var files = TestMultipleTrxLoggers(); - Assert.AreEqual(MultipleLoggerInstanceCount, files.Length, "All logger instances should get different file names!"); + Assert.HasCount(MultipleLoggerInstanceCount, files, "All logger instances should get different file names!"); } private string?[] TestMultipleTrxLoggers() @@ -854,7 +898,7 @@ public void IntializeShouldThrowExceptionIfBothPrefixAndNameProvided() _parameters[TrxLoggerConstants.LogFilePrefixKey] = trxPrefix; _parameters[DefaultLoggerParameterNames.TargetFramework] = ".NETFramework,Version=4.5.1"; - Assert.ThrowsException(() => _testableTrxLogger.Initialize(_events.Object, _parameters)); + Assert.ThrowsExactly(() => _testableTrxLogger.Initialize(_events.Object, _parameters)); } [TestMethod] @@ -896,7 +940,7 @@ private static void ValidateResultAttributesInTrx(string trxFileName, Guid testI private static void ValidateTimeWithinUtcLimits(DateTimeOffset dateTime) { - Assert.IsTrue(dateTime.UtcDateTime.Subtract(DateTime.UtcNow) < new TimeSpan(0, 0, 0, 60)); + Assert.IsLessThan(new TimeSpan(0, 0, 0, 60), dateTime.UtcDateTime.Subtract(DateTime.UtcNow)); } private static string? GetElementValueFromTrx(string trxFileName, string fieldName) @@ -914,6 +958,40 @@ private static void ValidateTimeWithinUtcLimits(DateTimeOffset dateTime) return null; } + [TestMethod] + public void TestResultHandlerCountersShouldBeThreadSafe() + { + const int threadCount = 10; + const int testsPerThread = 100; + var barrier = new Barrier(threadCount); + + var tasks = Enumerable.Range(0, threadCount).Select(t => Task.Run(() => + { + barrier.SignalAndWait(TestContext.CancellationToken); + for (int i = 0; i < testsPerThread; i++) + { + var testCase = CreateTestCase($"Test_{t}_{i}"); + var result = new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase) + { + Outcome = t % 2 == 0 ? TestOutcome.Passed : TestOutcome.Failed + }; + _testableTrxLogger.TestResultHandler(new object(), new Mock(result).Object); + } + }, TestContext.CancellationToken)).ToArray(); + + Task.WaitAll(tasks, TestContext.CancellationToken); + + Assert.AreEqual(threadCount * testsPerThread, _testableTrxLogger.TotalTestCount, + "Total test count should be exact under concurrent updates"); + + int expectedPassed = (threadCount / 2) * testsPerThread; + int expectedFailed = (threadCount / 2) * testsPerThread; + Assert.AreEqual(expectedPassed, _testableTrxLogger.PassedTestCount, + "Passed test count should be exact under concurrent updates"); + Assert.AreEqual(expectedFailed, _testableTrxLogger.FailedTestCount, + "Failed test count should be exact under concurrent updates"); + } + private static TestCase CreateTestCase(string testCaseName) { return new TestCase(testCaseName, new Uri("some://uri"), "DummySourceFileName"); diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerWarnOnOverwriteRegressionTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerWarnOnOverwriteRegressionTests.cs new file mode 100644 index 0000000000..30b3310ac6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/TrxLoggerWarnOnOverwriteRegressionTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.TestPlatform.Extensions.TrxLogger.Utility; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using TrxLoggerConstants = Microsoft.TestPlatform.Extensions.TrxLogger.Utility.Constants; + +namespace Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests; + +/// +/// Regression tests for TrxLogger WarnOnFileOverwrite parameter. +/// +[TestClass] +public class TrxLoggerWarnOnOverwriteRegressionTests +{ + private static readonly string DefaultTestRunDirectory = System.IO.Path.GetTempPath(); + + // Regression test for #5141 — Add option to overwrite trx without warning + [TestMethod] + public void Initialize_WarnOnFileOverwriteTrue_ShouldNotThrow() + { + var logger = new VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger(); + var events = new Mock(); + var parameters = new Dictionary + { + [DefaultLoggerParameterNames.TestRunDirectory] = DefaultTestRunDirectory, + [TrxLoggerConstants.LogFileNameKey] = "test.trx", + [TrxLoggerConstants.WarnOnFileOverwrite] = "true" + }; + + // Should not throw + logger.Initialize(events.Object, parameters); + } + + // Regression test for #5141 + [TestMethod] + public void Initialize_WarnOnFileOverwriteFalse_ShouldNotThrow() + { + var logger = new VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger(); + var events = new Mock(); + var parameters = new Dictionary + { + [DefaultLoggerParameterNames.TestRunDirectory] = DefaultTestRunDirectory, + [TrxLoggerConstants.LogFileNameKey] = "test.trx", + [TrxLoggerConstants.WarnOnFileOverwrite] = "false" + }; + + // Should not throw + logger.Initialize(events.Object, parameters); + } + + // Regression test for #5141 + [TestMethod] + public void Initialize_WarnOnFileOverwriteInvalidValue_ShouldDefaultToTrue() + { + var logger = new VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger(); + var events = new Mock(); + var parameters = new Dictionary + { + [DefaultLoggerParameterNames.TestRunDirectory] = DefaultTestRunDirectory, + [TrxLoggerConstants.LogFileNameKey] = "test.trx", + [TrxLoggerConstants.WarnOnFileOverwrite] = "not-a-bool" + }; + + // Should not throw — invalid value falls back to true + logger.Initialize(events.Object, parameters); + } + + // Regression test for #5141 + [TestMethod] + public void Initialize_WarnOnFileOverwriteNotProvided_ShouldDefaultToTrue() + { + var logger = new VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger(); + var events = new Mock(); + var parameters = new Dictionary + { + [DefaultLoggerParameterNames.TestRunDirectory] = DefaultTestRunDirectory, + [TrxLoggerConstants.LogFileNameKey] = "test.trx", + }; + + // Should not throw — missing parameter falls back to true + logger.Initialize(events.Object, parameters); + } +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs index a459932bfc..f6e826a411 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs @@ -75,7 +75,7 @@ public void ToCollectionEntriesShouldRenameAttachmentUriIfTheAttachmentNameIsSam _converter = new Converter(new FileHelper(), _trxFileHelper); List collectorDataEntries = _converter.ToCollectionEntries(attachmentSets, testRun, testResultsDirectory); - Assert.AreEqual(2, collectorDataEntries[0].Attachments.Count); + Assert.HasCount(2, collectorDataEntries[0].Attachments); Assert.AreEqual($@"{Environment.MachineName}{Path.DirectorySeparatorChar}123.coverage", ((ObjectModel.UriDataAttachment)collectorDataEntries[0].Attachments[0]).Uri.OriginalString); Assert.AreEqual($@"{Environment.MachineName}{Path.DirectorySeparatorChar}123[1].coverage", ((ObjectModel.UriDataAttachment)collectorDataEntries[0].Attachments[1]).Uri.OriginalString); @@ -181,7 +181,7 @@ public void ToResultFilesShouldAddAttachmentsWithRelativeUri() attachmentSets[0].Attachments.Add(uriDataAttachment1); var resultFiles = _converter.ToResultFiles(attachmentSets, testRun, @"c:\temp", null!); - Assert.IsTrue(resultFiles[0].Contains("abc.txt")); + Assert.Contains("abc.txt", resultFiles[0]); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings b/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings new file mode 100644 index 0000000000..823b5bb2d5 --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings @@ -0,0 +1,5 @@ + + + true + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AppDomainTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs similarity index 88% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AppDomainTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs index 140ad1431c..f0b67c9b96 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AppDomainTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs @@ -13,7 +13,7 @@ using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; [TestClass] [TestCategory("Windows-Review")] @@ -21,6 +21,7 @@ public class AppDomainTests : AcceptanceTestBase { [TestMethod] [TestCategory("Windows-Review")] + // AppDomains are .NET Framework only, run in .NET Framework runner and .NET runner [NetFullTargetFrameworkDataSource] public void RunTestExecutionWithDisableAppDomain(RunnerInfo runnerInfo) { @@ -53,9 +54,7 @@ public void RunTestExecutionWithDisableAppDomain(RunnerInfo runnerInfo) Assert.IsTrue( IsFilesContentEqual(testAppDomainDetailFileName, dataCollectorAppDomainDetailFileName), - "Different AppDomains, test: {0} datacollector: {1}", - File.ReadAllText(testAppDomainDetailFileName), - File.ReadAllText(dataCollectorAppDomainDetailFileName)); + $"Different AppDomains, test: {File.ReadAllText(testAppDomainDetailFileName)} datacollector: {File.ReadAllText(dataCollectorAppDomainDetailFileName)}"); ValidateSummaryStatus(1, 1, 1); File.Delete(runsettingsFilePath); } @@ -66,14 +65,15 @@ private static bool IsFilesContentEqual(string filePath1, string filePath2) Assert.IsTrue(File.Exists(filePath2), "File doesn't exist: {0}.", filePath2); var content1 = File.ReadAllText(filePath1); var content2 = File.ReadAllText(filePath2); - Assert.IsTrue(string.Equals(content1, content2, StringComparison.Ordinal), "Content mismatch{2}- file1 content:{2}{0}{2}- file2 content:{2}{1}{2}", content1, content2, Environment.NewLine); + var errorSummary = string.Format(CultureInfo.InvariantCulture, "Content mismatch{2}- file1 content:{2}{0}{2}- file2 content:{2}{1}{2}", content1, content2, Environment.NewLine); + Assert.IsTrue(string.Equals(content1, content2, StringComparison.Ordinal), errorSummary); return string.Equals(content1, content2, StringComparison.Ordinal); } private string GetInProcDataCollectionRunsettingsFile(bool disableAppDomain, TempDirectory tempDirectory) { var runSettings = Path.Combine(tempDirectory.Path, "test_" + Guid.NewGuid() + ".runsettings"); - var inprocasm = _testEnvironment.GetTestAsset("SimpleDataCollector.dll"); + var inprocasm = _testEnvironment.GetTestAsset("SimpleDataCollector.dll", "netstandard2.0"); #if !NETFRAMEWORK var assemblyName = AssemblyLoadContext.GetAssemblyName(inprocasm); #else diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyMetadataProviderTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyMetadataProviderTests.cs similarity index 89% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyMetadataProviderTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyMetadataProviderTests.cs index c52f1c2f5c..cf1f4df6b2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyMetadataProviderTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyMetadataProviderTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -15,7 +16,7 @@ using Moq; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; [TestClass] public class AssemblyMetadataProviderTests : AcceptanceTestBase @@ -55,7 +56,7 @@ public void Cleanup() [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetArchitectureShouldReturnCorrentArchForx64Assembly(string framework) { TestDotnetAssemblyArch("SimpleTestProject3", framework, Architecture.X64, expectedElapsedTime: ExpectedTimeForFindingArchForDotNetAssembly); @@ -63,7 +64,7 @@ public void GetArchitectureShouldReturnCorrentArchForx64Assembly(string framewor [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetArchitectureShouldReturnCorrentArchForx86Assembly(string framework) { TestDotnetAssemblyArch("SimpleTestProjectx86", framework, Architecture.X86, expectedElapsedTime: ExpectedTimeForFindingArchForDotNetAssembly); @@ -71,7 +72,7 @@ public void GetArchitectureShouldReturnCorrentArchForx86Assembly(string framewor [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetArchitectureShouldReturnCorrentArchForAnyCpuAssembly(string framework) { TestDotnetAssemblyArch("SimpleTestProject", framework, Architecture.AnyCPU, expectedElapsedTime: ExpectedTimeForFindingArchForDotNetAssembly); @@ -79,10 +80,10 @@ public void GetArchitectureShouldReturnCorrentArchForAnyCpuAssembly(string frame [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] - public void GetArchitectureShouldReturnCorrentArchForArmAssembly(string framework) + [DataRow("net8.0")] + public void GetArchitectureShouldReturnCorrentArchForArm64Assembly(string framework) { - TestDotnetAssemblyArch("SimpleTestProjectARM", framework, Architecture.ARM, expectedElapsedTime: ExpectedTimeForFindingArchForDotNetAssembly); + TestDotnetAssemblyArch("SimpleTestProjectARM64", framework, Architecture.ARM64, expectedElapsedTime: ExpectedTimeForFindingArchForDotNetAssembly); } [TestMethod] @@ -92,7 +93,7 @@ public void GetArchitectureForNativeDll(string platform) { var expectedElapsedTime = 5; var platformPath = platform.Equals("x64") ? platform : string.Empty; - var assemblyPath = $@"{_testEnvironment.PackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/" + var assemblyPath = $@"{_testEnvironment.GlobalPackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/" + $@"contentFiles/any/any/{platformPath}/Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; LoadAssemblyIntoMemory(assemblyPath); var stopWatch = Stopwatch.StartNew(); @@ -108,7 +109,7 @@ public void GetArchitectureForNativeDll(string platform) [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetFrameWorkForDotNetAssembly(string framework) { var expectedElapsedTime = 5; @@ -126,7 +127,7 @@ public void GetFrameWorkForDotNetAssembly(string framework) } else { - Assert.AreEqual(".NETCoreApp,Version=v3.1", actualFx.FullName); + Assert.AreEqual(".NETCoreApp,Version=v8.0", actualFx.FullName); } Console.WriteLine("Framework:{0}, {1}", framework, string.Format(CultureInfo.CurrentCulture, PerfAssertMessageFormat, expectedElapsedTime, stopWatch.ElapsedMilliseconds)); @@ -139,7 +140,7 @@ public void GetFrameWorkForDotNetAssembly(string framework) public void GetFrameworkForNativeDll() { var expectedElapsedTime = 5; - var assemblyPath = $@"{_testEnvironment.PackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; + var assemblyPath = $@"{_testEnvironment.GlobalPackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; LoadAssemblyIntoMemory(assemblyPath); var stopWatch = Stopwatch.StartNew(); var fx = _assemblyMetadataProvider.GetFrameworkName(assemblyPath); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyPropertiesTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyPropertiesTests.cs similarity index 80% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyPropertiesTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyPropertiesTests.cs index aa98715583..4c7dde0704 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AssemblyPropertiesTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/AssemblyPropertiesTests.cs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; [TestClass] public class AssemblyPropertiesTests : AcceptanceTestBase @@ -19,7 +20,7 @@ public AssemblyPropertiesTests() [TestMethod] [DataRow("net462")] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetAssemblyTypeForManagedDll(string framework) { var assemblyPath = _testEnvironment.GetTestAsset("SimpleTestProject3.dll", framework); @@ -31,7 +32,7 @@ public void GetAssemblyTypeForManagedDll(string framework) [TestMethod] public void GetAssemblyTypeForNativeDll() { - var assemblyPath = $@"{_testEnvironment.PackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; + var assemblyPath = $@"{_testEnvironment.GlobalPackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.NativeCPP.dll"; var assemblyType = _assemblyProperties.GetAssemblyType(assemblyPath); Assert.AreEqual(AssemblyType.Native, assemblyType); @@ -47,7 +48,7 @@ public void GetAssemblyTypeForManagedExe() } [TestMethod] - [DataRow("netcoreapp3.1")] + [DataRow("net8.0")] public void GetAssemblyTypeForNetCoreManagedExe(string framework) { var assemblyPath = _testEnvironment.GetTestAsset("ConsoleManagedApp.dll", framework); @@ -59,7 +60,7 @@ public void GetAssemblyTypeForNetCoreManagedExe(string framework) [TestMethod] public void GetAssemblyTypeForNativeExe() { - var assemblyPath = $@"{_testEnvironment.PackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.ConsoleNativeApp.exe"; + var assemblyPath = $@"{_testEnvironment.GlobalPackageDirectory}/microsoft.testplatform.testasset.nativecpp/2.0.0/contentFiles/any/any/Microsoft.TestPlatform.TestAsset.ConsoleNativeApp.exe"; var assemblyType = _assemblyProperties.GetAssemblyType(assemblyPath); Assert.AreEqual(AssemblyType.Native, assemblyType); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/BannedSymbols.txt b/test/Microsoft.TestPlatform.Library.IntegrationTests/BannedSymbols.txt new file mode 100644 index 0000000000..c8eaef56e1 --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/BannedSymbols.txt @@ -0,0 +1,3 @@ +M:System.IO.Path.GetTempPath(); Use 'IntegrationTestBase.GetTempPath()' instead +M:System.Environment.SetEnvironmentVariable(System.String,System.String); Use one of the overload accepting a dictionary of environment variables instead +M:System.Environment.SetEnvironmentVariable(System.String,System.String,System.EnvironmentVariableTarget); Use one of the overload accepting a dictionary of environment variables instead \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/Build.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/Build.cs new file mode 100644 index 0000000000..27338ba1df --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/Build.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Library.IntegrationTests; + +[TestClass] +public static class Build +{ + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext testContext) + { + // Increase the ThreadPool minimum threads to avoid starvation during parallel test + // execution. Each test class spawns vstest.console and testhost processes via the + // TranslationLayer — the socket-based communication blocks ThreadPool threads during + // connection setup, causing starvation with the default MinThreads. + var additionalThreadsCount = System.Environment.ProcessorCount * 4; + System.Threading.ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); + System.Threading.ThreadPool.SetMinThreads(workerThreads + additionalThreadsCount, completionPortThreads + additionalThreadsCount); + + IntegrationTestBuild.BuildTestAssetsForIntegrationTests(testContext); + } + + [AssemblyCleanup] + public static void AssemblyCleanup() + { + IntegrationTestBuild.CleanupTestAssets(); + } +} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DeprecateExtensionsPathWarningTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/DeprecateExtensionsPathWarningTests.cs similarity index 94% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DeprecateExtensionsPathWarningTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/DeprecateExtensionsPathWarningTests.cs index 7f0ca13acc..df8ef1f114 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DeprecateExtensionsPathWarningTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/DeprecateExtensionsPathWarningTests.cs @@ -7,10 +7,10 @@ using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; -[TestClass] [TestCategory("Windows-Review")] +// TODO: but where are some tests? :D public class DeprecateExtensionsPathWarningTests : AcceptanceTestBase { private readonly IList _adapterDependencies; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiaSessionTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/DiaSessionTests.cs similarity index 58% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiaSessionTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/DiaSessionTests.cs index f78fbaef84..b9f01f13dd 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiaSessionTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/DiaSessionTests.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Diagnostics; using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; [TestClass] +// TODO: these tests potentially not test anything useful that we would not test in other tests? Replace them with integration tests that have portable and full symbols test file, and that collect source info, unless we have such tests already. Right now we test just portable symbol reader it seems. public class DiaSessionTests : AcceptanceTestBase { public static string? GetAndSetTargetFrameWork(IntegrationTestEnvironment testEnvironment) @@ -20,7 +20,7 @@ public class DiaSessionTests : AcceptanceTestBase #if NETFRAMEWORK "net462"; #else - "netcoreapp3.1"; + "net8.0"; #endif return currentTargetFrameWork; } @@ -35,10 +35,9 @@ public void GetNavigationDataShouldReturnCorrectFileNameAndLineNumber() DiaNavigationData? diaNavigationData = diaSession.GetNavigationData("SimpleClassLibrary.Class1", "PassingTest"); Assert.IsNotNull(diaNavigationData, "Failed to get navigation data"); - StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/")); + Assert.EndsWith(@"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"), diaNavigationData.FileName!.Replace("\\", "/")); - ValidateMinLineNumber(11, diaNavigationData.MinLineNumber); - Assert.AreEqual(13, diaNavigationData.MaxLineNumber, "Incorrect max line number"); + SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "PassingTest", diaNavigationData.MinLineNumber, "Incorrect min line number"); _testEnvironment.TargetFramework = currentTargetFrameWork; } @@ -53,10 +52,10 @@ public void GetNavigationDataShouldReturnCorrectDataForAsyncMethod() DiaNavigationData? diaNavigationData = diaSession.GetNavigationData("SimpleClassLibrary.Class1+d__1", "MoveNext"); Assert.IsNotNull(diaNavigationData, "Failed to get navigation data"); - StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/")); + Assert.EndsWith(@"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"), diaNavigationData.FileName!.Replace("\\", "/")); - ValidateMinLineNumber(16, diaNavigationData.MinLineNumber); - Assert.AreEqual(18, diaNavigationData.MaxLineNumber, "Incorrect max line number"); + // The async state machine's MoveNext maps back to the original async method source lines. + SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "AsyncTestMethod", diaNavigationData.MinLineNumber, "Incorrect min line number"); _testEnvironment.TargetFramework = currentTargetFrameWork; } @@ -71,11 +70,11 @@ public void GetNavigationDataShouldReturnCorrectDataForOverLoadedMethod() DiaNavigationData? diaNavigationData = diaSession.GetNavigationData("SimpleClassLibrary.Class1", "OverLoadedMethod"); Assert.IsNotNull(diaNavigationData, "Failed to get navigation data"); - StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/")); + Assert.EndsWith(@"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"), diaNavigationData.FileName!.Replace("\\", "/")); - // Weird why DiaSession is now returning the first overloaded method - // as compared to before when it used to return second method - ValidateLineNumbers(diaNavigationData.MinLineNumber, diaNavigationData.MaxLineNumber); + // DiaSession may return any overload;verify min line falls within one of them. + SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "OverLoadedMethod", diaNavigationData.MinLineNumber, + $"Min line number ({diaNavigationData.MinLineNumber}) is not at the body start of any OverLoadedMethod overload."); _testEnvironment.TargetFramework = currentTargetFrameWork; } @@ -112,49 +111,13 @@ public void DiaSessionPerfTest() watch.Stop(); Assert.IsNotNull(diaNavigationData, "Failed to get navigation data"); - StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\HugeMethodSet.cs".Replace("\\", "/")); - ValidateMinLineNumber(9, diaNavigationData.MinLineNumber); - Assert.AreEqual(10, diaNavigationData.MaxLineNumber); - var expectedTime = 150; - Assert.IsTrue(watch.Elapsed.Milliseconds < expectedTime, $"DiaSession Perf test Actual time:{watch.Elapsed.Milliseconds} ms Expected time:{expectedTime} ms"); + Assert.EndsWith(@"\SimpleClassLibrary\HugeMethodSet.cs".Replace("\\", "/"), diaNavigationData.FileName!.Replace("\\", "/")); - _testEnvironment.TargetFramework = currentTargetFrameWork; - } + SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "MSTest_D1_01", diaNavigationData.MinLineNumber, "Incorrect min line number"); - private static void ValidateLineNumbers(int min, int max) - { - // Release builds optimize code, hence min line numbers are different. - if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase)) - { - Assert.AreEqual(min, max, "Incorrect min line number"); - } - else - { - if (max == 23) - { - Assert.AreEqual(min + 1, max, "Incorrect min line number"); - } - else if (max == 27) - { - Assert.AreEqual(min + 1, max, "Incorrect min line number"); - } - else - { - Assert.Fail($"Incorrect min/max line number. Expected Max to be 23 or 27. And Min to be 22 or 26. But Min was {min}, and Max was {max}."); - } - } - } + var expectedTime = 150; + Assert.IsLessThan(expectedTime, watch.Elapsed.Milliseconds, $"DiaSession Perf test Actual time:{watch.Elapsed.Milliseconds} ms Expected time:{expectedTime} ms"); - private static void ValidateMinLineNumber(int expected, int actual) - { - // Release builds optimize code, hence min line numbers are different. - if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase)) - { - Assert.AreEqual(expected + 1, actual, "Incorrect min line number"); - } - else - { - Assert.AreEqual(expected, actual, "Incorrect min line number"); - } + _testEnvironment.TargetFramework = currentTargetFrameWork; } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MetadataReaderHelperTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/MetadataReaderHelperTests.cs similarity index 73% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MetadataReaderHelperTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/MetadataReaderHelperTests.cs index 090009c7e8..773b46a0b5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MetadataReaderHelperTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/MetadataReaderHelperTests.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Reflection; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests; [TestClass] public class MetadataReaderHelperTests : AcceptanceTestBase @@ -18,9 +19,9 @@ public void MetadataReaderHelper_GetCollectorExtensionTypes() var dataCollectorFilePath = GetTestDllForFramework("AttachmentProcessorDataCollector.dll", "netstandard2.0"); var types = MetadataReaderExtensionsHelper.DiscoverTestExtensionTypesV2Attribute(Assembly.LoadFile(dataCollectorFilePath), dataCollectorFilePath); Assert.IsTrue(types.Any(), $"File {dataCollectorFilePath}"); - Assert.IsTrue(types[0].AssemblyQualifiedName!.StartsWith("AttachmentProcessorDataCollector.SampleDataCollectorV2"), $"File {dataCollectorFilePath}"); + Assert.StartsWith("AttachmentProcessorDataCollector.SampleDataCollectorV2", types[0].AssemblyQualifiedName!, $"File {dataCollectorFilePath}"); Assert.AreEqual(dataCollectorFilePath.Replace("/", @"\"), types[0].Assembly.Location.Replace("/", @"\"), $"File {dataCollectorFilePath}"); - Assert.IsTrue(types[1].AssemblyQualifiedName!.StartsWith("AttachmentProcessorDataCollector.SampleDataCollectorV1"), $"File {dataCollectorFilePath}"); + Assert.StartsWith("AttachmentProcessorDataCollector.SampleDataCollectorV1", types[1].AssemblyQualifiedName!, $"File {dataCollectorFilePath}"); Assert.AreEqual(dataCollectorFilePath.Replace("/", @"\"), types[1].Assembly.Location.Replace("/", @"\"), $"File {dataCollectorFilePath}"); } } diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/Microsoft.TestPlatform.Library.IntegrationTests.csproj b/test/Microsoft.TestPlatform.Library.IntegrationTests/Microsoft.TestPlatform.Library.IntegrationTests.csproj new file mode 100644 index 0000000000..681869f67d --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/Microsoft.TestPlatform.Library.IntegrationTests.csproj @@ -0,0 +1,52 @@ + + + + true + true + true + $(TestRunnerAdditionalArguments) --settings "$(MSBuildThisFileDirectory)\.runsettings" + + + + Exe + net9.0;net48 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/Properties/AssemblyInfo.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f79978e327 --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +// Enable IAP at method level with as many threads as possible based on CPU and core count. +// Method level is safe because integration tests offload work to child processes (vstest.console, testhost). +[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/README.MD b/test/Microsoft.TestPlatform.Library.IntegrationTests/README.MD new file mode 100644 index 0000000000..39231e5952 --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/README.MD @@ -0,0 +1,3 @@ +Integration tests that run inside of the test process, some work might be delegated to child process, +but we are testing a component that is loaded into the process, e.g. VSTestConsoleWrapper. For this reason we need to +multi target the test project to ensure that the component works correctly in all supported TFMs. diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs similarity index 81% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs index a5df1f4fcb..a857826d73 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -12,13 +12,16 @@ using Castle.Core.Internal; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] //Code coverage only supported on windows (based on the message in output) @@ -31,9 +34,9 @@ public class CodeCoverageTests : CodeCoverageAcceptanceTestBase private TestRunAttachmentsProcessingEventHandler? _testRunAttachmentsProcessingEventHandler; [MemberNotNull(nameof(_vstestConsoleWrapper), nameof(_testRunAttachmentsProcessingEventHandler), nameof(_runEventHandler), nameof(_telemetryEventsHandler))] - private void Setup() + private void Setup(Dictionary? environmentVariables = null) { - _vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(environmentVariables); _runEventHandler = new RunEventHandler(); _testRunAttachmentsProcessingEventHandler = new TestRunAttachmentsProcessingEventHandler(); _telemetryEventsHandler = new TelemetryEventsHandler(); @@ -46,7 +49,6 @@ public void Cleanup() } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) { @@ -59,11 +61,11 @@ public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) new TestPlatformOptions { CollectMetrics = true }, null, _runEventHandler, _telemetryEventsHandler); // assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); int expectedNumberOfAttachments = 1; - Assert.AreEqual(expectedNumberOfAttachments, _runEventHandler.Attachments.Count); - Assert.IsTrue(_telemetryEventsHandler.Events.Any(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); + Assert.HasCount(expectedNumberOfAttachments, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.IsNotEmpty(_telemetryEventsHandler.Events.Where(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); AssertCoverageResults(_runEventHandler.Attachments); @@ -72,7 +74,6 @@ public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TestRunWithCodeCoverageUsingClrIe(RunnerInfo runnerInfo) { @@ -85,11 +86,11 @@ public void TestRunWithCodeCoverageUsingClrIe(RunnerInfo runnerInfo) new TestPlatformOptions { CollectMetrics = true }, null, _runEventHandler, _telemetryEventsHandler); // assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.IsTrue(_telemetryEventsHandler.Events.Any(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.IsNotEmpty(_telemetryEventsHandler.Events.Where(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); int expectedNumberOfAttachments = 1; - Assert.AreEqual(expectedNumberOfAttachments, _runEventHandler.Attachments.Count); + Assert.HasCount(expectedNumberOfAttachments, _runEventHandler.Attachments, _runEventHandler.ToString()); AssertCoverageResults(_runEventHandler.Attachments); @@ -98,7 +99,6 @@ public void TestRunWithCodeCoverageUsingClrIe(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) { @@ -111,9 +111,9 @@ public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) new TestPlatformOptions { CollectMetrics = true }, null, _runEventHandler, _telemetryEventsHandler); // assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.Attachments.Count); - Assert.IsTrue(_telemetryEventsHandler.Events.Any(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.IsNotEmpty(_telemetryEventsHandler.Events.Where(e => e.Name.StartsWith("vs/codecoverage") && e.Properties.Any())); AssertCoverageResults(_runEventHandler.Attachments); @@ -122,13 +122,11 @@ public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingWithInvokedDataCollectors(RunnerInfo runnerInfo) => await TestRunWithCodeCoverageAndAttachmentsProcessingInternal(runnerInfo, true); [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingWithoutInvokedDataCollectors(RunnerInfo runnerInfo) => await TestRunWithCodeCoverageAndAttachmentsProcessingInternal(runnerInfo, false); @@ -142,9 +140,9 @@ private async Task TestRunWithCodeCoverageAndAttachmentsProcessingInternal(Runne _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.Attachments.Count); - Assert.AreEqual(2, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); // act @@ -158,7 +156,7 @@ await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync( // Assert _testRunAttachmentsProcessingEventHandler.EnsureSuccess(); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments.Count); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments); AssertCoverageResults(_testRunAttachmentsProcessingEventHandler.Attachments); @@ -169,7 +167,7 @@ await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync( { TestRunAttachmentsProcessingProgressEventArgs progressArgs = _testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; Assert.AreEqual(i + 1, progressArgs.CurrentAttachmentProcessorIndex); - Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorUris.Count); + Assert.ContainsSingle(progressArgs.CurrentAttachmentProcessorUris); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); Assert.AreEqual(withInvokedDataCollectors ? 2 : 1, progressArgs.AttachmentProcessorsCount); if (_testRunAttachmentsProcessingEventHandler.ProgressArgs.Count == 2) @@ -188,7 +186,6 @@ await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync( } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(RunnerInfo runnerInfo) { @@ -200,22 +197,22 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(Runne _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.Attachments.Count); - Assert.AreEqual(2, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); // act await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(_runEventHandler.Attachments, _runEventHandler.InvokedDataCollectors, GetCodeCoverageRunSettings(1), true, false, _testRunAttachmentsProcessingEventHandler, CancellationToken.None); // Assert - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count()); - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count(), _runEventHandler.ToString()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count(), _runEventHandler.ToString()); Assert.IsTrue(Regex.IsMatch(_runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Single(), @"Microsoft\.VisualStudio\.Coverage\.DynamicCoverageDataCollectorWithAttachmentProcessorAndTelemetry, Microsoft\.VisualStudio\.TraceDataCollector, Version=.*, Culture=neutral, PublicKeyToken=.*")); _testRunAttachmentsProcessingEventHandler.EnsureSuccess(); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments.Count); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments); AssertCoverageResults(_testRunAttachmentsProcessingEventHandler.Attachments); @@ -226,7 +223,7 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(Runne { TestRunAttachmentsProcessingProgressEventArgs progressArgs = _testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; Assert.AreEqual(i + 1, progressArgs.CurrentAttachmentProcessorIndex); - Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorUris.Count); + Assert.ContainsSingle(progressArgs.CurrentAttachmentProcessorUris); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); Assert.AreEqual(2, progressArgs.AttachmentProcessorsCount); if (_testRunAttachmentsProcessingEventHandler.ProgressArgs.Count == 2) @@ -242,7 +239,6 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(Runne } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicated(RunnerInfo runnerInfo) { @@ -251,25 +247,26 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicate Setup(); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); + // The same library runs twice _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(9, _runEventHandler.TestResults.Count); - Assert.AreEqual(3, _runEventHandler.Attachments.Count); - Assert.AreEqual(3, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(9, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(3, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(3, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); // act await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(_runEventHandler.Attachments, _runEventHandler.InvokedDataCollectors, GetCodeCoverageRunSettings(1), true, true, _testRunAttachmentsProcessingEventHandler, CancellationToken.None); // Assert - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count()); - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count(), _runEventHandler.ToString()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count(), _runEventHandler.ToString()); Assert.IsTrue(Regex.IsMatch(_runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Single(), @"Microsoft\.VisualStudio\.Coverage\.DynamicCoverageDataCollectorWithAttachmentProcessorAndTelemetry, Microsoft\.VisualStudio\.TraceDataCollector, Version=.*, Culture=neutral, PublicKeyToken=.*")); _testRunAttachmentsProcessingEventHandler.EnsureSuccess(); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments.Count); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments); AssertCoverageResults(_testRunAttachmentsProcessingEventHandler.Attachments); @@ -299,37 +296,40 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicate } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingSameReportFormat(RunnerInfo runnerInfo) { // arrange SetTestEnvironment(_testEnvironment, runnerInfo); - Setup(); + Setup(environmentVariables: new Dictionary + { + // Override timeout this fails in CI. + [EnvironmentHelper.VstestConnectionTimeout] = "90", + }); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1, outputFormat: "Coverage"), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1, outputFormat: "Coverage"), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.Attachments.Count); - Assert.AreEqual(2, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); // act await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(_runEventHandler.Attachments, _runEventHandler.InvokedDataCollectors, GetCodeCoverageRunSettings(1), true, true, _testRunAttachmentsProcessingEventHandler, CancellationToken.None); // Assert - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count()); - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count(), _runEventHandler.ToString()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count(), _runEventHandler.ToString()); Assert.IsTrue(Regex.IsMatch(_runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Single(), @"Microsoft\.VisualStudio\.Coverage\.DynamicCoverageDataCollectorWithAttachmentProcessorAndTelemetry, Microsoft\.VisualStudio\.TraceDataCollector, Version=.*, Culture=neutral, PublicKeyToken=.*")); _testRunAttachmentsProcessingEventHandler.EnsureSuccess(); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments.Count); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments.Count); - Assert.IsTrue(_testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[0].Uri.LocalPath.Contains(".coverage")); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments); + Assert.Contains(".coverage", _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[0].Uri.LocalPath); AssertCoverageResults(_testRunAttachmentsProcessingEventHandler.Attachments); @@ -359,7 +359,6 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingSameReportForma } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingDifferentReportFormats(RunnerInfo runnerInfo) { @@ -372,26 +371,26 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingDifferentReport _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1, outputFormat: "Cobertura"), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1, outputFormat: "Cobertura"), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(12, _runEventHandler.TestResults.Count); - Assert.AreEqual(4, _runEventHandler.Attachments.Count); - Assert.AreEqual(4, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(12, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(4, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(4, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); // act await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(_runEventHandler.Attachments, _runEventHandler.InvokedDataCollectors, GetCodeCoverageRunSettings(1, outputFormat: "Coverage"), true, true, _testRunAttachmentsProcessingEventHandler, CancellationToken.None); // Assert - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count()); - Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.FilePath).Distinct().Count(), _runEventHandler.ToString()); + Assert.AreEqual(1, _runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Count(), _runEventHandler.ToString()); Assert.IsTrue(Regex.IsMatch(_runEventHandler.InvokedDataCollectors.Select(x => x.AssemblyQualifiedName).Distinct().Single(), @"Microsoft\.VisualStudio\.Coverage\.DynamicCoverageDataCollectorWithAttachmentProcessorAndTelemetry, Microsoft\.VisualStudio\.TraceDataCollector, Version=.*, Culture=neutral, PublicKeyToken=.*")); _testRunAttachmentsProcessingEventHandler.EnsureSuccess(); - Assert.AreEqual(1, _testRunAttachmentsProcessingEventHandler.Attachments.Count); - Assert.AreEqual(2, _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments.Count); - Assert.IsTrue(_testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[0].Uri.LocalPath.Contains(".cobertura.xml")); - Assert.IsTrue(_testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[1].Uri.LocalPath.Contains(".coverage")); + Assert.ContainsSingle(_testRunAttachmentsProcessingEventHandler.Attachments); + Assert.HasCount(2, _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments); + Assert.Contains(".cobertura.xml", _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[0].Uri.LocalPath); + Assert.Contains(".coverage", _testRunAttachmentsProcessingEventHandler.Attachments[0].Attachments[1].Uri.LocalPath); AssertCoverageResults(_testRunAttachmentsProcessingEventHandler.Attachments); @@ -423,33 +422,34 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingDifferentReport } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] - [DoNotParallelize] public async Task EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo) { - var numOfProcesses = Process.GetProcessesByName("vstest.console").Length; - SetTestEnvironment(_testEnvironment, runnerInfo); Setup(); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Take(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); _vstestConsoleWrapper.RunTests(GetTestAssemblies().Skip(1), GetCodeCoverageRunSettings(1), null, null, _runEventHandler, _telemetryEventsHandler); - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.Attachments.Count); - Assert.AreEqual(2, _runEventHandler.InvokedDataCollectors.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.Attachments, _runEventHandler.ToString()); + Assert.HasCount(2, _runEventHandler.InvokedDataCollectors, _runEventHandler.ToString()); Assert.IsFalse(_telemetryEventsHandler.Events.Any()); await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(_runEventHandler.Attachments, _runEventHandler.InvokedDataCollectors, GetCodeCoverageRunSettings(1), true, true, _testRunAttachmentsProcessingEventHandler, CancellationToken.None); + // TODO: this is ugly and it could be useful for the consumer of wrapper to actually know what process they are using, so publishing this would be better + var processManager = (_vstestConsoleWrapper).GetType().GetField("_vstestConsoleProcessManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!.GetValue(_vstestConsoleWrapper)!; + var processId = (int)processManager.GetType().GetProperty("ProcessId", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)!.GetValue(processManager)!; + var consoleProcess = Process.GetProcessById(processId); + Assert.IsFalse(consoleProcess.HasExited, $"vstest.console should be running"); + // act - _vstestConsoleWrapper?.EndSession(); + _vstestConsoleWrapper!.EndSession(); + _vstestConsoleWrapper = null; // Assert - Assert.AreEqual(numOfProcesses, Process.GetProcessesByName("vstest.console").Length); - - _vstestConsoleWrapper = null; + Assert.IsTrue(consoleProcess.HasExited, "vstest.console process did not exit"); } private IList GetTestAssemblies() diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs new file mode 100644 index 0000000000..7fd76ff675 --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +using FluentAssertions; + +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; + +/// +/// The Run Tests using VsTestConsoleWrapper API's +/// +[TestClass] +public class CustomTestHostLauncherTests : AcceptanceTestBase +{ + private IVsTestConsoleWrapper? _vstestConsoleWrapper; + + [TestCleanup] + public void Cleanup() + { + _vstestConsoleWrapper?.EndSession(); + } + + [TestMethod] + [TestCategory("Windows-Review")] + [WrapperCompatibilityDataSource()] + public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvidedLauncher(RunnerInfo runnerInfo) + { + // Arrange + SetTestEnvironment(_testEnvironment, runnerInfo); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); + var runEventHandler = new RunEventHandler(); + + // Act + var customTestHostLauncher = new TestHostLauncherV2(); + _vstestConsoleWrapper.RunTestsWithCustomTestHost(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler, customTestHostLauncher); + + // Assert + EnsureTestsRunWithoutErrors(runEventHandler, passed: 2, failed: 2, skipped: 2); + + customTestHostLauncher.Should().BeAssignableTo(); + customTestHostLauncher.AttachDebuggerProcessId.Should().NotBeNull("we should be asked to attach a debugger to some process and save the pid of the process"); + customTestHostLauncher.LaunchProcessProcessId.Should().BeNull("we should not be asked to launch some real process, that flow is not used when vstest.console supports it and is given ITestHostLauncher2"); + } + + [TestMethod] + [TestCategory("Windows-Review")] + [TestCategory("Feature")] + [WrapperCompatibilityDataSource] + public void RunAllTestsWithMixedTFMsWillProvideAdditionalInformationToTheDebugger(RunnerInfo runnerInfo) + { + // Arrange + SetTestEnvironment(_testEnvironment, runnerInfo); + + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); + var runEventHandler = new RunEventHandler(); + var netFrameworkDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETFX); + var netDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETCORE); + var testHostLauncher = new TestHostLauncherV3(); + + // Act + // We have no preference around what TFM is used. It will be autodetected. + var runsettingsXml = ""; + _vstestConsoleWrapper.RunTestsWithCustomTestHost(new[] { netFrameworkDll, netDll }, runsettingsXml, runEventHandler, testHostLauncher); + + // Assert + if (runEventHandler.Errors.Any()) + { + var tempPath = TempDirectory.Path; + var files = System.IO.Directory.GetFiles(tempPath, "*.txt").ToList(); + if (files.Count == 0) + { + throw new InvalidOperationException($"No error files found in {tempPath}. {string.Join("\n", Directory.GetFiles(tempPath))}"); + } + + var allText = new StringBuilder(); + foreach (var file in files) + { +#pragma warning disable CA1305 // Specify IFormatProvider + allText.AppendLine($"Error file: {file}"); + allText.AppendLine(File.ReadAllText(file)); + allText.AppendLine(); +#pragma warning restore CA1305 // Specify IFormatProvider + } + throw new InvalidOperationException($"Logs: {allText}"); + } + + runEventHandler.Errors.Should().BeEmpty(); + testHostLauncher.AttachDebuggerInfos.Should().HaveCount(2); + var targetFrameworks = testHostLauncher.AttachDebuggerInfos.Select(i => i.TargetFramework).ToList(); + targetFrameworks.Should().OnlyContain(tfm => tfm.StartsWith(".NETFramework") || tfm.StartsWith(".NET ")); + + runEventHandler.TestResults.Should().HaveCount(6, "we run all tests from both assemblies"); + } + + private static void EnsureTestsRunWithoutErrors(RunEventHandler runEventHandler, int passed, int failed, int skipped) + { + runEventHandler.Errors.Should().BeEmpty(); + runEventHandler.TestResults.Should().HaveCount(passed + failed + skipped); + runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed).Should().Be(passed); + runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed).Should().Be(failed); + runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped).Should().Be(skipped); + } + + /// + /// The custom test host launcher implementing ITestHostLauncher. + /// + private class TestHostLauncherV1 : ITestHostLauncher + { + public int? LaunchProcessProcessId { get; private set; } + + /// + public bool IsDebug => true; + + /// + public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo) + { + return LaunchTestHost(defaultTestHostStartInfo, CancellationToken.None); + } + + /// + public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, CancellationToken cancellationToken) + { + var processInfo = new ProcessStartInfo( + defaultTestHostStartInfo.FileName!, + defaultTestHostStartInfo.Arguments!) + { + WorkingDirectory = defaultTestHostStartInfo.WorkingDirectory + }; + processInfo.UseShellExecute = false; + + var process = new Process { StartInfo = processInfo }; + process.Start(); + + LaunchProcessProcessId = process?.Id; + return LaunchProcessProcessId ?? -1; + } + } + + /// + /// The custom test host launcher implementing ITestHostLauncher2, and through that also ITestHostLauncher. + /// + private class TestHostLauncherV2 : TestHostLauncherV1, ITestHostLauncher2 + { + + public int? AttachDebuggerProcessId { get; private set; } + + public bool AttachDebuggerToProcess(int pid) => AttachDebuggerToProcess(pid, CancellationToken.None); + + public bool AttachDebuggerToProcess(int pid, CancellationToken cancellationToken) + { + AttachDebuggerProcessId = pid; + return true; + } + } + +#pragma warning disable CS0618 // Type or member is obsolete + private class TestHostLauncherV3 : ITestHostLauncher3 + { + public bool IsDebug => true; + + public List AttachDebuggerInfos { get; } = new(); + + public bool AttachDebuggerToProcess(AttachDebuggerInfo attachDebuggerInfo, CancellationToken cancellationToken) + { + AttachDebuggerInfos.Add(attachDebuggerInfo); + + return true; + } + + public bool AttachDebuggerToProcess(int pid) + { + return AttachDebuggerToProcess(new AttachDebuggerInfo + { + ProcessId = pid, + TargetFramework = null, + }, CancellationToken.None); + } + + public bool AttachDebuggerToProcess(int pid, CancellationToken cancellationToken) + { + return AttachDebuggerToProcess(new AttachDebuggerInfo + { + ProcessId = pid, + TargetFramework = null, + }, CancellationToken.None); + } + + public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo) + { + return -1; + } + + public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, CancellationToken cancellationToken) + { + return -1; + } + } +} +#pragma warning restore CS0618 // Type or member is obsolete + diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs similarity index 95% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs index 0fa571fdf2..32fa1eb801 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs @@ -11,6 +11,7 @@ using System.Xml; using System.Xml.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -18,7 +19,7 @@ using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] [TestCategory("Windows-Review")] @@ -42,7 +43,6 @@ public void Cleanup() } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public async Task AttachmentProcessorDataCollector_ExtensionFileNotLocked(RunnerInfo runnerInfo) { @@ -74,7 +74,7 @@ public async Task AttachmentProcessorDataCollector_ExtensionFileNotLocked(Runner Directory.Delete(extensionPath, true); // Ensure we ran the extension. - using var logFile = new FileStream(Path.Combine(TempDirectory.Path, "log.txt"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var logFile = new FileStream(Path.Combine(TempDirectory.Path, "logs", "log.txt"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using var streamReader = new StreamReader(logFile); string logFileContent = streamReader.ReadToEnd(); Assert.IsTrue(Regex.IsMatch(logFileContent, $@"DataCollectorAttachmentsProcessorsFactory: Collector attachment processor 'AttachmentProcessorDataCollector\.SampleDataCollectorAttachmentProcessor, AttachmentProcessorDataCollector, Version=.*, Culture=neutral, PublicKeyToken=null' from file '{extensionPath.Replace(@"\", @"\\")}\\AttachmentProcessorDataCollector.dll' added to the 'run list'")); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs similarity index 58% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs index c744db464b..23ebdf9ca3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs @@ -1,18 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +//using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; /// /// The Run Tests using VsTestConsoleWrapper API's @@ -38,7 +39,6 @@ public void Cleanup() [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithNunitAdapter(RunnerInfo runnerInfo) { @@ -55,29 +55,21 @@ public void RunTestsWithNunitAdapter(RunnerInfo runnerInfo) GetDefaultRunSettings(), _runEventHandler); - var testCase = - _runEventHandler.TestResults.Where(tr => tr.TestCase.DisplayName.Equals("PassTestMethod1")); + var testResult = _runEventHandler.TestResults.Where(tr => tr.TestCase.DisplayName.Equals("PassTestMethod1")).First(); + Assert.EndsWith("PassTestMethod1", testResult.TestCase.FullyQualifiedName); // Assert - Assert.AreEqual(2, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); + Assert.HasCount(2, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); - // Release builds optimize code, hence line numbers are different. - if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase)) - { - Assert.AreEqual(14, testCase.First().TestCase.LineNumber); - } - else - { - Assert.AreEqual(13, testCase.First().TestCase.LineNumber); - } + var nunitSourceFile = SourceAssert.FindSourceFile("NUTestProject.dll", "PassTestMethod1"); + SourceAssert.LineIsAtMethodBodyStart(nunitSourceFile, "PassTestMethod1", testResult.TestCase.LineNumber); } [TestMethod] // there are logs in the diagnostic log, it is failing with NullReferenceException because path is null [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) { @@ -91,30 +83,29 @@ public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) _vstestConsoleWrapper.RunTests( sources, - GetDefaultRunSettings(), + $@" + + + {runnerInfo.TargetFramework} + + ", _runEventHandler); - var testCase = - _runEventHandler.TestResults.Where(tr => tr.TestCase.DisplayName.Equals("xUnitTestProject.Class1.PassTestMethod1")); + var testResult = _runEventHandler.TestResults.Where(tr => tr.TestCase.DisplayName.Equals("xUnitTestProject.Class1.PassTestMethod1")).First(); + Assert.EndsWith("PassTestMethod1", testResult.TestCase.FullyQualifiedName); // Assert - Assert.AreEqual(2, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); + Assert.HasCount(2, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); - // Release builds optimize code, hence line numbers are different. - if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase)) - { - Assert.AreEqual(15, testCase.First().TestCase.LineNumber); - } - else - { - Assert.AreEqual(14, testCase.First().TestCase.LineNumber); - } + var xunitSourceFile = SourceAssert.FindSourceFile("XUTestProject.dll", "PassTestMethod1"); + SourceAssert.LineIsAtMethodBodyStart(xunitSourceFile, "PassTestMethod1", testResult.TestCase.LineNumber); } [TestMethod] [TestCategory("Windows-Review")] + // TODO: this does not work with netcore testhost, why? [NetFullTargetFrameworkDataSource] public void RunTestsWithNonDllAdapter(RunnerInfo runnerInfo) { @@ -138,8 +129,8 @@ public void RunTestsWithNonDllAdapter(RunnerInfo runnerInfo) var testCase = _runEventHandler.TestResults.Where(tr => tr.TestCase.DisplayName.Equals("TestMethod1")); // Assert - Assert.AreEqual(1, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(0, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); + Assert.ContainsSingle(_runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.AreEqual(0, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DiscoverTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs similarity index 84% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DiscoverTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs index b84914d873..5a96e743cd 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/DiscoverTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs @@ -10,6 +10,7 @@ using FluentAssertions; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; @@ -19,7 +20,7 @@ using Moq; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] public class DiscoverTests : AcceptanceTestBase @@ -44,7 +45,7 @@ public void Cleanup() [TestMethod] [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [WrapperCompatibilityDataSource] public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -53,16 +54,16 @@ public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) _discoveryEventHandler = new DiscoveryEventHandler(); _discoveryEventHandler2 = new DiscoveryEventHandler2(); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); - vstestConsoleWrapper.DiscoverTests(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), _discoveryEventHandler); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper.DiscoverTests(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), _discoveryEventHandler); // Assert. - Assert.AreEqual(6, _discoveryEventHandler.DiscoveredTestCases.Count); + Assert.HasCount(6, _discoveryEventHandler.DiscoveredTestCases); } [TestMethod] [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [WrapperCompatibilityDataSource] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -71,21 +72,22 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerI _discoveryEventHandler = new DiscoveryEventHandler(); _discoveryEventHandler2 = new DiscoveryEventHandler2(); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); - vstestConsoleWrapper.DiscoverTests( + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper.DiscoverTests( GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), new TestPlatformOptions() { CollectMetrics = false }, _discoveryEventHandler2); // Assert. - Assert.AreEqual(6, _discoveryEventHandler2.DiscoveredTestCases.Count); - Assert.AreEqual(0, _discoveryEventHandler2.Metrics!.Count); + Assert.HasCount(6, _discoveryEventHandler2.DiscoveredTestCases); + Assert.HasCount(0, _discoveryEventHandler2.Metrics!); } [TestMethod] - [NetFullTargetFrameworkDataSource] + [TestCategory("Smoke")] [NetCoreTargetFrameworkDataSource] + [NetFullTargetFrameworkDataSource(useVsixRunner: true)] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -94,7 +96,7 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerIn _vstestConsoleWrapper.DiscoverTests(GetTestAssemblies(), GetDefaultRunSettings(), new TestPlatformOptions() { CollectMetrics = true }, _discoveryEventHandler2); // Assert. - Assert.AreEqual(6, _discoveryEventHandler2.DiscoveredTestCases.Count); + Assert.HasCount(6, _discoveryEventHandler2.DiscoveredTestCases); Assert.IsTrue(_discoveryEventHandler2.Metrics!.ContainsKey(TelemetryDataConstants.TargetDevice)); Assert.IsTrue(_discoveryEventHandler2.Metrics.ContainsKey(TelemetryDataConstants.NumberOfAdapterUsedToDiscoverTests)); Assert.IsTrue(_discoveryEventHandler2.Metrics.ContainsKey(TelemetryDataConstants.TimeTakenInSecByAllAdapters)); @@ -103,7 +105,6 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerIn } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void DiscoverTestsUsingEventHandler2AndBatchSize(RunnerInfo runnerInfo) { @@ -133,7 +134,6 @@ public void DiscoverTestsUsingEventHandler2AndBatchSize(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void DiscoverTestsUsingEventHandler1AndBatchSize(RunnerInfo runnerInfo) { @@ -163,7 +163,6 @@ public void DiscoverTestsUsingEventHandler1AndBatchSize(RunnerInfo runnerInfo) [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] public void DiscoverTestUsingEventHandler2ShouldContainAllSourcesAsFullyDiscovered(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -178,12 +177,15 @@ public void DiscoverTestUsingEventHandler2ShouldContainAllSourcesAsFullyDiscover eventHandler2); // Assert. - Assert.AreEqual(2, eventHandler2.FullyDiscoveredSources!.Count); + Assert.HasCount(2, eventHandler2.FullyDiscoveredSources!); } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + // Normally we test on two runner, against single .NET Testhost, but because source navigation happens in testhost + // it is better to test against both desktop and core runners to make sure source navigation discovery works in both scenarios. + // We run .NET Runner -> .NET Testhost and .NET Framework Runner -> .NET Frameworks Testhost. + [NetFullTargetFrameworkDataSource(useCoreRunner: false)] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -195,22 +197,14 @@ public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) _discoveryEventHandler); // Assert. - var testCase = - _discoveryEventHandler.DiscoveredTestCases.Where(dt => dt.FullyQualifiedName.Equals("SampleUnitTestProject.UnitTest1.PassingTest")); + var testCase = _discoveryEventHandler.DiscoveredTestCases.Where(dt => dt.FullyQualifiedName.Equals("SampleUnitTestProject.UnitTest1.PassingTest")).First(); + Assert.EndsWith("PassingTest", testCase.FullyQualifiedName); - // Release builds optimize code, hence line numbers are different. - if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase)) - { - Assert.AreEqual(25, testCase.First().LineNumber); - } - else - { - Assert.AreEqual(24, testCase.First().LineNumber); - } + var sourceFile = SourceAssert.FindSourceFile("SimpleTestProject.dll", "PassingTest"); + SourceAssert.LineIsWithinMethod(sourceFile, "PassingTest", testCase.LineNumber); } [TestMethod] - [NetFullTargetFrameworkDataSource(inProcess: true)] [NetCoreTargetFrameworkDataSource] [Ignore("Flaky on CI")] public async Task CancelTestDiscovery(RunnerInfo runnerInfo) @@ -270,7 +264,7 @@ public async Task CancelTestDiscovery(RunnerInfo runnerInfo) // Act Console.WriteLine("Starting Discovery."); - await Task.Run(() => _vstestConsoleWrapper.DiscoverTests(testAssemblies, runSettingsXml, discoveryEvents.Object)); + await Task.Run(() => _vstestConsoleWrapper.DiscoverTests(testAssemblies, runSettingsXml, discoveryEvents.Object), TestContext.CancellationToken); Console.WriteLine("Discovery finished."); // Assert diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/ConcurrentList.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/ConcurrentList.cs new file mode 100644 index 0000000000..37d7b9a00d --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/ConcurrentList.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; + +public sealed class ConcurrentList : IList +{ + private readonly object _lock = new(); + private readonly List _list = new(); + + public T this[int index] + { + get { lock (_lock) { return _list[index]; } } + set { lock (_lock) { _list[index] = value; } } + } + + public int Count { get { lock (_lock) { return _list.Count; } } } + public bool IsReadOnly => false; + + public void AddRange(IEnumerable items) { lock (_lock) { _list.AddRange(items); } } + public void Add(T item) { lock (_lock) { _list.Add(item); } } + public void Clear() { lock (_lock) { _list.Clear(); } } + public bool Contains(T item) { lock (_lock) { return _list.Contains(item); } } + public void CopyTo(T[] array, int arrayIndex) { lock (_lock) { _list.CopyTo(array, arrayIndex); } } + public int IndexOf(T item) { lock (_lock) { return _list.IndexOf(item); } } + public void Insert(int index, T item) { lock (_lock) { _list.Insert(index, item); } } + public bool Remove(T item) { lock (_lock) { return _list.Remove(item); } } + public void RemoveAt(int index) { lock (_lock) { _list.RemoveAt(index); } } + + // Enumerates over a snapshot — safe even if the list is modified during enumeration + public IEnumerator GetEnumerator() { lock (_lock) { return new List(_list).GetEnumerator(); } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + // Returns an independent copy of the internal list + public List ToList() { lock (_lock) { return new List(_list); } } +} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs similarity index 94% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs index 0c3936c0b6..3af3f43e70 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/DiscoveryEventHandler.cs @@ -9,7 +9,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; /// public class DiscoveryEventHandler : ITestDiscoveryEventsHandler @@ -128,16 +128,16 @@ public class DiscoveryEventHandlerForBatchSize : ITestDiscoveryEventsHandler2, I /// /// Gets the batch size. /// - public List Batches { get; } = new List(); + public ConcurrentList Batches { get; } = new ConcurrentList(); /// /// Gets the discovered test cases. /// - public List DiscoveredTestCases { get; } + public ConcurrentList DiscoveredTestCases { get; } public DiscoveryEventHandlerForBatchSize() { - DiscoveredTestCases = new List(); + DiscoveredTestCases = new ConcurrentList(); } public void HandleRawMessage(string rawMessage) @@ -150,7 +150,7 @@ public void HandleLogMessage(TestMessageLevel level, string? message) if (level == TestMessageLevel.Error) { Console.WriteLine($"ERROR:{message}"); - }; + } } public void HandleDiscoveryComplete(DiscoveryCompleteEventArgs discoveryCompleteEventArgs, IEnumerable? lastChunk) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs similarity index 75% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs index f61c3e9d94..3cbe8900d5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/RunEventHandler.cs @@ -9,7 +9,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; /// public class RunEventHandler : ITestRunEventsHandler @@ -17,17 +17,17 @@ public class RunEventHandler : ITestRunEventsHandler /// /// Gets the test results. /// - public List TestResults { get; private set; } + public ConcurrentList TestResults { get; private set; } /// /// Gets the attachments. /// - public List Attachments { get; private set; } + public ConcurrentList Attachments { get; private set; } /// /// Gets the list of the invoked data collectors. /// - public List InvokedDataCollectors { get; private set; } + public ConcurrentList InvokedDataCollectors { get; private set; } /// /// Gets the metrics. @@ -39,7 +39,7 @@ public class RunEventHandler : ITestRunEventsHandler /// public string? LogMessage { get; private set; } - public List Errors { get; set; } + public ConcurrentList Errors { get; set; } /// /// Gets the test message level. @@ -48,10 +48,10 @@ public class RunEventHandler : ITestRunEventsHandler public RunEventHandler() { - TestResults = new List(); - Errors = new List(); - Attachments = new List(); - InvokedDataCollectors = new List(); + TestResults = new ConcurrentList(); + Errors = new ConcurrentList(); + Attachments = new ConcurrentList(); + InvokedDataCollectors = new ConcurrentList(); } public void EnsureSuccess() @@ -114,4 +114,15 @@ public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessSta // No op return -1; } + + public override string ToString() + { + return $""" + TestResults: {TestResults.Count} + {string.Join("\n ", TestResults?.Select(t => t.DisplayName + " " + t.Outcome) ?? [])} + Errors: {Errors.Count} + {string.Join("\n ", Errors ?? [])} + + """; + } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs similarity index 68% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs index 718c2ef36d..3c844c37a9 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TelemetryEventsHandler.cs @@ -1,16 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Collections.Concurrent; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; internal class TelemetryEventsHandler : ITelemetryEventsHandler { - public ConcurrentBag Events { get; private set; } = new ConcurrentBag(); + public ConcurrentList Events { get; private set; } = new ConcurrentList(); public void HandleTelemetryEvent(TelemetryEvent telemetryEvent) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs similarity index 86% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs index 2dd74862ac..9ceac63b39 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs @@ -10,23 +10,23 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; /// public class TestRunAttachmentsProcessingEventHandler : ITestRunAttachmentsProcessingEventsHandler { - public List Attachments { get; private set; } + public ConcurrentList Attachments { get; private set; } public TestRunAttachmentsProcessingCompleteEventArgs? CompleteArgs { get; private set; } - public List ProgressArgs { get; private set; } + public ConcurrentList ProgressArgs { get; private set; } /// /// Gets the log message. /// public string? LogMessage { get; private set; } - public List Errors { get; set; } + public ConcurrentList Errors { get; set; } /// /// Gets the test message level. @@ -36,8 +36,8 @@ public class TestRunAttachmentsProcessingEventHandler : ITestRunAttachmentsProce public TestRunAttachmentsProcessingEventHandler() { Errors = new(); - Attachments = new List(); - ProgressArgs = new List(); + Attachments = new ConcurrentList(); + ProgressArgs = new ConcurrentList(); } public void EnsureSuccess() diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs similarity index 81% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs index e7e1d5cd40..21afa15e53 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs @@ -5,11 +5,13 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] public class LiveUnitTestingTests : AcceptanceTestBase @@ -34,8 +36,8 @@ public void Cleanup() [TestMethod] + // Touches appdomain settings, preferring .NET Framework testhost here. [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -55,12 +57,12 @@ public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) _discoveryEventHandler); // Assert - Assert.AreEqual(6, _discoveryEventHandler.DiscoveredTestCases.Count); + Assert.HasCount(6, _discoveryEventHandler.DiscoveredTestCases); } [TestMethod] + // Touches appdomain settings, preferring .NET Framework testhost here. [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] public void RunTestsWithLiveUnitTesting(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -80,10 +82,10 @@ public void RunTestsWithLiveUnitTesting(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped), _runEventHandler.ToString()); } private IList GetTestAssemblies() diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs similarity index 85% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs index 0ac08c4827..2357b9f7cc 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs @@ -5,13 +5,15 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] public class RunSelectedTests : AcceptanceTestBase @@ -35,7 +37,6 @@ public void Cleanup() } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithoutTestPlatformOptions(RunnerInfo runnerInfo) { @@ -48,14 +49,13 @@ public void RunSelectedTestsWithoutTestPlatformOptions(RunnerInfo runnerInfo) _vstestConsoleWrapper.RunTests(testCases, GetDefaultRunSettings(), _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped), _runEventHandler.ToString()); } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithTestPlatformOptions(RunnerInfo runnerInfo) { @@ -72,7 +72,7 @@ public void RunSelectedTestsWithTestPlatformOptions(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); Assert.IsTrue(_runEventHandler.Metrics!.ContainsKey(TelemetryDataConstants.TargetDevice)); Assert.IsTrue(_runEventHandler.Metrics.ContainsKey(TelemetryDataConstants.TargetFramework)); Assert.IsTrue(_runEventHandler.Metrics.ContainsKey(TelemetryDataConstants.TargetOS)); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs similarity index 65% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs index 7f0b3249c1..f60630186e 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Threading; using FluentAssertions; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; @@ -19,7 +20,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; /// /// The Run Tests using VsTestConsoleWrapper API's @@ -45,62 +46,61 @@ public void Cleanup() [TestMethod] [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [WrapperCompatibilityDataSource] public void RunAllTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); var runEventHandler = new RunEventHandler(); - vstestConsoleWrapper.RunTests(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler); + _vstestConsoleWrapper.RunTests(GetTestDlls("MSTestProject1.dll", "MSTestProject2.dll"), GetDefaultRunSettings(), runEventHandler); // Assert - Assert.AreEqual(6, runEventHandler.TestResults.Count); + Assert.HasCount(6, runEventHandler.TestResults); Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); } [TestMethod] - [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource(BeforeFeature = Features.MULTI_TFM)] - public void RunAllTestsWithMixedTFMsWillFailToRunTestsFromTheIncompatibleTFMDll(RunnerInfo runnerInfo) + [NetCoreTargetFrameworkDataSource] + [NetFullTargetFrameworkDataSource(useVsixRunner: true)] + [TestCategory("Smoke")] + public void RunAllTestsFromDlls(RunnerInfo runnerInfo) { - // Arrange SetTestEnvironment(_testEnvironment, runnerInfo); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); var runEventHandler = new RunEventHandler(); - var compatibleDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETFX); - var incompatibleDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETCORE); - - // Act - // We have no preference around what TFM is used. It will be autodetected. - var runsettingsXml = ""; - vstestConsoleWrapper.RunTests(new[] { compatibleDll, incompatibleDll }, runsettingsXml, runEventHandler); + _vstestConsoleWrapper.RunTests([GetAssetFullPath("SimpleTestProject.dll"), GetAssetFullPath("SimpleTestProject2.dll")], GetDefaultRunSettings(), runEventHandler); // Assert - runEventHandler.TestResults.Should().HaveCount(3, "we failed to run those tests because they are not compatible."); + Assert.HasCount(6, runEventHandler.TestResults); + Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); + Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); + Assert.AreEqual(2, runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); } [TestMethod] [TestCategory("Windows-Review")] - [TestHostCompatibilityDataSource] - [RunnerCompatibilityDataSource(AfterFeature = Features.MULTI_TFM)] + [WrapperCompatibilityDataSource()] public void RunAllTestsWithMixedTFMsWillRunTestsFromAllProvidedDllEvenWhenTheyMixTFMs(RunnerInfo runnerInfo) { // Arrange SetTestEnvironment(_testEnvironment, runnerInfo); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); var runEventHandler = new RunEventHandler(); - var netFrameworkDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETFX); - var netDll = GetTestDllForFramework("MSTestProject1.dll", DEFAULT_HOST_NETCORE); + // Use SimpleTestProject4 which has a minimal test adapter. This helps us test that the console / testhost are compatible. + // Rather than testing that the console & mstest (or other adapter) and compatible. If we use mstest directly it fails on + // very old versions of vstest.console. + var netFrameworkDll = GetTestDllForFramework("SimpleTestProject4.dll", DEFAULT_HOST_NETFX, automaticallyResolveCompatibilityTestAsset: false); + var netDll = GetTestDllForFramework("SimpleTestProject4.dll", DEFAULT_HOST_NETCORE, automaticallyResolveCompatibilityTestAsset: false); // Act // We have no preference around what TFM is used. It will be autodetected. var runsettingsXml = ""; - vstestConsoleWrapper.RunTests(new[] { netFrameworkDll, netDll }, runsettingsXml, runEventHandler); + _vstestConsoleWrapper.RunTests(new[] { netFrameworkDll, netDll }, runsettingsXml, runEventHandler); // Assert runEventHandler.Errors.Should().BeEmpty(); @@ -108,30 +108,26 @@ public void RunAllTestsWithMixedTFMsWillRunTestsFromAllProvidedDllEvenWhenTheyMi } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] - [DoNotParallelize] public void EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo) { - var numOfProcesses = Process.GetProcessesByName("vstest.console").Length; - SetTestEnvironment(_testEnvironment, runnerInfo); Setup(); _vstestConsoleWrapper.RunTests(GetTestAssemblies(), GetDefaultRunSettings(), _runEventHandler); - _vstestConsoleWrapper?.EndSession(); - - // Assert - // TODO: This still works reliably, but it is accidental. Correctly we should look at our "tree" of processes - // but there is no such thing on Windows. We can still replicate it quite well. There is code for it in blame - // hang collector. - Assert.AreEqual(numOfProcesses, Process.GetProcessesByName("vstest.console").Length); + // TODO: this is ugly and it could be useful for the consumer of wrapper to actually know what process they are using, so publishing this would be better + var processManager = (_vstestConsoleWrapper).GetType().GetField("_vstestConsoleProcessManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!.GetValue(_vstestConsoleWrapper)!; + var processId = (int)processManager.GetType().GetProperty("ProcessId", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)!.GetValue(processManager)!; + var consoleProcess = Process.GetProcessById(processId); + Assert.IsFalse(consoleProcess.HasExited, "vstest.console process did not exit"); + _vstestConsoleWrapper!.EndSession(); _vstestConsoleWrapper = null; + + Assert.IsTrue(consoleProcess.HasExited, "vstest.console did not start"); } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithTelemetryOptedIn(RunnerInfo runnerInfo) { @@ -145,7 +141,7 @@ public void RunTestsWithTelemetryOptedIn(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); Assert.IsTrue(_runEventHandler.Metrics!.ContainsKey(TelemetryDataConstants.TargetDevice)); Assert.IsTrue(_runEventHandler.Metrics.ContainsKey(TelemetryDataConstants.TargetFramework)); Assert.IsTrue(_runEventHandler.Metrics.ContainsKey(TelemetryDataConstants.TargetOS)); @@ -155,7 +151,6 @@ public void RunTestsWithTelemetryOptedIn(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithTelemetryOptedOut(RunnerInfo runnerInfo) { @@ -169,25 +164,19 @@ public void RunTestsWithTelemetryOptedOut(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(0, _runEventHandler.Metrics!.Count); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.HasCount(0, _runEventHandler.Metrics!, _runEventHandler.ToString()); } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + // This is testing the behavior of crash in testhost, run on different testhost, and just .NET runner. + [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); Setup(); - if (IntegrationTestEnvironment.BuildConfiguration.Equals("release", StringComparison.OrdinalIgnoreCase)) - { - // On release, x64 builds, recursive calls may be replaced with loops (tail call optimization) - Assert.Inconclusive("On StackOverflowException testhost not exited in release configuration."); - return; - } - var source = new[] { GetAssetFullPath("SimpleTestProject3.dll") }; _vstestConsoleWrapper.RunTests( @@ -196,17 +185,18 @@ public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) new TestPlatformOptions() { TestCaseFilter = "ExitWithStackoverFlow" }, _runEventHandler); - var errorMessage = runnerInfo.TargetFramework == "net462" - ? $"The active test run was aborted. Reason: Test host process crashed : Process is terminated due to StackOverflowException.{Environment.NewLine}" - : $"The active test run was aborted. Reason: Test host process crashed : Stack overflow.{Environment.NewLine}"; + var errorMessagePattern = runnerInfo.IsNetFrameworkTarget + ? $"The active test run was aborted. Reason: Test host process crashed : Process is terminated due to StackOverflowException.*" + : $"The active test run was aborted. Reason: Test host process crashed : Stack overflow.*"; - _runEventHandler.Errors.Should().Contain(errorMessage); + _runEventHandler.Errors.Should().ContainSingle() + .Which.Should().Match(errorMessagePattern); } [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] - [NetCoreTargetFrameworkDataSource(useCoreRunner: false)] + [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunTestsShouldShowProperWarningOnNoTestsForTestCaseFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -231,8 +221,8 @@ public void RunTestsShouldShowProperWarningOnNoTestsForTestCaseFilter(RunnerInfo var expectedFilter = veryLongTestCaseFilter.Substring(0, 256) + "..."; // Assert - StringAssert.StartsWith(_runEventHandler.LogMessage, $"No test matches the given testcase filter `{expectedFilter}` in"); - StringAssert.EndsWith(_runEventHandler.LogMessage, testAssemblyName); + Assert.StartsWith($"No test matches the given testcase filter `{expectedFilter}` in", _runEventHandler.LogMessage); + Assert.EndsWith(testAssemblyName, _runEventHandler.LogMessage); Assert.AreEqual(TestMessageLevel.Warning, _runEventHandler.TestMessageLevel); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs similarity index 65% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs index 426ce341cf..9ac4c8f513 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Text; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; /// /// The Run Tests using VsTestConsoleWrapper API's @@ -21,7 +21,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; [TestClass] public class RunTestsWithDifferentConfigurationTests : AcceptanceTestBase { - private const string Netcoreapp = "netcoreapp"; + private const string NetFramework = "net4"; private const string Message = "VsTestConsoleWrapper does not support .Net Core Runner"; private IVsTestConsoleWrapper? _vstestConsoleWrapper; @@ -44,7 +44,6 @@ public void Cleanup() } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithTestAdapterPath(RunnerInfo runnerInfo) { @@ -60,14 +59,13 @@ public void RunTestsWithTestAdapterPath(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped), _runEventHandler.ToString()); } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithRunSettingsWithParallel(RunnerInfo runnerInfo) { @@ -92,46 +90,14 @@ public void RunTestsWithRunSettingsWithParallel(RunnerInfo runnerInfo) // Assert _runEventHandler.EnsureSuccess(); - Assert.AreEqual(6, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); + Assert.HasCount(6, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); + Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped), _runEventHandler.ToString()); AssertExpectedNumberOfHostProcesses(expectedNumOfProcessCreated, _logsDir.Path, testHostNames); } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - public void RunTestsWithTestSettings(RunnerInfo runnerInfo) - { - SetTestEnvironment(_testEnvironment, runnerInfo); - ExecuteNotSupportedRunnerFrameworkTests(runnerInfo.RunnerFramework, Netcoreapp, Message); - Setup(); - - var testsettingsFile = Path.Combine(TempDirectory.Path, "tempsettings.testsettings"); - string testSettingsXml = @""; - - File.WriteAllText(testsettingsFile, testSettingsXml, Encoding.UTF8); - var runSettings = $"{FrameworkArgValue}" + testsettingsFile + ""; - var sources = new List - { - GetAssetFullPath("MstestV1UnitTestProject.dll") - }; - - _vstestConsoleWrapper.RunTests( - sources, - runSettings, - _runEventHandler); - - // Assert - Assert.AreEqual(5, _runEventHandler.TestResults.Count); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(2, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Skipped)); - } - - [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunTestsWithX64Source(RunnerInfo runnerInfo) { @@ -154,8 +120,8 @@ public void RunTestsWithX64Source(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(1, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); + Assert.ContainsSingle(_runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); AssertExpectedNumberOfHostProcesses(expectedNumOfProcessCreated, _logsDir.Path, testhostProcessNames); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs similarity index 67% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs index 77627b3e3d..decb300853 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs @@ -1,16 +1,18 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; /// /// The Run Tests using VsTestConsoleWrapper API's @@ -36,30 +38,30 @@ public void Cleanup() [TestMethod] [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [WrapperCompatibilityDataSource] public void RunTestsWithTestCaseFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); _runEventHandler = new RunEventHandler(); - var vstestConsoleWrapper = GetVsTestConsoleWrapper(); + _vstestConsoleWrapper = GetVsTestConsoleWrapper(); var sources = new List { GetAssetFullPath("MSTestProject1.dll") }; - vstestConsoleWrapper.RunTests( + _vstestConsoleWrapper.RunTests( sources, GetDefaultRunSettings(), new TestPlatformOptions() { TestCaseFilter = "FullyQualifiedName=MSTestProject1.UnitTest1.PassingTest" }, _runEventHandler); // Assert - Assert.AreEqual(1, _runEventHandler.TestResults.Count); + Assert.ContainsSingle(_runEventHandler.TestResults, _runEventHandler.ToString()); Assert.AreEqual(TestOutcome.Passed, _runEventHandler.TestResults.First().Outcome); } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + // Validates filter expression that is passed all the way down to testhost, unlikely that we will see difference in beharior between desktop and netcore runners. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunTestsWithFastFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -74,8 +76,8 @@ public void RunTestsWithFastFilter(RunnerInfo runnerInfo) _runEventHandler); // Assert - Assert.AreEqual(2, _runEventHandler.TestResults.Count); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Passed)); - Assert.AreEqual(1, _runEventHandler.TestResults.Count(t => t.Outcome == TestOutcome.Failed)); + Assert.HasCount(2, _runEventHandler.TestResults, _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Passed), _runEventHandler.ToString()); + Assert.ContainsSingle(_runEventHandler.TestResults.Where(t => t.Outcome == TestOutcome.Failed), _runEventHandler.ToString()); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs similarity index 74% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs index f4f71b2a58..1c470a41b6 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs @@ -7,13 +7,16 @@ using System.Linq; using System.Text; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] +// TODO: this comment seems inaccurate and would mean all our linux and macos tests are broken? // We need to dogfood the package built in this repo *-dev and we pack tha tp only on windows [TestCategory("Windows-Review")] public class SerialTestRunDecoratorTests : AcceptanceTestBase @@ -46,8 +49,9 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] + // This is testhost concept, try it on combination of testhosts, and .NET Runner. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) { // Arrange @@ -61,14 +65,15 @@ public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) _runEventHandler.EnsureSuccess(); // Assert - Assert.AreEqual(10, _discoveryEventHandler.DiscoveredTestCases.Count); + Assert.HasCount(10, _discoveryEventHandler.DiscoveredTestCases); int failedTests = _runEventHandler.TestResults.Count(x => x.Outcome == TestOutcome.Failed); - Assert.IsFalse(failedTests > 0, $"Number of failed tests {failedTests}"); + Assert.AreEqual(0, failedTests, $"Number of failed tests {failedTests}"); } [TestMethod] - [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] + // This is testhost concept, try it on combination of testhosts, and .NET Runner. + [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInfo runnerInfo) { // Arrange @@ -83,9 +88,9 @@ public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInf _runEventHandler.EnsureSuccess(); // Assert - Assert.AreEqual(10, _discoveryEventHandler.DiscoveredTestCases.Count); + Assert.HasCount(10, _discoveryEventHandler.DiscoveredTestCases); int failedTests = _runEventHandler.TestResults.Count(x => x.Outcome == TestOutcome.Failed); - Assert.IsTrue(failedTests > 0, $"Number of failed tests {failedTests}"); + Assert.IsGreaterThan(0, failedTests, $"Number of failed tests {failedTests}"); } [TestMethod] @@ -100,7 +105,7 @@ public void DiscoverTestsAndRunTestsSequentially_IsNotSupportedForSources(Runner // Act var testDll = GetAssetFullPath("SerializeTestRunTestProject.dll"); _vstestConsoleWrapper.RunTests(new string[] { testDll }, _runsettings, _runEventHandler); - _ = Assert.ThrowsException(_runEventHandler.EnsureSuccess); + _ = Assert.ThrowsExactly(_runEventHandler.EnsureSuccess); StringBuilder builder = new(); foreach (string? error in _runEventHandler.Errors) @@ -108,7 +113,7 @@ public void DiscoverTestsAndRunTestsSequentially_IsNotSupportedForSources(Runner builder.AppendLine(error); } - Assert.IsTrue(_runEventHandler.Errors.Count > 0); - Assert.IsTrue(_runEventHandler.Errors.Contains(VisualStudio.TestPlatform.Common.Resources.Resources.SerialTestRunInvalidScenario), $"Error messages\n:{builder}"); + Assert.IsNotEmpty(_runEventHandler.Errors, _runEventHandler.ToString()); + Assert.Contains(VisualStudio.TestPlatform.Common.Resources.Resources.SerialTestRunInvalidScenario, _runEventHandler.Errors, $"Error messages\n:{builder}"); } } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs similarity index 78% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs rename to test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs index 814d4fa70d..bb553f04ca 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs @@ -6,11 +6,14 @@ using System.IO; using System.Linq; +using Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests.EventHandler; +using Microsoft.TestPlatform.TestUtilities; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests; +namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] // We need to dogfood the package built in this repo *-dev and we pack tha tp only on windows @@ -42,7 +45,6 @@ public void ExecuteContainerInMultiHost(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, 3); [TestMethod] - [Ignore("Flaky on server with object has been disposed")] [NetCoreTargetFrameworkDataSource] [NetFullTargetFrameworkDataSource] public void ExecuteContainerInMultiHost_MoreHostsThanTests(RunnerInfo runnerInfo) @@ -58,7 +60,13 @@ private void ExecuteContainerInMultiHost(RunnerInfo runnerInfo, int expectedHost { // Arrange SetTestEnvironment(_testEnvironment, runnerInfo); - Dictionary? environmentVariables = new() { { "VSTEST_LOGFOLDER", TempDirectory.Path } }; + Dictionary? environmentVariables = new() + { + // Times out sometimes in CI with the 5 second timeout we normally use to get faster failure feedback. + // Probably because we start many testhosts here and the server is slow and busy. + [EnvironmentHelper.VstestConnectionTimeout] = "90", + ["VSTEST_LOGFOLDER"] = TempDirectory.Path, + }; Setup(environmentVariables); string runsettings = $""" @@ -76,12 +84,12 @@ private void ExecuteContainerInMultiHost(RunnerInfo runnerInfo, int expectedHost _runEventHandler.EnsureSuccess(); // Assert - Assert.AreEqual(10, _discoveryEventHandler.DiscoveredTestCases.Count); + Assert.HasCount(10, _discoveryEventHandler.DiscoveredTestCases); int failedTests = _runEventHandler.TestResults.Count(x => x.Outcome == TestOutcome.Failed); - Assert.IsFalse(failedTests > 0, $"Number of failed tests {failedTests}"); + Assert.IsLessThanOrEqualTo(0, failedTests, $"Number of failed tests {failedTests}"); string[] hosts = Directory.GetFiles(TempDirectory.Path, "TestHost*"); - Assert.AreEqual(expectedHost == -1 ? 1 : expectedHost > 10 ? 10 : expectedHost, hosts.Length); + Assert.HasCount(expectedHost == -1 ? 1 : expectedHost > 10 ? 10 : expectedHost, hosts); List tests = new(); int testsRunInsideHost; @@ -103,15 +111,15 @@ private void ExecuteContainerInMultiHost(RunnerInfo runnerInfo, int expectedHost if (expectedHost == 3) { - Assert.IsTrue(testsRunInsideHost >= 3); + Assert.IsGreaterThanOrEqualTo(3, testsRunInsideHost); } } - Assert.AreEqual(10, tests.Count); + Assert.HasCount(10, tests); for (int i = 1; i <= 10; i++) { tests.Remove($"TestMethod{i}"); } - Assert.AreEqual(0, tests.Count); + Assert.IsEmpty(tests); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs index cead712f27..1567335e6d 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Client/BaseTestRunCriteriaTests.cs @@ -14,54 +14,21 @@ public class BaseTestRunCriteriaTests [TestMethod] public void ConstructorShouldThrowIfFrequencyOfRunStatsChangeIsZero() { - var isExceptionThrown = false; - - try - { - var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 0); - } - catch (ArgumentOutOfRangeException ex) - { - isExceptionThrown = true; - StringAssert.Contains(ex.Message, "Notification frequency need to be a positive value."); - } - - Assert.IsTrue(isExceptionThrown); + var ex = Assert.ThrowsExactly(() => new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 0)); + Assert.Contains("Notification frequency need to be a positive value.", ex.Message); } [TestMethod] public void ConstructorShouldThrowIfFrequencyOfRunStatsChangeIsLesssThanZero() { - var isExceptionThrown = false; - - try - { - var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: -10); - } - catch (ArgumentOutOfRangeException ex) - { - isExceptionThrown = true; - StringAssert.Contains(ex.Message, "Notification frequency need to be a positive value."); - } - - Assert.IsTrue(isExceptionThrown); + var ex = Assert.ThrowsExactly(() => new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: -10)); + Assert.Contains("Notification frequency need to be a positive value.", ex.Message); } [TestMethod] public void ConstructorShouldThrowIfRunStatsChangeEventTimeoutIsMinimumTimeSpanValue() { - var isExceptionThrown = false; - - try - { - var criteria = new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 1, keepAlive: false, testSettings: null, runStatsChangeEventTimeout: TimeSpan.MinValue); - } - catch (ArgumentOutOfRangeException ex) - { - isExceptionThrown = true; - StringAssert.Contains(ex.Message, "Notification timeout must be greater than zero."); - } - - Assert.IsTrue(isExceptionThrown); + var ex = Assert.ThrowsExactly(() => new BaseTestRunCriteria(frequencyOfRunStatsChangeEvent: 1, keepAlive: false, testSettings: null, runStatsChangeEventTimeout: TimeSpan.MinValue)); + Assert.Contains("Notification timeout must be greater than zero.", ex.Message); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomKeyValueConverterTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomKeyValueConverterTests.cs index 804cf98ce2..8692d0361b 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomKeyValueConverterTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomKeyValueConverterTests.cs @@ -28,7 +28,7 @@ public void CustomKeyValueConverterShouldDeserializeWellformedJson() var data = _customKeyValueConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as KeyValuePair[]; Assert.IsNotNull(data); - Assert.AreEqual(1, data.Length); + Assert.HasCount(1, data); Assert.AreEqual("key1", data[0].Key); Assert.AreEqual("val1", data[0].Value); } @@ -41,7 +41,7 @@ public void CustomKeyValueConverterShouldDeserializeKeyValuePairArray() var data = _customKeyValueConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as KeyValuePair[]; Assert.IsNotNull(data); - Assert.AreEqual(2, data.Length); + Assert.HasCount(2, data); Assert.AreEqual("key1", data[0].Key); Assert.AreEqual("val1", data[0].Value); Assert.AreEqual("key2", data[1].Key); @@ -56,7 +56,7 @@ public void CustomKeyValueConverterShouldDeserializeEmptyArray() var data = _customKeyValueConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as KeyValuePair[]; Assert.IsNotNull(data); - Assert.AreEqual(0, data.Length); + Assert.IsEmpty(data); } [TestMethod] @@ -67,7 +67,7 @@ public void CustomKeyValueConverterShouldDeserializeEmptyKeyOrValue() var data = _customKeyValueConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as KeyValuePair[]; Assert.IsNotNull(data); - Assert.AreEqual(1, data.Length); + Assert.HasCount(1, data); Assert.AreEqual(string.Empty, data[0].Key); Assert.AreEqual(string.Empty, data[0].Value); } @@ -80,7 +80,7 @@ public void CustomKeyValueConverterShouldDeserializeDuplicateKeysKvps() var data = _customKeyValueConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as KeyValuePair[]; Assert.IsNotNull(data); - Assert.AreEqual(2, data.Length); + Assert.HasCount(2, data); Assert.AreEqual("key1", data[0].Key); Assert.AreEqual("val1", data[0].Value); Assert.AreEqual("key1", data[1].Key); diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomStringArrayConverterTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomStringArrayConverterTests.cs index 3e235f3792..eaac20df33 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomStringArrayConverterTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/CustomStringArrayConverterTests.cs @@ -27,7 +27,7 @@ public void CustomStringArrayConverterShouldDeserializeWellformedJson() var data = _customStringArrayConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as string[]; Assert.IsNotNull(data); - Assert.AreEqual(2, data.Length); + Assert.HasCount(2, data); CollectionAssert.AreEqual(new[] { "val2", "val1" }, data); } @@ -39,7 +39,7 @@ public void CustomStringArrayConverterShouldDeserializeEmptyArray() var data = _customStringArrayConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as string[]; Assert.IsNotNull(data); - Assert.AreEqual(0, data.Length); + Assert.IsEmpty(data); } [TestMethod] @@ -50,7 +50,7 @@ public void CustomStringArrayConverterShouldDeserializeNullKeyOrValue() var data = _customStringArrayConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as string[]; Assert.IsNotNull(data); - Assert.AreEqual(2, data.Length); + Assert.HasCount(2, data); Assert.IsNull(data[0]); Assert.AreEqual("val", data[1]); } @@ -63,7 +63,7 @@ public void CustomStringArrayConverterShouldDeserializeEmptyKeyOrValue() var data = _customStringArrayConverter.ConvertFrom(null, CultureInfo.InvariantCulture, json) as string[]; Assert.IsNotNull(data); - Assert.AreEqual(2, data.Length); + Assert.HasCount(2, data); Assert.AreEqual(string.Empty, data[0]); Assert.AreEqual(string.Empty, data[1]); } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCompleteEventArgsRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCompleteEventArgsRegressionTests.cs new file mode 100644 index 0000000000..e6dd5c5d40 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCompleteEventArgsRegressionTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for DiscoveryCompleteEventArgs source status properties. +/// +[TestClass] +public class DiscoveryCompleteEventArgsRegressionTests +{ + // Regression test for #3381 — Change serializer settings to not send empty values + // DiscoveryCompleteEventArgs was extended with source status lists. + [TestMethod] + public void Constructor_ShouldSetTotalCountAndAborted() + { + var args = new DiscoveryCompleteEventArgs(42, false); + + Assert.AreEqual(42, args.TotalCount); + Assert.IsFalse(args.IsAborted); + } + + // Regression test for #3381 + [TestMethod] + public void FullyDiscoveredSources_ShouldBeSettableAndGettable() + { + var args = new DiscoveryCompleteEventArgs(10, false) + { + FullyDiscoveredSources = new List { "a.dll", "b.dll" } + }; + + Assert.IsNotNull(args.FullyDiscoveredSources); + Assert.HasCount(2, args.FullyDiscoveredSources!); + } + + // Regression test for #3381 + [TestMethod] + public void PartiallyDiscoveredSources_ShouldBeSettableAndGettable() + { + var args = new DiscoveryCompleteEventArgs(5, true) + { + PartiallyDiscoveredSources = new List { "partial.dll" } + }; + + Assert.IsNotNull(args.PartiallyDiscoveredSources); + Assert.HasCount(1, args.PartiallyDiscoveredSources!); + } + + // Regression test for #3381 + [TestMethod] + public void NotDiscoveredSources_ShouldBeSettableAndGettable() + { + var args = new DiscoveryCompleteEventArgs(0, true) + { + NotDiscoveredSources = new List { "missing.dll" } + }; + + Assert.IsNotNull(args.NotDiscoveredSources); + Assert.HasCount(1, args.NotDiscoveredSources!); + } + + // Regression test for #3381 + [TestMethod] + public void SkippedDiscoveredSources_ShouldBeSettableAndGettable() + { + var args = new DiscoveryCompleteEventArgs(0, false) + { + SkippedDiscoveredSources = new List { "skipped.dll" } + }; + + Assert.IsNotNull(args.SkippedDiscoveredSources); + Assert.HasCount(1, args.SkippedDiscoveredSources!); + } + + // Regression test for #3381 + [TestMethod] + public void AllSourceStatusLists_DefaultToEmptyOrNull() + { + var args = new DiscoveryCompleteEventArgs(0, false); + + // The default may be empty lists or null depending on the constructor + // Verify at least that they are accessible without throwing + _ = args.FullyDiscoveredSources; + _ = args.PartiallyDiscoveredSources; + _ = args.NotDiscoveredSources; + _ = args.SkippedDiscoveredSources; + } + + // Regression test for #3381 + [TestMethod] + public void Metrics_ShouldBeSettableAndGettable() + { + var args = new DiscoveryCompleteEventArgs(10, false) + { + Metrics = new Dictionary + { + { "TestDiscovery.TotalTests", 10 }, + { "TestDiscovery.TimeTaken", 1.5 } + } + }; + + Assert.IsNotNull(args.Metrics); + Assert.HasCount(2, args.Metrics!); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCriteriaRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCriteriaRegressionTests.cs new file mode 100644 index 0000000000..236bbcdac4 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DiscoveryCriteriaRegressionTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for DiscoveryCriteria source management. +/// +[TestClass] +public class DiscoveryCriteriaRegressionTests +{ + // Regression test for #3381 — Change serializer settings to not send empty values + // DiscoveryCriteria was extended to track source discovery status. + + [TestMethod] + public void Constructor_WithSources_ShouldPopulateAdapterSourceMap() + { + var sources = new[] { "test1.dll", "test2.dll" }; + var criteria = new DiscoveryCriteria(sources, frequencyOfDiscoveredTestsEvent: 100, testSettings: null); + + Assert.IsNotNull(criteria.AdapterSourceMap); + Assert.IsNotEmpty(criteria.AdapterSourceMap); + } + + [TestMethod] + public void Sources_ShouldReturnAllConfiguredSources() + { + var sources = new[] { "test1.dll", "test2.dll", "test3.dll" }; + var criteria = new DiscoveryCriteria(sources, frequencyOfDiscoveredTestsEvent: 100, testSettings: null); + + var actualSources = criteria.Sources.ToList(); + Assert.HasCount(3, actualSources); + } + + [TestMethod] + public void Package_ShouldBeSettableAndGettable() + { + var criteria = new DiscoveryCriteria( + new[] { "test.dll" }, + frequencyOfDiscoveredTestsEvent: 100, + testSettings: null) + { + Package = @"C:\Path\app.msix" + }; + + Assert.AreEqual(@"C:\Path\app.msix", criteria.Package); + } + + // Regression test for #3381 + [TestMethod] + public void Constructor_WithRunSettings_ShouldStoreRunSettings() + { + var runSettings = ""; + var criteria = new DiscoveryCriteria( + new[] { "test.dll" }, + frequencyOfDiscoveredTestsEvent: 50, + testSettings: runSettings); + + Assert.AreEqual(runSettings, criteria.RunSettings); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Hosting/TestRunnerConnectionInfoExtensionsTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Hosting/TestRunnerConnectionInfoExtensionsTests.cs index abdc716457..37bdd0a664 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Hosting/TestRunnerConnectionInfoExtensionsTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Hosting/TestRunnerConnectionInfoExtensionsTests.cs @@ -18,7 +18,7 @@ public void ToCommandLineOptionsShouldIncludePort() var options = connectionInfo.ToCommandLineOptions(); - StringAssert.StartsWith(options, "--port 123 --endpoint 127.0.0.0:123 --role client"); + Assert.StartsWith("--port 123 --endpoint 127.0.0.0:123 --role client", options); } [TestMethod] @@ -28,7 +28,7 @@ public void ToCommandLineOptionsShouldIncludeEndpoint() var options = connectionInfo.ToCommandLineOptions(); - StringAssert.Contains(options, "--endpoint 127.0.0.0:123"); + Assert.Contains("--endpoint 127.0.0.0:123", options); } [TestMethod] @@ -38,7 +38,7 @@ public void ToCommandLineOptionsShouldIncludeRole() var options = connectionInfo.ToCommandLineOptions(); - StringAssert.Contains(options, "--role client"); + Assert.Contains("--role client", options); } [TestMethod] @@ -48,7 +48,7 @@ public void ToCommandLineOptionsShouldIncludeParentProcessId() var options = connectionInfo.ToCommandLineOptions(); - Assert.IsTrue(options.IndexOf("--parentprocessid 123", StringComparison.OrdinalIgnoreCase) >= 0); + Assert.IsGreaterThanOrEqualTo(0, options.IndexOf("--parentprocessid 123", StringComparison.OrdinalIgnoreCase)); } [TestMethod] @@ -58,7 +58,7 @@ public void ToCommandLineOptionsShouldNotIncludeDiagnosticsOptionIfNotEnabled() var options = connectionInfo.ToCommandLineOptions(); - Assert.IsFalse(options.IndexOf("--diag", StringComparison.OrdinalIgnoreCase) >= 0); + Assert.IsLessThan(0, options.IndexOf("--diag", StringComparison.OrdinalIgnoreCase)); } [TestMethod] @@ -68,6 +68,6 @@ public void ToCommandLineOptionsShouldIncludeDiagnosticsOptionIfEnabled() var options = connectionInfo.ToCommandLineOptions(); - StringAssert.EndsWith(options, "--diag log.txt --tracelevel 3"); + Assert.EndsWith("--diag log.txt --tracelevel 3", options); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.csproj b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.csproj index 6429c9b26b..744ca8bb29 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Microsoft.TestPlatform.ObjectModel.UnitTests.csproj @@ -6,8 +6,8 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.ObjectModel.UnitTests @@ -15,11 +15,4 @@ $(NewtonsoftJsonVersion) - - - - - - - diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Program.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Program.cs deleted file mode 100644 index 3911cf3a23..0000000000 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.ObjectModel.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RegressionBugFixTests.cs new file mode 100644 index 0000000000..31eb39f1e2 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RegressionBugFixTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection.Metadata; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression test for GH-3454: +/// PortablePdbReader(MetadataReaderProvider) constructor must validate its argument. +/// The fix added null-arg-check: passing null must throw ArgumentNullException. +/// +[TestClass] +public class RegressionBugFixTests +{ + [TestMethod] + public void PortablePdbReader_NullMetadataReaderProvider_MustThrowArgumentNullException() + { + // GH-3454: The constructor added for embedded PDB support validates its parameter. + // If the fix were reverted (constructor removed or null check removed), + // this would either not compile or throw a different exception (NullReferenceException). + var ex = Assert.ThrowsExactly( + () => new PortablePdbReader((MetadataReaderProvider)null!)); + + Assert.AreEqual("metadataReaderProvider", ex.ParamName, + "GH-3454: Parameter name in ArgumentNullException must be 'metadataReaderProvider'."); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/RunConfigurationTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/RunConfigurationTests.cs index cc548cd386..0a9561aa91 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/RunConfigurationTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/RunSettings/RunConfigurationTests.cs @@ -9,8 +9,6 @@ using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MSTest.TestFramework.AssertExtensions; - namespace Microsoft.TestPlatform.ObjectModel.UnitTests; [TestClass] @@ -119,23 +117,23 @@ public void SetTargetFrameworkVersionShouldSetTargetFramework() { var runConfiguration = new RunConfiguration(); runConfiguration.TargetFrameworkVersion = FrameworkVersion.Framework35; - Equals(Framework.FromString("Framework35")!.Name, runConfiguration.TargetFramework!.Name); + Assert.AreEqual(Framework.FromString("Framework35")!.Name, runConfiguration.TargetFramework!.Name); Assert.AreEqual(FrameworkVersion.Framework35, runConfiguration.TargetFrameworkVersion); runConfiguration.TargetFrameworkVersion = FrameworkVersion.Framework40; - Equals(Framework.FromString("Framework40")!.Name, runConfiguration.TargetFramework.Name); + Assert.AreEqual(Framework.FromString("Framework40")!.Name, runConfiguration.TargetFramework.Name); Assert.AreEqual(FrameworkVersion.Framework40, runConfiguration.TargetFrameworkVersion); runConfiguration.TargetFrameworkVersion = FrameworkVersion.Framework45; - Equals(Framework.FromString("Framework45")!.Name, runConfiguration.TargetFramework.Name); + Assert.AreEqual(Framework.FromString("Framework45")!.Name, runConfiguration.TargetFramework.Name); Assert.AreEqual(FrameworkVersion.Framework45, runConfiguration.TargetFrameworkVersion); runConfiguration.TargetFrameworkVersion = FrameworkVersion.FrameworkCore10; - Equals(Framework.FromString("FrameworkCore10")!.Name, runConfiguration.TargetFramework.Name); + Assert.AreEqual(Framework.FromString("FrameworkCore10")!.Name, runConfiguration.TargetFramework.Name); Assert.AreEqual(FrameworkVersion.FrameworkCore10, runConfiguration.TargetFrameworkVersion); runConfiguration.TargetFrameworkVersion = FrameworkVersion.FrameworkUap10; - Equals(Framework.FromString("FrameworkUap10")!.Name, runConfiguration.TargetFramework.Name); + Assert.AreEqual(Framework.FromString("FrameworkUap10")!.Name, runConfiguration.TargetFramework.Name); Assert.AreEqual(FrameworkVersion.FrameworkUap10, runConfiguration.TargetFrameworkVersion); } @@ -172,9 +170,9 @@ public void RunConfigurationFromXmlThrowsSettingsExceptionIfBatchSizeIsInvalid() "; - Assert.That.Throws( - () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)) - .WithExactMessage("Invalid settings 'RunConfiguration'. Invalid value 'Foo' specified for 'BatchSize'."); + var exception = Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + Assert.AreEqual("Invalid settings 'RunConfiguration'. Invalid value 'Foo' specified for 'BatchSize'.", exception.Message); } [TestMethod] @@ -189,9 +187,9 @@ public void RunConfigurationFromXmlThrowsSettingsExceptionIfTestSessionTimeoutIs "; - Assert.That.Throws( - () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)) - .WithExactMessage("Invalid settings 'RunConfiguration'. Invalid value '-1' specified for 'TestSessionTimeout'."); + var exception = Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + Assert.AreEqual("Invalid settings 'RunConfiguration'. Invalid value '-1' specified for 'TestSessionTimeout'.", exception.Message); } [TestMethod] @@ -221,9 +219,9 @@ public void RunConfigurationFromXmlThrowsSettingsExceptionIfExecutionThreadApart "; - Assert.That.Throws( - () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)) - .WithExactMessage("Invalid settings 'RunConfiguration'. Invalid value 'RandomValue' specified for 'ExecutionThreadApartmentState'."); + var exception = Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + Assert.AreEqual("Invalid settings 'RunConfiguration'. Invalid value 'RandomValue' specified for 'ExecutionThreadApartmentState'.", exception.Message); } [TestMethod] @@ -237,14 +235,14 @@ public void RunConfigurationFromXmlThrowsSettingsExceptionIfBatchSizeIsNegativeI "; - Assert.That.Throws( - () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)) - .WithExactMessage("Invalid settings 'RunConfiguration'. Invalid value '-10' specified for 'BatchSize'."); + var exception = Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + Assert.AreEqual("Invalid settings 'RunConfiguration'. Invalid value '-10' specified for 'BatchSize'.", exception.Message); } [DataRow(true)] [DataRow(false)] - [DataTestMethod] + [TestMethod] public void RunConfigurationShouldReadValueForDesignMode(bool designModeValue) { string settingsXml = string.Format( @@ -282,12 +280,12 @@ public void RunConfigurationToXmlShouldProvideDesignMode() { var runConfiguration = new RunConfiguration { DesignMode = true }; - StringAssert.Contains(runConfiguration.ToXml().InnerXml, "True"); + Assert.Contains("True", runConfiguration.ToXml().InnerXml); } [DataRow(true)] [DataRow(false)] - [DataTestMethod] + [TestMethod] public void RunConfigurationShouldReadValueForCollectSourceInformation(bool val) { string settingsXml = string.Format( @@ -320,13 +318,13 @@ public void RunConfigurationShouldSetCollectSourceInformationSameAsDesignModeByD Assert.AreEqual(runConfiguration.DesignMode, runConfiguration.ShouldCollectSourceInformation); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void RunConfigurationToXmlShouldProvideCollectSourceInformationSameAsDesignMode(bool val) { var runConfiguration = new RunConfiguration { DesignMode = val }; - StringAssert.Contains(runConfiguration.ToXml().InnerXml.ToUpperInvariant(), $"{val}".ToUpperInvariant()); + Assert.Contains($"{val}".ToUpperInvariant(), runConfiguration.ToXml().InnerXml.ToUpperInvariant()); } [TestMethod] @@ -334,7 +332,7 @@ public void RunConfigurationToXmlShouldProvideExecutionThreadApartmentState() { var runConfiguration = new RunConfiguration { ExecutionThreadApartmentState = PlatformApartmentState.STA }; - StringAssert.Contains(runConfiguration.ToXml().InnerXml, "STA"); + Assert.Contains("STA", runConfiguration.ToXml().InnerXml); } [TestMethod] @@ -348,8 +346,58 @@ public void RunConfigurationShouldThrowSettingsExceptionIfResultsirectoryIsEmpty "; - Assert.That.Throws( - () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)) - .WithExactMessage("Invalid settings 'RunConfiguration'. Invalid value '' specified for 'ResultsDirectory'."); + var exception = Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + Assert.AreEqual("Invalid settings 'RunConfiguration'. Invalid value '' specified for 'ResultsDirectory'.", exception.Message); + } + + [TestMethod] + public void RunConfigurationDefaultValueForCreateNoNewWindowShouldBeTrue() + { + var runConfiguration = new RunConfiguration(); + + Assert.IsTrue(runConfiguration.CreateNoNewWindow); + } + + [DataRow(true)] + [DataRow(false)] + [TestMethod] + public void RunConfigurationShouldReadValueForCreateNoNewWindow(bool createNoNewWindowValue) + { + string settingsXml = string.Format( + CultureInfo.CurrentCulture, + @" + + + {0} + + ", createNoNewWindowValue); + + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml); + + Assert.AreEqual(createNoNewWindowValue, runConfiguration.CreateNoNewWindow); + } + + [TestMethod] + public void RunConfigurationFromXmlThrowsSettingsExceptionIfCreateNoNewWindowIsInvalid() + { + string settingsXml = + @" + + + InvalidValue + + "; + + Assert.ThrowsExactly( + () => XmlRunSettingsUtilities.GetRunConfigurationNode(settingsXml)); + } + + [TestMethod] + public void RunConfigurationToXmlShouldProvideCreateNoNewWindow() + { + var runConfiguration = new RunConfiguration(); + + Assert.Contains("True", runConfiguration.ToXml().InnerXml); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestCaseSerializationRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestCaseSerializationRegressionTests.cs new file mode 100644 index 0000000000..23b17aaf53 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestCaseSerializationRegressionTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for TestCase serialization and TestProperty behavior. +/// +[TestClass] +public class TestCaseSerializationRegressionTests +{ + // Regression test for #15370 — Fix .NET 10 regression for traits + // When a TestCase is serialized and deserialized, traits should survive because + // TestProperty.Equals compares by Id (not reference). + [TestMethod] + public void TestCase_TraitsShouldSurvive_PropertyRegisteredSeparately() + { + // This tests the core of the #15370 fix: when TestProperty instances differ in reference + // but have the same Id, traits should still be accessible. + var testCase = new TestCase("Namespace.TestClass.Method", new Uri("executor://test"), "test.dll"); + testCase.Traits.Add("Priority", "1"); + testCase.Traits.Add("Category", "Regression"); + + // Get traits — this exercises the fix in TraitCollection.GetTraits() + // which now uses EqualityComparer.Default + var traits = testCase.Traits.ToList(); + + Assert.HasCount(2, traits); + var priorityTrait = traits.First(t => t.Name == "Priority"); + Assert.AreEqual("1", priorityTrait.Value); + var categoryTrait = traits.First(t => t.Name == "Category"); + Assert.AreEqual("Regression", categoryTrait.Value); + } + + // Regression test for #15370 + [TestMethod] + public void TestProperty_Register_SameId_ShouldReturnEqualProperties() + { + // Register two TestProperty instances with the same Id + var prop1 = TestProperty.Register("MyTest.Property.Same", "Label1", typeof(string), typeof(TestCase)); + var prop2 = TestProperty.Register("MyTest.Property.Same", "Label2", typeof(string), typeof(TestCase)); + + // They should be equal (by Id) + Assert.AreEqual(prop1, prop2); + Assert.IsTrue(prop1.Equals(prop2)); + Assert.IsTrue(prop1.Equals((object)prop2)); + } + + // Regression test for #15370 + [TestMethod] + public void TestCase_SetPropertyAndRetrieve_WithDifferentPropertyInstance() + { + var testCase = new TestCase("Ns.Class.Method", new Uri("executor://test"), "test.dll"); + + // Register a custom property + var prop1 = TestProperty.Register("Custom.Property.Id", "Custom Property", typeof(string), typeof(TestCase)); + testCase.SetPropertyValue(prop1, "TestValue"); + + // Try retrieving with a "different" registration (same Id) + var prop2 = TestProperty.Register("Custom.Property.Id", "Custom Property", typeof(string), typeof(TestCase)); + var value = testCase.GetPropertyValue(prop2, null); + + Assert.AreEqual("TestValue", value); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectPropertyStorageRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectPropertyStorageRegressionTests.cs new file mode 100644 index 0000000000..9f4b599875 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectPropertyStorageRegressionTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for TestObject property storage behavior. +/// +[TestClass] +public class TestObjectPropertyStorageRegressionTests +{ + // Regression test for #15370 — Fix .NET 10 regression for traits + // Properties collection should use proper equality (by Id, not reference). + [TestMethod] + public void Properties_Contains_ShouldFindPropertyById() + { + var testCase = new TestCase("Ns.Class.Method", new System.Uri("executor://test"), "source.dll"); + + // The TestCase has several built-in properties + var properties = testCase.Properties; + + // TestCase.FullyQualifiedNameProperty should be found + bool found = properties.Any(p => p.Id == TestCaseProperties.FullyQualifiedName.Id); + Assert.IsTrue(found, "FullyQualifiedName property should be present."); + } + + // Regression test for #15370 + [TestMethod] + public void SetAndGetProperty_WithSameIdDifferentInstances_ShouldWork() + { + var testCase = new TestCase("Ns.Class.Method", new System.Uri("executor://test"), "source.dll"); + + var prop = TestProperty.Register("TestObj.Storage.Test1", "Test Label", typeof(string), typeof(TestCase)); + testCase.SetPropertyValue(prop, "hello"); + + // Re-register (same id, effectively gets the same or equal instance) + var prop2 = TestProperty.Register("TestObj.Storage.Test1", "Test Label", typeof(string), typeof(TestCase)); + var value = testCase.GetPropertyValue(prop2, null); + + Assert.AreEqual("hello", value); + } + + // Regression test for #15370 + [TestMethod] + public void TestCase_Traits_ShouldBeAccessibleViaEnumerator() + { + var testCase = new TestCase("Ns.Class.Method", new System.Uri("executor://test"), "source.dll"); + testCase.Traits.Add("key1", "val1"); + testCase.Traits.Add("key2", "val2"); + + var traitList = new List(); + foreach (var trait in testCase.Traits) + { + traitList.Add(trait); + } + + Assert.HasCount(2, traitList); + } + + // Regression test for #15249 — Avoid iterator in TraitCollection.GetTraits + [TestMethod] + public void TestCase_ManyTraits_ShouldAllBeReturned() + { + var testCase = new TestCase("Ns.Class.Method", new System.Uri("executor://test"), "source.dll"); + + for (int i = 0; i < 100; i++) + { + testCase.Traits.Add($"Key{i}", $"Value{i}"); + } + + var traits = testCase.Traits.ToList(); + Assert.HasCount(100, traits); + + // Verify first and last traits + var firstTrait = traits.First(t => t.Name == "Key0"); + Assert.AreEqual("Value0", firstTrait.Value); + var lastTrait = traits.First(t => t.Name == "Key99"); + Assert.AreEqual("Value99", lastTrait.Value); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs index 51278d94ea..3980e88d1e 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs @@ -41,6 +41,6 @@ public void GetPropertiesShouldReturnListOfPropertiesInStore() TestCase.SetPropertyValue(kvp.Key, kvp.Value); var properties = TestCase.GetProperties().ToList(); - Assert.IsTrue(properties.Contains(kvp)); + Assert.Contains(kvp, properties); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestPropertyEqualityRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestPropertyEqualityRegressionTests.cs new file mode 100644 index 0000000000..2377d10330 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestPropertyEqualityRegressionTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for TestProperty.Equals behavior. +/// +[TestClass] +public class TestPropertyEqualityRegressionTests +{ + // Regression test for #15370 — Fix .NET 10 regression for traits + // TestProperty.Equals(object) was calling base.Equals() (reference equality) + // instead of this.Equals() (Id-based equality). + [TestMethod] + public void Equals_TwoPropertiesWithSameId_ShouldBeEqual() + { + var property1 = TestProperty.Register("TestId.Property1", "Label1", typeof(string), typeof(TestCase)); + var property2 = TestProperty.Register("TestId.Property1", "Label2", typeof(string), typeof(TestCase)); + + Assert.IsTrue(property1.Equals((object)property2), + "TestProperty.Equals(object) should compare by Id, not by reference."); + } + + // Regression test for #15370 + [TestMethod] + public void Equals_TwoPropertiesWithDifferentIds_ShouldNotBeEqual() + { + var property1 = TestProperty.Register("TestId.PropertyA", "LabelA", typeof(string), typeof(TestCase)); + var property2 = TestProperty.Register("TestId.PropertyB", "LabelB", typeof(string), typeof(TestCase)); + + Assert.IsFalse(property1.Equals((object)property2)); + } + + // Regression test for #15370 + [TestMethod] + public void Equals_NullObject_ShouldReturnFalse() + { + var property = TestProperty.Register("TestId.NullTest", "Label", typeof(string), typeof(TestCase)); + + Assert.IsFalse(property.Equals((object?)null)); + } + + // Regression test for #15370 + [TestMethod] + public void Equals_NonTestPropertyObject_ShouldReturnFalse() + { + var property = TestProperty.Register("TestId.TypeTest", "Label", typeof(string), typeof(TestCase)); + + Assert.IsFalse(property.Equals("not a TestProperty")); + } + + // Regression test for #15370 + [TestMethod] + public void GetHashCode_TwoPropertiesWithSameId_ShouldBeEqual() + { + var property1 = TestProperty.Register("TestId.HashTest", "Label1", typeof(string), typeof(TestCase)); + var property2 = TestProperty.Register("TestId.HashTest", "Label2", typeof(string), typeof(TestCase)); + + Assert.AreEqual(property1.GetHashCode(), property2.GetHashCode()); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTests.cs index a6219cc386..6951e1d274 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTests.cs @@ -25,20 +25,20 @@ public TestResultTests() [TestMethod] public void TestResultShouldInitializeEmptyAttachments() { - Assert.AreEqual(0, _result.Attachments.Count); + Assert.IsEmpty(_result.Attachments); } [TestMethod] public void TestResultShouldInitializeEmptyMessages() { - Assert.AreEqual(0, _result.Messages.Count); + Assert.IsEmpty(_result.Messages); } [TestMethod] public void TestResultShouldInitializeStartAndEndTimeToCurrent() { - Assert.IsTrue(_result.StartTime.Subtract(DateTimeOffset.UtcNow) < new TimeSpan(0, 0, 0, 10)); - Assert.IsTrue(_result.EndTime.Subtract(DateTimeOffset.UtcNow) < new TimeSpan(0, 0, 0, 10)); + Assert.IsLessThan(new TimeSpan(0, 0, 0, 10), _result.StartTime.Subtract(DateTimeOffset.UtcNow)); + Assert.IsLessThan(new TimeSpan(0, 0, 0, 10), _result.EndTime.Subtract(DateTimeOffset.UtcNow)); } #region GetSetPropertyValue Tests diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTimingRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTimingRegressionTests.cs new file mode 100644 index 0000000000..0cd5288cc7 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestResultTimingRegressionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for TestResult properties and timing behavior. +/// +[TestClass] +public class TestResultTimingRegressionTests +{ + // Regression test for #5143 — Fix timing in simple log + // TestResult should preserve duration and timing information correctly. + + [TestMethod] + public void TestResult_Duration_ShouldBeIndependentOfStartEndTime() + { + var testCase = new TestCase("Test1", new Uri("executor://test"), "test.dll"); + var result = new TestResult(testCase) + { + Duration = TimeSpan.FromSeconds(5) + }; + + Assert.AreEqual(TimeSpan.FromSeconds(5), result.Duration); + } + + [TestMethod] + public void TestResult_StartAndEndTime_ShouldBeSettable() + { + var testCase = new TestCase("Test2", new Uri("executor://test"), "test.dll"); + var start = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero); + var end = start + TimeSpan.FromSeconds(10); + + var result = new TestResult(testCase) + { + StartTime = start, + EndTime = end, + Duration = TimeSpan.FromSeconds(10) + }; + + Assert.AreEqual(start, result.StartTime); + Assert.AreEqual(end, result.EndTime); + Assert.AreEqual(TimeSpan.FromSeconds(10), result.Duration); + } + + // Regression test for #4894 — Time is reported incorrectly for xunit + [TestMethod] + public void TestResult_Outcome_ShouldPreserveAllValues() + { + var testCase = new TestCase("Test3", new Uri("executor://test"), "test.dll"); + + foreach (var outcome in Enum.GetValues(typeof(TestOutcome)).Cast()) + { + var result = new TestResult(testCase) { Outcome = outcome }; + Assert.AreEqual(outcome, result.Outcome); + } + } + + [TestMethod] + public void TestResult_DisplayName_ShouldBeSettable() + { + var testCase = new TestCase("Ns.Class.Method", new Uri("executor://test"), "test.dll"); + var result = new TestResult(testCase) + { + DisplayName = "Custom Display Name" + }; + + Assert.AreEqual("Custom Display Name", result.DisplayName); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TraitCollectionRegressionTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TraitCollectionRegressionTests.cs new file mode 100644 index 0000000000..23677d91a1 --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TraitCollectionRegressionTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +/// +/// Regression tests for TraitCollection.GetTraits behavior. +/// +[TestClass] +public class TraitCollectionRegressionTests +{ + // Regression test for #15370 — Fix .NET 10 regression for traits + // TraitCollection.GetTraits() was using default Contains (reference equality on TestProperty) + // instead of EqualityComparer.Default which uses TestProperty.Equals (Id-based). + [TestMethod] + public void GetTraits_ShouldReturnTraitsAfterAdding() + { + var testCase = new TestCase("Test1", new System.Uri("executor://test"), "source.dll"); + testCase.Traits.Add("Priority", "1"); + testCase.Traits.Add("Category", "Unit"); + + var traits = testCase.Traits.ToList(); + + Assert.HasCount(2, traits); + var priorityTrait = traits.First(t => t.Name == "Priority"); + Assert.AreEqual("1", priorityTrait.Value); + var categoryTrait = traits.First(t => t.Name == "Category"); + Assert.AreEqual("Unit", categoryTrait.Value); + } + + // Regression test for #15370 + [TestMethod] + public void GetTraits_EmptyTraits_ShouldReturnEmpty() + { + var testCase = new TestCase("Test2", new System.Uri("executor://test"), "source.dll"); + + var traits = testCase.Traits.ToList(); + + Assert.IsEmpty(traits); + } + + // Regression test for #15249 — Avoid iterator in TraitCollection.GetTraits + // GetTraits was changed from yield return to eagerly-allocated array. + // Verify that multiple enumerations return consistent results. + [TestMethod] + public void GetTraits_MultipleEnumerations_ShouldReturnConsistentResults() + { + var testCase = new TestCase("Test3", new System.Uri("executor://test"), "source.dll"); + testCase.Traits.Add("Key1", "Value1"); + testCase.Traits.Add("Key2", "Value2"); + + var first = testCase.Traits.ToList(); + var second = testCase.Traits.ToList(); + + Assert.HasCount(first.Count, second); + for (int i = 0; i < first.Count; i++) + { + Assert.AreEqual(first[i].Name, second[i].Name); + Assert.AreEqual(first[i].Value, second[i].Value); + } + } + + // Regression test for #15370 + [TestMethod] + public void GetTraits_AddRange_ShouldAccumulateTraits() + { + var testCase = new TestCase("Test4", new System.Uri("executor://test"), "source.dll"); + testCase.Traits.Add("Existing", "Value"); + + var newTraits = new[] { new Trait("New1", "V1"), new Trait("New2", "V2") }; + testCase.Traits.AddRange(newTraits); + + var allTraits = testCase.Traits.ToList(); + Assert.HasCount(3, allTraits); + } +} diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/FilterHelperTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/FilterHelperTests.cs index b167362632..b77a3effba 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/FilterHelperTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/FilterHelperTests.cs @@ -17,8 +17,8 @@ public class FilterHelpersTests [TestMethod] public void EscapeUnescapeNullThrowsArgumentNullException() { - Assert.ThrowsException(() => FilterHelper.Escape(null!)); - Assert.ThrowsException(() => FilterHelper.Unescape(null!)); + Assert.ThrowsExactly(() => FilterHelper.Escape(null!)); + Assert.ThrowsExactly(() => FilterHelper.Unescape(null!)); } [TestMethod] @@ -70,13 +70,13 @@ public void EscapeUnescapeStringWithPrefix() public void UnescapeForInvalidStringThrowsArgumentException1() { var invalidString = @"TestClass\$""a %4 b""%2.TestMethod"; - Assert.ThrowsException(() => FilterHelper.Unescape(invalidString), string.Format(CultureInfo.CurrentCulture, Resources.TestCaseFilterEscapeException, invalidString)); + Assert.ThrowsExactly(() => FilterHelper.Unescape(invalidString), string.Format(CultureInfo.CurrentCulture, Resources.TestCaseFilterEscapeException, invalidString)); } [TestMethod] public void UnescapeForInvalidStringThrowsArgumentException2() { var invalidString = @"TestClass\"; - Assert.ThrowsException(() => FilterHelper.Unescape(invalidString), string.Format(CultureInfo.CurrentCulture, Resources.TestCaseFilterEscapeException, invalidString)); + Assert.ThrowsExactly(() => FilterHelper.Unescape(invalidString), string.Format(CultureInfo.CurrentCulture, Resources.TestCaseFilterEscapeException, invalidString)); } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs index 11e62c213a..56461df75d 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/Utilities/XmlRunSettingsUtilitiesTests.cs @@ -90,7 +90,7 @@ public void GetTestRunParametersReturnsEmptyDictionaryOnNullRunSettings() { Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(null); Assert.IsNotNull(trp); - Assert.AreEqual(0, trp.Count); + Assert.IsEmpty(trp); } [TestMethod] @@ -108,7 +108,7 @@ public void GetTestRunParametersReturnsEmptyDictionaryWhenNoTestRunParameters() Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); Assert.IsNotNull(trp); - Assert.AreEqual(0, trp.Count); + Assert.IsEmpty(trp); } [TestMethod] @@ -128,7 +128,7 @@ public void GetTestRunParametersReturnsEmptyDictionaryForEmptyTestRunParametersN Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); Assert.IsNotNull(trp); - Assert.AreEqual(0, trp.Count); + Assert.IsEmpty(trp); } [TestMethod] @@ -149,7 +149,7 @@ public void GetTestRunParametersReturns1EntryOn1TestRunParameter() Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); Assert.IsNotNull(trp); - Assert.AreEqual(1, trp.Count); + Assert.HasCount(1, trp); // Verify Parameter Values. Assert.IsTrue(trp.ContainsKey("webAppUrl")); @@ -176,7 +176,7 @@ public void GetTestRunParametersReturns3EntryOn3TestRunParameter() Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); Assert.IsNotNull(trp); - Assert.AreEqual(3, trp.Count); + Assert.HasCount(3, trp); // Verify Parameter Values. Assert.IsTrue(trp.ContainsKey("webAppUrl")); @@ -203,7 +203,7 @@ public void GetTestRunParametersThrowsWhenTrpNodeHasAttributes() "; - Assert.ThrowsException(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); + Assert.ThrowsExactly(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); } [TestMethod] @@ -223,7 +223,7 @@ public void GetTestRunParametersThrowsWhenTrpNodeHasNonParameterTypeChildNodes() "; - Assert.ThrowsException(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); + Assert.ThrowsExactly(() => XmlRunSettingsUtilities.GetTestRunParameters(settingsXml)); } [TestMethod] @@ -244,7 +244,7 @@ public void GetTestRunParametersIgnoresMalformedKeyValues() Dictionary trp = XmlRunSettingsUtilities.GetTestRunParameters(settingsXml); Assert.IsNotNull(trp); - Assert.AreEqual(0, trp.Count); + Assert.IsEmpty(trp); } [TestMethod] @@ -263,7 +263,7 @@ public void GetInProcDataCollectionRunSettingsFromSettings() "; var inProcDcRunSettings = XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settingsXml); Assert.IsNotNull(inProcDcRunSettings); - Assert.AreEqual(1, inProcDcRunSettings.DataCollectorSettingsList.Count); + Assert.HasCount(1, inProcDcRunSettings.DataCollectorSettingsList); } [TestMethod] @@ -281,7 +281,7 @@ public void GetInProcDataCollectionRunSettingsThrowsExceptionWhenXmlNotValid() "; - Assert.ThrowsException( + Assert.ThrowsExactly( () => XmlRunSettingsUtilities.GetInProcDataCollectionRunSettings(settingsXml)); } #endregion @@ -480,12 +480,13 @@ public void GetLoggerRunSettingsShouldThrowWhenInvalidUri() exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains( + Assert.Contains( string.Format( CultureInfo.CurrentCulture, Resources.InvalidUriInSettings, "invalidUri", - "Logger"))); + "Logger"), + exceptionMessage); } [TestMethod] @@ -609,7 +610,7 @@ public void GetLoggerRunSettingsShouldThrowIfDuplicateAttributesPresent() exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(CommonResources.MalformedRunSettingsFile)); + Assert.Contains(CommonResources.MalformedRunSettingsFile, exceptionMessage); } [TestMethod] @@ -684,7 +685,7 @@ public void GetLoggerRunSettingsShouldThrowShouldThrowOnMalformedLoggerSettings( exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(CommonResources.MalformedRunSettingsFile)); + Assert.Contains(CommonResources.MalformedRunSettingsFile, exceptionMessage); } [TestMethod] @@ -713,11 +714,11 @@ public void GetLoggerRunSettingsShouldThrowWhenAttribtuesPresentInLoggerRunSetti exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(string.Format( + Assert.Contains(string.Format( CultureInfo.CurrentCulture, Resources.InvalidSettingsXmlAttribute, "LoggerRunSettings", - "name"))); + "name"), exceptionMessage); } [TestMethod] @@ -733,7 +734,7 @@ public void GetLoggerRunSettingsShouldReturnEmptyLoggerRunSettingsWhenLoggerRunS "; var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithEmptyLoggerRunSettingsNode)!; - Assert.AreEqual(0, loggerRunSettings.LoggerSettingsList.Count); + Assert.IsEmpty(loggerRunSettings.LoggerSettingsList); } [TestMethod] @@ -748,7 +749,7 @@ public void GetLoggerRunSettingsShouldReturnEmptyLoggerRunSettingsWhenLoggerRunS "; var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithEmptyLoggerRunSettingsNode)!; - Assert.AreEqual(0, loggerRunSettings.LoggerSettingsList.Count); + Assert.IsEmpty(loggerRunSettings.LoggerSettingsList); } [TestMethod] @@ -777,11 +778,11 @@ public void GetLoggerRunSettingsShouldThrowWhenNodeOtherThanLoggersPresentInLogg exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(string.Format( + Assert.Contains(string.Format( CultureInfo.CurrentCulture, Resources.InvalidSettingsXmlElement, "LoggerRUNSettings", - "LoggersInvalid"))); + "LoggersInvalid"), exceptionMessage); } [TestMethod] @@ -810,11 +811,11 @@ public void GetLoggerRunSettingsShouldThrowWhenAttribtuesPresentInLoggersNode() exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(string.Format( + Assert.Contains(string.Format( CultureInfo.CurrentCulture, Resources.InvalidSettingsXmlAttribute, "Loggers", - "nameAttr"))); + "nameAttr"), exceptionMessage); } [TestMethod] @@ -832,7 +833,7 @@ public void GetLoggerRunSettingsShouldReturnEmptyLoggersWhenLoggersIsEmpty() "; var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithEmptyLoggersNode)!; - Assert.AreEqual(0, loggerRunSettings.LoggerSettingsList.Count); + Assert.IsEmpty(loggerRunSettings.LoggerSettingsList); } [TestMethod] @@ -849,7 +850,7 @@ public void GetLoggerRunSettingsShouldReturnEmptyLoggersWhenLoggersIsSelfEnding( "; var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithEmptyLoggersNode)!; - Assert.AreEqual(0, loggerRunSettings.LoggerSettingsList.Count); + Assert.IsEmpty(loggerRunSettings.LoggerSettingsList); } [TestMethod] @@ -878,11 +879,11 @@ public void GetLoggerRunSettingsShouldThrowWhenNodeOtherThanLoggerPresentInLogge exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(string.Format( + Assert.Contains(string.Format( CultureInfo.CurrentCulture, Resources.InvalidSettingsXmlElement, "Loggers", - "LoggerInvalid"))); + "LoggerInvalid"), exceptionMessage); } [TestMethod] @@ -912,7 +913,7 @@ public void GetLoggerRunSettingsShouldThrowWhenRequiredAttributesNotPresentInLog exceptionMessage = ex.Message; } - Assert.IsTrue(exceptionMessage.Contains(string.Format(CultureInfo.CurrentCulture, Resources.MissingLoggerAttributes, "LogGer"))); + Assert.Contains(string.Format(CultureInfo.CurrentCulture, Resources.MissingLoggerAttributes, "LogGer"), exceptionMessage); } [TestMethod] @@ -1051,7 +1052,7 @@ public void GetLoggerRunSettingsShouldReturnMultipleLoggersIfPresent() var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithMultipleLoggers)!; - Assert.AreEqual(3, loggerRunSettings.LoggerSettingsList.Count); + Assert.HasCount(3, loggerRunSettings.LoggerSettingsList); // 1st logger var loggerFirst = loggerRunSettings.LoggerSettingsList[0]; @@ -1102,7 +1103,7 @@ public void GetLoggerRunSettingsShouldReturnLoggersWhenLoggerHasSelfEndingTag() var loggerRunSettings = XmlRunSettingsUtilities.GetLoggerRunSettings(runSettingsWithSelfEndingLoggers)!; - Assert.AreEqual(3, loggerRunSettings.LoggerSettingsList.Count); + Assert.HasCount(3, loggerRunSettings.LoggerSettingsList); Assert.AreEqual("TestLoggerWithParameterExtension", loggerRunSettings.LoggerSettingsList[0].FriendlyName); Assert.AreEqual("TestLogger", loggerRunSettings.LoggerSettingsList[1].FriendlyName); Assert.AreEqual("TestLogger", loggerRunSettings.LoggerSettingsList[1].FriendlyName); @@ -1174,7 +1175,7 @@ public void GetDataCollectionRunSettingsShouldReturnDataCollectorRunSettingsEven [TestMethod] public void GetDataCollectionRunSettingsShouldThrowOnMalformedDataCollectorSettings() { - Assert.ThrowsException(() => XmlRunSettingsUtilities.GetDataCollectionRunSettings(_runSettingsXmlWithIncorrectDataCollectorSettings)); + Assert.ThrowsExactly(() => XmlRunSettingsUtilities.GetDataCollectionRunSettings(_runSettingsXmlWithIncorrectDataCollectorSettings)); } #endregion @@ -1195,7 +1196,7 @@ public void GetDataCollectorsFriendlyNameShouldReturnListOfFriendlyName() var friendlyNameList = XmlRunSettingsUtilities.GetDataCollectorsFriendlyName(settingsXml).ToList(); - Assert.AreEqual(2, friendlyNameList.Count, "There should be two friendly name"); + Assert.HasCount(2, friendlyNameList); CollectionAssert.AreEqual(friendlyNameList, new List { "DummyDataCollector1", "DummyDataCollector2" }); } diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs index 0055035827..68bf9ab331 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs @@ -47,6 +47,8 @@ public class DefaultTestHostManagerTests private int _exitCode; private int _testHostId; + public TestContext TestContext { get; set; } = null!; + public DefaultTestHostManagerTests() { _mockProcessHelper = new Mock(); @@ -70,13 +72,13 @@ public void ConstructorShouldSetX86ProcessForX86Architecture() var info = _testHostManager.GetTestHostProcessStartInfo([], null, default); - StringAssert.EndsWith(info.FileName, "testhost.x86.exe"); + Assert.EndsWith("testhost.x86.exe", info.FileName); } [TestMethod] public void ConstructorShouldSetX64ProcessForX64Architecture() { - StringAssert.EndsWith(_startInfo.FileName, "testhost.exe"); + Assert.EndsWith("testhost.exe", _startInfo.FileName); } [TestMethod] @@ -86,7 +88,7 @@ public void GetTestHostProcessStartInfoShouldIncludeFileNameFromSubFolderTestHos _mockFileHelper.Setup(x => x.Exists(It.IsAny())).Returns(false); var startInfo = _testHostManager.GetTestHostProcessStartInfo([], null, default); - Assert.IsTrue(startInfo.FileName!.EndsWith(Path.Combine("TestHostNetFramework", "testhost.exe"))); + Assert.EndsWith(Path.Combine("TestHostNetFramework", "testhost.exe"), startInfo.FileName!); } [TestMethod] @@ -96,8 +98,8 @@ public void GetTestHostProcessStartInfoShouldNotIncludeFileNameFromSubFolderTest _mockFileHelper.Setup(x => x.Exists(It.IsAny())).Returns(true); var startInfo = _testHostManager.GetTestHostProcessStartInfo([], null, default); - Assert.IsFalse(startInfo.FileName!.EndsWith(Path.Combine("TestHost", "testhost.exe"))); - Assert.IsTrue(startInfo.FileName!.EndsWith("testhost.exe")); + Assert.DoesNotEndWith(Path.Combine("TestHost", "testhost.exe"), startInfo.FileName!); + Assert.EndsWith("testhost.exe", startInfo.FileName!); } [TestMethod] @@ -106,8 +108,8 @@ public void GetTestHostProcessStartInfoShouldNotIncludeFileNameFromSubFolderTest _mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns("devenv.exe"); var startInfo = _testHostManager.GetTestHostProcessStartInfo([], null, default); - Assert.IsFalse(startInfo.FileName!.EndsWith(Path.Combine("TestHost", "testhost.exe"))); - Assert.IsTrue(startInfo.FileName!.EndsWith("testhost.exe")); + Assert.DoesNotEndWith(Path.Combine("TestHost", "testhost.exe"), startInfo.FileName!); + Assert.EndsWith("testhost.exe", startInfo.FileName!); } [TestMethod] @@ -140,7 +142,7 @@ public void GetTestHostConnectionInfoShouldIncludeEndpointRoleAndChannelType() [TestMethod] public void GetTestHostProcessStartInfoShouldIncludeEmptyEnvironmentVariables() { - Assert.AreEqual(0, _startInfo.EnvironmentVariables!.Count); + Assert.IsEmpty(_startInfo.EnvironmentVariables!); } [TestMethod] @@ -188,7 +190,7 @@ public void GetTestHostProcessStartInfoShouldUseMonoAsHostOnNonWindowsIfNotStart default); Assert.AreEqual("/usr/bin/mono", info.FileName); - StringAssert.Contains(info.Arguments, Path.Combine("TestHostNetFramework", "testhost.exe")); + Assert.Contains(Path.Combine("TestHostNetFramework", "testhost.exe"), info.Arguments!); } [TestMethod] @@ -205,8 +207,8 @@ public void GetTestHostProcessStartInfoShouldNotUseMonoAsHostOnNonWindowsIfStart default); var testHostPath = Path.Combine("TestHostNetFramework", "testhost.exe"); - StringAssert.EndsWith(info.FileName, testHostPath); - Assert.IsFalse(info.Arguments!.Contains(testHostPath)); + Assert.EndsWith(testHostPath, info.FileName); + Assert.DoesNotContain(testHostPath, info.Arguments!); } [TestMethod] @@ -350,15 +352,16 @@ public void LaunchTestHostShouldReturnTestHostProcessId() It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())).Returns(Process.GetCurrentProcess()); + It.IsAny>(), + It.IsAny())).Returns(Process.GetCurrentProcess()); _testHostManager.Initialize(_mockMessageLogger.Object, $" {Architecture.X64} {Framework.DefaultFramework} {false} "); var startInfo = _testHostManager.GetTestHostProcessStartInfo([], null, default); _testHostManager.HostLaunched += TestHostManagerHostLaunched; - Task processId = _testHostManager.LaunchTestHostAsync(startInfo, CancellationToken.None); - processId.Wait(); + Task processId = _testHostManager.LaunchTestHostAsync(startInfo, TestContext.CancellationToken); + processId.Wait(TestContext.CancellationToken); Assert.IsTrue(processId.Result); @@ -385,13 +388,13 @@ public void LaunchTestHostAsyncShouldNotStartHostProcessIfCancellationTokenIsSet CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.Cancel(); - Assert.ThrowsException(() => _testableTestHostManager.LaunchTestHostAsync(GetDefaultStartInfo(), cancellationTokenSource.Token).Wait()); + Assert.ThrowsExactly(() => _testableTestHostManager.LaunchTestHostAsync(GetDefaultStartInfo(), cancellationTokenSource.Token).Wait(TestContext.CancellationToken)); } [TestMethod] public void PropertiesShouldReturnEmptyDictionary() { - Assert.AreEqual(0, _testHostManager.Properties.Count); + Assert.IsEmpty(_testHostManager.Properties); } [TestMethod] @@ -424,8 +427,8 @@ public void LaunchTestHostShouldUseCustomHostIfSet() _testHostManager.HostLaunched += TestHostManagerHostLaunched; - Task pid = _testHostManager.LaunchTestHostAsync(_startInfo, CancellationToken.None); - pid.Wait(); + Task pid = _testHostManager.LaunchTestHostAsync(_startInfo, TestContext.CancellationToken); + pid.Wait(TestContext.CancellationToken); mockCustomLauncher.Verify(mc => mc.LaunchTestHost(It.IsAny(), It.IsAny()), Times.Once); Assert.IsTrue(pid.Result); @@ -439,7 +442,7 @@ public void LaunchTestHostShouldSetExitCallbackInCaseCustomHost() _testHostManager.SetCustomLauncher(mockCustomLauncher.Object); var currentProcess = Process.GetCurrentProcess(); mockCustomLauncher.Setup(mc => mc.LaunchTestHost(It.IsAny(), It.IsAny())).Returns(currentProcess.Id); - _testHostManager.LaunchTestHostAsync(_startInfo, CancellationToken.None).Wait(); + _testHostManager.LaunchTestHostAsync(_startInfo, TestContext.CancellationToken).Wait(TestContext.CancellationToken); _mockProcessHelper.Verify(ph => ph.SetExitCallback(currentProcess.Id, It.IsAny>())); } @@ -591,12 +594,14 @@ private void ErrorCallBackTestHelper(string errorMessage, int exitCode) It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Callback, Action, Action, Action>( - (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback) => + It.IsAny>(), + It.IsAny())) + .Callback, Action, Action, Action, bool>( + (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback, createNoNewWindow) => { var process = Process.GetCurrentProcess(); + Assert.IsTrue(createNoNewWindow, "createNoNewWindow should default to true"); errorCallback(process, errorMessage); exitCallback(process); }).Returns(Process.GetCurrentProcess()); @@ -625,11 +630,13 @@ private void ExitCallBackTestHelper(int exitCode) It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Callback, Action, Action, Action>( - (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback) => + It.IsAny>(), + It.IsAny())) + .Callback, Action, Action, Action, bool>( + (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback, createNoNewWindow) => { var process = Process.GetCurrentProcess(); + Assert.IsTrue(createNoNewWindow, "createNoNewWindow should default to true"); exitCallback(process); }).Returns(Process.GetCurrentProcess()); diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index 7d643fcae4..c181459315 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -54,6 +54,8 @@ public class DotnetTestHostManagerTests private readonly string _temp = Path.GetTempPath(); + public TestContext TestContext { get; set; } = null!; + public DotnetTestHostManagerTests() { _mockTestHostLauncher = new Mock(); @@ -86,7 +88,6 @@ public DotnetTestHostManagerTests() _mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns(DefaultDotnetPath); _mockProcessHelper.Setup(ph => ph.GetTestEngineDirectory()).Returns(DefaultDotnetPath); _mockProcessHelper.Setup(ph => ph.GetCurrentProcessArchitecture()).Returns(PlatformArchitecture.X64); - _mockEnvironmentVariable.Setup(ev => ev.GetEnvironmentVariable(It.IsAny())).Returns(Path.GetDirectoryName(DefaultDotnetPath)!); _mockFileHelper.Setup(ph => ph.Exists(_defaultTestHostPath)).Returns(true); _mockFileHelper.Setup(ph => ph.Exists(DefaultDotnetPath)).Returns(true); @@ -102,9 +103,11 @@ public DotnetTestHostManagerTests() .Setup(th => th.LaunchTestHost(It.IsAny(), It.IsAny())) .Returns(pid); +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - Moq setup pattern _mockTestHostLauncher .Setup(th => th.LaunchTestHost(It.IsAny())) .Returns(pid); +#pragma warning restore MSTEST0049 _defaultTestProcessStartInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { defaultSourcePath }, null, _defaultConnectionInfo); } @@ -114,7 +117,7 @@ public void GetTestHostProcessStartInfoShouldThrowIfSourceIsNull() { Action action = () => _dotnetHostManager.GetTestHostProcessStartInfo(null!, null, _defaultConnectionInfo); - Assert.ThrowsException(action); + Assert.ThrowsExactly(action); } [TestMethod] @@ -123,7 +126,7 @@ public void GetTestHostProcessStartInfoShouldThrowIfMultipleSourcesAreProvided() var sources = new[] { "test1.dll", "test2.dll" }; Action action = () => _dotnetHostManager.GetTestHostProcessStartInfo(sources, null, _defaultConnectionInfo); - Assert.ThrowsException(action); + Assert.ThrowsExactly(action); } [TestMethod] @@ -154,7 +157,7 @@ public void GetTestHostProcessStartInfoShouldInvokeDotnetExec() _mockFileHelper.Setup(ph => ph.Exists("testhost.dll")).Returns(true); var startInfo = GetDefaultStartInfo(); - StringAssert.StartsWith(startInfo.Arguments, "exec"); + Assert.StartsWith("exec", startInfo.Arguments); } [TestMethod] @@ -165,7 +168,7 @@ public void GetTestHostProcessStartInfoShouldAddRuntimeConfigJsonIfExists() var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.Arguments, "--runtimeconfig \"test.runtimeconfig.json\""); + Assert.Contains("--runtimeconfig \"test.runtimeconfig.json\"", startInfo.Arguments!); } [TestMethod] @@ -176,7 +179,7 @@ public void GetTestHostProcessStartInfoShouldNotAddRuntimeConfigJsonIfNotExists( var startInfo = GetDefaultStartInfo(); - Assert.IsFalse(startInfo.Arguments!.Contains("--runtimeconfig \"test.runtimeconfig.json\"")); + Assert.DoesNotContain("--runtimeconfig \"test.runtimeconfig.json\"", startInfo.Arguments!); } [TestMethod] @@ -187,7 +190,7 @@ public void GetTestHostProcessStartInfoShouldAddDepsFileJsonIfExists() var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.Arguments, "--depsfile \"test.deps.json\""); + Assert.Contains("--depsfile \"test.deps.json\"", startInfo.Arguments!); } [TestMethod] @@ -198,7 +201,7 @@ public void GetTestHostProcessStartInfoShouldNotAddDepsFileJsonIfNotExists() var startInfo = GetDefaultStartInfo(); - Assert.IsFalse(startInfo.Arguments!.Contains("--depsfile \"test.deps.json\"")); + Assert.DoesNotContain("--depsfile \"test.deps.json\"", startInfo.Arguments!); } [TestMethod] @@ -209,7 +212,7 @@ public void GetTestHostProcessStartInfoShouldIncludeConnectionInfo() var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(_testSource, null, connectionInfo); - StringAssert.Contains(startInfo.Arguments, "--port " + connectionInfo.Port + " --endpoint " + connectionInfo.ConnectionInfo.Endpoint + " --role client --parentprocessid 101"); + Assert.Contains("--port " + connectionInfo.Port + " --endpoint " + connectionInfo.ConnectionInfo.Endpoint + " --role client --parentprocessid 101", startInfo.Arguments!); } [TestMethod] @@ -240,7 +243,7 @@ public void GetTestHostProcessStartIfDepsFileNotFoundAndTestHostFoundShouldNotTh _mockFileHelper.Setup(ph => ph.Exists("testhost.dll")).Returns(true); var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.Arguments, "testhost.dll"); + Assert.Contains("testhost.dll", startInfo.Arguments!); } [TestMethod] @@ -253,7 +256,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostX64ExePresentOnWindows() var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.FileName, testhostExePath); + Assert.Contains(testhostExePath, startInfo.FileName!); } [TestMethod] @@ -265,8 +268,8 @@ public void GetTestHostProcessStartInfoShouldUseDotnetExeOnUnixWithTestHostDllPa var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.FileName, "dotnet"); - StringAssert.Contains(startInfo.Arguments, "testhost.dll"); + Assert.Contains("dotnet", startInfo.FileName!); + Assert.Contains("testhost.dll", startInfo.Arguments!); } [TestMethod] @@ -280,7 +283,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostExeIfPresentOnWindows() _dotnetHostManager.Initialize(_mockMessageLogger.Object, "x64"); var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.FileName, testhostExePath); + Assert.Contains(testhostExePath, startInfo.FileName!); } [TestMethod] @@ -292,7 +295,7 @@ public void GetTestHostProcessStartInfoShouldUseDotnetHostPathFromRunsettings() _dotnetHostManager.Initialize(_mockMessageLogger.Object, $"{dotnetHostPath}"); var startInfo = GetDefaultStartInfo(); - StringAssert.Contains(startInfo.FileName, dotnetHostPath); + Assert.Contains(dotnetHostPath, startInfo.FileName!); } [TestMethod] @@ -302,7 +305,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostExeFromNugetIfNotFoundIn var testhostExePath = "testhost.exe"; _dotnetHostManager.Initialize(_mockMessageLogger.Object, "x64"); _mockFileHelper.Setup(ph => ph.Exists(testhostExePath)).Returns(false); - _mockFileHelper.Setup(ph => ph.Exists("C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\netcoreapp3.1\\x64\\testhost.exe")).Returns(true); + _mockFileHelper.Setup(ph => ph.Exists("C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\net8.0\\x64\\testhost.exe")).Returns(true); _mockEnvironment.Setup(ev => ev.OperatingSystem).Returns(PlatformOperatingSystem.Windows); var sourcePath = Path.Combine(_temp, "test.dll"); @@ -327,7 +330,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostExeFromNugetIfNotFoundIn ""microsoft.testplatform.testhost/15.0.0-Dev"": { ""dependencies"": { ""Microsoft.TestPlatform.ObjectModel"": ""15.0.0-Dev"", - ""Newtonsoft.Json"": ""13.0.1"" + ""Newtonsoft.Json"": ""13.0.3"" }, ""runtime"": { ""lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll"": { }, @@ -362,7 +365,10 @@ public void GetTestHostProcessStartInfoShouldUseTestHostExeFromNugetIfNotFoundIn var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - StringAssert.Contains(startInfo.FileName, "C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\netcoreapp3.1\\x64\\testhost.exe"); + + // If this starts failing after updating TFMs of packakges, the GetTestHostProcessStartInfo defines the default version + // to use in GetTestHostProcessStartInfo, change that to the lowest supported netcore version, and pass this test. + Assert.Contains("C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\net8.0\\x64\\testhost.exe", startInfo.FileName!); } [TestMethod] @@ -372,7 +378,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostX86ExeFromNugetIfNotFoun var testhostExePath = "testhost.x86.exe"; _dotnetHostManager.Initialize(_mockMessageLogger.Object, "x86"); _mockFileHelper.Setup(ph => ph.Exists(testhostExePath)).Returns(false); - _mockFileHelper.Setup(ph => ph.Exists($"C:\\packages{Path.DirectorySeparatorChar}microsoft.testplatform.testhost\\15.0.0-Dev{Path.DirectorySeparatorChar}build\\netcoreapp3.1\\x86\\testhost.x86.exe")).Returns(true); + _mockFileHelper.Setup(ph => ph.Exists($"C:\\packages{Path.DirectorySeparatorChar}microsoft.testplatform.testhost\\15.0.0-Dev{Path.DirectorySeparatorChar}build\\net8.0\\x86\\testhost.x86.exe")).Returns(true); _mockEnvironment.Setup(ev => ev.OperatingSystem).Returns(PlatformOperatingSystem.Windows); var sourcePath = Path.Combine(_temp, "test.dll"); @@ -397,7 +403,7 @@ public void GetTestHostProcessStartInfoShouldUseTestHostX86ExeFromNugetIfNotFoun ""microsoft.testplatform.testhost/15.0.0-Dev"": { ""dependencies"": { ""Microsoft.TestPlatform.ObjectModel"": ""15.0.0-Dev"", - ""Newtonsoft.Json"": ""13.0.1"" + ""Newtonsoft.Json"": ""13.0.3"" }, ""runtime"": { ""lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll"": { }, @@ -432,7 +438,9 @@ public void GetTestHostProcessStartInfoShouldUseTestHostX86ExeFromNugetIfNotFoun var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - StringAssert.Contains(startInfo.FileName, "C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\netcoreapp3.1\\x86\\testhost.x86.exe"); + // If this starts failing after updating TFMs of packakges, the GetTestHostProcessStartInfo defines the default version + // to use in GetTestHostProcessStartInfo, change that to the lowest supported netcore version, and pass this test. + Assert.Contains("C:\\packages\\microsoft.testplatform.testhost\\15.0.0-Dev\\build\\net8.0\\x86\\testhost.x86.exe", startInfo.FileName!); } [TestMethod] @@ -452,8 +460,8 @@ public void LaunchTestHostShouldLaunchProcessWithNullEnvironmentVariablesOrArgs( _dotnetHostManager.HostLaunched += DotnetHostManagerHostLaunched; - Task processId = _dotnetHostManager.LaunchTestHostAsync(startInfo, CancellationToken.None); - processId.Wait(); + Task processId = _dotnetHostManager.LaunchTestHostAsync(startInfo, TestContext.CancellationToken); + processId.Wait(TestContext.CancellationToken); Assert.IsTrue(processId.Result); Assert.AreEqual(expectedProcessId, _testHostId); @@ -469,14 +477,16 @@ public void LaunchTestHostAsyncShouldNotStartHostProcessIfCancellationTokenIsSet using (var p = Process.GetCurrentProcess()) expectedProcessId = p.Id; #endif +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - Moq setup pattern _mockTestHostLauncher.Setup(thl => thl.LaunchTestHost(It.IsAny())).Returns(expectedProcessId); +#pragma warning restore MSTEST0049 _mockFileHelper.Setup(ph => ph.Exists("testhost.dll")).Returns(true); var startInfo = GetDefaultStartInfo(); CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.Cancel(); - Assert.ThrowsException(() => _dotnetHostManager.LaunchTestHostAsync(startInfo, cancellationTokenSource.Token).Wait()); + Assert.ThrowsExactly(() => _dotnetHostManager.LaunchTestHostAsync(startInfo, cancellationTokenSource.Token).Wait(TestContext.CancellationToken)); } [TestMethod] @@ -488,8 +498,8 @@ public void LaunchTestHostShouldLaunchProcessWithEnvironmentVariables() _dotnetHostManager.HostLaunched += DotnetHostManagerHostLaunched; - Task processId = _dotnetHostManager.LaunchTestHostAsync(startInfo, CancellationToken.None); - processId.Wait(); + Task processId = _dotnetHostManager.LaunchTestHostAsync(startInfo, TestContext.CancellationToken); + processId.Wait(TestContext.CancellationToken); Assert.IsTrue(processId.Result); _mockTestHostLauncher.Verify(thl => thl.LaunchTestHost(It.Is(x => x.EnvironmentVariables!.Equals(variables)), It.IsAny()), Times.Once); @@ -530,7 +540,7 @@ public void GetTestHostProcessStartInfoShouldThrowExceptionWhenDotnetIsNotInstal Action action = () => GetDefaultStartInfo(); - Assert.ThrowsException(action); + Assert.ThrowsExactly(action); } [TestMethod] @@ -593,7 +603,7 @@ public async Task LaunchTestHostShouldLaunchProcessWithConnectionInfo() #endif + " --port 123 --endpoint 127.0.0.1:123 --role client --parentprocessid 0"; _dotnetHostManager.SetCustomLauncher(_mockTestHostLauncher.Object); - await _dotnetHostManager.LaunchTestHostAsync(_defaultTestProcessStartInfo, CancellationToken.None); + await _dotnetHostManager.LaunchTestHostAsync(_defaultTestProcessStartInfo, TestContext.CancellationToken); _mockTestHostLauncher.Verify(thl => thl.LaunchTestHost(It.Is(x => x.Arguments!.Equals(expectedArgs)), It.IsAny()), Times.Once); } @@ -613,7 +623,7 @@ public void LaunchTestHostShouldSetExitCallBackInCaseCustomHost() var startInfo = GetDefaultStartInfo(); _dotnetHostManager.SetCustomLauncher(_mockTestHostLauncher.Object); - _dotnetHostManager.LaunchTestHostAsync(startInfo, CancellationToken.None).Wait(); + _dotnetHostManager.LaunchTestHostAsync(startInfo, TestContext.CancellationToken).Wait(TestContext.CancellationToken); _mockProcessHelper.Verify(ph => ph.SetExitCallback(expectedProcessId, It.IsAny>())); } @@ -629,10 +639,10 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromSourceDirect var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - StringAssert.Contains(startInfo.Arguments, expectedTestHostPath); + Assert.Contains(expectedTestHostPath, startInfo.Arguments!); } - // TODO: This assembly was previously compiled as net472 and so it was skipped and only ran as netcoreapp3.1. This fails in test, but works in code that is not isolated in appdomain. Might be worth fixing because we get one null here, and another in DotnetTestHostManager. + // TODO: This assembly was previously compiled as net472 and so it was skipped and only ran as net8.0. This fails in test, but works in code that is not isolated in appdomain. Might be worth fixing because we get one null here, and another in DotnetTestHostManager. // Assembly.GetEntryAssembly().Location is null because of running in app domain. #if NET [TestMethod] @@ -649,18 +659,18 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunner var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - StringAssert.Contains(startInfo.Arguments, expectedTestHostPath); + Assert.Contains(expectedTestHostPath, startInfo.Arguments!); var expectedAdditionalDepsPath = Path.Combine(here, "testhost.deps.json"); - StringAssert.Contains(startInfo.Arguments, $"--additional-deps \"{expectedAdditionalDepsPath}\""); + Assert.Contains($"--additional-deps \"{expectedAdditionalDepsPath}\"", startInfo.Arguments!); var expectedAdditionalProbingPath = here; - StringAssert.Contains(startInfo.Arguments, $"--additionalprobingpath \"{expectedAdditionalProbingPath}\""); + Assert.Contains($"--additionalprobingpath \"{expectedAdditionalProbingPath}\"", startInfo.Arguments!); var expectedRuntimeConfigPath = Path.Combine(here, "testhost-latest.runtimeconfig.json"); - StringAssert.Contains(startInfo.Arguments, $"--runtimeconfig \"{expectedRuntimeConfigPath}\""); + Assert.Contains($"--runtimeconfig \"{expectedRuntimeConfigPath}\"", startInfo.Arguments!); } #endif - // TODO: This assembly was previously compiled as net472 and so it was skipped and only ran as netcoreapp3.1. This fails in test, but works in code that is not isolated in appdomain. Might be worth fixing because we get one null here, and another in DotnetTestHostManager. + // TODO: This assembly was previously compiled as net472 and so it was skipped and only ran as net8.0. This fails in test, but works in code that is not isolated in appdomain. Might be worth fixing because we get one null here, and another in DotnetTestHostManager. // Assembly.GetEntryAssembly().Location is null because of running in app domain. #if NET @@ -669,14 +679,13 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunner // we can't put in a "default" value, and we don't have other way to determine if this provided value is the // runtime default or the actual value that user provided, so right now the default will use the latest, instead // or the more correct 1.0, it should be okay, as that version is not supported anymore anyway - [DataRow("netcoreapp3.1", "3.1", true)] - [DataRow("net5.0", "5.0", true)] - - // net6.0 is currently the latest released version, but it still has it's own runtime config, it is not the same as - // "latest" which means the latest you have on system. So if you have only 5.0 SDK then net6.0 will fail because it can't find net6.0, - // but latest would use net5.0 because that is the latest one on your system. - [DataRow("net6.0", "6.0", true)] - [DataRow("net6.0", "latest", false)] + [DataRow("net8.0", "8.0", true)] + + // net9.0 is currently the latest released version, but it still has it's own runtime config, it is not the same as + // "latest" which means the latest you have on system. So if you have only 5.0 SDK then net8.0 will fail because it can't find net8.0, + // but latest would use net9.0 because that is the latest one on your system. + [DataRow("net9.0", "9.0", true)] + [DataRow("net9.0", "latest", false)] public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunnerIfTesthostDllIsNoFoundAndDepsFileNotFoundWithTheCorrectTfm(string tfm, string suffix, bool runtimeConfigExists) { // Absolute path to the source directory @@ -694,7 +703,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunner var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); var expectedRuntimeConfigPath = Path.Combine(here, $"testhost-{suffix}.runtimeconfig.json"); - StringAssert.Contains(startInfo.Arguments, $"--runtimeconfig \"{expectedRuntimeConfigPath}\""); + Assert.Contains($"--runtimeconfig \"{expectedRuntimeConfigPath}\"", startInfo.Arguments!); } #endif @@ -710,7 +719,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromSourceDirect var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - Assert.IsTrue(startInfo.Arguments!.Contains(expectedTestHostPath)); + Assert.Contains(expectedTestHostPath, startInfo.Arguments!); } [TestMethod] @@ -740,7 +749,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromDepsFile() ""microsoft.testplatform.testhost/15.0.0-Dev"": { ""dependencies"": { ""Microsoft.TestPlatform.ObjectModel"": ""15.0.0-Dev"", - ""Newtonsoft.Json"": ""13.0.1"" + ""Newtonsoft.Json"": ""13.0.3"" }, ""runtime"": { ""lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll"": { }, @@ -775,7 +784,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromDepsFile() var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - Assert.IsTrue(startInfo.Arguments!.Contains(testHostFullPath)); + Assert.Contains(testHostFullPath, startInfo.Arguments!); } [TestMethod] @@ -805,7 +814,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromSourceDirect ""microsoft.testplatform.testhost/15.0.0-Dev"": { ""dependencies"": { ""Microsoft.TestPlatform.ObjectModel"": ""15.0.0-Dev"", - ""Newtonsoft.Json"": ""13.0.1"" + ""Newtonsoft.Json"": ""13.0.3"" }, ""runtime"": { ""lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll"": { }, @@ -842,7 +851,7 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromSourceDirect var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - Assert.IsTrue(startInfo.Arguments!.Contains(testHostPath)); + Assert.Contains(testHostPath, startInfo.Arguments!); } [TestMethod] @@ -873,7 +882,7 @@ public void GetTestHostProcessStartInfoShouldSkipInvalidAdditionalProbingPaths() ""microsoft.testplatform.testhost/15.0.0-Dev"": { ""dependencies"": { ""Microsoft.TestPlatform.ObjectModel"": ""15.0.0-Dev"", - ""Newtonsoft.Json"": ""13.0.1"" + ""Newtonsoft.Json"": ""13.0.3"" }, ""runtime"": { ""lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll"": { }, @@ -908,32 +917,27 @@ public void GetTestHostProcessStartInfoShouldSkipInvalidAdditionalProbingPaths() var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - Assert.IsTrue(startInfo.Arguments!.Contains(testHostFullPath)); + Assert.Contains(testHostFullPath, startInfo.Arguments!); } [TestMethod] - [DataRow("DOTNET_ROOT(x86)", "x86")] - [DataRow("DOTNET_ROOT", "x64")] - [DataRow("DOTNET_ROOT_WRONG", "")] - [TestCategory("Windows")] - public void GetTestHostProcessStartInfoShouldForwardDOTNET_ROOTEnvVarsForAppHost(string envVar, string expectedValue) + [DataRow("x64")] + [DataRow("x86")] + [DataRow("arm64")] + public void GetTestHostProcessStartInfoShouldForwardDOTNET_ROOTEnvVarsForAppHost(string architecture) { + var path = @"C:\dotnet"; _mockFileHelper.Setup(ph => ph.Exists("testhost.exe")).Returns(true); _mockEnvironment.Setup(ev => ev.OperatingSystem).Returns(PlatformOperatingSystem.Windows); _mockEnvironmentVariable.Reset(); - _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable($"VSTEST_WINAPPHOST_{envVar}")).Returns(expectedValue); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable($"VSTEST_DOTNET_ROOT_PATH")).Returns(path); + _mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable($"VSTEST_DOTNET_ROOT_ARCHITECTURE")).Returns(architecture); var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(_testSource, null, _defaultConnectionInfo); - if (!string.IsNullOrEmpty(expectedValue)) - { - Assert.AreEqual(1, startInfo.EnvironmentVariables!.Count); - Assert.IsNotNull(startInfo.EnvironmentVariables[envVar]); - Assert.AreEqual(startInfo.EnvironmentVariables[envVar], expectedValue); - } - else - { - Assert.AreEqual(0, startInfo.EnvironmentVariables!.Count); - } + + var envVar = $"DOTNET_ROOT_{architecture.ToUpperInvariant()}"; + Assert.IsNotNull(startInfo.EnvironmentVariables![envVar]); + Assert.AreEqual(startInfo.EnvironmentVariables![envVar], path); } [TestMethod] @@ -1060,9 +1064,10 @@ private void ErrorCallBackTestHelper(string errorMessage, int exitCode) It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Callback, Action, Action, Action>( - (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback) => + It.IsAny>(), + It.IsAny())) + .Callback, Action, Action, Action, bool>( + (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback, createNoNewWindow) => { var process = Process.GetCurrentProcess(); @@ -1084,9 +1089,10 @@ private void ExitCallBackTestHelper(int exitCode) It.IsAny>(), It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Callback, Action, Action, Action>( - (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback) => + It.IsAny>(), + It.IsAny())) + .Callback, Action, Action, Action, bool>( + (var1, var2, var3, dictionary, errorCallback, exitCallback, outputCallback, createNoNewWindow) => { var process = Process.GetCurrentProcess(); exitCallback(process); diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/RegressionBugFixTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/RegressionBugFixTests.cs new file mode 100644 index 0000000000..dfc9d4b2d9 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/RegressionBugFixTests.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Text; + +using Microsoft.TestPlatform.TestHostProvider.Hosting; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace TestPlatform.TestHostProvider.Hosting.UnitTests; + +/// +/// Regression tests for: +/// - GH-5184: stderr must be forwarded as Informational, not Error. +/// - GH-2479: ARM64 on Windows must not use testhost.exe. +/// +[TestClass] +public class RegressionBugFixTests +{ + #region GH-5184: Stderr forwarded as Informational + + [TestMethod] + public void ErrorReceivedCallback_ForwardEnabled_MustSendInformational_NotError() + { + // GH-5184: ErrorReceivedCallback changed TestMessageLevel.Error to Informational. + // If the fix were reverted, SendMessage would be called with Error. + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(true, mockLogger.Object); + var stdError = new StringBuilder(0, Microsoft.VisualStudio.TestPlatform.CoreUtilities.Constants.StandardErrorMaxLength); + + callbacks.ErrorReceivedCallback(stdError, "debug output from testhost"); + + // Must be Informational + mockLogger.Verify( + l => l.SendMessage(TestMessageLevel.Informational, "debug output from testhost"), + Times.Once(), + "GH-5184: Stderr must be forwarded as Informational."); + + // Must NOT be Error + mockLogger.Verify( + l => l.SendMessage(TestMessageLevel.Error, It.IsAny()), + Times.Never(), + "GH-5184: Stderr must NOT be forwarded as Error."); + } + + [TestMethod] + public void ErrorReceivedCallback_ForwardDisabled_MustNotSendAnyMessage() + { + // When forwardOutput=false, no messages should be sent regardless of level. + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(false, mockLogger.Object); + var stdError = new StringBuilder(0, Microsoft.VisualStudio.TestPlatform.CoreUtilities.Constants.StandardErrorMaxLength); + + callbacks.ErrorReceivedCallback(stdError, "some stderr text"); + + mockLogger.Verify( + l => l.SendMessage(It.IsAny(), It.IsAny()), + Times.Never()); + } + + #endregion + + #region GH-2479: ARM64 doesn't use testhost.exe + + [TestMethod] + public void GetTestHostProcessStartInfo_ARM64OnWindows_MustNotUseTestHostExe() + { + // GH-2479: On ARM64 Windows, testhost.exe must not be used because the + // apphost cannot be relied upon. The fix added an IsWinOnArm() guard that + // checks the Machine-level PROCESSOR_ARCHITECTURE environment variable. + // + // To be a true regression test, testhost.arm64.exe is mocked as existing + // so the code WOULD use it if the guard weren't there. On ARM64 CI where + // IsWinOnArm() returns true, the guard blocks entry into the exe-search + // block entirely. + var mockProcessHelper = new Mock(); + var mockFileHelper = new Mock(); + var mockEnvironment = new Mock(); + var mockWindowsRegistry = new Mock(); + var mockRunsettingHelper = new Mock(); + var mockEnvironmentVariable = new Mock(); + var mockMessageLogger = new Mock(); + + var temp = Path.GetTempPath(); + var testSourcePath = Path.Combine(temp, "test.dll"); + var testhostDllPath = Path.Combine(temp, "testhost.dll"); + var engineDir = @"c:\tmp"; + var dotnetPath = Path.Combine(engineDir, "dotnet.exe"); + + // For ARM64, the code generates "testhost.arm64.exe". Mock it as + // existing so the exe-search code path is actually exercised. + var testhostExePath = Path.Combine(temp, "testhost.arm64.exe"); + mockFileHelper.Setup(fh => fh.Exists(testhostExePath)).Returns(true); + mockFileHelper.Setup(fh => fh.Exists(testhostDllPath)).Returns(true); + mockFileHelper.Setup(fh => fh.Exists(dotnetPath)).Returns(true); + + mockEnvironment.SetupGet(e => e.Architecture).Returns(PlatformArchitecture.ARM64); + mockEnvironment.Setup(ev => ev.OperatingSystem).Returns(PlatformOperatingSystem.Windows); + mockRunsettingHelper.SetupGet(r => r.IsDefaultTargetArchitecture).Returns(false); + mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns(dotnetPath); + mockProcessHelper.Setup(ph => ph.GetTestEngineDirectory()).Returns(engineDir); + mockProcessHelper.Setup(ph => ph.GetCurrentProcessArchitecture()).Returns(PlatformArchitecture.ARM64); + + // PROCESSOR_ARCHITECTURE mock — IsWinOnArm() reads the Machine-level + // env var via Environment.GetEnvironmentVariable (not this helper), so + // the guard's behavior depends on the real hardware. This mock documents + // the intended scenario. + mockEnvironmentVariable + .Setup(ev => ev.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")) + .Returns("ARM64"); + + var hostManager = new DotnetTestHostManager( + mockProcessHelper.Object, + mockFileHelper.Object, + new DotnetHostHelper(mockFileHelper.Object, mockEnvironment.Object, + mockWindowsRegistry.Object, mockEnvironmentVariable.Object, mockProcessHelper.Object), + mockEnvironment.Object, + mockRunsettingHelper.Object, + mockWindowsRegistry.Object, + mockEnvironmentVariable.Object); + + hostManager.Initialize(mockMessageLogger.Object, + "ARM64"); + + var connectionInfo = new TestRunnerConnectionInfo + { + Port = 123, + ConnectionInfo = new TestHostConnectionInfo + { + Endpoint = "127.0.0.1:123", + Role = ConnectionRole.Client, + }, + RunnerProcessId = 0, + }; + + var startInfo = hostManager.GetTestHostProcessStartInfo( + new[] { testSourcePath }, null, connectionInfo); + + Assert.IsNotNull(startInfo); + Assert.IsFalse( + startInfo.FileName!.EndsWith("testhost.exe", StringComparison.OrdinalIgnoreCase), + "GH-2479: ARM64 on Windows must NOT use testhost.exe."); + Assert.IsFalse( + startInfo.FileName!.EndsWith("testhost.x86.exe", StringComparison.OrdinalIgnoreCase), + "GH-2479: ARM64 on Windows must NOT use testhost.x86.exe."); + } + + #endregion +} diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksRegressionTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksRegressionTests.cs new file mode 100644 index 0000000000..904caea832 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksRegressionTests.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Text; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using TestHostManagerCallbacks = Microsoft.TestPlatform.TestHostProvider.Hosting.TestHostManagerCallbacks; + +namespace TestPlatform.TestHostProvider.UnitTests.Hosting; + +/// +/// Regression tests for TestHostManagerCallbacks error output handling. +/// +[TestClass] +public class TestHostManagerCallbacksRegressionTests +{ + // Regression test for #5192 — Forward error output from testhost as info + // Before the fix, stderr output from testhost was forwarded as Error level, + // causing test runs to appear to have failed even when tests passed. + [TestMethod] + public void ErrorReceivedCallback_WithData_ShouldForwardAsInformational() + { + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(forwardOutput: true, mockLogger.Object); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, "Some debug output to stderr"); + + mockLogger.Verify( + l => l.SendMessage(TestMessageLevel.Informational, "Some debug output to stderr"), + Times.Once); + } + + // Regression test for #5192 + [TestMethod] + public void ErrorReceivedCallback_WithData_ShouldNotForwardAsError() + { + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(forwardOutput: true, mockLogger.Object); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, "Some stderr text"); + + mockLogger.Verify( + l => l.SendMessage(TestMessageLevel.Error, It.IsAny()), + Times.Never); + } + + // Regression test for #5192 + [TestMethod] + public void ErrorReceivedCallback_WithNullData_ShouldNotForward() + { + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(forwardOutput: true, mockLogger.Object); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, null); + + mockLogger.Verify( + l => l.SendMessage(It.IsAny(), It.IsAny()), + Times.Never); + } + + // Regression test for #5192 + [TestMethod] + public void ErrorReceivedCallback_WithEmptyData_ShouldNotForward() + { + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(forwardOutput: true, mockLogger.Object); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, " "); + + mockLogger.Verify( + l => l.SendMessage(It.IsAny(), It.IsAny()), + Times.Never); + } + + // Regression test for #5192 + [TestMethod] + public void ErrorReceivedCallback_ForwardDisabled_ShouldNotSendMessage() + { + var mockLogger = new Mock(); + var callbacks = new TestHostManagerCallbacks(forwardOutput: false, mockLogger.Object); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, "Some error output"); + + mockLogger.Verify( + l => l.SendMessage(It.IsAny(), It.IsAny()), + Times.Never); + } + + // Regression test for #5192 + [TestMethod] + public void ErrorReceivedCallback_ShouldAppendToStdError() + { + var callbacks = new TestHostManagerCallbacks(forwardOutput: false, null); + var stdError = new StringBuilder(); + + callbacks.ErrorReceivedCallback(stdError, "line1"); + callbacks.ErrorReceivedCallback(stdError, "line2"); + + var output = stdError.ToString(); + Assert.Contains("line1", output); + Assert.Contains("line2", output); + } +} diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Microsoft.TestPlatform.TestHostProvider.UnitTests.csproj b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Microsoft.TestPlatform.TestHostProvider.UnitTests.csproj index 2fc30c8612..09db15d7e8 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Microsoft.TestPlatform.TestHostProvider.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Microsoft.TestPlatform.TestHostProvider.UnitTests.csproj @@ -7,17 +7,10 @@ Microsoft.TestPlatform.TestHostProvider.UnitTests - net6.0;net48 - Exe + net9.0;net48 + Exe - - - - - - - diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Program.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Program.cs deleted file mode 100644 index 79c3743c99..0000000000 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AcceptanceTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/AcceptanceTestBase.cs similarity index 71% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AcceptanceTestBase.cs rename to test/Microsoft.TestPlatform.TestUtilities/AcceptanceTestBase.cs index cac7da3fb0..eb285985b8 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/AcceptanceTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/AcceptanceTestBase.cs @@ -4,10 +4,9 @@ using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; -using Microsoft.TestPlatform.TestUtilities; - -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; public class AcceptanceTestBase : IntegrationTestBase { @@ -17,9 +16,10 @@ public class AcceptanceTestBase : IntegrationTestBase public const string Net471TargetFramework = "net471"; public const string Net472TargetFramework = "net472"; public const string Net48TargetFramework = "net48"; - public const string Core31TargetFramework = "netcoreapp3.1"; - public const string Core50TargetFramework = "net5.0"; - public const string Core60TargetFramework = "net6.0"; + public const string Core80TargetFramework = "net8.0"; + public const string Core90TargetFramework = "net9.0"; + public const string Core10TargetFramework = "net10.0"; + public const string Core11TargetFramework = "net11.0"; public const string DesktopFrameworkArgValue = Net462FrameworkArgValue; public const string Net462FrameworkArgValue = ".NETFramework,Version=v4.6.2"; @@ -28,27 +28,34 @@ public class AcceptanceTestBase : IntegrationTestBase public const string Net472FrameworkArgValue = ".NETFramework,Version=v4.7.2"; public const string Net48FrameworkArgValue = ".NETFramework,Version=v4.8"; - public const string Core31FrameworkArgValue = ".NETCoreApp,Version=v3.1"; - public const string Core50FrameworkArgValue = ".NETCoreApp,Version=v5.0"; - public const string Core60FrameworkArgValue = ".NETCoreApp,Version=v6.0"; + public const string Core80FrameworkArgValue = ".NETCoreApp,Version=v8.0"; + public const string Core90FrameworkArgValue = ".NETCoreApp,Version=v9.0"; + public const string Core10FrameworkArgValue = ".NETCoreApp,Version=v10.0"; + public const string Core11FrameworkArgValue = ".NETCoreApp,Version=v11.0"; public const string DesktopRunnerTargetRuntime = "win7-x64"; public const string CoreRunnerTargetRuntime = ""; public const string InIsolation = "/InIsolation"; public const string NETFX462_48 = "net462;net472;net48"; - public const string NETFX462_NET50 = "net462;net472;net48;netcoreapp3.1;net5.0"; - public const string DEFAULT_RUNNER_NETFX = Net462TargetFramework; + public const string NETFX462_NET11 = "net462;net472;net48;net8.0;net9.0;net10.0;net11.0"; + public const string DEFAULT_RUNNER_NETFX = Net48TargetFramework; public const string DEFAULT_HOST_NETFX = Net462TargetFramework; - public const string DEFAULT_RUNNER_NETCORE = Core31TargetFramework; - public const string DEFAULT_HOST_NETCORE = Core31TargetFramework; + public const string DEFAULT_RUNNER_NETCORE = Core80TargetFramework; + public const string DEFAULT_HOST_NETCORE = Core80TargetFramework; /// /// Our current defaults for .NET and .NET Framework. /// - public const string DEFAULT_HOST_NETFX_AND_NET = "net462;netcoreapp3.1"; + public const string DEFAULT_HOST_NETFX_AND_NET = "net462;net8.0"; + public const string DEFAULT_HOST_NET = "net8.0"; + public const string DEFAULT_RUNNER_NETFX_AND_NET = "net48;net10.0"; + public const string DEFAULT_RUNNER_NET = "net10.0"; public const string LATEST_TO_LEGACY = "Latest;LatestPreview;LatestStable;RecentStable;MostDownloaded;PreviousStable;LegacyStable"; + public const string LATEST_TO_RECENT_STABLE = "Latest;LatestPreview;LatestStable;RecentStable"; public const string LATESTPREVIEW_TO_LEGACY = "LatestPreview;LatestStable;RecentStable;MostDownloaded;PreviousStable;LegacyStable"; public const string LATEST = "Latest"; + // "Special" version for runner, to take the latest from VSIX we don't ship any other component that way, so we need separate value to control it. + public const string LATESTVSIX = "LatestVsix"; public const string LATESTSTABLE = "LatestStable"; internal const string MSTEST = "MSTest"; @@ -74,9 +81,10 @@ protected static void SetTestEnvironment(IntegrationTestEnvironment testEnvironm protected static string DeriveFrameworkArgValue(IntegrationTestEnvironment testEnvironment) => testEnvironment.TargetFramework switch { - Core31TargetFramework => Core31FrameworkArgValue, - Core50TargetFramework => Core50FrameworkArgValue, - Core60TargetFramework => Core60FrameworkArgValue, + Core80TargetFramework => Core80FrameworkArgValue, + Core90TargetFramework => Core90FrameworkArgValue, + Core10TargetFramework => Core10FrameworkArgValue, + Core11TargetFramework => Core11FrameworkArgValue, Net462TargetFramework => Net462FrameworkArgValue, Net47TargetFramework => Net47FrameworkArgValue, Net471TargetFramework => Net471FrameworkArgValue, @@ -130,7 +138,7 @@ public static string GetRunSettingsWithTargetFramework(string targetFramework) return runSettingsXml; } - protected string GetIsolatedTestAsset(string assetName) + protected string GetIsolatedTestAsset(string assetName, string targetFramework) { var projectPath = GetProjectFullPath(assetName); @@ -140,6 +148,13 @@ protected string GetIsolatedTestAsset(string assetName) if (file.Extension.Equals(".csproj", StringComparison.OrdinalIgnoreCase)) { + // Build just for the given tfm + var projFile = Path.Combine(TempDirectory.Path, Path.GetFileName(file.FullName)); + var csprojContent = File.ReadAllText(projFile); + csprojContent = Regex.Replace(csprojContent, ".*?", $"{targetFramework}"); + csprojContent = Regex.Replace(csprojContent, ".*?", $"{targetFramework}"); + File.WriteAllText(projFile, csprojContent); + string root = IntegrationTestEnvironment.RepoRootDirectory; var testAssetsRoot = Path.GetFullPath(Path.Combine(root, "test", "TestAssets")); @@ -156,11 +171,13 @@ protected string GetIsolatedTestAsset(string assetName) Directory.CreateDirectory(Path.Combine(TempDirectory.Path, "eng")); File.Copy(Path.Combine(root, "eng", "Versions.props"), Path.Combine(TempDirectory.Path, "eng", "Versions.props")); + File.Copy(Path.Combine(root, "eng", "Version.Details.props"), + Path.Combine(TempDirectory.Path, "eng", "Version.Details.props")); // Copy NuGet.config var nugetContent = File.ReadAllText(Path.Combine(root, "NuGet.config")) - // and make packages folder point to vstest packages folder - .Replace("\".packages\"", "\"" + Path.Combine(root, ".packages") + "\"") + // Point packages folder to vstest's local .packages cache used by test assets. + .Replace("", $"""""") // and add local package source .Replace("", $""""""); File.WriteAllText(Path.Combine(TempDirectory.Path, "NuGet.config"), nugetContent); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageAcceptanceTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/CodeCoverageAcceptanceTestBase.cs similarity index 89% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageAcceptanceTestBase.cs rename to test/Microsoft.TestPlatform.TestUtilities/CodeCoverageAcceptanceTestBase.cs index 9036785a94..4137b7434c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageAcceptanceTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/CodeCoverageAcceptanceTestBase.cs @@ -9,11 +9,10 @@ using Microsoft.CodeCoverage.Core; using Microsoft.CodeCoverage.Core.Reports.Coverage; -using Microsoft.TestPlatform.TestUtilities; - +using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; public class CodeCoverageAcceptanceTestBase : AcceptanceTestBase { @@ -53,7 +52,7 @@ protected static void AssertCoverage(ModuleData module, double expectedCoverage) string coverageData = module.CoverageBuffer.Length == 0 ? module.LineCoverage : module.BlockCoverage; var coverage = double.Parse(coverageData, CultureInfo.InvariantCulture); Console.WriteLine($"Checking coverage for {module.Name}. Expected at least: {expectedCoverage}. Result: {coverage}"); - Assert.IsTrue(coverage > expectedCoverage, $"Coverage check failed for {module.Name}. Expected at least: {expectedCoverage}. Found: {coverage}"); + Assert.IsGreaterThan(expectedCoverage, coverage, $"Coverage check failed for {module.Name}. Expected at least: {expectedCoverage}. Found: {coverage}"); } protected static string GetCoverageFileNameFromTrx(string trxFilePath, string resultsDirectory) @@ -63,10 +62,10 @@ protected static string GetCoverageFileNameFromTrx(string trxFilePath, string re using var trxStream = new FileStream(trxFilePath, FileMode.Open, FileAccess.Read); doc.Load(trxStream); var deploymentElements = doc.GetElementsByTagName("Deployment"); - Assert.IsTrue(deploymentElements.Count == 1, + Assert.HasCount(1, deploymentElements, "None or more than one Deployment tags found in trx file:{0}", trxFilePath); var deploymentDir = deploymentElements[0]!.Attributes!.GetNamedItem("runDeploymentRoot")?.Value; - Assert.IsTrue(string.IsNullOrEmpty(deploymentDir) == false, + Assert.IsFalse(StringUtils.IsNullOrEmpty(deploymentDir), "runDeploymentRoot attribute not found in trx file:{0}", trxFilePath); var collectors = doc.GetElementsByTagName("Collector"); @@ -81,7 +80,7 @@ protected static string GetCoverageFileNameFromTrx(string trxFilePath, string re } } - Assert.IsTrue(string.IsNullOrEmpty(fileName) == false, "Coverage file name not found in trx file: {0}", + Assert.IsFalse(StringUtils.IsNullOrEmpty(fileName), "Coverage file name not found in trx file: {0}", trxFilePath); return Path.Combine(resultsDirectory, deploymentDir, "In", fileName); } diff --git a/test/Microsoft.TestPlatform.TestUtilities/CompatibilityDataSourceAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityDataSourceAttribute.cs new file mode 100644 index 0000000000..b997ce1d2f --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityDataSourceAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Common "marker" class for compatibility sources, to make finding them easier. +/// +public abstract class CompatibilityDataSourceAttribute : TestDataSourceAttribute +{ + public CompatibilityDataSourceAttribute() + { + } +} diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/CompatibilityRowsBuilder.cs b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityRowsBuilder.cs similarity index 64% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/CompatibilityRowsBuilder.cs rename to test/Microsoft.TestPlatform.TestUtilities/CompatibilityRowsBuilder.cs index d798463ae2..b1dee9cfd5 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/CompatibilityRowsBuilder.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityRowsBuilder.cs @@ -6,28 +6,30 @@ using System.IO; using System.Linq; using System.Xml; -using NuGet.Versioning; -using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using NuGet.Versioning; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; public class CompatibilityRowsBuilder { private static XmlDocument? s_depsXml; private readonly string[] _runnerFrameworks; - private readonly string[] _runnerVersions; private readonly string[] _hostFrameworks; - private readonly string[] _adapterVersions; private readonly string[] _adapters; + + private readonly string[] _runnerVersions; private readonly string[] _hostVersions; + private readonly string[] _adapterVersions; - public CompatibilityRowsBuilder(string runnerFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string runnerVersions = AcceptanceTestBase.LATEST_TO_LEGACY, - string hostFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string hostVersions = AcceptanceTestBase.LATEST_TO_LEGACY, - string adapterVersions = AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY, - string adapters = AcceptanceTestBase.MSTEST) + public CompatibilityRowsBuilder(string runnerVersions, + string runnerFrameworks, + string hostVersions, + string hostFrameworks, + string adapterVersions, + string adapters) { _runnerFrameworks = runnerFrameworks.Split(';'); _runnerVersions = runnerVersions.Split(';'); @@ -38,13 +40,11 @@ public CompatibilityRowsBuilder(string runnerFrameworks = AcceptanceTestBase.DEF } /// - /// Add run for in-process using the selected .NET Framework runners, and and all selected adapters. + /// Add run for in-process using the selected .NET Framework runners, and all selected adapters. /// - public bool WithInProcess { get; set; } = true; - public bool WithEveryVersionOfRunner { get; set; } = true; - public bool WithEveryVersionOfHost { get; set; } = true; - public bool WithEveryVersionOfAdapter { get; set; } = true; - public bool WithOlderConfigurations { get; set; } = true; + public bool WithInProcess { get; set; } + // Add runner from VSIX to check the shipment we make into VisualStudio. + public bool WithVSIXRunner { get; set; } public string? BeforeRunnerFeature { get; set; } public string? AfterRunnerFeature { get; set; } @@ -59,25 +59,18 @@ public CompatibilityRowsBuilder(string runnerFrameworks = AcceptanceTestBase.DEF public bool DebugStopAtEntrypoint { get; set; } public int? JustRow { get; internal set; } - public List CreateData() + public List> CreateData() { var dataRows = new List(); - if (WithEveryVersionOfRunner) - AddEveryVersionOfRunner(dataRows); - - if (WithEveryVersionOfHost) - AddEveryVersionOfHost(dataRows); - - if (WithEveryVersionOfAdapter) - AddEveryVersionOfAdapter(dataRows); - - if (WithOlderConfigurations) - AddOlderConfigurations(dataRows); + AddRows(dataRows); if (WithInProcess) AddInProcess(dataRows); + if (WithVSIXRunner) + AddVsix(dataRows, WithInProcess); + var minVersion = ParseAndPatchSemanticVersion("0.0.0-alpha.1"); var maxVersion = ParseAndPatchSemanticVersion("9999.0.0"); SemanticVersion? beforeRunnerVersion = maxVersion; @@ -139,14 +132,53 @@ public List CreateData() && r.TestHostInfo != null && isInRange(ParseAndPatchSemanticVersion(r.TestHostInfo.Version), beforeTestHostVersion, afterTestHostVersion) && r.AdapterInfo != null && isInRange(ParseAndPatchSemanticVersion(r.AdapterInfo.Version), beforeAdapterVersion, afterAdapterVersion)).ToList(); - // We use ToString to determine which values are unique. Not great solution, but works better than using records. + // Figure out the distinct rows, and shrink the values, so if we have multiple rows that use the same versions and same setup, and only differ in how the version types + // are called (e.g. Latest and LatestStable have the same version), then we get just single row, with description for both. + // like RunMultipleTestAssemblies1 (Row: 0, Matrix, Runner = net10.0, TargetFramework = net462, InIsolation, vstest.console = 18.6.0-dev [Latest], Testhost = 18.6.0-dev [Latest], MSTest = 3.9.3 [LatestPreview, LatestStable]) var distinctRows = new Dictionary(); - rows.ForEach(r => distinctRows[r.ToString()] = r); + foreach (var r in rows) + { + var key = $"{r.Batch}|{r.RunnerFramework}|{r.VSTestConsoleInfo!.Version}|{r.TargetFramework}|{r.TestHostInfo!.Version}|{r.InIsolationValue}|{r.AdapterInfo!.Name}|{r.AdapterInfo.Version}"; + if (distinctRows.TryGetValue(key, out var value)) + { + if (value.VSTestConsoleInfo!.Version == r.VSTestConsoleInfo.Version) + { + value.VSTestConsoleInfo.VersionType += $",{r.VSTestConsoleInfo.VersionType}"; + } + + if (value.TestHostInfo!.Version == r.TestHostInfo.Version) + { + value.TestHostInfo.VersionType += $",{r.TestHostInfo.VersionType}"; + } + + if (r.AdapterInfo!.Version == value.AdapterInfo!.Version) + { + value.AdapterInfo.VersionType += $",{r.AdapterInfo.VersionType}"; + } + } + else + { + distinctRows.Add(key, r); + } + } + + // We added all the version types together (would have been better to have this property as array originally, but that would complicate other code), now deduplicate the names to make it less confusing for user. + // Latest, Latest, Latest, LatestPreview -> Latest, LatestPreview + foreach (var r in distinctRows.Values) + { + r.VSTestConsoleInfo!.VersionType = string.Join(", ", r.VSTestConsoleInfo!.VersionType!.Split(',').Distinct()); + r.TestHostInfo!.VersionType = string.Join(", ", r.TestHostInfo!.VersionType!.Split(',').Distinct()); + r.AdapterInfo!.VersionType = string.Join(", ", r.AdapterInfo!.VersionType!.Split(',').Distinct()); + } if (distinctRows.Count == 0) { - // TODO: This needs to be way more specific about what happened. And possibly propagate as inconclusive state if we decide to update versions automatically? - throw new InvalidOperationException("There were no rows that matched the specified criteria."); + throw new InvalidOperationException( + $"No compatibility rows matched the specified criteria. " + + $"Runner version range: [{afterRunnerVersion}, {beforeRunnerVersion}), " + + $"TestHost version range: [{afterTestHostVersion}, {beforeTestHostVersion}), " + + $"Adapter version range: [{afterAdapterVersion}, {beforeAdapterVersion}). " + + $"Total candidate rows before filtering: {dataRows.Count}, after version filtering: {rows.Count}, after deduping: {distinctRows.Count}."); } var allRows = distinctRows.Values.ToList(); @@ -155,7 +187,22 @@ public List CreateData() allRows[i].Index = i; } - return JustRow == null ? allRows : [allRows[JustRow.Value]]; + foreach (var r in allRows) + { + var hasLatest = r.VSTestConsoleInfo!.VersionType!.Split(',').Any(a => a.Trim() == "Latest") + || r.TestHostInfo!.VersionType!.Split(',').Any(a => a.Trim() == "Latest") + || r.AdapterInfo!.VersionType!.Split(',').Any(a => a.Trim() == "Latest"); + if (!hasLatest) + { + throw new InvalidOperationException($"Row {r.ToString()} does not have any version marked as Latest. You are testing only versions that were already shipped."); + } + } + + var selectedRows = JustRow == null ? allRows : [allRows[JustRow.Value]]; + return [.. selectedRows.Select(r => new TestDataRow(r) + { + TestCategories = ["Compatibility"], + })]; } private static SemanticVersion ParseAndPatchSemanticVersion(string? version) @@ -166,32 +213,8 @@ private static SemanticVersion ParseAndPatchSemanticVersion(string? version) return SemanticVersion.Parse(v?.TrimStart('v')); } - private void AddInProcess(List dataRows) - { - foreach (var runnerFramework in _runnerFrameworks) - { - if (!runnerFramework.StartsWith("net4")) - { - continue; - } - - foreach (var runnerVersion in _runnerVersions) - { - foreach (var adapter in _adapters) - { - foreach (var adapterVersion in _adapterVersions) - { - AddRow(dataRows, "In process", runnerVersion, runnerFramework, runnerVersion, runnerFramework, adapterVersion, inIsolation: false); - } - } - } - } - } - - private void AddOlderConfigurations(List dataRows) + private void AddRows(List dataRows) { - // Older configurations where the runner, host and adapter version are the same. - // We already added the row where all are newest when adding combination with all runners. foreach (var runnerVersion in _runnerVersions) { foreach (var runnerFramework in _runnerFrameworks) @@ -199,91 +222,74 @@ private void AddOlderConfigurations(List dataRows) foreach (var hostFramework in _hostFrameworks) { var isNetFramework = hostFramework.StartsWith("net4"); - var hostVersion = runnerVersion; - foreach (var _ in _adapters) + + foreach (var hostVersion in _hostVersions) { - var adapterVersion = _adapterVersions[0]; - AddRow(dataRows, "Older", runnerVersion, runnerFramework, hostVersion, hostFramework, adapterVersion, inIsolation: true); + if (isNetFramework && runnerVersion != hostVersion) + { + // For .NET Framework, runner and host versions must be the same, + // becase ship them in one package, and we will select the testhost from vstest.console package. + continue; + } + + foreach (var adapterVersion in _adapterVersions) + { + foreach (var adapter in _adapters) + { + AddRow(dataRows, "Matrix", runnerVersion, runnerFramework, hostVersion, hostFramework, adapterVersion, adapter, inIsolation: true); + } + } } } } } } - private void AddEveryVersionOfAdapter(List dataRows) + private void AddInProcess(List dataRows) { - var runnerVersion = _runnerVersions[0]; foreach (var runnerFramework in _runnerFrameworks) { - foreach (var hostFramework in _hostFrameworks) + if (!runnerFramework.StartsWith("net4")) + { + continue; + } + + foreach (var runnerVersion in _runnerVersions) { - var isNetFramework = hostFramework.StartsWith("net4"); - // .NET Framework testhost ships with the runner, and the version from the - // runner directory is always selected, otherwise select the newest version from _hostFrameworks. - var hostVersion = isNetFramework ? runnerVersion : _hostVersions[0]; foreach (var adapter in _adapters) { foreach (var adapterVersion in _adapterVersions) { - AddRow(dataRows, "Every adapter", runnerVersion, runnerFramework, hostVersion, hostFramework, adapterVersion, inIsolation: true); + AddRow(dataRows, "In process", runnerVersion, runnerFramework, runnerVersion, runnerFramework, adapterVersion, adapter, inIsolation: false); } } } } } - private void AddEveryVersionOfHost(List dataRows) + private void AddVsix(List dataRows, bool inProcess) { - var runnerVersion = _runnerVersions[0]; - - foreach (var runnerFramework in _runnerFrameworks) + // Runs outside of process, test both tfms of testhost. + foreach (var hostFramework in _hostFrameworks) { - foreach (var hostFramework in _hostFrameworks) - { - var isNetFramework = hostFramework.StartsWith("net4"); - // .NET Framework testhost ships with the runner, and the version from the - // runner directory is always the same as the runner. There are no variations - // so we just need to add host versions for .NET testhosts. - var hostVersions = isNetFramework ? [] : _hostVersions.ToArray(); - foreach (var hostVersion in hostVersions) - { - foreach (var _ in _adapters) - { - // use the newest - var adapterVersion = _adapterVersions[0]; - AddRow(dataRows, "Every host", runnerVersion, runnerFramework, hostVersion, hostFramework, adapterVersion, inIsolation: true); - } - } - } + AddRow(dataRows, "VSIX", AcceptanceTestBase.LATESTVSIX, AcceptanceTestBase.DEFAULT_RUNNER_NETFX, AcceptanceTestBase.LATEST, hostFramework, AcceptanceTestBase.LATESTSTABLE, AcceptanceTestBase.MSTEST, inIsolation: true); } - } - private void AddEveryVersionOfRunner(List dataRows) - { - foreach (var runnerVersion in _runnerVersions) + if (inProcess) { - foreach (var runnerFramework in _runnerFrameworks) - { - foreach (var hostFramework in _hostFrameworks) - { - var isNetFramework = hostFramework.StartsWith("net4"); - // .NET Framework testhost ships with the runner, and the version from the - // runner directory is always selected, otherwise select the newest version from _hostFrameworks. - var hostVersion = isNetFramework ? runnerVersion : _hostVersions[0]; - foreach (var _ in _adapters) - { - // use the newest - var adapterVersion = _adapterVersions[0]; - AddRow(dataRows, "Every runner", runnerVersion, runnerFramework, hostVersion, hostFramework, adapterVersion, inIsolation: true); - } - } - } + // Runs in process. We specify the testhost, but it has no impact. + AddRow(dataRows, "VSIX", AcceptanceTestBase.LATESTVSIX, AcceptanceTestBase.DEFAULT_RUNNER_NETFX, AcceptanceTestBase.LATEST, AcceptanceTestBase.DEFAULT_HOST_NETFX, AcceptanceTestBase.LATESTSTABLE, AcceptanceTestBase.MSTEST, inIsolation: false); } } private void AddRow(List dataRows, string batch, - string runnerVersion, string runnerFramework, string hostVersion, string hostFramework, string adapterVersion, bool inIsolation) + string runnerVersion, string runnerFramework, string hostVersion, string hostFramework, string adapterVersion, string adapter, bool inIsolation) { + if (adapter != AcceptanceTestBase.MSTEST) + { + throw new NotSupportedException($"Adapter {adapter} is not supported. Only {AcceptanceTestBase.MSTEST} is supported."); + } + RunnerInfo runnerInfo = GetRunnerInfo(batch, runnerFramework, hostFramework, inIsolation); runnerInfo.DebugInfo = GetDebugInfo(); runnerInfo.VSTestConsoleInfo = GetVSTestConsoleInfo(runnerVersion, runnerInfo); @@ -336,6 +342,16 @@ private static DllInfo GetMSTestInfo(string msTestVersion) private static VSTestConsoleInfo GetVSTestConsoleInfo(string vstestConsoleVersion, RunnerInfo runnerInfo) { + if (vstestConsoleVersion == AcceptanceTestBase.LATESTVSIX) + { + return new VSTestConsoleInfo + { + VersionType = vstestConsoleVersion, + Version = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion, + Path = Path.Combine(IntegrationTestEnvironment.PublishDirectory, Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion), "vstest.console.exe"), + }; + } + var depsXml = GetDependenciesXml(); var packageName = runnerInfo.IsNetFrameworkRunner ? "microsoft.testplatform" @@ -358,6 +374,8 @@ private static VSTestConsoleInfo GetVSTestConsoleInfo(string vstestConsoleVersio XmlNode? node = depsXml.DocumentElement?.SelectSingleNode($"PropertyGroup/{propertyName}"); version = node?.InnerText.Replace("[", "").Replace("]", "") ?? "--WRONG-VERSION--"; } + + // Target frameworks changed in the package over time as we are moving forward, this table selects the correct one. var vstestConsolePath = runnerInfo.IsNetFrameworkRunner switch { true when NuGetVersion.TryParse(version, out var v) @@ -366,7 +384,11 @@ true when NuGetVersion.TryParse(version, out var v) false when version.StartsWith("15.") => GetContentFilesPath("netcoreapp2.0"), false when NuGetVersion.TryParse(version, out var v) && new NuGetVersion(v.Major, v.Minor, v.Patch) < new NuGetVersion("17.4.0") => GetContentFilesPath("netcoreapp2.1"), - false => GetContentFilesPath("netcoreapp3.1"), + false when NuGetVersion.TryParse(version, out var v) + && new NuGetVersion(v.Major, v.Minor, v.Patch) <= new NuGetVersion("17.12.0") => GetContentFilesPath("netcoreapp3.1"), + false when NuGetVersion.TryParse(version, out var v) + && new NuGetVersion(v.Major, v.Minor, v.Patch) <= new NuGetVersion("18.3.0") => GetContentFilesPath("net9.0"), + false => GetContentFilesPath("net10.0"), }; return new VSTestConsoleInfo @@ -376,10 +398,10 @@ false when NuGetVersion.TryParse(version, out var v) Path = vstestConsolePath, }; - string GetToolsPath(string fwkVersion) => Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, ".packages", + string GetToolsPath(string fwkVersion) => Path.Combine(IntegrationTestEnvironment.TestAssetsNuGetCacheDirectory, packageName, version, "tools", fwkVersion, "Common7", "IDE", "Extensions", "TestPlatform", "vstest.console.exe"); - string GetContentFilesPath(string fwkVersion) => Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, ".packages", + string GetContentFilesPath(string fwkVersion) => Path.Combine(IntegrationTestEnvironment.TestAssetsNuGetCacheDirectory, packageName, version, "contentFiles", "any", fwkVersion, "vstest.console.dll"); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/MSTestCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.TestUtilities/CustomCompatibilityDataSource.cs similarity index 61% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/MSTestCompatibilityDataSource.cs rename to test/Microsoft.TestPlatform.TestUtilities/CustomCompatibilityDataSource.cs index 27d41b3401..c9b1ca540e 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/MSTestCompatibilityDataSource.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/CustomCompatibilityDataSource.cs @@ -3,31 +3,25 @@ using System.Reflection; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; -public class MSTestCompatibilityDataSource : TestDataSourceAttribute +/// +/// A data source that provides a custom mix of runners, hosts and mstest adapter. You can control everything, +/// so be careful how many tests you generate. This is meant to be used for experimentation, and only when , , or do not fit the need. +/// +public class CustomCompatibilityDataSource : CompatibilityDataSourceAttribute { private readonly CompatibilityRowsBuilder _builder; - public MSTestCompatibilityDataSource( - string runnerFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, + public CustomCompatibilityDataSource( + string runnerFrameworks = AcceptanceTestBase.DEFAULT_RUNNER_NETFX_AND_NET, + string runnerVersions = AcceptanceTestBase.LATEST_TO_LEGACY, string hostFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string adapterVersions = AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY) + string hostVersions = AcceptanceTestBase.LATEST_TO_LEGACY, + string adapterVersions = AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY, + string adapters = AcceptanceTestBase.MSTEST) { - // TODO: We actually don't generate values to use different translation layers, because we don't have a good way to do - // that right now. Translation layer is loaded directly into the acceptance test, and so we don't have easy way to substitute it. - - _builder = new CompatibilityRowsBuilder( - runnerFrameworks, - // runner versions - AcceptanceTestBase.LATEST_TO_LEGACY, - hostFrameworks, - // host versions - AcceptanceTestBase.LATEST_TO_LEGACY, - adapterVersions, - // adapters - AcceptanceTestBase.MSTEST); - + _builder = new CompatibilityRowsBuilder(runnerFrameworks, runnerVersions, hostFrameworks, hostVersions, adapterVersions, adapters); // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. } @@ -39,7 +33,9 @@ public MSTestCompatibilityDataSource( /// /// Add run for in-process using the selected .NET Framework runners, and and all selected adapters. /// - public bool InProcess { get; set; } + public bool WithInProcess { get; set; } = true; + + public bool WithVSIXRunner { get; set; } = true; public string? BeforeRunnerFeature { get; set; } public string? AfterRunnerFeature { get; set; } @@ -52,11 +48,8 @@ public MSTestCompatibilityDataSource( public override void CreateData(MethodInfo methodInfo) { - _builder.WithEveryVersionOfRunner = false; - _builder.WithEveryVersionOfHost = false; - _builder.WithEveryVersionOfAdapter = true; - _builder.WithOlderConfigurations = false; - _builder.WithInProcess = InProcess; + _builder.WithVSIXRunner = WithVSIXRunner; + _builder.WithInProcess = WithInProcess; _builder.BeforeRunnerFeature = BeforeRunnerFeature; _builder.AfterRunnerFeature = AfterRunnerFeature; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Feature.cs b/test/Microsoft.TestPlatform.TestUtilities/Feature.cs similarity index 88% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Feature.cs rename to test/Microsoft.TestPlatform.TestUtilities/Feature.cs index 9014349f06..9f6f805508 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/Feature.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/Feature.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; public class Feature { diff --git a/test/Microsoft.TestPlatform.TestUtilities/Features.cs b/test/Microsoft.TestPlatform.TestUtilities/Features.cs new file mode 100644 index 0000000000..5a43cd2c1c --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/Features.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Table of features and the versions in which they were introduced, so we can test with them in compatibility data sources. +/// As the versions of the compatibility matrix get updated, the tests will eventually become irrelevant and can be deleted. +/// +public static class Features +{ + // History of features for reference, do not delete: + // public const string ATTACH_DEBUGGER_FLOW = nameof(ATTACH_DEBUGGER_FLOW); + // public const string MULTI_TFM = nameof(MULTI_TFM); + public const string MSTEST_EXAMPLE_FEATURE = nameof(MSTEST_EXAMPLE_FEATURE); + + + public static IImmutableDictionary TestPlatformFeatures { get; } = new Dictionary + { + // History of features for reference. + // [ATTACH_DEBUGGER_FLOW] = new(version: "v16.7.0-preview-20200519-01", issue: "https://github.com/microsoft/vstest/pull/2325"), + // [MULTI_TFM] = new(version: "v17.3.0", issue: "https://github.com/microsoft/vstest/pull/3412") + }.ToImmutableDictionary(); + + public static IImmutableDictionary AdapterFeatures { get; internal set; } = new Dictionary + { + [MSTEST_EXAMPLE_FEATURE] = new("2.2.8", issue: "This feature does not actually exist."), + }.ToImmutableDictionary(); +} diff --git a/test/Microsoft.TestPlatform.TestUtilities/FileAssert.cs b/test/Microsoft.TestPlatform.TestUtilities/FileAssert.cs index 622f6d2886..d5cc8de32f 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/FileAssert.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/FileAssert.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace TestPlatform.TestUtilities; +namespace Microsoft.TestPlatform.TestUtilities; public static class FileAssert { @@ -17,7 +17,7 @@ public static void Contains(string filePath, params string[] substrs) var fileContent = File.ReadAllText(filePath); foreach (var substr in substrs) { - Assert.IsTrue(fileContent.Contains(substr), + Assert.Contains(substr, fileContent, $"{filePath}: file doesn't contains {StringHighlighter} {substr} {StringHighlighter}"); } } diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs index 885b65906a..f39014d87a 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -29,8 +30,8 @@ namespace Microsoft.TestPlatform.TestUtilities; /// public class IntegrationTestBase { - public const string DesktopRunnerFramework = "net462"; - public const string CoreRunnerFramework = "netcoreapp3.1"; + public const string DesktopRunnerFramework = "net48"; + public const string CoreRunnerFramework = "net10.0"; private const string TotalTestsMessage = "Total tests: {0}"; private const string PassedTestsMessage = " Passed: {0}"; @@ -42,11 +43,11 @@ public class IntegrationTestBase private int _runnerExitCode = -1; private string? _arguments = string.Empty; - + private readonly List _attachments = new(); protected readonly IntegrationTestEnvironment _testEnvironment; private readonly string _msTestPre3_0AdapterRelativePath = @"mstest.testadapter\{0}\build\_common".Replace('\\', Path.DirectorySeparatorChar); - private readonly string _msTestAdapterRelativePath = @"mstest.testadapter\{0}\build\{1}".Replace('\\', Path.DirectorySeparatorChar); + private readonly string _msTestAdapterRelativePath = @"mstest.testadapter\{0}\buildTransitive\{1}".Replace('\\', Path.DirectorySeparatorChar); private readonly string _nUnitTestAdapterRelativePath = @"nunit3testadapter\{0}\build".Replace('\\', Path.DirectorySeparatorChar); private readonly string _xUnitTestAdapterRelativePath = @"xunit.runner.visualstudio\{0}\build\{1}".Replace('\\', Path.DirectorySeparatorChar); @@ -59,14 +60,24 @@ public IntegrationTestBase() { _testEnvironment = new IntegrationTestEnvironment(); BuildConfiguration = IntegrationTestEnvironment.BuildConfiguration; + + TempDirectory.NuGetConfigPath = Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "NuGet.config"); TempDirectory = new TempDirectory(); + var drive = new DriveInfo(Directory.GetDirectoryRoot(TempDirectory.Path)); Console.WriteLine($"Available space for TEMP: {drive.Name} {drive.AvailableFreeSpace / (1024 * 1024)} MB"); IsCI = IntegrationTestEnvironment.IsCI; } + [TestInitialize] + public void IntegrationTestBaseSetup() + { + // Write test name so we know what the temp folder is for. + File.WriteAllText(Path.Combine(TempDirectory.Path, "testName.txt"), $"{TestContext?.FullyQualifiedTestClassName}.{TestContext?.TestName}"); + } + public string StdOut => _standardTestOutput; public string StdOutWithWhiteSpace { get; private set; } = string.Empty; @@ -75,22 +86,37 @@ public IntegrationTestBase() public TempDirectory TempDirectory { get; } - public TestContext? TestContext { get; set; } + public TestContext TestContext { get; set; } = null!; public string BuildConfiguration { get; } public bool IsCI { get; } [TestCleanup] - public void TempDirectoryCleanup() + public void IntegrationTestBaseTestCleanup() { - // In CI always delete the results, because we have limited disk space there. - // - // Locally delete the directory only when the test succeeded, so we can look - // at results and logs of failed tests. - if (IsCI || TestContext?.CurrentTestOutcome == UnitTestOutcome.Passed) + // Delete the files only when test passes, so we can upload the attachments. They need to survive till the end of run. + if (TestContext?.CurrentTestOutcome is not (UnitTestOutcome.Failed or UnitTestOutcome.Aborted)) { TempDirectory.Dispose(); + return; + } + + // Attach files that are of interest. + foreach (var attachment in _attachments) + { + if (Directory.Exists(attachment)) + { + foreach (var file in Directory.EnumerateFiles(attachment, "*.*", SearchOption.AllDirectories)) + { + TestContext.AddResultFile(file); + } + } + + if (File.Exists(attachment)) + { + TestContext.AddResultFile(attachment); + } } } @@ -184,8 +210,18 @@ public static string PrepareArguments(string testAssembly, string? testAdapterPa /// /// Arguments provided to vstest.console.exe /// Environment variables to set to the started process. - public void InvokeVsTest(string? arguments, Dictionary? environmentVariables = null) + /// When true, automatically adds --diag flag and attaches logs to test results on failure. + public void InvokeVsTest(string? arguments, Dictionary? environmentVariables = null, bool collectDiagnostics = true) { + if (collectDiagnostics && !IsDiagAlreadyEnabled(arguments ?? "")) + { + var diagLogsDir = Path.Combine(TempDirectory.Path, "logs"); + Directory.CreateDirectory(diagLogsDir); + arguments = string.Concat(arguments, GetDiagArg(diagLogsDir)); + _attachments.Add(diagLogsDir); + Console.WriteLine($"Diagnostic logs directory: {diagLogsDir}"); + } + var debugEnvironmentVariables = AddDebugEnvironmentVariables(environmentVariables); ExecuteVsTestConsole(arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables); FormatStandardOutCome(); @@ -196,8 +232,33 @@ public void InvokeVsTest(string? arguments, Dictionary? environ /// /// Arguments provided to vstest.console.exe /// Environment variables to set to the started process. - public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null) + /// + /// When true, automatically adds --diag flag and attaches logs to test results on failure. + public void InvokeDotnetTest(string arguments, Dictionary? environmentVariables = null, string? workingDirectory = null, bool collectDiagnostics = true) { + string globalJsonPath = Path.Combine(workingDirectory!, "global.json"); + if (workingDirectory is not null && !File.Exists(globalJsonPath)) + { + // Add global.json to the working directory, to ensure we use vstest to run tests, + // global.json is resolved from the working directory, and its parents, so this just makes sure we enforce vstest, + // even though we use TestingPlatform to run unit tests. + File.WriteAllText(Path.Combine(workingDirectory, "global.json"), """ + { + "test": { + "runner": "vstest" + } + } + """); + } + else + { + string globalJsonText = File.ReadAllText(globalJsonPath); + if (!globalJsonText.Contains("\"runner\": \"vstest\"")) + { + throw new InvalidOperationException($"Custom global.json in path '{globalJsonPath}' does not specify test runner as VSTest.\nContent:\n{globalJsonText}"); + } + } + var debugEnvironmentVariables = AddDebugEnvironmentVariables(environmentVariables); var vstestConsolePath = GetDotnetRunnerPath(); @@ -222,7 +283,29 @@ public void InvokeDotnetTest(string arguments, Dictionary? envi // https://github.com/dotnet/sdk/blob/main/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs#L30-L39 debugEnvironmentVariables["VSTEST_CONSOLE_PATH"] = vstestConsolePath; - IntegrationTestBase.ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables); + if (collectDiagnostics && !IsDiagAlreadyEnabled(arguments)) + { + var diagLogsDir = Path.Combine(TempDirectory.Path, "logs"); + Directory.CreateDirectory(diagLogsDir); + var diagPath = Path.Combine(diagLogsDir, "log.txt"); + var diagArg = " --diag " + diagPath.AddDoubleQuote(); + + // Insert --diag before the -- separator so dotnet test forwards it to vstest.console. + var separatorPos = arguments.IndexOf(" -- "); + if (separatorPos == -1) + { + arguments += diagArg; + } + else + { + arguments = arguments.Insert(separatorPos, diagArg); + } + + _attachments.Add(diagLogsDir); + Console.WriteLine($"Diagnostic logs directory: {diagLogsDir}"); + } + + IntegrationTestBase.ExecutePatchedDotnet("test", arguments, out _standardTestOutput, out _standardTestError, out _runnerExitCode, debugEnvironmentVariables, workingDirectory); FormatStandardOutCome(); } @@ -234,14 +317,16 @@ public void InvokeDotnetTest(string arguments, Dictionary? envi /// Dotnet Framework of test assembly. /// Run settings for execution. /// Environment variables to set to the started process. + /// When true, automatically adds --diag flag and attaches logs to test results on failure. public void InvokeVsTestForExecution(string testAssembly, string? testAdapterPath, string framework, string? runSettings = "", - Dictionary? environmentVariables = null) + Dictionary? environmentVariables = null, + bool collectDiagnostics = true) { var arguments = PrepareArguments(testAssembly, testAdapterPath, runSettings, framework, _testEnvironment.InIsolationValue, resultsDirectory: TempDirectory.Path); - InvokeVsTest(arguments, environmentVariables); + InvokeVsTest(arguments, environmentVariables, collectDiagnostics); } private Dictionary AddDebugEnvironmentVariables(Dictionary? environmentVariables) @@ -250,6 +335,8 @@ public void InvokeVsTestForExecution(string testAssembly, if (_testEnvironment.DebugInfo != null) { + environmentVariables["VSTEST_DEBUG_ATTACHVS_PATH"] = + Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location)!, "AttachVS.exe"); if (_testEnvironment.DebugInfo.DebugVSTestConsole) { environmentVariables["VSTEST_RUNNER_DEBUG_ATTACHVS"] = "1"; @@ -294,11 +381,11 @@ public void InvokeVsTestForDiscovery(string testAssembly, string testAdapterPath /// Execute Tests that are not supported with given Runner framework. /// /// Runner Framework - /// Framework for which Tests are not supported + /// Framework for which Tests are supported /// Message to be shown public static void ExecuteNotSupportedRunnerFrameworkTests(string runnerFramework, string framework, string message) { - if (runnerFramework.StartsWith(framework)) + if (!runnerFramework.StartsWith(framework, StringComparison.OrdinalIgnoreCase)) { Assert.Inconclusive(message); } @@ -330,14 +417,16 @@ public void ValidateSummaryStatus(int passed, int failed, int skipped) @"\d+", @"\d+", @"\d+"); - StringAssert.DoesNotMatch( - _standardTestOutput, - new Regex(summaryStatus), + var errorSummary = string.Format(CultureInfo.InvariantCulture, "Excepted: There should not be test summary{2}Actual: {0}{2}Standard Error: {1}{2}Arguments: {3}{2}", _standardTestOutput, _standardTestError, Environment.NewLine, _arguments); + Assert.DoesNotMatchRegex( + new Regex(summaryStatus), + _standardTestOutput, errorSummary) + ; } else { @@ -357,14 +446,17 @@ public void ValidateSummaryStatus(int passed, int failed, int skipped) summaryStatus += string.Format(CultureInfo.CurrentCulture, SkippedTestsMessage, skipped); } - Assert.IsTrue( - _standardTestOutput.Contains(summaryStatus), - "The Test summary does not match.{3}Expected summary: {1}{3}Test Output: {0}{3}Standard Error: {2}{3}Arguments: {4}{3}", + var errorSummary = string.Format(CultureInfo.InvariantCulture, "The Test summary does not match.{3}Expected summary: {1}{3}Test Output: {0}{3}Standard Error: {2}{3}Arguments: {4}{3}", _standardTestOutput, summaryStatus, _standardTestError, Environment.NewLine, _arguments); + Assert.Contains( + summaryStatus, + _standardTestOutput, + errorSummary + ); } } @@ -381,14 +473,15 @@ public void ValidateSummaryStatusv15(int passed, int failed, int skipped) if (totalTestCount == 0) { // No test should be found/run - StringAssert.DoesNotMatch( - _standardTestOutput, - new Regex("Total tests\\:"), - "Excepted: There should not be test summary{2}Actual: {0}{2}Standard Error: {1}{2}Arguments: {3}{2}", + var errorSummary = string.Format(CultureInfo.InvariantCulture, "Excepted: There should not be test summary{2}Actual: {0}{2}Standard Error: {1}{2}Arguments: {3}{2}", _standardTestOutput, _standardTestError, Environment.NewLine, _arguments); + Assert.DoesNotMatchRegex( + new Regex("Total tests\\:"), + _standardTestOutput, + errorSummary); } else { @@ -408,40 +501,42 @@ public void ValidateSummaryStatusv15(int passed, int failed, int skipped) summaryStatus += $" Skipped: {skipped}."; } - Assert.IsTrue( - _standardTestOutput.Contains(summaryStatus), - "The Test summary does not match.{3}Expected summary: {1}{3}Test Output: {0}{3}Standard Error: {2}{3}Arguments: {4}{3}", + var errorSummary = String.Format(CultureInfo.InvariantCulture, "The Test summary does not match.{3}Expected summary: {1}{3}Test Output: {0}{3}Standard Error: {2}{3}Arguments: {4}{3}", _standardTestOutput, summaryStatus, _standardTestError, Environment.NewLine, _arguments); + Assert.Contains( + summaryStatus, + _standardTestOutput, + errorSummary); } } public void StdErrorContains(string substring) { - Assert.IsTrue(_standardTestError.Contains(substring), "StdErrorOutput - [{0}] did not contain expected string '{1}'", _standardTestError, substring); + Assert.Contains(substring, _standardTestError, $"StdErrorOutput - [{_standardTestError}] did not contain expected string '{substring}'"); } public void StdErrorRegexIsMatch(string pattern) { - Assert.IsTrue(Regex.IsMatch(_standardTestError, pattern), "StdErrorOutput - [{0}] did not contain expected pattern '{1}'", _standardTestError, pattern); + Assert.IsTrue(Regex.IsMatch(_standardTestError, pattern), $"StdErrorOutput - [{_standardTestError}] did not contain expected pattern '{pattern}'"); } public void StdErrorDoesNotContains(string substring) { - Assert.IsFalse(_standardTestError.Contains(substring), "StdErrorOutput - [{0}] did not contain expected string '{1}'", _standardTestError, substring); + Assert.DoesNotContain(substring, _standardTestError, $"StdErrorOutput - [{_standardTestError}] did not contain expected string '{substring}'"); } public void StdOutputContains(string substring) { - Assert.IsTrue(_standardTestOutput.Contains(substring), $"{Environment.NewLine}StdOutput:{Environment.NewLine}{Environment.NewLine}Expected substring: {substring}{Environment.NewLine}{Environment.NewLine}Actual string: {_standardTestOutput}"); + Assert.Contains(substring, _standardTestOutput, $"{Environment.NewLine}StdOutput:{Environment.NewLine}{Environment.NewLine}Expected substring: {substring}{Environment.NewLine}{Environment.NewLine}Actual string: {_standardTestOutput}"); } public void StdOutputDoesNotContains(string substring) { - Assert.IsFalse(_standardTestOutput.Contains(substring), $"{Environment.NewLine}StdOutput:{Environment.NewLine}{Environment.NewLine}Not expected substring: {substring}{Environment.NewLine}{Environment.NewLine}Actual string: {_standardTestOutput}"); + Assert.DoesNotContain(substring, _standardTestOutput, $"{Environment.NewLine}StdOutput:{Environment.NewLine}{Environment.NewLine}Not expected substring: {substring}{Environment.NewLine}{Environment.NewLine}Actual string: {_standardTestOutput}"); } public void ExitCodeEquals(int exitCode) @@ -487,7 +582,7 @@ public void ValidateFailedTests(params string[] failedTests) Assert.IsTrue(flag, "Test {0} does not appear in failed tests list.", test); // Verify stack information as well. - Assert.IsTrue(_standardTestOutput.Contains(GetTestMethodName(test)), "No stack trace for failed test: {0}", test); + Assert.Contains(GetTestMethodName(test), _standardTestOutput, $"No stack trace for failed test: {test}"); } } @@ -541,7 +636,7 @@ public void ValidateTestsNotDiscovered(params string[] testsList) public void ValidateFullyQualifiedDiscoveredTests(string filePath, params string[] discoveredTestsList) { var fileOutput = File.ReadAllLines(filePath); - Assert.IsTrue(fileOutput.Length == 3); + Assert.HasCount(3, fileOutput); foreach (var test in discoveredTestsList) { @@ -563,9 +658,9 @@ protected string GetAssetFullPath(string assetName) return _testEnvironment.GetTestAsset(assetName); } - protected string GetTestDllForFramework(string assetName, string targetFramework) + protected string GetTestDllForFramework(string assetName, string targetFramework, bool automaticallyResolveCompatibilityTestAsset = true) { - return _testEnvironment.GetTestAsset(assetName, targetFramework); + return _testEnvironment.GetTestAsset(assetName, targetFramework, automaticallyResolveCompatibilityTestAsset); } protected List GetTestDlls(params string[] assetNames) @@ -603,9 +698,14 @@ protected string GetTestAdapterPath(UnitTestFramework testFramework = UnitTestFr if (testFramework == UnitTestFramework.MSTest) { var version = IntegrationTestEnvironment.DependencyVersions["MSTestTestAdapterVersion"]; - if (version.StartsWith("3")) + if (version.StartsWith("4")) { - var tfm = _testEnvironment.TargetFramework.StartsWith("net4") ? "net462" : _testEnvironment.TargetFramework; + var tfm = _testEnvironment.IsNetFrameworkTarget ? "net462" : "net9.0"; + adapterRelativePath = string.Format(CultureInfo.InvariantCulture, _msTestAdapterRelativePath, version, tfm); + } + else if (version.StartsWith("3")) + { + var tfm = _testEnvironment.IsNetFrameworkTarget ? "net462" : "netcoreapp3.1"; adapterRelativePath = string.Format(CultureInfo.InvariantCulture, _msTestAdapterRelativePath, version, tfm); } else @@ -619,7 +719,7 @@ protected string GetTestAdapterPath(UnitTestFramework testFramework = UnitTestFr } else if (testFramework == UnitTestFramework.XUnit) { - var tfm = _testEnvironment.TargetFramework.StartsWith("net4") ? "net462" : "netcoreapp3.1"; + var tfm = _testEnvironment.IsNetFrameworkTarget ? "net462" : "netcoreapp3.1"; adapterRelativePath = string.Format(CultureInfo.InvariantCulture, _xUnitTestAdapterRelativePath, IntegrationTestEnvironment.DependencyVersions["XUnitAdapterVersion"], tfm); } @@ -659,7 +759,7 @@ public virtual string GetConsoleRunnerPath() } else { - Assert.Fail("Unknown Runner framework - [{0}]", _testEnvironment.RunnerFramework); + Assert.Fail($"Unknown Runner framework - [{_testEnvironment.RunnerFramework}]"); } Assert.IsTrue(File.Exists(consoleRunnerPath), "GetConsoleRunnerPath: Path not found: \"{0}\"", consoleRunnerPath); @@ -693,13 +793,21 @@ public IVsTestConsoleWrapper GetVsTestConsoleWrapper(Dictionary } // Directory is already unique so there is no need to have a unique file name. - var logFilePath = Path.Combine(TempDirectory.Path, "log.txt"); + var logDirectory = Path.Combine(TempDirectory.Path, "logs"); + var logFilePath = Path.Combine(logDirectory, "log.txt"); + if (!Directory.Exists(logDirectory)) + { + Directory.CreateDirectory(logDirectory); + } + + _attachments.Add(logDirectory); + if (!File.Exists(logFilePath)) { File.Create(logFilePath).Close(); } - Console.WriteLine($"Logging diagnostics in {logFilePath}"); + Console.WriteLine($"Logging diagnostics in {logDirectory}"); consoleParameters.LogFilePath = logFilePath; } @@ -721,6 +829,21 @@ public IVsTestConsoleWrapper GetVsTestConsoleWrapper(Dictionary Console.WriteLine($"Console runner path: {consoleRunnerPath}"); + // When testing with older vstest.console.dll they need to have an older runtime installed to run, but there are rarely + // incompatibilities between runtimes, so we roll forward to latest major to minimize the amount of runtimes we need to install. + // Especially very old runtimes like netcoreapp2.1, which makes us flagged by compliance. + // + // This is applicable only to vstest.console and datacollector, for testhost the project dictates the tfm that is used because + // we pass the test project runtimeconfig to the testhost. + if (IsNetCoreRunner()) + { + environmentVariables ??= new(); + if (!environmentVariables.ContainsKey("DOTNET_ROLL_FORWARD")) + { + environmentVariables.Add("DOTNET_ROLL_FORWARD", "LatestMajor"); + } + } + // Providing any environment variable to vstest.console will clear all existing environment variables, // this works around it by copying all existing variables, and adding debug. But we only want to do that // when we are setting any debug variables. @@ -790,16 +913,15 @@ protected void ExecuteVsTestConsole(string? args, out string stdOut, out string /// /// /// Environment variables to set to the started process. + /// private static void ExecutePatchedDotnet(string command, string args, out string stdOut, out string stdError, out int exitCode, - Dictionary? environmentVariables = null) + Dictionary? environmentVariables = null, string? workingDirectory = null) { environmentVariables ??= new(); - environmentVariables["DOTNET_MULTILEVEL_LOOKUP"] = "0"; - var executablePath = OSUtils.IsWindows ? @"dotnet.exe" : @"dotnet"; var patchedDotnetPath = Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "artifacts", "tmp", ".dotnet", executablePath)); - ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables); + ExecuteApplication(patchedDotnetPath, string.Join(" ", command, args), out stdOut, out stdError, out exitCode, environmentVariables, workingDirectory); } protected static void ExecuteApplication(string path, string? args, out string stdOut, out string stdError, out int exitCode, @@ -817,8 +939,8 @@ protected static void ExecuteApplication(string path, string? args, out string s var executableName = Path.GetFileName(path); + var line = new string('=', 30); using var process = new Process(); - Console.WriteLine($"IntegrationTestBase.Execute: Starting {executableName}"); process.StartInfo.FileName = path; process.StartInfo.Arguments = args; process.StartInfo.UseShellExecute = false; @@ -853,8 +975,10 @@ protected static void ExecuteApplication(string path, string? args, out string s process.OutputDataReceived += (sender, eventArgs) => stdoutBuffer.AppendLine(eventArgs.Data); process.ErrorDataReceived += (sender, eventArgs) => stderrBuffer.AppendLine(eventArgs.Data); - Console.WriteLine("IntegrationTestBase.Execute: Path = {0}", process.StartInfo.FileName); - Console.WriteLine("IntegrationTestBase.Execute: Arguments = {0}", process.StartInfo.Arguments); + Console.WriteLine(); + Console.WriteLine($"{line}{line}"); + Console.WriteLine($"IntegrationTestBase.Execute: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); + Console.WriteLine("IntegrationTestBase.Execute: WorkingDirectory = {0}", StringUtils.IsNullOrWhiteSpace(process.StartInfo.WorkingDirectory) ? $"(Current Directory) {Directory.GetCurrentDirectory()}" : process.StartInfo.WorkingDirectory); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -876,15 +1000,13 @@ protected static void ExecuteApplication(string path, string? args, out string s stopwatch.Stop(); - Console.WriteLine($"IntegrationTestBase.Execute: Total execution time: {stopwatch.Elapsed.Duration()}"); - stdError = stderrBuffer.ToString(); stdOut = stdoutBuffer.ToString(); exitCode = process.ExitCode; - Console.WriteLine("IntegrationTestBase.Execute: stdError = {0}", stdError); - Console.WriteLine("IntegrationTestBase.Execute: stdOut = {0}", stdOut); - Console.WriteLine($"IntegrationTestBase.Execute: Stopped {executableName}. Exit code = {0}", exitCode); + Console.WriteLine("IntegrationTestBase.Execute: stdError = {0}", StringUtils.IsNullOrWhiteSpace(stdError) ? null : stdError); + Console.WriteLine("IntegrationTestBase.Execute: stdOut = {0}", StringUtils.IsNullOrWhiteSpace(stdOut) ? null : stdOut); + Console.WriteLine($"IntegrationTestBase.Execute: {line} Stopped {executableName}. Exit code = {exitCode} Duration = {stopwatch.Elapsed.Duration()} {line}"); } private void FormatStandardOutCome() @@ -952,6 +1074,21 @@ protected string BuildMultipleAssemblyPath(params string[] assetNames) return string.Join(" ", GetTestDlls(assetNames).Select(a => a.AddDoubleQuote())); } + private static bool IsDiagAlreadyEnabled(string arguments) + { + // Check args for --diag, /diag, -diag (case-insensitive) + if (arguments.Contains("--diag", StringComparison.OrdinalIgnoreCase) + || arguments.Contains("/diag", StringComparison.OrdinalIgnoreCase) + || arguments.Contains("-diag", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Check environment variable + var envDiag = Environment.GetEnvironmentVariable("VSTEST_DIAG"); + return !StringUtils.IsNullOrEmpty(envDiag); + } + protected static string GetDiagArg(string rootDir) => " --diag:" + Path.Combine(rootDir, "log.txt"); @@ -959,7 +1096,8 @@ protected static string GetDiagArg(string rootDir) /// Counts the number of logs following the '*.host.*' pattern in the given folder. /// protected static int CountTestHostLogs(string diagLogsDir) - => Directory.GetFiles(diagLogsDir, "*.host.*").Length; + // We put the files in logs subfolder or TMP. + => Directory.GetFiles(diagLogsDir, "*.host.*", SearchOption.AllDirectories).Length; protected static void AssertExpectedNumberOfHostProcesses(int expectedNumOfProcessCreated, string diagLogsDir, IEnumerable testHostProcessNames, string? arguments = null, string? runnerPath = null) @@ -990,7 +1128,7 @@ protected static string GetDownloadedDotnetMuxerFromTools(string architecture) } protected string GetDotnetRunnerPath() => - _testEnvironment.VSTestConsoleInfo?.Path ?? Path.Combine(IntegrationTestEnvironment.PublishDirectory, $"Microsoft.TestPlatform.CLI.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", "contentFiles", "any", "netcoreapp3.1", "vstest.console.dll"); + _testEnvironment.VSTestConsoleInfo?.Path ?? Path.Combine(IntegrationTestEnvironment.PublishDirectory, $"Microsoft.TestPlatform.CLI.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", "contentFiles", "any", "net10.0", "vstest.console.dll"); protected void StdOutHasNoWarnings() { diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBuild.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBuild.cs new file mode 100644 index 0000000000..f18ecf7f95 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBuild.cs @@ -0,0 +1,619 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml.Linq; + +using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Newtonsoft.Json; + +namespace Microsoft.TestPlatform.TestUtilities; + +public class IntegrationTestBuild : IntegrationTestBase +{ + private static readonly string Root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..")); + private static readonly string DotnetDir = Path.GetFullPath(Path.Combine(Root, ".dotnet")); + private static readonly string Dotnet = Path.GetFullPath(Path.Combine(Root, ".dotnet", OSUtils.IsWindows ? "dotnet.exe" : "dotnet")); + + // Session mutex: held for the entire test session (build + tests). + // The process that creates it (createdNew=true) is the builder. + private static Mutex? s_sessionMutex; + private static bool s_isSessionMutexOwner; + + public static void BuildTestAssetsForIntegrationTests(TestContext context) + { + // This is common setup that should happen even when build will not run. + SetDotnetEnvironment(); + + var skipBuild = GetTestParameter(context, "SkipIntegrationTestBuild"); + if (skipBuild) + { + Debug.WriteLine("SkipIntegrationBuild parameter is true, skipping build."); + return; + } + + var buildCompatibility = GetTestParameter(context, "BuildCompatibility"); + + // Avoid collisions across different clones of this repo. + var baseName = "VSTest_AcceptanceTests_" + IntegrationTestEnvironment.RepoRootDirectory! + .Replace('\\', '-').Replace('/', '-').Replace(':', '-'); + + // Session mutex: the process that creates it is the builder. + // Held for the entire session (build + tests), destroyed in CleanupTestAssets. + s_sessionMutex = CreateMutex(baseName + "_Session", out bool isBuilder); + s_isSessionMutexOwner = isBuilder; + + if (isBuilder) + { + // We are the builder. Create a build mutex to signal "build in progress" + // to other processes. It exists while we're building, then we destroy it. + Debug.WriteLine("Session mutex created — we are the builder."); + using var buildMutex = CreateMutex(baseName + "_Build", out _); + + var sw = Stopwatch.StartNew(); + var nugetCache = IntegrationTestEnvironment.TestAssetsNuGetCacheDirectory; + var packagesAreNew = UnzipExecutablePackages(); + if (packagesAreNew) + { + CleanNugetCacheAndProjects(nugetCache); + } + Debug.WriteLine($"Unzipping packages took: {sw.ElapsedMilliseconds} ms"); sw.Restart(); + BuildTestAssets(nugetCache); + Debug.WriteLine($"Building test assets took: {sw.ElapsedMilliseconds} ms"); sw.Restart(); + if (buildCompatibility) + { + BuildTestAssetsCompatibility(nugetCache); + Debug.WriteLine($"Building compatibility test assets took: {sw.ElapsedMilliseconds} ms"); sw.Restart(); + } + else + { + Debug.WriteLine("BuildCompatibility parameter is false, skipping build."); + } + CopyAndPatchDotnet(); + Debug.WriteLine($"Copying and patching dotnet took: {sw.ElapsedMilliseconds} ms"); + + // Build mutex is disposed here (end of using), signaling to other processes + // that the build is done. + Debug.WriteLine("Build complete, releasing build mutex."); + } + else + { + // We are NOT the builder. Check if a build is in progress. + Debug.WriteLine("Session mutex already exists — another process is the builder."); + var buildMutex = TryOpenMutex(baseName + "_Build"); + if (buildMutex is not null) + { + // Build mutex exists — build is in progress. Wait for it to be destroyed. + Debug.WriteLine("Build in progress, waiting for it to finish..."); + using (buildMutex) + { + // Wait to acquire it (builder will release/destroy it when done). + try + { + if (!buildMutex.WaitOne(TimeSpan.FromMinutes(15))) + { + throw new TimeoutException("Timed out waiting for the build to complete."); + } + } + catch (AbandonedMutexException) + { + // Builder crashed — we got the mutex. Destroy it and retry + // as a fresh session (we might become the builder). + Debug.WriteLine("Build mutex was abandoned (builder crashed). Retrying."); + } + } + // Build mutex destroyed. Build is done (or builder crashed and we'll + // see a stale state — but test assets should still be usable). + Debug.WriteLine("Build finished, proceeding to tests."); + } + else + { + // Build mutex doesn't exist — build already completed. + Debug.WriteLine("No build mutex found — build already done, proceeding to tests."); + } + } + } + + /// + /// Create a named mutex. If abandoned by a previous process, destroy and retry. + /// + private static Mutex CreateMutex(string name, out bool createdNew) + { + while (true) + { + var mutex = new Mutex(initiallyOwned: true, name, out createdNew); + if (!createdNew) + { + try + { + // We didn't create it, try to acquire to check if it's alive. + if (mutex.WaitOne(0)) + { + // We acquired it — previous owner released. Release and return. + mutex.ReleaseMutex(); + } + // If WaitOne returned false, mutex is held by another process — that's fine. + } + catch (AbandonedMutexException) + { + // Previous owner crashed. Destroy and retry. + Debug.WriteLine($"Mutex '{name}' was abandoned, destroying and retrying."); + mutex.Dispose(); + continue; + } + } + return mutex; + } + } + + /// + /// Try to open an existing named mutex. Returns null if it doesn't exist. + /// + private static Mutex? TryOpenMutex(string name) + { + var mutex = new Mutex(initiallyOwned: false, name, out bool createdNew); + if (createdNew) + { + // We created it — meaning it didn't exist. Destroy it and return null. + mutex.Dispose(); + return null; + } + return mutex; + } + + public static void CleanupTestAssets() + { + if (s_sessionMutex is not null) + { + if (s_isSessionMutexOwner) + { + try { s_sessionMutex.ReleaseMutex(); } catch (ApplicationException) { } + } + s_sessionMutex.Dispose(); + s_sessionMutex = null; + } + } + + private static void BuildTestAssets(string nugetCache) + { + var testAssets = Path.GetFullPath(Path.Combine(Root, "test", "TestAssets", "TestAssets.slnx")); + var nugetFeeds = GetNugetSourceParameters(Root); + + var netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; + + ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" -p:PackageVersion={netTestSdkVersion} "{testAssets}" """); + ExecuteApplication2(Dotnet, $"""build "{testAssets}" --configuration {IntegrationTestEnvironment.BuildConfiguration} --no-restore"""); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Build special project written in IL. + // This project is used on Windows only Tests. On non-Windows the build fails with: "IlasmToolPath must be set in order to build ilproj's outside of Windows.". + var cilProject = Path.Combine(Root, "test", "TestAssets", "CILProject", "CILProject.proj"); + var binPath = Path.Combine(Root, "artifacts", "bin", "TestAssets", "CILProject", IntegrationTestEnvironment.BuildConfiguration, "net462"); + ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{cilProject}" """); + ExecuteApplication2(Dotnet, $"""build "{cilProject}" --configuration {IntegrationTestEnvironment.BuildConfiguration} --no-restore --output {binPath}"""); + } + } + + private static void SetDotnetEnvironment() + { + // We need to set this to point to our dotnet, because we cannot guarantee what is installed on the machine in Program Files, + // and we install all the required SDKs and runtimes ourselves in Build.cmd. +#pragma warning disable RS0030 // Do not used banned APIs + Environment.SetEnvironmentVariable("DOTNET_ROOT", DotnetDir); + Environment.SetEnvironmentVariable("DOTNET_ROOT(x86)", Path.Combine(DotnetDir, "dotnet-sdk-x86")); + Environment.SetEnvironmentVariable("PATH", $"{DotnetDir};{Environment.GetEnvironmentVariable("PATH")}"); +#pragma warning restore RS0030 // Do not used banned APIs + } + + private static void CopyAndPatchDotnet() + { + var patchedDotnetDir = Path.GetFullPath(Path.Combine(Root, "artifacts", "tmp", ".dotnet")); + + var dotnetExe = OSUtils.IsWindows ? "dotnet.exe" : "dotnet"; + var originalDotnetExePath = Path.Combine(DotnetDir, dotnetExe); + var patchedDotnetExePath = Path.Combine(patchedDotnetDir, dotnetExe); + + // It is not necessary to copy whole dotnet folder before each test run + // we just need to make sure the build files are updated automatically, + // so dotnet test tests reflect what is in our local build targets. + bool skipCopy = File.Exists(originalDotnetExePath) + && File.Exists(patchedDotnetExePath) + && File.GetLastWriteTime(originalDotnetExePath) == File.GetLastWriteTime(patchedDotnetExePath); + + if (!skipCopy) + { + // Copy .dotnet + DirectoryUtils.CopyDirectory(new DirectoryInfo(DotnetDir), new DirectoryInfo(patchedDotnetDir)); + } + + // e.g. artifacts\tmp\.dotnet\sdk\ + var sdkDirectory = Path.Combine(patchedDotnetDir, "sdk"); + // e.g. artifacts\tmp\.dotnet\sdk\8.0.100-preview.6.23330.14 + var dotnetSdkDirectories = Directory.GetDirectories(sdkDirectory); + if (dotnetSdkDirectories.Length == 0) + { + throw new InvalidOperationException($"No .NET SDK directories found in '{sdkDirectory}'."); + } + if (dotnetSdkDirectories.Length > 1) + { + throw new InvalidOperationException($"More than 1 .NET SDK directories found in '{sdkDirectory}': {string.Join(", ", dotnetSdkDirectories)}."); + } + + var dotnetSdkDirectory = dotnetSdkDirectories.Single(); + + // Copy target file and build task dll into it. + // This updates the definition for running dotnet test from what we have built locally. + var netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; + var packageName = $"Microsoft.TestPlatform.Build.{netTestSdkVersion}.nupkg"; + var packagePath = Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.PublishDirectory, packageName)); + + DirectoryUtils.CopyDirectory(Path.Combine(packagePath, "lib", "netstandard2.0"), dotnetSdkDirectory); + DirectoryUtils.CopyDirectory(Path.Combine(packagePath, "runtimes", "any", "native"), dotnetSdkDirectory); + } + + private static void BuildTestAssetsCompatibility(string nugetCache) + { + var testAssetsDir = Path.GetFullPath(Path.Combine(Root, "test", "TestAssets")); + + var generated = Path.GetFullPath(Path.Combine(Root, "artifacts", "tmp", "GeneratedTestAssets")); + var generatedSln = Path.Combine(generated, "CompatibilityTestAssets.slnx"); + + var dependenciesPath = Path.Combine(Root, "eng", "Versions.props"); + var dependenciesXml = XDocument.Load(dependenciesPath); + var propsNode = dependenciesXml!.Element("Project")! + .Descendants("PropertyGroup") + .Where(p => p.Attributes().Any(a => a.Name == "Label" && a.Value == "VSTest test settings")) + .Single()!; + + var cacheId = new OrderedDictionary(); + + // Restore previous versions of TestPlatform (for vstest.console.exe), and TestPlatform.CLI (for vstest.console.dll). + // These properties are coming from TestPlatform.Dependencies.props. + var vstestConsoleVersionProperties = new[] { + "VSTestConsoleLatestVersion", + "VSTestConsoleLatestPreviewVersion", + "VSTestConsoleLatestStableVersion", + "VSTestConsoleRecentStableVersion", + "VSTestConsoleMostDownloadedVersion", + "VSTestConsolePreviousStableVersion", + "VSTestConsoleLegacyStableVersion", + }; + + var projects = new[] + { + Path.Combine(Root, "test", "TestAssets", "MSTestProject1", "MSTestProject1.csproj"), + Path.Combine(Root, "test", "TestAssets", "MSTestProject2", "MSTestProject2.csproj"), + }; + + var msTestVersionProperties = new[] { + "MSTestFrameworkLatestPreviewVersion", + "MSTestFrameworkLatestStableVersion", + "MSTestFrameworkRecentStableVersion", + "MSTestFrameworkMostDownloadedVersion", + "MSTestFrameworkPreviousStableVersion", + "MSTestFrameworkLegacyStableVersion", + }; + + var nugetFeeds = GetNugetSourceParameters(Root); + + // We use the same version properties for NET.Test.Sdk as for VSTestConsole, for now. + foreach (var sdkPropertyName in vstestConsoleVersionProperties) + { + string? netTestSdkVersion; + if (sdkPropertyName == "VSTestConsoleLatestVersion") + { + netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; + } + else + { + netTestSdkVersion = propsNode.Element(sdkPropertyName!)!.Value; + } + + if (netTestSdkVersion.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException($"{nameof(netTestSdkVersion)} should contain version of the package to restore, but it is empty."); + } + + cacheId[sdkPropertyName] = netTestSdkVersion; + + var netTestSdkVersionDir = netTestSdkVersion.TrimStart('[').TrimEnd(']'); + if (Directory.Exists(Path.Combine(nugetCache, "microsoft.testplatform", netTestSdkVersionDir)) && Directory.Exists(Path.Combine(nugetCache, "microsoft.testplatform.cli", netTestSdkVersionDir))) + { + continue; + } + + // We restore this project to download TestPlatform and TestPlatform.CLI nugets, into our package cache. + // Using nuget.exe install errors out in various weird ways. + var tools = Path.Combine(Root, "test", "TestAssets", "Tools", "Tools.csproj"); + ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{tools}" -p:PackageVersion={netTestSdkVersionDir} """); + } + + foreach (var propertyName in msTestVersionProperties) + { + var mstestVersion = propsNode.Element(propertyName)!.Value; + cacheId[propertyName] = mstestVersion; + } + + cacheId["projects"] = projects; + + var cacheIdText = JsonConvert.SerializeObject(cacheId, Formatting.Indented); + + var currentCacheId = File.Exists(Path.Combine(generated, "checksum.json")) ? File.ReadAllText(Path.Combine(generated, "checksum.json")) : null; + + var rebuild = true; + if (cacheIdText == currentCacheId) + { + // Project cache is up-to-date, just rebuilding solution. + ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{generatedSln}" """); + ExecuteApplication2(Dotnet, $"build {generatedSln} --no-restore --configuration {IntegrationTestEnvironment.BuildConfiguration} -v:minimal"); + rebuild = false; + } + + if (rebuild) + { + if (Directory.Exists(generated)) + { + Directory.Delete(generated, recursive: true); + } + + Directory.CreateDirectory(generated); + + // Fix repo root, we are 1 level deeper than in the test/TestAssets location. + var buildPropsContent = File.ReadAllText(Path.Combine(testAssetsDir, "Directory.Build.props")); + buildPropsContent = Regex.Replace(buildPropsContent, "", "$(MSBuildThisFileDirectory)../../../"); + File.WriteAllText(Path.Combine(generated, "Directory.Build.props"), buildPropsContent); + + File.Copy(Path.Combine(testAssetsDir, "Directory.Build.targets"), Path.Combine(generated, "Directory.Build.targets")); + + ExecuteApplication2(Dotnet, $"""new sln --name CompatibilityTestAssets --output "{generated}"""); + + var projectsToAdd = new List(); + foreach (var project in projects) + { + var projectName = Path.GetFileName(project); + var projectBaseName = Path.GetFileNameWithoutExtension(projectName); + var projectDir = Path.GetDirectoryName(project)!; + var projectItems = Directory.GetFiles(projectDir, "*", SearchOption.AllDirectories).Where(p => + !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}") + && !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}") + // Is a file, and not a directory. + && File.Exists(p)).ToList(); + + foreach (var sdkPropertyName in vstestConsoleVersionProperties) + { + string netTestSdkVersion; + if (sdkPropertyName == "VSTestConsoleLatestVersion") + { + netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; + } + else + { + netTestSdkVersion = propsNode.Element(sdkPropertyName!)!.Value; + } + + var dirNetTestSdkVersion = netTestSdkVersion.TrimStart('[').TrimEnd(']'); + var dirNetTestSdkPropertyName = sdkPropertyName + .Replace("Framework", "") + .Replace("Version", "") + .Replace("VSTestConsole", "NETTestSdk"); + + foreach (var propertyName in msTestVersionProperties) + { + var mstestVersion = propsNode.Element(propertyName)!.Value; + + var dirMSTestVersion = mstestVersion.TrimStart('[').TrimEnd(']'); + var dirMSTestPropertyName = propertyName + .Replace("Framework", "") + .Replace("Version", ""); + + // Do not make this a folder structure, it will break the relative reference to scripts\build\TestAssets.props that we have in the project, + // because the relative path will be different. + // It would be nice to use fully descriptive name but it is too long, hash the versions instead. + // $compatibilityProjectDir = "$generated/$projectBaseName--$dirNetTestSdkPropertyName-$dirNetTestSdkVersion--$dirMSTestPropertyName-$dirMSTestVersion" + + var versions = $"{dirNetTestSdkPropertyName}-{dirNetTestSdkVersion}--{dirMSTestPropertyName}-{dirMSTestVersion}"; + var hash = IntegrationTestEnvironment.GetPathHash(versions); + var projectShortName = $"{projectBaseName}--{hash}"; + var compatibilityProjectDir = Path.Combine(generated, projectShortName); + + Directory.CreateDirectory(compatibilityProjectDir); + + foreach (var projectItem in projectItems) + { + var relativePath = projectItem.Replace(projectDir, "").TrimStart(Path.DirectorySeparatorChar); + var fullPath = Path.Combine(compatibilityProjectDir, relativePath); + File.Copy(projectItem, fullPath); + } + + var compatibilityCsproj = Directory.GetFiles(compatibilityProjectDir, "*.csproj", SearchOption.AllDirectories); + + if (!compatibilityCsproj.Any()) + { + throw new InvalidOperationException($"No .csproj file was found in directory {compatibilityProjectDir}."); + } + + if (compatibilityCsproj.Length > 1) + { + throw new InvalidOperationException($"More than 1 .csproj file was found in directory {compatibilityProjectDir}."); + } + + var csproj = compatibilityCsproj.Single(); + + var content = File.ReadAllText(csproj); + // We replace the content rather than using MSBuild properties, because that allows us to create a solution with + // many versions of the package, and let msbuild figure out how to correctly restore and build in parallel. If we did use + // MSBuild properties we would have to build each combination one by one in sequence. + var newContent = content + .Replace("$(MSTestTestFrameworkVersion)", mstestVersion) + .Replace("$(MSTestTestAdapterVersion)", mstestVersion) + .Replace("$(PackageVersion)", netTestSdkVersion); + File.WriteAllText(csproj, newContent); + + var uniqueCsprojName = Path.Combine(compatibilityProjectDir, $"{projectShortName}.csproj"); + File.Move(csproj, uniqueCsprojName); + projectsToAdd.Add(uniqueCsprojName); + } + } + } + + ExecuteApplication2(Dotnet, $"""sln {generatedSln} add "{string.Join("\" \"", projectsToAdd)}" """); + + ExecuteApplication2(Dotnet, $"""restore --packages {nugetCache} {nugetFeeds} --source "{IntegrationTestEnvironment.LocalPackageSource}" "{generatedSln}" """); + ExecuteApplication2(Dotnet, $"""build --no-restore --configuration {IntegrationTestEnvironment.BuildConfiguration} "{generatedSln}" """); + + File.WriteAllText(Path.Combine(generated, "checksum.json"), cacheIdText); + } + } + + private static string GetNugetSourceParameters(string root) + { + string nugetConfigPath = Path.Combine(root, "NuGet.config"); + var nugetConfig = XDocument.Load(nugetConfigPath); + + var feeds = nugetConfig! + .Element("configuration")! + .Element("packageSources")! + .Descendants("add") + .Where(p => p.Attributes().Any(a => a.Name == "key")) + .SelectMany(p => p.Attributes()) + .Where(a => a.Name == "value") + .Select(a => a.Value) + .ToList(); + + if (feeds.Count == 0) + { + throw new InvalidOperationException($"No feeds were loaded from '{nugetConfigPath}'."); + } + + // --source "value1" --source "value2", including quotes + var parameters = $"""--source "{string.Join("\" --source \"", feeds)}" """; + + return parameters; + } + + protected static void ExecuteApplication2(string path, string? args, + Dictionary? environmentVariables = null, string? workingDirectory = null) + { + + ExecuteApplication(path, args, out string stdOut, out string stdError, out int exitCode, environmentVariables, workingDirectory); + if (exitCode != 0) + { + throw new InvalidOperationException( + $""" + Executing '{path} {args}' failed. + STDOUT: {stdOut}, + STDERR: {stdError} + """); + } + } + + private static bool UnzipExecutablePackages() + { + var netTestSdkVersion = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion; + + // Extract locally built packages that have our tools (like vstest.console.exe) into tmp directory, + // so we can use them to run tests. + var packagesToExtract = new[] +{ + $"Microsoft.TestPlatform.{netTestSdkVersion}.nupkg", + $"Microsoft.TestPlatform.CLI.{netTestSdkVersion}.nupkg", + $"Microsoft.TestPlatform.Build.{netTestSdkVersion}.nupkg", + $"Microsoft.CodeCoverage.{netTestSdkVersion}.nupkg", + $"Microsoft.TestPlatform.Portable.{netTestSdkVersion}.nupkg", + }; + + var packagesAreNew = false; + foreach (var packageName in packagesToExtract) + { + var packagePath = Path.Combine(IntegrationTestEnvironment.LocalPackageSource, packageName); + var unzipPath = Path.Combine(IntegrationTestEnvironment.PublishDirectory, packageName); + + var cacheMarkerPath = Path.Combine(unzipPath, packageName + ".cache"); + if (File.Exists(cacheMarkerPath)) + { + if (File.ReadAllText(cacheMarkerPath) == File.GetLastWriteTimeUtc(packagePath).ToString("o", CultureInfo.InvariantCulture)) + { + // Already extracted and using the latest built packages. + continue; + } + } + + // I any package is new we will clean the package cache before restore and build. + packagesAreNew |= true; + + if (Directory.Exists(unzipPath)) + { + Directory.Delete(unzipPath, recursive: true); + } + + ZipFile.ExtractToDirectory(packagePath, unzipPath); + File.WriteAllText(cacheMarkerPath, File.GetLastWriteTimeUtc(packagePath).ToString("o", CultureInfo.InvariantCulture)); + } + + return packagesAreNew; + } + + private static void CleanNugetCacheAndProjects(string nugetCache) + { + // dotnet clean needs the packages in place, but here we don't yet know what projects we will build + // luckily they are all built into artifacts/bin/TestAssets and artifacts/obj/TestAssets so we just need to delete + // the obj to force re-build in the next steps. + + var objPath = Path.Combine(Root, "artifacts", "obj", "TestAssets"); + if (Directory.Exists(objPath)) + { + Directory.Delete(objPath, recursive: true); + } + + // Then clean all -dev and -ci packages from the cache to force updating from local source. + if (Directory.Exists(nugetCache)) + { + foreach (var packageDir in Directory.GetDirectories(nugetCache)) + { + foreach (var versionDir in Directory.GetDirectories(packageDir)) + { + if (versionDir.EndsWith("-dev") || versionDir.EndsWith("-ci")) + { + Directory.Delete(versionDir, recursive: true); + } + } + } + } + + // Unzip VSIX so we can test with it on Windows. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var vsixPath = IntegrationTestEnvironment.LocalVsixInsertion; + var vsixUnzipPath = Path.Combine(IntegrationTestEnvironment.PublishDirectory, Path.GetFileName(vsixPath)); + if (Directory.Exists(vsixUnzipPath)) + { + Directory.Delete(vsixUnzipPath, recursive: true); + } + + ZipFile.ExtractToDirectory(vsixPath, vsixUnzipPath); + } + } + + public static bool GetTestParameter(TestContext testContext, string parameterName) + { + return testContext.Properties.TryGetValue(parameterName, out var skipBuildValue) + ? bool.TryParse((string?)skipBuildValue, out var value) + ? value + : false + : false; + } + +} diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestEnvironment.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestEnvironment.cs index 9bcfc89cb9..1cb73e0705 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestEnvironment.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestEnvironment.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Xml; using Microsoft.VisualStudio.TestPlatform.Common; @@ -29,6 +30,7 @@ public class IntegrationTestEnvironment public static string ArtifactsTempDirectory { get; } = Path.Combine(RepoRootDirectory, "artifacts", "tmp", BuildConfiguration); public static string LocalPackageSource { get; } = Path.Combine(RepoRootDirectory, "artifacts", "packages", BuildConfiguration, "Shipping").TrimEnd(Path.DirectorySeparatorChar); + public static string LocalVsixInsertion { get; } = Path.Combine(RepoRootDirectory, "artifacts", "VSSetup", BuildConfiguration, "Insertion", "Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix"); public static string LatestLocallyBuiltNugetVersion { get; } = GetLatestLocallyBuiltPackageVersion(); /// @@ -45,6 +47,12 @@ public class IntegrationTestEnvironment /// public static string ExtensionsDirectory { get; } = Path.Combine(PublishDirectory, $"Microsoft.TestPlatform.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", "tools", "net462", "Common7", "IDE", "Extensions", "TestPlatform", "Extensions"); + /// + /// Gets the local NuGet cache directory used by test assets. + /// Test assets are restored into this directory by *Build.cs via dotnet restore --packages. + /// + public static string TestAssetsNuGetCacheDirectory { get; } = Path.Combine(RepoRootDirectory, "artifacts", ".packages"); + private static Dictionary? s_dependencyVersions; private string? _targetRuntime; @@ -62,9 +70,13 @@ public IntegrationTestEnvironment() // There is an assumption that integration tests will always run from a source enlistment. // Need to remove this assumption when we move to a CDP. - PackageDirectory = Path.Combine(RepoRootDirectory, @".packages"); + LocalPackageDirectory = TestAssetsNuGetCacheDirectory; + GlobalPackageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES") + ?? (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages") + : Path.Combine(Environment.GetEnvironmentVariable("HOME") ?? "", ".nuget", "packages")); TestArtifactsDirectory = Path.Combine(RepoRootDirectory, "artifacts", "testArtifacts"); - RunnerFramework = "net462"; + RunnerFramework = "net48"; } /// @@ -86,17 +98,27 @@ public static Dictionary DependencyVersions => s_dependencyVersions ??= GetDependencies(RepoRootDirectory); /// - /// Gets the nuget packages directory for enlistment. + /// Gets the local nuget packages directory for integration tests. + /// + public string LocalPackageDirectory { get; } + + /// + /// Gets the global nuget packages directory. /// - public string PackageDirectory { get; } + public string GlobalPackageDirectory { get; } /// /// Gets the target framework. - /// Supported values = net462, netcoreapp3.1. + /// Supported values = net462, net8.0. /// [NotNull] public string? TargetFramework { get; set; } + /// + /// Is targeting .NET Framework testhost? + /// + public bool IsNetFrameworkTarget => TargetFramework!.StartsWith("net4", StringComparison.InvariantCultureIgnoreCase); + /// /// Gets the target runtime. /// Supported values = win7-x64. @@ -143,7 +165,7 @@ public string? TargetRuntime /// /// Gets the application type. - /// Supported values = net462, netcoreapp3.1. + /// Supported values = net48, net10.0. /// public string RunnerFramework { get; set; } @@ -175,6 +197,7 @@ public string GetTestAsset(string assetName) /// /// Name of the asset with extension. E.g. SimpleUnitTest.dll /// asset project target framework. E.g net462 + /// Whether to automatically resolve the test asset from the compatibility matrix based on the DllInfos. This should be set to false only if you want to get a test asset that is not built from the compatibility matrix, e.g. a test project. /// Full path to the test asset. /// /// Test assets follow several conventions: @@ -183,7 +206,7 @@ public string GetTestAsset(string assetName) /// (c) Name of the test asset matches the parent directory name. E.g. TestAssets\SimpleUnitTest\SimpleUnitTest.csproj must /// produce TestAssets\SimpleUnitTest\bin\Debug\net462\SimpleUnitTest.dll /// - public string GetTestAsset(string assetName, string targetFramework) + public string GetTestAsset(string assetName, string targetFramework, bool automaticallyResolveCompatibilityTestAsset = true) { var simpleAssetName = Path.GetFileNameWithoutExtension(assetName); var assetPath = Path.Combine( @@ -197,17 +220,17 @@ public string GetTestAsset(string assetName, string targetFramework) assetName); // Update the path to be taken from the compatibility matrix instead of from the root folder. - if (DllInfos.Count > 0) + if (automaticallyResolveCompatibilityTestAsset && DllInfos.Count > 0) { // The path is really ugly: S:\p\vstest3\test\GeneratedTestAssets\NETTestSdkLegacyStable-15.9.2--MSTestMostDownloaded-2.1.0--MSTestProject2\bin\Debug\net462\MSTestProject2-NETTestSdkLegacyStable-15.9.2--MSTestMostDownloaded-2.1.0.dll // And we need to hash the versions in it to get shorter path as well. var versions = string.Join("--", DllInfos.Select(d => d.Path)); var versionsHash = GetPathHash(versions); assetPath = Path.Combine(RepoRootDirectory, "artifacts", "bin", "TestAssets", $"{simpleAssetName}--{versionsHash}", BuildConfiguration, targetFramework, $"{simpleAssetName}--{versionsHash}.dll"); + Assert.IsTrue(File.Exists(assetPath), $"GetTestAsset: Path not found: '{assetPath}'. Compatibility test assets are not built automatically anymore in AssemblyInitialize."); + return assetPath; } - Assert.IsTrue(File.Exists(assetPath), "GetTestAsset: Path not found: \"{0}\". Most likely you need to build using build.cmd -s PrepareAcceptanceTests.", assetPath); - // If you are thinking about wrapping the path in double quotes here, // then don't. File.Exist cannot handle quoted paths, and we use it in a lot of places. return assetPath; @@ -235,7 +258,7 @@ public static string GetPathHash(string value) /// GetNugetPackage("foobar") will return a path to packages\foobar. public string GetNugetPackage(string packageSuffix) { - var packagePath = Path.Combine(PackageDirectory, packageSuffix); + var packagePath = Path.Combine(LocalPackageDirectory, packageSuffix); Assert.IsTrue(Directory.Exists(packagePath), "GetNugetPackage: Directory not found: {0}.", packagePath); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/IsExternalInit.cs b/test/Microsoft.TestPlatform.TestUtilities/IsExternalInit.cs similarity index 100% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/IsExternalInit.cs rename to test/Microsoft.TestPlatform.TestUtilities/IsExternalInit.cs diff --git a/test/Microsoft.TestPlatform.TestUtilities/MSTestCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.TestUtilities/MSTestCompatibilityDataSource.cs new file mode 100644 index 0000000000..a7db4ff1db --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/MSTestCompatibilityDataSource.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Test compatibility of vstest.console and testhost with different MSTest versions. +/// +public class MSTestCompatibilityDataSource : CompatibilityDataSourceAttribute +{ + private readonly CompatibilityRowsBuilder _builder; + + public MSTestCompatibilityDataSource() + { + // 1 runner version and 1 testhost version + // This tests different mstest versions against our latest runner and testhost. + + _builder = new CompatibilityRowsBuilder( + // runner, use just .NET, because the adapter runs in the testhost, and we will add InProcess and VSIX, that will test running with the Runner. + AcceptanceTestBase.LATEST, + AcceptanceTestBase.DEFAULT_RUNNER_NET, + // host + AcceptanceTestBase.LATEST, + AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, + // adapter + AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY, + AcceptanceTestBase.MSTEST); + + // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. + } + + public bool DebugVSTestConsole { get; set; } + public bool DebugTestHost { get; set; } + public bool DebugDataCollector { get; set; } + public bool DebugStopAtEntrypoint { get; set; } + + public string? BeforeRunnerFeature { get; set; } + public string? AfterRunnerFeature { get; set; } + + public string? BeforeTestHostFeature { get; set; } + public string? AfterTestHostFeature { get; set; } + + public string? BeforeAdapterFeature { get; set; } + public string? AfterAdapterFeature { get; set; } + + public override void CreateData(MethodInfo methodInfo) + { + _builder.WithInProcess = true; + _builder.WithVSIXRunner = true; + + _builder.BeforeRunnerFeature = BeforeRunnerFeature; + _builder.AfterRunnerFeature = AfterRunnerFeature; + + _builder.BeforeTestHostFeature = BeforeTestHostFeature; + _builder.AfterTestHostFeature = AfterTestHostFeature; + + _builder.BeforeAdapterFeature = BeforeAdapterFeature; + _builder.AfterAdapterFeature = AfterAdapterFeature; + + _builder.DebugDataCollector = DebugDataCollector; + _builder.DebugVSTestConsole = DebugVSTestConsole; + _builder.DebugTestHost = DebugTestHost; + _builder.DebugStopAtEntrypoint = DebugStopAtEntrypoint; + + var data = _builder.CreateData(); + data.ForEach(AddData); + } +} diff --git a/test/Microsoft.TestPlatform.TestUtilities/Microsoft.TestPlatform.TestUtilities.csproj b/test/Microsoft.TestPlatform.TestUtilities/Microsoft.TestPlatform.TestUtilities.csproj index 1823de200f..a03778beec 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/Microsoft.TestPlatform.TestUtilities.csproj +++ b/test/Microsoft.TestPlatform.TestUtilities/Microsoft.TestPlatform.TestUtilities.csproj @@ -3,7 +3,7 @@ Microsoft.TestPlatform.TestUtilities - net6.0;net48 + net9.0;net48 false true @@ -17,6 +17,7 @@ + @@ -25,19 +26,19 @@ - + - + - - - - - - + + + + + + diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreRunnerAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/NetCoreRunnerAttribute.cs similarity index 91% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreRunnerAttribute.cs rename to test/Microsoft.TestPlatform.TestUtilities/NetCoreRunnerAttribute.cs index a1362f1c56..b65ae128cc 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreRunnerAttribute.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/NetCoreRunnerAttribute.cs @@ -7,16 +7,14 @@ using System.Linq; using System.Reflection; -using Microsoft.TestPlatform.TestUtilities; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// /// Runs tests using the dotnet vstest.console.dll built against .NET Core 3.1. /// Provide a list of target frameworks to run the tests from given as a ';' separated list, or using a constant containing that range such as -/// AcceptanceTestBase.NETFX462_NET50 = "net462;net472;net48;netcoreapp3.1;net5.0" to determine which target framework of the project +/// AcceptanceTestBase.NETFX462_NET9 = "net462;net472;net48;net8.0;net9.0" to determine which target framework of the project /// to test. The target project must list those TFMs in the TargetFrameworks property in csproj. /// [AttributeUsage(AttributeTargets.Method)] @@ -28,7 +26,7 @@ public class NetCoreRunnerAttribute : Attribute, ITestDataSource /// Initializes a new instance of the class. /// /// To run tests with desktop runner(vstest.console.exe), use AcceptanceTestBase.Net462TargetFramework or alike values. - public NetCoreRunnerAttribute(string targetFrameworks = AcceptanceTestBase.NETFX462_NET50) + public NetCoreRunnerAttribute(string targetFrameworks = AcceptanceTestBase.NETFX462_NET11) { _targetFrameworks = targetFrameworks; } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreTargetFrameworkDataSourceAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/NetCoreTargetFrameworkDataSourceAttribute.cs similarity index 95% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreTargetFrameworkDataSourceAttribute.cs rename to test/Microsoft.TestPlatform.TestUtilities/NetCoreTargetFrameworkDataSourceAttribute.cs index a227c37af1..f40e30350c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetCoreTargetFrameworkDataSourceAttribute.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/NetCoreTargetFrameworkDataSourceAttribute.cs @@ -6,11 +6,9 @@ using System.Globalization; using System.Reflection; -using Microsoft.TestPlatform.TestUtilities; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// /// The attribute defining runner framework, target framework and target runtime for netcoreapp1.* @@ -68,14 +66,14 @@ public IEnumerable GetData(MethodInfo methodInfo) { var runnerFramework = IntegrationTestBase.DesktopRunnerFramework; - AddRunnerDataRow(dataRows, runnerFramework, AcceptanceTestBase.Core31TargetFramework); + AddRunnerDataRow(dataRows, runnerFramework, AcceptanceTestBase.Core80TargetFramework); } if (_useCoreRunner) { var runnerFramework = IntegrationTestBase.CoreRunnerFramework; - AddRunnerDataRow(dataRows, runnerFramework, AcceptanceTestBase.Core31TargetFramework); + AddRunnerDataRow(dataRows, runnerFramework, AcceptanceTestBase.Core80TargetFramework); } return dataRows; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFrameworkRunnerAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/NetFrameworkRunnerAttribute.cs similarity index 90% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFrameworkRunnerAttribute.cs rename to test/Microsoft.TestPlatform.TestUtilities/NetFrameworkRunnerAttribute.cs index 037bb03a1a..cd427216b3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFrameworkRunnerAttribute.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/NetFrameworkRunnerAttribute.cs @@ -6,16 +6,14 @@ using System.Globalization; using System.Reflection; -using Microsoft.TestPlatform.TestUtilities; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// -/// Runs tests using the dotnet vstest.console.dll built against .NET Core 2.1. +/// Runs tests using the dotnet vstest.console.dll built against .NET 6.0. /// Provide a list of target frameworks to run the tests from given as a ';' separated list, or using a constant containing that range such as -/// AcceptanceTestBase.NETFX462_NET50 = "net462;net472;net48;netcoreapp3.1;net5.0" to determine which target framework of the project +/// AcceptanceTestBase.NETFX462_NET9 = "net462;net472;net48;net8.0;net9.0" to determine which target framework of the project /// to test. The target project must list those TFMs in the TargetFrameworks property in csproj. /// [AttributeUsage(AttributeTargets.Method)] @@ -25,7 +23,7 @@ public class NetFrameworkRunnerAttribute : Attribute, ITestDataSource /// Initializes a new instance of the class. /// /// To run tests with desktop runner(vstest.console.exe), use AcceptanceTestBase.Net462TargetFramework or alike values. - public NetFrameworkRunnerAttribute(string targetFrameworks = AcceptanceTestBase.NETFX462_NET50) + public NetFrameworkRunnerAttribute(string targetFrameworks = AcceptanceTestBase.NETFX462_NET11) { _targetFrameworks = targetFrameworks; } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFullTargetFrameworkDataSourceAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/NetFullTargetFrameworkDataSourceAttribute.cs similarity index 76% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFullTargetFrameworkDataSourceAttribute.cs rename to test/Microsoft.TestPlatform.TestUtilities/NetFullTargetFrameworkDataSourceAttribute.cs index eeec157d7e..218a54735a 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/NetFullTargetFrameworkDataSourceAttribute.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/NetFullTargetFrameworkDataSourceAttribute.cs @@ -4,13 +4,12 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Reflection; -using Microsoft.TestPlatform.TestUtilities; - using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// /// The attribute defining runner framework and target framework for net462. @@ -25,6 +24,7 @@ public class NetFullTargetFrameworkDataSourceAttribute : Attribute, ITestDataSou private readonly bool _inProcess; private readonly bool _useDesktopRunner; private readonly bool _useCoreRunner; + private readonly bool _useVsixRunner; /// /// Initializes a new instance of the class. @@ -33,12 +33,15 @@ public class NetFullTargetFrameworkDataSourceAttribute : Attribute, ITestDataSou /// Run tests in process /// To run tests with desktop runner(vstest.console.exe) /// To run tests with core runner(dotnet vstest.console.dll) - public NetFullTargetFrameworkDataSourceAttribute(bool inIsolation = true, bool inProcess = false, bool useDesktopRunner = true, bool useCoreRunner = true) + /// To run tests with runner from VSIX. + public NetFullTargetFrameworkDataSourceAttribute(bool inIsolation = true, bool inProcess = false, bool useDesktopRunner = true, bool useCoreRunner = true, bool useVsixRunner = false) { _inIsolation = inIsolation; _inProcess = inProcess; _useDesktopRunner = useDesktopRunner; _useCoreRunner = useCoreRunner; + _useVsixRunner = useVsixRunner; + _useVsixRunner = useVsixRunner; } public bool DebugVSTestConsole { get; set; } @@ -105,6 +108,29 @@ public IEnumerable GetData(MethodInfo methodInfo) }; dataRows.Add([runnerInfo]); } + + if (_useVsixRunner) + { + var runnerInfo = new RunnerInfo + { + RunnerFramework = IntegrationTestBase.DesktopRunnerFramework, + TargetFramework = AcceptanceTestBase.DesktopTargetFramework, + VSTestConsoleInfo = new VSTestConsoleInfo + { + Version = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion, + Path = Path.Combine(IntegrationTestEnvironment.PublishDirectory, Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion), "vstest.console.exe"), + }, + InIsolationValue = null + }; + runnerInfo.DebugInfo = new DebugInfo + { + DebugVSTestConsole = DebugVSTestConsole, + DebugTestHost = DebugTestHost, + DebugDataCollector = DebugDataCollector, + DebugStopAtEntrypoint = DebugStopAtEntrypoint, + }; + dataRows.Add([runnerInfo]); + } } return dataRows; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnerCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.TestUtilities/RunnerCompatibilityDataSource.cs similarity index 60% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnerCompatibilityDataSource.cs rename to test/Microsoft.TestPlatform.TestUtilities/RunnerCompatibilityDataSource.cs index 6dc008c732..2c948d03a3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnerCompatibilityDataSource.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/RunnerCompatibilityDataSource.cs @@ -3,34 +3,32 @@ using System.Reflection; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// -/// A data source that provides every version of runner. +/// A data source that checks compatibility of changes in vstest.console with different versions of testhost. It also adds in-process mode and runner from VSIX. +/// We are testing with all versions of testhost, because we want to make sure that even project with very old Microsoft.NET.Test.Sdk is able to be opened and used +/// in Visual Studio. +/// We test with VSIX and in-process to avoid duplicating tests only because they need runner from a different place. +/// Use for testing changes specific to vstest.console. Or for interaction between runner and testhost. /// /// When that adds up to no configuration exception is thrown. /// -public class RunnerCompatibilityDataSource : TestDataSourceAttribute +public class RunnerCompatibilityDataSource : CompatibilityDataSourceAttribute { private readonly CompatibilityRowsBuilder _builder; - public RunnerCompatibilityDataSource( - string runnerFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string runnerVersions = AcceptanceTestBase.LATEST_TO_LEGACY, - string hostFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET) + public RunnerCompatibilityDataSource() { - // TODO: We actually don't generate values to use different translation layers, because we don't have a good way to do - // that right now. Translation layer is loaded directly into the acceptance test, and so we don't have easy way to substitute it. - _builder = new CompatibilityRowsBuilder( - runnerFrameworks, - runnerVersions, - hostFrameworks, - // host versions + // runner AcceptanceTestBase.LATEST, - // adapter versions + AcceptanceTestBase.DEFAULT_RUNNER_NETFX_AND_NET, + // host + AcceptanceTestBase.LATEST_TO_LEGACY, + AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, + // adapter AcceptanceTestBase.LATESTSTABLE, - // adapters AcceptanceTestBase.MSTEST); // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. @@ -42,11 +40,6 @@ public RunnerCompatibilityDataSource( public bool DebugStopAtEntrypoint { get; set; } public int JustRow { get; set; } = -1; - /// - /// Add run for in-process using the selected .NET Framework runners, and and all selected adapters. - /// - public bool InProcess { get; set; } - public string? BeforeFeature { get; set; } public string? AfterFeature { get; set; } @@ -58,11 +51,8 @@ public RunnerCompatibilityDataSource( public override void CreateData(MethodInfo methodInfo) { - _builder.WithEveryVersionOfRunner = true; - _builder.WithEveryVersionOfHost = false; - _builder.WithEveryVersionOfAdapter = false; - _builder.WithOlderConfigurations = false; - _builder.WithInProcess = InProcess; + _builder.WithVSIXRunner = true; + _builder.WithInProcess = true; _builder.BeforeRunnerFeature = BeforeFeature; _builder.AfterRunnerFeature = AfterFeature; @@ -84,4 +74,3 @@ public override void CreateData(MethodInfo methodInfo) data.ForEach(AddData); } } - diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnnerInfo.cs b/test/Microsoft.TestPlatform.TestUtilities/RunnnerInfo.cs similarity index 92% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnnerInfo.cs rename to test/Microsoft.TestPlatform.TestUtilities/RunnnerInfo.cs index c25929c04b..34da019400 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/RunnnerInfo.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/RunnnerInfo.cs @@ -5,9 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -using Microsoft.TestPlatform.TestUtilities; - -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; [Serializable] // Type should be serializable to allow the tree-view behavior of test discovery in Test Explorer public class RunnerInfo @@ -53,7 +51,7 @@ public override string ToString() Batch != null ? $"{Batch}" : null, $"Runner = {RunnerFramework}", $"TargetFramework = {TargetFramework}", - string.IsNullOrEmpty(InIsolationValue) ? "InProcess" : "InIsolation", + InIsolationValue == AcceptanceTestBase.InIsolation ? "InIsolation" : "InProcess", VSTestConsoleInfo?.ToString(), TestHostInfo == null ? null : string.Join(",", TestHostInfo), AdapterInfo == null ? null : string.Join(",", AdapterInfo) diff --git a/test/Microsoft.TestPlatform.TestUtilities/SourceAssert.cs b/test/Microsoft.TestPlatform.TestUtilities/SourceAssert.cs new file mode 100644 index 0000000000..f5ca5e6cbe --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/SourceAssert.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Provides assertion methods that validate PDB-reported line numbers against actual source files. +/// This removes the need for Debug/Release conditional checks in tests, since line numbers +/// from PDBs differ between configurations due to compiler optimizations. +/// +public static class SourceAssert +{ + /// + /// Asserts that is at the start of 's body. + /// In Debug builds, the PDB reports the opening brace line. In Release builds, the compiler + /// optimizes and reports the next line (first statement or closing brace). + /// This assertion accepts exactly those two possibilities: BodyStartLine or BodyStartLine + 1. + /// For overloaded methods, succeeds if the line matches any overload. + /// + public static void LineIsAtMethodBodyStart(string sourceFilePath, string methodName, int actualLineNumber, string? message = null) + { + var lines = File.ReadAllLines(sourceFilePath); + var bodyStarts = SourceNavigationParser.FindMethodBodyStartLines(lines, methodName); + Assert.IsNotEmpty(bodyStarts, $"Method '{methodName}' not found in '{sourceFilePath}'."); + + Assert.Contains( + bodyStart => actualLineNumber == bodyStart || actualLineNumber == bodyStart + 1, bodyStarts, + message ?? $"Line {actualLineNumber} is not at the body start of method '{methodName}' in '{Path.GetFileName(sourceFilePath)}'." + + $" Expected one of: {string.Join(", ", bodyStarts.SelectMany(b => new[] { b, b + 1 }).Distinct().OrderBy(x => x))}"); + } + + /// + /// Asserts that falls within the declaration range of + /// in . + /// The range spans from a few lines above the method signature (to cover attributes) through + /// the body start. For overloaded methods, succeeds if the line falls within any overload. + /// + public static void LineIsWithinMethod(string sourceFilePath, string methodName, int actualLineNumber, string? message = null) + { + var lines = File.ReadAllLines(sourceFilePath); + var locations = SourceNavigationParser.FindMethodLocations(lines, methodName); + Assert.IsNotEmpty(locations, $"Method '{methodName}' not found in '{sourceFilePath}'."); + + // Allow from a few lines before the signature (to cover attributes like [TestMethod]) through body start + 1. + const int attributeMargin = 5; + Assert.Contains( + loc => actualLineNumber >= loc.SignatureLine - attributeMargin && actualLineNumber <= loc.BodyStartLine + 1, locations, + message ?? $"Line {actualLineNumber} is not within any overload of method '{methodName}' in '{Path.GetFileName(sourceFilePath)}'." + + $" Method ranges: {string.Join(", ", locations.Select(loc => $"[{loc.SignatureLine - attributeMargin}-{loc.BodyStartLine + 1}]"))}"); + } + + /// + /// Finds the source file containing in the test asset project + /// identified by (e.g. "NUTestProject.dll"). + /// Looks in test/TestAssets/{projectName}/ relative to the repo root. + /// + public static string FindSourceFile(string assetName, string methodName) + { + var projectName = Path.GetFileNameWithoutExtension(assetName); + var testAssetsDir = Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "test", "TestAssets", projectName); + Assert.IsTrue(Directory.Exists(testAssetsDir), $"Test asset project directory not found: '{testAssetsDir}'."); + + foreach (var csFile in Directory.GetFiles(testAssetsDir, "*.cs", SearchOption.TopDirectoryOnly)) + { + var lines = File.ReadAllLines(csFile); + if (SourceNavigationParser.FindMethodBodyStartLines(lines, methodName).Count > 0) + { + return csFile; + } + } + + Assert.Fail($"No source file containing method '{methodName}' found in '{testAssetsDir}'."); + return null!; + } +} + +/// +/// Parses C# source text to find method body start lines. This class operates on string arrays +/// (lines of text) and has no file system dependencies, making it easy to unit test. +/// +public static class SourceNavigationParser +{ + /// + /// Finds each overload of and returns a + /// with the 1-based signature line and body start line (the line containing the opening brace). + /// + public static IReadOnlyList FindMethodLocations(string[] lines, string methodName) + { + var results = new List(); + + for (int i = 0; i < lines.Length; i++) + { + if (!ContainsMethodSignature(lines[i], methodName)) + { + continue; + } + + int signatureLine = i + 1; // 1-based + + // Find the next '{' starting from the signature line. + for (int j = i; j < lines.Length; j++) + { + if (lines[j].Contains('{')) + { + results.Add(new MethodLocation(signatureLine, j + 1)); // 1-based + break; + } + } + } + + return results; + } + + /// + /// Convenience wrapper returning just the body start lines for each overload. + /// + public static IReadOnlyList FindMethodBodyStartLines(string[] lines, string methodName) + { + var locations = FindMethodLocations(lines, methodName); + var results = new List(locations.Count); + foreach (var loc in locations) + { + results.Add(loc.BodyStartLine); + } + + return results; + } + + /// + /// Checks whether contains a method signature for , + /// identified by the pattern methodName( (with optional whitespace before the parenthesis). + /// + public static bool ContainsMethodSignature(string text, string methodName) + { + int startIndex = 0; + while (true) + { + int idx = text.IndexOf(methodName, startIndex, StringComparison.Ordinal); + if (idx < 0) + { + return false; + } + + // Check that the character after the method name (skipping whitespace) is '('. + for (int i = idx + methodName.Length; i < text.Length; i++) + { + if (text[i] == '(') + { + return true; + } + + if (!char.IsWhiteSpace(text[i])) + { + break; + } + } + + startIndex = idx + 1; + } + } +} + +/// +/// Represents the location of a method in a source file. All line numbers are 1-based. +/// +/// The line containing the method name and parameters. +/// The line containing the opening brace. +public readonly record struct MethodLocation(int SignatureLine, int BodyStartLine); diff --git a/test/Microsoft.TestPlatform.TestUtilities/TempDirectory.cs b/test/Microsoft.TestPlatform.TestUtilities/TempDirectory.cs index e8fb8fa0ad..8595e94e9b 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/TempDirectory.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/TempDirectory.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using IO = System.IO; @@ -23,6 +24,8 @@ public TempDirectory() public string Path { get; } + public static string? NuGetConfigPath { get; set; } + public void Dispose() { Dispose(true); @@ -91,12 +94,25 @@ public string CopyFile(string filePath) /// /// Path of the created directory. /// + [MethodImpl(MethodImplOptions.Synchronized)] internal static string CreateUniqueDirectory() { var temp = GetTempPath(); var directoryPath = IO.Path.Combine(temp, "vstest", RandomId.Next()); Directory.CreateDirectory(directoryPath); + if (NuGetConfigPath == null) + { + throw new InvalidOperationException("NuGetConfigPath on TempDirectory class must be set."); + } + + var tempNugetConfigPath = IO.Path.Combine(directoryPath, IO.Path.GetFileName(NuGetConfigPath)); + + if (!File.Exists(tempNugetConfigPath)) + { + File.Copy(NuGetConfigPath, tempNugetConfigPath); + } + return directoryPath; } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestDataSourceAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/TestDataSourceAttribute.cs similarity index 97% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestDataSourceAttribute.cs rename to test/Microsoft.TestPlatform.TestUtilities/TestDataSourceAttribute.cs index d67d02f975..fc5f3f99c4 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TestDataSourceAttribute.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/TestDataSourceAttribute.cs @@ -7,7 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; [AttributeUsage(AttributeTargets.Method)] public abstract class TestDataSourceAttribute : Attribute, ITestDataSource where T1 : notnull @@ -16,7 +16,7 @@ public abstract class TestDataSourceAttribute : Attribute, ITestDataSource w public abstract void CreateData(MethodInfo methodInfo); - public void AddData(T1 value1) + public void AddData(TestDataRow value1) { _data.Add([value1]); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TesthostCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.TestUtilities/TesthostCompatibilityDataSource.cs similarity index 54% rename from test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TesthostCompatibilityDataSource.cs rename to test/Microsoft.TestPlatform.TestUtilities/TesthostCompatibilityDataSource.cs index 70db6ca909..6d3591cb62 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/Extension/TesthostCompatibilityDataSource.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/TesthostCompatibilityDataSource.cs @@ -3,34 +3,30 @@ using System.Reflection; -namespace Microsoft.TestPlatform.AcceptanceTests; +namespace Microsoft.TestPlatform.TestUtilities; /// -/// A data source that provides every testhost. -/// -/// When that adds up to no configuration exception is thrown. +/// A data source for checking compatibility of changes in testhost with different versions of vstest.console. +/// This provides the development version of testhost and pairs it with different versions of vstest.console, from Latest to RecentStable. +/// To give us confidence that we are not breaking customers with recent versions. We don't test with older versions of vstest.console, +/// because they are less likely to be used by customers that update to latest NET.Test.Sdk. +/// Because .NET Framework testhost is shipped together with VSTest console, we add only Latest - Latest for .NET Framework testhost. /// -public class TestHostCompatibilityDataSource : TestDataSourceAttribute +public class TestHostCompatibilityDataSource : CompatibilityDataSourceAttribute { private readonly CompatibilityRowsBuilder _builder; - public TestHostCompatibilityDataSource( - string runnerFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string hostFrameworks = AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, - string hostVersions = AcceptanceTestBase.LATEST_TO_LEGACY) + public TestHostCompatibilityDataSource() { - // TODO: We actually don't generate values to use different translation layers, because we don't have a good way to do - // that right now. Translation layer is loaded directly into the acceptance test, and so we don't have easy way to substitute it. - _builder = new CompatibilityRowsBuilder( - runnerFrameworks, - // runner versions + // runner + AcceptanceTestBase.LATEST_TO_RECENT_STABLE, + AcceptanceTestBase.DEFAULT_RUNNER_NETFX_AND_NET, + // host AcceptanceTestBase.LATEST, - hostFrameworks, - hostVersions, - // adapter versions - AcceptanceTestBase.LATESTSTABLE, + AcceptanceTestBase.DEFAULT_HOST_NETFX_AND_NET, // adapter + AcceptanceTestBase.LATESTSTABLE, AcceptanceTestBase.MSTEST); // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. @@ -46,12 +42,6 @@ public TestHostCompatibilityDataSource( public override void CreateData(MethodInfo methodInfo) { - _builder.WithEveryVersionOfRunner = false; - _builder.WithEveryVersionOfHost = true; - _builder.WithEveryVersionOfAdapter = false; - _builder.WithOlderConfigurations = false; - _builder.WithInProcess = false; - _builder.BeforeTestHostFeature = BeforeFeature; _builder.AfterTestHostFeature = AfterFeature; diff --git a/test/Microsoft.TestPlatform.TestUtilities/WrapperCompatibilityDataSource.cs b/test/Microsoft.TestPlatform.TestUtilities/WrapperCompatibilityDataSource.cs new file mode 100644 index 0000000000..23dc63cc13 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/WrapperCompatibilityDataSource.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// A data source that checks compatibility of changes in VSTestConsoleWrapper with different versions of vstest.console. +/// We are testing with multiple recent versions of vstest.console, because the wrapper should (ideally) keep backwards compatibility with all +/// vstest.consoles in all supported products, not just with latest. +/// We also add VSIX to test that the shipment into VS works. +/// This does NOT test compatibility of changes in vstest.console, with older versions of the wrapper. +/// +public class WrapperCompatibilityDataSource : CompatibilityDataSourceAttribute +{ + private readonly CompatibilityRowsBuilder _builder; + + public WrapperCompatibilityDataSource() + { + _builder = new CompatibilityRowsBuilder( + // runner + AcceptanceTestBase.LATEST_TO_RECENT_STABLE, + AcceptanceTestBase.DEFAULT_RUNNER_NETFX_AND_NET, + // host + AcceptanceTestBase.LATEST, + AcceptanceTestBase.DEFAULT_HOST_NET, + // adapter + AcceptanceTestBase.LATESTSTABLE, + AcceptanceTestBase.MSTEST); + + // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. + } + + public bool DebugVSTestConsole { get; set; } + public bool DebugTestHost { get; set; } + public bool DebugDataCollector { get; set; } + public bool DebugStopAtEntrypoint { get; set; } + public int JustRow { get; set; } = -1; + + public string? BeforeFeature { get; set; } + public string? AfterFeature { get; set; } + + //public string? BeforeTestHostFeature { get; set; } + //public string? AfterTestHostFeature { get; set; } + + public string? BeforeAdapterFeature { get; set; } + public string? AfterAdapterFeature { get; set; } + + public override void CreateData(MethodInfo methodInfo) + { + _builder.WithVSIXRunner = true; + _builder.WithInProcess = false; + + _builder.BeforeRunnerFeature = BeforeFeature; + _builder.AfterRunnerFeature = AfterFeature; + + //_builder.BeforeTestHostFeature = BeforeTestHostFeature; + //_builder.AfterTestHostFeature = AfterTestHostFeature; + + _builder.BeforeAdapterFeature = BeforeAdapterFeature; + _builder.AfterAdapterFeature = AfterAdapterFeature; + + _builder.DebugDataCollector = DebugDataCollector; + _builder.DebugVSTestConsole = DebugVSTestConsole; + _builder.DebugTestHost = DebugTestHost; + _builder.DebugStopAtEntrypoint = DebugStopAtEntrypoint; + + _builder.JustRow = JustRow < 0 ? null : JustRow; + + var data = _builder.CreateData(); + data.ForEach(AddData); + } +} + diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs index 50849cada2..3b3328325e 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/ClientUtilitiesTests.cs @@ -17,14 +17,14 @@ public class ClientUtilitiesTests [TestMethod] public void FixRelativePathsInRunSettingsShouldThrowIfDocumentIsNull() { - Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(null!, "c:\\temp")); + Assert.ThrowsExactly(() => ClientUtilities.FixRelativePathsInRunSettings(null!, "c:\\temp")); } [TestMethod] public void FixRelativePathsInRunSettingsShouldThrowIfPathIsNullOrEmpty() { - Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), null!)); - Assert.ThrowsException(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), "")); + Assert.ThrowsExactly(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), null!)); + Assert.ThrowsExactly(() => ClientUtilities.FixRelativePathsInRunSettings(new XmlDocument(), "")); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs index 0dfa24dd46..05f7ba5e5e 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs @@ -53,13 +53,21 @@ public CodeCoverageDataAttachmentsHandlerTests() [ClassInitialize] public static void ClassInitialize(TestContext context) { - // Copying test files to correct place, - var assemblyPath = AppDomain.CurrentDomain.BaseDirectory; - var testFilesDirectory = Path.Combine(context.DeploymentDirectory!, "TestFiles"); - Directory.CreateDirectory(testFilesDirectory); - var files = Directory.GetFiles(Path.Combine(assemblyPath, "TestFiles")); - foreach (var file in files) - File.Copy(file, Path.Combine(testFilesDirectory, Path.GetFileName(file))); + // Annoyingly those paths are the same, but one does not end with slash, + // no matter how you compare, e.g. new DirectoryInfo(...).FullName, the slashes are not + // removed. And then you get obscure error: File already exists, or File is used by other process + // when you try to copy the file and provide both paths the same. + var assemblyPath = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); + var deploymentDirectory = context.DeploymentDirectory!.TrimEnd(Path.DirectorySeparatorChar); + if (new DirectoryInfo(deploymentDirectory).FullName != new DirectoryInfo(assemblyPath).FullName) + { + // Copying test files to deployment directory place, + var testFilesDirectory = Path.Combine(deploymentDirectory!, "TestFiles"); + Directory.CreateDirectory(testFilesDirectory); + var files = Directory.GetFiles(Path.Combine(assemblyPath, "TestFiles")); + foreach (var file in files) + File.Copy(file, Path.Combine(testFilesDirectory, Path.GetFileName(file)), overwrite: true); + } } [TestMethod] @@ -70,12 +78,12 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnEmptySetWhenNoAt _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 0); + Assert.IsEmpty(resultAttachmentSets); resultAttachmentSets = await _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, null, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 0); + Assert.IsEmpty(resultAttachmentSets); _mockProgressReporter.Verify(p => p.Report(It.IsAny()), Times.Never); } @@ -91,8 +99,8 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIfOnly1Atta _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 1); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 1); + Assert.HasCount(1, resultAttachmentSets); + Assert.HasCount(1, resultAttachmentSets.First().Attachments); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); Assert.AreEqual("file:///C:/temp/aa.coverage", resultAttachmentSets.First().Attachments.First().Uri.AbsoluteUri); } @@ -111,8 +119,8 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIf2Differen _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 1); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 2); + Assert.HasCount(1, resultAttachmentSets); + Assert.HasCount(2, resultAttachmentSets.First().Attachments); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.Last().Uri.AbsoluteUri); Assert.AreEqual(_filePrefix + file1Path.Replace("\\", "/").Replace(" ", "%20"), resultAttachmentSets.First().Attachments.First().Uri.AbsoluteUri); @@ -132,8 +140,8 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIf2SameForm _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 1); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 1); + Assert.HasCount(1, resultAttachmentSets); + Assert.HasCount(1, resultAttachmentSets.First().Attachments); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); Assert.AreEqual(_filePrefix + file1Path.Replace("\\", "/").Replace(" ", "%20"), resultAttachmentSets.First().Attachments.First().Uri.AbsoluteUri); } @@ -149,8 +157,8 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIfOnly1Logs _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 1); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 1); + Assert.HasCount(1, resultAttachmentSets); + Assert.HasCount(1, resultAttachmentSets.First().Attachments); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); Assert.AreEqual("file:///C:/temp/aa.logs", resultAttachmentSets.First().Attachments.First().Uri.AbsoluteUri); } @@ -170,9 +178,9 @@ public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIfOnlySever _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 2); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 1); - Assert.IsTrue(resultAttachmentSets.Last().Attachments.Count == 2); + Assert.HasCount(2, resultAttachmentSets); + Assert.HasCount(1, resultAttachmentSets.First().Attachments); + Assert.HasCount(2, resultAttachmentSets.Last().Attachments); } [TestMethod] @@ -189,9 +197,9 @@ public async Task HandleDataCollectionAttachmentSetsShouldThrowIfCancellationReq attachmentSet ]; - await Assert.ThrowsExceptionAsync(async () => await _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, cts.Token)); + await Assert.ThrowsExactlyAsync(async () => await _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(_configurationElement, attachment, _mockProgressReporter.Object, null, cts.Token)); - Assert.AreEqual(2, attachment.Count); + Assert.HasCount(2, attachment); _mockProgressReporter.Verify(p => p.Report(It.IsAny()), Times.Never); } @@ -212,8 +220,8 @@ public async Task MergingPerTestCodeCoverageReturnsOneCoverageFile() _coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(doc.DocumentElement!, attachment, _mockProgressReporter.Object, _messageLogger.Object, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 1); - Assert.IsTrue(resultAttachmentSets.First().Attachments.Count == 1); + Assert.HasCount(1, resultAttachmentSets); + Assert.HasCount(1, resultAttachmentSets.First().Attachments); Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); } } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageRunSettingsProcessorTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageRunSettingsProcessorTests.cs index e6023e99b7..a678cb6267 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageRunSettingsProcessorTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageRunSettingsProcessorTests.cs @@ -250,7 +250,7 @@ private static void CompareResults(XmlNode currentSettingsRoot, XmlNode defaultS { var nodes = ExtractNodes(currentSettingsRoot, defaultSettingsRoot, path); - Assert.AreEqual(nodes.Item1.ChildNodes.Count, nodes.Item2.ChildNodes.Count); + Assert.HasCount(nodes.Item1.ChildNodes.Count, nodes.Item2.ChildNodes); var set = new HashSet(); foreach (XmlNode child in nodes.Item1.ChildNodes) @@ -272,7 +272,7 @@ private static void CompareResults(XmlNode currentSettingsRoot, XmlNode defaultS set.Remove(child.OuterXml); } - Assert.AreEqual(0, set.Count); + Assert.IsEmpty(set); } #endregion } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesRegressionTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesRegressionTests.cs new file mode 100644 index 0000000000..20df01b190 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesRegressionTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Utilities.Tests; + +/// +/// Regression tests for CommandLineUtilities.SplitCommandLineIntoArguments. +/// +[TestClass] +public class CommandLineUtilitiesRegressionTests +{ + // Regression test for #15304 — Answer file parsing interprets `\"` as end of quoted string + // Before the fix, backslash-quote inside a quoted argument was misinterpreted. + [TestMethod] + public void SplitCommandLineIntoArguments_BackslashQuoteInArgument_ShouldEscapeQuote() + { + // \"value\" should produce a literal "value" + string input = """/Tests:"Test(\"iCT 256\")" """; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual(@"/Tests:Test(""iCT 256"")", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_DoubleBackslash_ShouldProduceSingleBackslash() + { + string input = @"arg\\value"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual(@"arg\value", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_BackslashBeforeNonSpecialChar_ShouldPreserveBackslash() + { + string input = @"arg\nvalue"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual(@"arg\nvalue", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_TrailingBackslash_ShouldPreserveBackslash() + { + string input = @"arg\"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual(@"arg\", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_UnbalancedQuotes_ShouldReportError() + { + string input = @"""unbalanced"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] _); + + Assert.IsTrue(hadError); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_CommentLine_ShouldBeIgnored() + { + string input = "arg1\n# This is a comment\narg2"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(2, args); + Assert.AreEqual("arg1", args[0]); + Assert.AreEqual("arg2", args[1]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_QuotedPathWithBackslashes_ShouldHandleCorrectly() + { + string input = """/testadapterpath:"c:\Path\To\Adapters" """; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual(@"/testadapterpath:c:\Path\To\Adapters", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_EmptyQuotedString_ShouldProduceEmptyArgument() + { + string input = @"arg1 """" arg3"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(3, args); + Assert.AreEqual("arg1", args[0]); + Assert.AreEqual("", args[1]); + Assert.AreEqual("arg3", args[2]); + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs index c1106695a7..1cc190cc45 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs @@ -10,23 +10,28 @@ namespace Microsoft.TestPlatform.Utilities.Tests; [TestClass] public class CommandLineUtilitiesTest { - private static void VerifyCommandLineSplitter(string commandLine, string[] expected) + [TestMethod] + [DataRow("", new string[] { })] + [DataRow(" /a:b ", new string[] { "/a:b" })] + [DataRow(""" + /param1 + /param2:value2 + /param3:"value with spaces" + """, new string[] { "/param1", "/param2:value2", "/param3:value with spaces" })] + [DataRow("""/param3 #comment""", new string[] { "/param3" })] + [DataRow(""" + /param3 #comment ends with newline \" \\ + /param4 + """, new string[] { "/param3", "/param4" })] + [DataRow("""/testadapterpath:"c:\Path" """, new string[] { @"/testadapterpath:c:\Path" })] + [DataRow("""/testadapterpath:"c:\Path" /logger:"trx" """, new string[] { @"/testadapterpath:c:\Path", "/logger:trx" })] + [DataRow("""/testadapterpath:"c:\Path" /logger:"trx" /diag:"log.txt" """, new string[] { @"/testadapterpath:c:\Path", "/logger:trx", "/diag:log.txt" })] + [DataRow("""/Tests:"Test(\"iCT 256\")" """, new string[] { """/Tests:Test("iCT 256")""" })] + public void VerifyCommandLineSplitter(string input, string[] expected) { - CommandLineUtilities.SplitCommandLineIntoArguments(commandLine, out var actual); + CommandLineUtilities.SplitCommandLineIntoArguments(input, out var actual); - Assert.AreEqual(expected.Length, actual.Length); - for (int i = 0; i < actual.Length; ++i) - { - Assert.AreEqual(expected[i], actual[i]); - } + CollectionAssert.AreEqual(expected, actual); } - [TestMethod] - public void TestCommandLineSplitter() - { - VerifyCommandLineSplitter("", []); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\"", [@"/testadapterpath:c:\Path"]); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\"", [@"/testadapterpath:c:\Path", "/logger:trx"]); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\" /diag:\"log.txt\"", [@"/testadapterpath:c:\Path", "/logger:trx", "/diag:log.txt"]); - } } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesWhitespaceRegressionTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesWhitespaceRegressionTests.cs new file mode 100644 index 0000000000..21031517c2 --- /dev/null +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesWhitespaceRegressionTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.Utilities.Tests; + +/// +/// Regression tests for CommandLineUtilities with multi-line and whitespace scenarios. +/// +[TestClass] +public class CommandLineUtilitiesWhitespaceRegressionTests +{ + // Regression test for #15304 — Answer file parsing + [TestMethod] + public void SplitCommandLineIntoArguments_MultiLineWithComments_ShouldParseCorrectly() + { + string input = "/param1\n# This is a comment\n/param2\n# Another comment\n/param3"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(3, args); + Assert.AreEqual("/param1", args[0]); + Assert.AreEqual("/param2", args[1]); + Assert.AreEqual("/param3", args[2]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_LeadingWhitespace_ShouldBeTrimmed() + { + string input = " /param1 /param2 "; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(2, args); + Assert.AreEqual("/param1", args[0]); + Assert.AreEqual("/param2", args[1]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_TabsAsWhitespace_ShouldSplit() + { + string input = "/param1\t/param2\t/param3"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(3, args); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_QuotedWithSpaces_ShouldKeepTogether() + { + string input = @"/filter:""Category=Unit Test"""; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual("/filter:Category=Unit Test", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_CommentWithHashInMiddle_ShouldTreatAsComment() + { + string input = "arg1 # comment with arg2"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.HasCount(1, args); + Assert.AreEqual("arg1", args[0]); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_MultipleBackslashesBeforeQuote_ShouldHandleCorrectly() + { + // Three backslashes before a quote: \\\" -> backslash + escaped quote + string input = """arg\\\\"value" """; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.IsNotEmpty(args); + } + + // Regression test for #15304 + [TestMethod] + public void SplitCommandLineIntoArguments_AllComments_ShouldReturnEmpty() + { + string input = "# Just a comment\n# Another comment"; + bool hadError = CommandLineUtilities.SplitCommandLineIntoArguments(input, out string[] args); + + Assert.IsFalse(hadError); + Assert.IsEmpty(args); + } +} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs index 897c96c87d..4e6276e557 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/InferRunSettingsHelperTests.cs @@ -12,8 +12,6 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MSTest.TestFramework.AssertExtensions; - using OMResources = Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources; namespace Microsoft.TestPlatform.Utilities.UnitTests; @@ -42,8 +40,8 @@ public void UpdateRunSettingsShouldThrowIfRunSettingsNodeDoesNotExist() Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(xmlDocument, Architecture.X86, Framework.DefaultFramework, "temp"); - Assert.That.Throws(action) - .WithMessage("An error occurred while loading the settings. Error: Could not find 'RunSettings' node.."); + var exception = Assert.ThrowsExactly(action); + Assert.AreEqual("An error occurred while loading the settings. Error: Could not find 'RunSettings' node..", exception.Message); } [TestMethod] @@ -54,8 +52,8 @@ public void UpdateRunSettingsShouldThrowIfPlatformNodeIsInvalid() Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(xmlDocument, Architecture.X86, Framework.DefaultFramework, "temp"); - Assert.That.Throws(action) - .WithMessage("An error occurred while loading the settings. Error: Invalid setting 'RunConfiguration'. Invalid value 'foo' specified for 'TargetPlatform'."); + var exception = Assert.ThrowsExactly(action); + Assert.AreEqual("An error occurred while loading the settings. Error: Invalid setting 'RunConfiguration'. Invalid value 'foo' specified for 'TargetPlatform'..", exception.Message); } [TestMethod] @@ -66,8 +64,8 @@ public void UpdateRunSettingsShouldThrowIfFrameworkNodeIsInvalid() Action action = () => InferRunSettingsHelper.UpdateRunSettingsWithUserProvidedSwitches(xmlDocument, Architecture.X86, Framework.DefaultFramework, "temp"); - Assert.That.Throws(action) - .WithMessage("An error occurred while loading the settings. Error: Invalid setting 'RunConfiguration'. Invalid value 'foo' specified for 'TargetFrameworkVersion'."); + var exception = Assert.ThrowsExactly(action); + Assert.AreEqual("An error occurred while loading the settings. Error: Invalid setting 'RunConfiguration'. Invalid value 'foo' specified for 'TargetFrameworkVersion'..", exception.Message); } [TestMethod] @@ -80,7 +78,7 @@ public void UpdateRunSettingsShouldUpdateWithPlatformSettings() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "X86"); + Assert.Contains("X86", xml); } [TestMethod] @@ -93,7 +91,7 @@ public void UpdateRunSettingsShouldUpdateWithFrameworkSettings() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, $"{Framework.DefaultFramework.Name}"); + Assert.Contains($"{Framework.DefaultFramework.Name}", xml); } [TestMethod] @@ -106,7 +104,7 @@ public void UpdateRunSettingsShouldUpdateWithResultsDirectorySettings() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "temp"); + Assert.Contains("temp", xml); } [TestMethod] @@ -119,7 +117,7 @@ public void UpdateRunSettingsShouldNotUpdatePlatformIfRunSettingsAlreadyHasIt() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "X86"); + Assert.Contains("X86", xml); } [TestMethod] @@ -132,7 +130,7 @@ public void UpdateRunSettingsShouldNotUpdateFrameworkIfRunSettingsAlreadyHasIt() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, ".NETFramework,Version=v4.0"); + Assert.Contains(".NETFramework,Version=v4.0", xml); } //TargetFrameworkMoniker @@ -147,7 +145,7 @@ public void UpdateRunSettingsShouldAllowTargetFrameworkMonikerValue() var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, ".NETFramework,Version=v4.0"); + Assert.Contains(".NETFramework,Version=v4.0", xml); } [TestMethod] @@ -160,7 +158,7 @@ public void UpdateRunSettingsShouldNotUpdateResultsDirectoryIfRunSettingsAlready var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "someplace"); + Assert.Contains("someplace", xml); } [TestMethod] @@ -173,9 +171,9 @@ public void UpdateRunSettingsShouldNotUpdatePlatformOrFrameworkOrResultsDirector var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "X86"); - StringAssert.Contains(xml, "Framework40"); - StringAssert.Contains(xml, "someplace"); + Assert.Contains("X86", xml); + Assert.Contains("Framework40", xml); + Assert.Contains("someplace", xml); } [TestMethod] @@ -188,9 +186,9 @@ public void UpdateRunSettingsWithAnEmptyRunSettingsShouldAddValuesSpecifiedInRun var xml = xmlDocument.OuterXml; - StringAssert.Contains(xml, "X64"); - StringAssert.Contains(xml, $"{Framework.DefaultFramework.Name}"); - StringAssert.Contains(xml, "temp"); + Assert.Contains("X64", xml); + Assert.Contains($"{Framework.DefaultFramework.Name}", xml); + Assert.Contains("temp", xml); } [TestMethod] @@ -220,7 +218,7 @@ public void UpdateDesignModeOrCsiShouldNotModifyXmlIfNodeIsAlreadyPresent() Assert.AreEqual("False", GetValueOf(xmlDocument, "/RunSettings/RunConfiguration/CollectSourceInformation")); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void UpdateDesignModeOrCsiShouldModifyXmlToValueProvided(bool val) @@ -242,7 +240,7 @@ public void MakeRunsettingsCompatibleShouldDeleteNewlyAddedRunConfigurationNode( var result = InferRunSettingsHelper.MakeRunsettingsCompatible(settings)!; - Assert.IsTrue(result.IndexOf("DesignMode", StringComparison.OrdinalIgnoreCase) < 0); + Assert.IsLessThan(0, result.IndexOf("DesignMode", StringComparison.OrdinalIgnoreCase)); } [TestMethod] @@ -265,14 +263,14 @@ public void MakeRunsettingsCompatibleShouldNotDeleteOldRunConfigurationNode() var result = InferRunSettingsHelper.MakeRunsettingsCompatible(settings)!; - Assert.IsTrue(result.IndexOf("TargetPlatform", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("TestAdaptersPaths", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("ResultsDirectory", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("SolutionDirectory", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("MaxCpuCount", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("DisableParallelization", StringComparison.OrdinalIgnoreCase) > 0); - Assert.IsTrue(result.IndexOf("DisableAppDomain", StringComparison.OrdinalIgnoreCase) > 0); + Assert.IsGreaterThan(0, result.IndexOf("TargetPlatform", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("TargetFrameworkVersion", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("TestAdaptersPaths", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("ResultsDirectory", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("SolutionDirectory", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("MaxCpuCount", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("DisableParallelization", StringComparison.OrdinalIgnoreCase)); + Assert.IsGreaterThan(0, result.IndexOf("DisableAppDomain", StringComparison.OrdinalIgnoreCase)); } [TestMethod] @@ -492,7 +490,7 @@ public void TryGetLegacySettingsForRunSettingsWithEmptyLegacySettingsShouldRetur "; Assert.IsTrue(InferRunSettingsHelper.TryGetLegacySettingElements(runSettingsXml, out Dictionary legacySettings)); - Assert.AreEqual(0, legacySettings.Count); + Assert.IsEmpty(legacySettings); } [TestMethod] @@ -521,7 +519,7 @@ public void TryGetLegacySettingsForRunSettingsWithValidLegacySettingsShouldRetur var expectedExecutionAttributes = "hostProcessPlatform, parallelTestCount"; Assert.IsTrue(InferRunSettingsHelper.TryGetLegacySettingElements(runSettingsXml, out Dictionary legacySettings)); - Assert.AreEqual(3, legacySettings.Count, "count does not match"); + Assert.HasCount(3, legacySettings, "count does not match"); Assert.AreEqual(expectedElements, legacySettings["Elements"]); Assert.AreEqual(expectedDeploymentAttributes, legacySettings["DeploymentAttributes"]); Assert.AreEqual(expectedExecutionAttributes, legacySettings["ExecutionAttributes"]); @@ -541,7 +539,7 @@ public void GetEnvironmentVariablesWithValidValuesInRunSettingsShouldReturnValid var envVars = InferRunSettingsHelper.GetEnvironmentVariables(runSettingsXml)!; - Assert.AreEqual(2, envVars.Count); + Assert.HasCount(2, envVars); Assert.AreEqual(@"C:\temp", envVars["RANDOM_PATH"]); Assert.AreEqual(@"C:\temp2", envVars["RANDOM_PATH2"]); } @@ -560,7 +558,7 @@ public void GetEnvironmentVariablesWithDuplicateEnvValuesInRunSettingsShouldRetu var envVars = InferRunSettingsHelper.GetEnvironmentVariables(runSettingsXml)!; - Assert.AreEqual(1, envVars.Count); + Assert.HasCount(1, envVars); Assert.AreEqual(@"C:\temp", envVars["RANDOM_PATH"]); } @@ -575,7 +573,7 @@ public void GetEnvironmentVariablesWithEmptyVariablesInRunSettingsShouldReturnEm "; var envVars = InferRunSettingsHelper.GetEnvironmentVariables(runSettingsXml)!; - Assert.AreEqual(0, envVars.Count); + Assert.IsEmpty(envVars); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs index 7b5e257b1d..d7b3e8baf0 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/MSTestSettingsUtilitiesTests.cs @@ -7,8 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MSTest.TestFramework.AssertExtensions; - namespace Microsoft.TestPlatform.Utilities.Tests; [TestClass] @@ -50,7 +48,8 @@ public void ImportShouldThrowIfNotLegacySettingsFile() MSTestSettingsUtilities.Import( "C:\\temp\\r.runsettings", xmlDocument); - Assert.That.Throws(action).WithMessage("Unexpected settings file specified."); + var exception = Assert.ThrowsExactly(action); + Assert.AreEqual("Unexpected settings file specified.", exception.Message); } [TestMethod] @@ -65,7 +64,8 @@ public void ImportShouldThrowIfDefaultRunSettingsIsIncorrect() MSTestSettingsUtilities.Import( "C:\\temp\\r.testsettings", xmlDocument); - Assert.That.Throws(action).WithMessage("Could not find 'RunSettings' node."); + var exception = Assert.ThrowsExactly(action); + Assert.AreEqual("Could not find 'RunSettings' node.", exception.Message); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.csproj b/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.csproj index 2ef1dbe427..86bbf395d8 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/Microsoft.TestPlatform.Utilities.UnitTests.csproj @@ -7,8 +7,8 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe Microsoft.TestPlatform.Utilities.UnitTests @@ -17,13 +17,6 @@ - - - - - - - Always diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/Program.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/Program.cs deleted file mode 100644 index 20f06189b8..0000000000 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Utilities.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs index 85bb55149b..d967672431 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/StringUtilitiesTests.cs @@ -29,8 +29,8 @@ public void SplitShouldReturnWhenStringDoesntContainSplitChar() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 1); - Assert.IsTrue(enumerable.First().Equals(data)); + Assert.HasCount(1, enumerable); + Assert.AreEqual(data, enumerable.First()); } [TestMethod] @@ -40,7 +40,7 @@ public void SplitShouldSplitWhenStringContainsSplitChar() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 2); + Assert.HasCount(2, enumerable); } [TestMethod] @@ -50,7 +50,7 @@ public void SplitShouldSplitWhenStringWithSplitCharStartEnd() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 4); + Assert.HasCount(4, enumerable); } [TestMethod] @@ -60,8 +60,8 @@ public void SplitShouldEscapeSplitCharWhenEscapedCharPresent() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 1); - Assert.IsTrue(enumerable.First().Equals("foo,bar")); + Assert.HasCount(1, enumerable); + Assert.AreEqual("foo,bar", enumerable.First()); } [TestMethod] @@ -70,8 +70,8 @@ public void SplitShouldEscapeSplitCharWhenEscapedNonEscapedCharPresent() var data = "foo\\,,bar"; var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 2); - Assert.IsTrue(enumerable.First().Equals("foo,")); + Assert.HasCount(2, enumerable); + Assert.AreEqual("foo,", enumerable.First()); } [TestMethod] @@ -81,7 +81,7 @@ public void SplitShouldSplitWhenOnlySplitCharPresent() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 2); + Assert.HasCount(2, enumerable); } [TestMethod] @@ -91,7 +91,7 @@ public void SplitShouldNotSplitWhenNoSplitCharPresent() var argsList = data.Tokenize(SplitChar, EscapeChar); var enumerable = argsList as string[] ?? argsList.ToArray(); - Assert.IsTrue(enumerable.Length == 1); + Assert.HasCount(1, enumerable); } private const char SplitChar = ','; diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs index 41c4f3c481..e03395b724 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/XmlUtilitiesTests.cs @@ -19,7 +19,7 @@ public class XmlUtilitiesTests [TestMethod] public void GetNodeXmlShouldThrowIfxmlDocumentIsNull() { - Assert.ThrowsException(() => XmlUtilities.GetNodeXml(null!, @"/RunSettings/RunConfiguration")); + Assert.ThrowsExactly(() => XmlUtilities.GetNodeXml(null!, @"/RunSettings/RunConfiguration")); } [TestMethod] @@ -28,7 +28,7 @@ public void GetNodeXmlShouldThrowIfXPathIsNull() var settingsXml = @""; var xmlDocument = GetXmlDocument(settingsXml); - Assert.ThrowsException(() => XmlUtilities.GetNodeXml(xmlDocument.CreateNavigator()!, null!)); + Assert.ThrowsExactly(() => XmlUtilities.GetNodeXml(xmlDocument.CreateNavigator()!, null!)); } [TestMethod] @@ -37,7 +37,7 @@ public void GetNodeXmlShouldThrowIfXPathIsInvalid() var settingsXml = @""; var xmlDocument = GetXmlDocument(settingsXml); - Assert.ThrowsException(() => XmlUtilities.GetNodeXml(xmlDocument.CreateNavigator()!, @"Rs\r")); + Assert.ThrowsExactly(() => XmlUtilities.GetNodeXml(xmlDocument.CreateNavigator()!, @"Rs\r")); } [TestMethod] diff --git a/test/SettingsMigrator.UnitTests/MigratorTests.cs b/test/SettingsMigrator.UnitTests/MigratorTests.cs index 7ee4dfc83b..bce464e8b4 100644 --- a/test/SettingsMigrator.UnitTests/MigratorTests.cs +++ b/test/SettingsMigrator.UnitTests/MigratorTests.cs @@ -93,7 +93,7 @@ public void MigratorGeneratesCorrectRunsettingsWithDc() Assert.IsNotNull(root); var dataCollectorNode = root.SelectNodes(@"/RunSettings/DataCollectionRunSettings/DataCollectors/DataCollector"); Assert.IsNotNull(dataCollectorNode); - Assert.AreEqual(2, dataCollectorNode.Count, "Data collector is missing"); + Assert.HasCount(2, dataCollectorNode, "Data collector is missing"); } [TestMethod] @@ -105,26 +105,33 @@ public void MigratorGeneratesCorrectRunsettingsForTestSettings() } [TestMethod] - [ExpectedException(typeof(XmlException))] public void InvalidSettingsThrowsException() { _oldTestsettingsPath = Path.Combine(Path.GetTempPath(), "oldTestsettings.testsettings"); + try + { + File.WriteAllText(_oldTestsettingsPath, InvalidSettings); + File.WriteAllText(_newRunsettingsPath, string.Empty); - File.WriteAllText(_oldTestsettingsPath, InvalidSettings); - File.WriteAllText(_newRunsettingsPath, string.Empty); - - _migrator.Migrate(_oldTestsettingsPath, _newRunsettingsPath); - - File.Delete(_oldTestsettingsPath); + Assert.ThrowsExactly(() => _migrator.Migrate(_oldTestsettingsPath, _newRunsettingsPath)); + } + finally + { + if (File.Exists(_oldRunsettingsPath)) + { + File.Delete(_oldRunsettingsPath); + } + } } [TestMethod] - [ExpectedException(typeof(DirectoryNotFoundException))] public void InvalidPathThrowsException() { string oldTestsettingsPath = @"X:\generatedRun,settings.runsettings"; - _migrator.Migrate(oldTestsettingsPath, _newRunsettingsPath); + // On some systems this throws file not found, on some it throws directory not found, + // I don't know why and it does not matter for the test. As long as it throws. + Assert.Throws(() => _migrator.Migrate(oldTestsettingsPath, _newRunsettingsPath)); } private static void Validate(string newRunsettingsPath) diff --git a/test/SettingsMigrator.UnitTests/PathResolverTests.cs b/test/SettingsMigrator.UnitTests/PathResolverTests.cs index 678865ddd4..82257bad9f 100644 --- a/test/SettingsMigrator.UnitTests/PathResolverTests.cs +++ b/test/SettingsMigrator.UnitTests/PathResolverTests.cs @@ -67,7 +67,7 @@ public void PathResolverShouldReturnRunsettingsPathOfSameLocationAsTestSettings( var newFilePath = _pathResolver.GetTargetPath(["C:\\asd.testsettings"]); Assert.IsNotNull(newFilePath, "File path should not be null."); Assert.IsTrue(string.Equals(Path.GetExtension(newFilePath), ".runsettings"), "File path should be .runsettings"); - Assert.IsTrue(newFilePath!.Contains("C:\\asd_"), "File should be of same name as testsettings"); + Assert.Contains("C:\\asd_", newFilePath!, "File should be of same name as testsettings"); var time = newFilePath.Substring(7, 19); Assert.IsTrue(DateTime.TryParseExact(time, "MM-dd-yyyy_hh-mm-ss", CultureInfo.CurrentCulture, DateTimeStyles.None, out _), "File name should have datetime"); } diff --git a/test/SettingsMigrator.UnitTests/SettingsMigrator.UnitTests.csproj b/test/SettingsMigrator.UnitTests/SettingsMigrator.UnitTests.csproj index e1eb198f26..03f7b9ec40 100644 --- a/test/SettingsMigrator.UnitTests/SettingsMigrator.UnitTests.csproj +++ b/test/SettingsMigrator.UnitTests/SettingsMigrator.UnitTests.csproj @@ -4,16 +4,18 @@ true true false + Exe - + net48 SettingsMigrator.UnitTests + diff --git a/test/TestAssets/AppDomainGetAssembliesTestProject/AppDomainGetAssembliesTestProject.csproj b/test/TestAssets/AppDomainGetAssembliesTestProject/AppDomainGetAssembliesTestProject.csproj index c342b0d39a..7571a78b2c 100644 --- a/test/TestAssets/AppDomainGetAssembliesTestProject/AppDomainGetAssembliesTestProject.csproj +++ b/test/TestAssets/AppDomainGetAssembliesTestProject/AppDomainGetAssembliesTestProject.csproj @@ -1,14 +1,14 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) false - - - + + + diff --git a/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj b/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj index 4330294307..ca790a13f9 100644 --- a/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj +++ b/test/TestAssets/ArchitectureSwitch/ArchitectureSwitch.csproj @@ -1,13 +1,12 @@ - net7.0;net6.0;net5.0 - net7.0;net6.0;netcoreapp3.1 + net8.0;net9.0 + net8.0;net9.0 false - - - - + + + diff --git a/test/TestAssets/ArchitectureSwitch/global.json b/test/TestAssets/ArchitectureSwitch/global.json deleted file mode 100644 index c8c7401e65..0000000000 --- a/test/TestAssets/ArchitectureSwitch/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "6.0.200-preview" - } -} \ No newline at end of file diff --git a/test/TestAssets/BlameUnitTestProject/BlameUnitTestProject.csproj b/test/TestAssets/BlameUnitTestProject/BlameUnitTestProject.csproj index cdefdc3c6f..53f5b27ac3 100644 --- a/test/TestAssets/BlameUnitTestProject/BlameUnitTestProject.csproj +++ b/test/TestAssets/BlameUnitTestProject/BlameUnitTestProject.csproj @@ -7,7 +7,7 @@ BlameUnitTestProject - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe @@ -15,10 +15,10 @@ + + - - - + diff --git a/test/TestAssets/CILProject/CILProject.proj b/test/TestAssets/CILProject/CILProject.proj index 294c781c9a..b1382122e7 100644 --- a/test/TestAssets/CILProject/CILProject.proj +++ b/test/TestAssets/CILProject/CILProject.proj @@ -1,7 +1,7 @@ $(TP_PACKAGES_DIR)\ - $(RepoRoot).packages\ + $(RepoRoot)artifacts\.packages\ diff --git a/test/TestAssets/CUITTestProject/CUITTestProject.csproj b/test/TestAssets/CUITTestProject/CUITTestProject.csproj deleted file mode 100644 index 44dffdbef0..0000000000 --- a/test/TestAssets/CUITTestProject/CUITTestProject.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - - true - true - - - - CUITTestProject - $(NetFrameworkMinimum) - Exe - - - - - ..\..\..\.packages\microsoft.visualstudio.cuit\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.QualityTools.CodedUITestFramework.dll - - - ..\..\..\.packages\microsoft.visualstudio.qualitytools\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - - - ..\..\..\.packages\microsoft.visualstudio.cuit\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.TestTools.UITest.Common.dll - - - ..\..\..\.packages\microsoft.visualstudio.cuit\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.TestTools.UITest.Extension.dll - - - ..\..\..\.packages\microsoft.visualstudio.cuit\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.TestTools.UITesting.dll - - - - - - - - $(TestPlatformExternalsVersion) - - - $(TestPlatformExternalsVersion) - - - - - - - diff --git a/test/TestAssets/CUITTestProject/CodedUITest1.cs b/test/TestAssets/CUITTestProject/CodedUITest1.cs deleted file mode 100644 index 808ac345d0..0000000000 --- a/test/TestAssets/CUITTestProject/CodedUITest1.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.VisualStudio.TestTools.UITesting; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace CodedUITestProject -{ - [CodedUITest] - public class CodedUITestProject - { - [TestMethod] - public void CodedUITestMethod1() - { - UITestControl.Desktop.DrawHighlight(); - } - } -} diff --git a/test/TestAssets/CodeCoverageTest/CodeCoverageTest.csproj b/test/TestAssets/CodeCoverageTest/CodeCoverageTest.csproj index 549fb66abc..e61c3e385b 100644 --- a/test/TestAssets/CodeCoverageTest/CodeCoverageTest.csproj +++ b/test/TestAssets/CodeCoverageTest/CodeCoverageTest.csproj @@ -3,7 +3,7 @@ CodeCoverageTest - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -17,12 +17,5 @@ $(PackageVersion) - - - - - - - - + diff --git a/test/TestAssets/CodeCoverageTest/UnitTest1.cs b/test/TestAssets/CodeCoverageTest/UnitTest1.cs index 6213c4c3ec..50bcef9587 100644 --- a/test/TestAssets/CodeCoverageTest/UnitTest1.cs +++ b/test/TestAssets/CodeCoverageTest/UnitTest1.cs @@ -18,17 +18,17 @@ public UnitTest1() [TestMethod] public void TestAbs() { - Assert.AreEqual(_logic.Abs(0), 0); - Assert.AreEqual(_logic.Abs(-5), 5); - Assert.AreEqual(_logic.Abs(7), 7); + Assert.AreEqual(0, _logic.Abs(0)); + Assert.AreEqual(5, _logic.Abs(-5)); + Assert.AreEqual(7, _logic.Abs(7)); } [TestMethod] public void TestSign() { - Assert.AreEqual(_logic.Sign(0), 0); - Assert.AreEqual(_logic.Sign(-5), -1); - Assert.AreEqual(_logic.Sign(7), 1); + Assert.AreEqual(0, _logic.Sign(0)); + Assert.AreEqual(-1, _logic.Sign(-5)); + Assert.AreEqual(1, _logic.Sign(7)); } [TestMethod] @@ -36,7 +36,7 @@ public void TestSign() public void __CxxPureMSILEntry_Test() #pragma warning restore IDE1006 // Naming Styles { - Assert.AreEqual(_logic.Abs(0), 0); + Assert.AreEqual(0, _logic.Abs(0)); } } } diff --git a/test/TestAssets/ConsoleManagedApp/ConsoleManagedApp.csproj b/test/TestAssets/ConsoleManagedApp/ConsoleManagedApp.csproj index a2788b9408..14d23d09f5 100644 --- a/test/TestAssets/ConsoleManagedApp/ConsoleManagedApp.csproj +++ b/test/TestAssets/ConsoleManagedApp/ConsoleManagedApp.csproj @@ -2,7 +2,7 @@ ConsoleManagedApp - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe diff --git a/test/TestAssets/ConsoleRunners/ConsoleRunners.csproj b/test/TestAssets/ConsoleRunners/ConsoleRunners.csproj index b64c2b099b..258f8fd0b7 100644 --- a/test/TestAssets/ConsoleRunners/ConsoleRunners.csproj +++ b/test/TestAssets/ConsoleRunners/ConsoleRunners.csproj @@ -1,7 +1,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) false diff --git a/test/TestAssets/Directory.Build.props b/test/TestAssets/Directory.Build.props index 372f9f3c91..e459a04c93 100644 --- a/test/TestAssets/Directory.Build.props +++ b/test/TestAssets/Directory.Build.props @@ -10,23 +10,32 @@ - + true $(NoWarn); - - MSB3270; - - NETSDK1138; - NU1603 + NU1603; + + NETSDK1057; + + + CA1837; + - net462 - netcoreapp3.1 + net8.0 + net462;net472;net48 + net8.0;net9.0;net10.0;net11.0 + $(NetFrameworkTargetFrameworks);$(NetCoreAppTargetFrameworks) Latest + + <_LocalVSTestPackagesDirectory>$(RepoRoot)artifacts/packages/$(Configuration)/Shipping + $(RestoreAdditionalProjectSources);$(_LocalVSTestPackagesDirectory) + + diff --git a/test/TestAssets/Directory.Build.targets b/test/TestAssets/Directory.Build.targets index fb8841991d..3595f737c5 100644 --- a/test/TestAssets/Directory.Build.targets +++ b/test/TestAssets/Directory.Build.targets @@ -3,4 +3,7 @@ + + $(Version)-dev + diff --git a/test/TestAssets/DisableAppdomainTest1/DisableAppdomainTest1.csproj b/test/TestAssets/DisableAppdomainTest1/DisableAppdomainTest1.csproj index 512e92b4e6..cbfa5cc848 100644 --- a/test/TestAssets/DisableAppdomainTest1/DisableAppdomainTest1.csproj +++ b/test/TestAssets/DisableAppdomainTest1/DisableAppdomainTest1.csproj @@ -8,26 +8,16 @@ DisableAppdomainTest1 - $(NetFrameworkMinimum) + $(NetFrameworkTargetFrameworks) Exe - - - - - - - - - - - + diff --git a/test/TestAssets/DisableAppdomainTest2/DisableAppdomainTest2.csproj b/test/TestAssets/DisableAppdomainTest2/DisableAppdomainTest2.csproj index da92a884e4..ad85a70c9f 100644 --- a/test/TestAssets/DisableAppdomainTest2/DisableAppdomainTest2.csproj +++ b/test/TestAssets/DisableAppdomainTest2/DisableAppdomainTest2.csproj @@ -8,25 +8,16 @@ DisableAppDomainTest2 - $(NetFrameworkMinimum) + $(NetFrameworkTargetFrameworks) Exe - - - - - - - - - - + diff --git a/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj b/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj index 454c931654..cd6571e3be 100644 --- a/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj +++ b/test/TestAssets/DiscoveryTestProject/DiscoveryTestProject.csproj @@ -3,7 +3,7 @@ DiscoveryTestProject - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) x64 @@ -12,11 +12,5 @@ - - - - - - - + diff --git a/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs b/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs index ea1516eac8..df65829b5c 100644 --- a/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs +++ b/test/TestAssets/DiscoveryTestProject/LongDiscoveryTestClass.cs @@ -3,6 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.CompilerServices; using System.Threading; namespace DiscoveryTestProject3 @@ -25,7 +26,7 @@ public void LongDiscoveryTestMethod() internal class TestMethodWithDelayAttribute : TestMethodAttribute { - public TestMethodWithDelayAttribute() + public TestMethodWithDelayAttribute([CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) : base(callerFilePath, callerLineNumber) { // This will be multiplied by 3 because the framework will internally create this // attribute 3 times. diff --git a/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj b/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj new file mode 100644 index 0000000000..e05f807a2d --- /dev/null +++ b/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj @@ -0,0 +1,74 @@ + + + + + + DtaLikeHost + net472 + Exe + + false + false + true + + false + false + + + + + + + + + <_TestPlatformToolsDir>$(PkgMicrosoft_TestPlatform)\tools\net462\Common7\IDE\Extensions\TestPlatform + + + + + <_TestPlatformToolsDir>$(TestPlatformToolsDirOverride) + + + + + + $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.Common.dll + true + + + $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + true + + + + + + + + + + + + \ No newline at end of file diff --git a/test/TestAssets/DtaLikeHost/Program.cs b/test/TestAssets/DtaLikeHost/Program.cs new file mode 100644 index 0000000000..7999bed8a5 --- /dev/null +++ b/test/TestAssets/DtaLikeHost/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + +namespace DtaLikeHost; + +internal static class Program +{ + private static int Main() + { + // Report what Common.dll expects and what we ship next to it, so the mismatch + // (or agreement) is visible in the console output regardless of whether the + // CLR actually fails to bind. + var commonAsm = typeof(FilterExpressionWrapper).Assembly; + Console.WriteLine($"Common.dll path: {commonAsm.Location}"); + Console.WriteLine($"Common.dll version: {commonAsm.GetName().Version}"); + foreach (var r in commonAsm.GetReferencedAssemblies()) + { + if (r.Name == "System.Collections.Immutable" || r.Name == "System.Reflection.Metadata") + { + Console.WriteLine($" Common.dll references {r.Name}, Version={r.Version}"); + } + } + + try + { + // A simple equality filter produces a FastFilter, which triggers + // FastFilter.Builder.ctor -> ImmutableDictionary.CreateBuilder(...) + // -> forces the CLR to resolve System.Collections.Immutable at the version + // baked into Common.dll's metadata. + var wrapper = new FilterExpressionWrapper("TestCategory=Foo"); + Console.WriteLine($"FilterExpressionWrapper constructed: FilterString='{wrapper.FilterString}', ParseError='{wrapper.ParseError}'"); + + // Reflect on the private FastFilter field to prove it was actually built. + var fastFilterField = typeof(FilterExpressionWrapper).GetField("FastFilter", BindingFlags.Instance | BindingFlags.NonPublic); + var fastFilter = fastFilterField?.GetValue(wrapper); + Console.WriteLine($"FastFilter built: {fastFilter is not null}"); + } + catch (Exception ex) + { + Console.Error.WriteLine("REPRO HIT: exception constructing FilterExpressionWrapper:"); + Console.Error.WriteLine(ex); + return 1; + } + + Console.WriteLine("OK - no binding exception."); + return 0; + } +} diff --git a/test/TestAssets/EnvironmentVariablesTestProject/EnvironmentVariablesTestProject.csproj b/test/TestAssets/EnvironmentVariablesTestProject/EnvironmentVariablesTestProject.csproj index f5490fcaa9..7571a78b2c 100644 --- a/test/TestAssets/EnvironmentVariablesTestProject/EnvironmentVariablesTestProject.csproj +++ b/test/TestAssets/EnvironmentVariablesTestProject/EnvironmentVariablesTestProject.csproj @@ -1,14 +1,14 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) false - - - + + + diff --git a/test/TestAssets/EventLogUnitTestProject/EventLogUnitTestProject.csproj b/test/TestAssets/EventLogUnitTestProject/EventLogUnitTestProject.csproj index dfec5464d0..da224fd31f 100644 --- a/test/TestAssets/EventLogUnitTestProject/EventLogUnitTestProject.csproj +++ b/test/TestAssets/EventLogUnitTestProject/EventLogUnitTestProject.csproj @@ -8,24 +8,16 @@ EventLogUnitTestProject - $(NetFrameworkMinimum) + $(NetFrameworkTargetFrameworks) Exe - - - - - - - - - + diff --git a/test/TestAssets/LegacySettingsUnitTestProject/DependencyAssembly/DependencyAssemblyForTest.dll b/test/TestAssets/LegacySettingsUnitTestProject/DependencyAssembly/DependencyAssemblyForTest.dll deleted file mode 100644 index 436cd2316c..0000000000 Binary files a/test/TestAssets/LegacySettingsUnitTestProject/DependencyAssembly/DependencyAssemblyForTest.dll and /dev/null differ diff --git a/test/TestAssets/LegacySettingsUnitTestProject/DeploymentFile.xml b/test/TestAssets/LegacySettingsUnitTestProject/DeploymentFile.xml deleted file mode 100644 index 42c1bdf977..0000000000 --- a/test/TestAssets/LegacySettingsUnitTestProject/DeploymentFile.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/TestAssets/LegacySettingsUnitTestProject/LegacySettingsUnitTestProject.csproj b/test/TestAssets/LegacySettingsUnitTestProject/LegacySettingsUnitTestProject.csproj deleted file mode 100644 index b5c73fc491..0000000000 --- a/test/TestAssets/LegacySettingsUnitTestProject/LegacySettingsUnitTestProject.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - true - true - - - library - $(NetFrameworkMinimum) - LegacySettingsUnitTestProject - - - - DependencyAssembly\DependencyAssemblyForTest.dll - false - - - - - - ..\..\..\.packages\microsoft.visualstudio.qualitytools\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - - - - - - - - - - - - - diff --git a/test/TestAssets/LegacySettingsUnitTestProject/UnitTest1.cs b/test/TestAssets/LegacySettingsUnitTestProject/UnitTest1.cs deleted file mode 100644 index 89490d09f3..0000000000 --- a/test/TestAssets/LegacySettingsUnitTestProject/UnitTest1.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.IO; -using System.Reflection; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace LegacySettingsUnitTestProject -{ - [TestClass] - public class UnitTest1 - { - /// - /// Test for scripts - /// - [TestMethod] - public void ScriptsTest() - { - // Setup script should create a dummyfile in temp directory - var scriptPath = Path.Combine(Path.GetTempPath() + "ScriptTestingFile.txt"); - Assert.IsTrue(File.Exists(scriptPath)); - } - - /// - /// Test for deployment item - /// - [TestMethod] - public void DeploymentItemTest() - { - // File exists check for deployment item and scripts - var deploymentFullPath = Path.Combine(Path.GetDirectoryName(typeof(UnitTest1).GetTypeInfo().Assembly.Location), "DeploymentFile.xml"); - Assert.IsTrue(File.Exists(deploymentFullPath)); - } - - /// - /// Runs for 1 seconds - /// - [TestMethod] - public void OneSecTimeTest() - { - System.Threading.Thread.Sleep(1000); - } - - /// - /// Runs for 3 seconds - /// - [TestMethod] - public void ThreeSecTimeTest() - { - System.Threading.Thread.Sleep(3000); - } - - /// - /// Has dependency on another dll, needs assembly resolution - /// - [TestMethod] - public void DependencyTest() - { - var unitTest = new DependencyAssemblyForTest.Class1(); - } - } -} diff --git a/test/TestAssets/MSTestProject1/MSTestProject1.csproj b/test/TestAssets/MSTestProject1/MSTestProject1.csproj index 9bd9e59217..f29079b02e 100644 --- a/test/TestAssets/MSTestProject1/MSTestProject1.csproj +++ b/test/TestAssets/MSTestProject1/MSTestProject1.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -16,9 +16,4 @@ $(PackageVersion) - - - - - diff --git a/test/TestAssets/MSTestProject1/UnitTest1.cs b/test/TestAssets/MSTestProject1/UnitTest1.cs index 17c1760f18..5e84dc6cec 100644 --- a/test/TestAssets/MSTestProject1/UnitTest1.cs +++ b/test/TestAssets/MSTestProject1/UnitTest1.cs @@ -3,6 +3,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +// Class level just because we want the tests to run in predictable order. +[assembly: Parallelize(Scope = ExecutionScope.ClassLevel, Workers = 0)] + namespace MSTestProject1; [TestClass] @@ -11,13 +14,12 @@ public class UnitTest1 [TestMethod] public void PassingTest() { - Assert.AreEqual(2, 2); } [TestMethod] public void FailingTest() { - Assert.AreEqual(2, 3); + Assert.Fail(); } [Ignore] diff --git a/test/TestAssets/MSTestProject2/MSTestProject2.csproj b/test/TestAssets/MSTestProject2/MSTestProject2.csproj index 9bd9e59217..f29079b02e 100644 --- a/test/TestAssets/MSTestProject2/MSTestProject2.csproj +++ b/test/TestAssets/MSTestProject2/MSTestProject2.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -16,9 +16,4 @@ $(PackageVersion) - - - - - diff --git a/test/TestAssets/MSTestProject2/UnitTest1.cs b/test/TestAssets/MSTestProject2/UnitTest1.cs index 1f6d2bf47e..1fdc0d6b62 100644 --- a/test/TestAssets/MSTestProject2/UnitTest1.cs +++ b/test/TestAssets/MSTestProject2/UnitTest1.cs @@ -3,6 +3,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +// Class level just because we want the tests to run in predictable order. +[assembly: Parallelize(Scope = ExecutionScope.ClassLevel, Workers = 0)] + namespace MSTestProject2; [TestClass] @@ -11,13 +14,12 @@ public class UnitTest1 [TestMethod] public void PassingTest() { - Assert.AreEqual(2, 2); } [TestMethod] public void FailingTest() { - Assert.AreEqual(2, 3); + Assert.Fail(); } [Ignore] diff --git a/test/TestAssets/MstestV1UnitTestProject/MstestV1UnitTestProject.csproj b/test/TestAssets/MstestV1UnitTestProject/MstestV1UnitTestProject.csproj index a4dc9f9322..7c79899f24 100644 --- a/test/TestAssets/MstestV1UnitTestProject/MstestV1UnitTestProject.csproj +++ b/test/TestAssets/MstestV1UnitTestProject/MstestV1UnitTestProject.csproj @@ -7,7 +7,7 @@ library - $(NetFrameworkMinimum) + $(NetFrameworkTargetFrameworks) MstestV1UnitTestProject @@ -15,7 +15,7 @@ - ..\..\..\.packages\microsoft.visualstudio.qualitytools\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll + $(RepoRoot)artifacts\.packages\microsoft.visualstudio.qualitytools\$(TestPlatformExternalsVersion)\tools\net451\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll @@ -28,8 +28,6 @@ PreserveNewest - - - + diff --git a/test/TestAssets/MultiHostTestExecutionProject/MultiHostTestExecutionProject.csproj b/test/TestAssets/MultiHostTestExecutionProject/MultiHostTestExecutionProject.csproj index 02776870c2..fb53ae06bf 100644 --- a/test/TestAssets/MultiHostTestExecutionProject/MultiHostTestExecutionProject.csproj +++ b/test/TestAssets/MultiHostTestExecutionProject/MultiHostTestExecutionProject.csproj @@ -1,7 +1,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -15,10 +15,5 @@ $(PackageVersion) - - - - - diff --git a/test/TestAssets/MultiHostTestExecutionProject/UnitTest1.cs b/test/TestAssets/MultiHostTestExecutionProject/UnitTest1.cs index fc3a3e6f4d..424fa1a28f 100644 --- a/test/TestAssets/MultiHostTestExecutionProject/UnitTest1.cs +++ b/test/TestAssets/MultiHostTestExecutionProject/UnitTest1.cs @@ -10,7 +10,7 @@ // Parallelize the execution [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] -namespace SerializeTestRunTestProject +namespace MultiHostTestExecutionProject { [TestClass] public class UnitTest1 @@ -22,6 +22,10 @@ private void LogToFile(string testName) lock (Lock) { string folderToLogTo = Environment.GetEnvironmentVariable("VSTEST_LOGFOLDER"); + if (string.IsNullOrWhiteSpace( folderToLogTo)) + { + throw new InvalidOperationException($"Path provided through \"VSTEST_LOGFOLDER\" is null or empty."); + } File.AppendAllText(Path.Combine(folderToLogTo, $"TestHost_{Process.GetCurrentProcess().Id}.txt"), testName + "\n"); } } diff --git a/test/TestAssets/MultitargetedNetFrameworkProject/MultitargetedNetFrameworkProject.csproj b/test/TestAssets/MultitargetedNetFrameworkProject/MultitargetedNetFrameworkProject.csproj index 11e097b528..025cce7d4a 100644 --- a/test/TestAssets/MultitargetedNetFrameworkProject/MultitargetedNetFrameworkProject.csproj +++ b/test/TestAssets/MultitargetedNetFrameworkProject/MultitargetedNetFrameworkProject.csproj @@ -2,13 +2,12 @@ - $(NetFrameworkMinimum);net47;net471;net472;net48 + $(NetFrameworkTargetFrameworks) true true - - TRACE + @@ -17,7 +16,5 @@ - - - + diff --git a/test/TestAssets/NUTestProject/NUTestProject.csproj b/test/TestAssets/NUTestProject/NUTestProject.csproj index 731bd2c81d..681b693efa 100644 --- a/test/TestAssets/NUTestProject/NUTestProject.csproj +++ b/test/TestAssets/NUTestProject/NUTestProject.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) NUTestProject diff --git a/test/TestAssets/NetStandard2Library/Class1.cs b/test/TestAssets/NetStandard2Library/Class1.cs index 7d5d17aa11..036fa82d3a 100644 --- a/test/TestAssets/NetStandard2Library/Class1.cs +++ b/test/TestAssets/NetStandard2Library/Class1.cs @@ -4,6 +4,7 @@ using System; namespace NetStandard2Library; + public class Class1 { diff --git a/test/TestAssets/NewtonSoftDependency/NewtonSoftDependency.csproj b/test/TestAssets/NewtonSoftDependency/NewtonSoftDependency.csproj index ea1f133060..0fa29171a1 100644 --- a/test/TestAssets/NewtonSoftDependency/NewtonSoftDependency.csproj +++ b/test/TestAssets/NewtonSoftDependency/NewtonSoftDependency.csproj @@ -8,27 +8,17 @@ NewtonSoftDependency - $(NetFrameworkMinimum) + $(NetFrameworkTargetFrameworks) Exe - - - - - - - - - + - - - + diff --git a/test/TestAssets/OutOfProcDataCollector/OutOfProcDataCollector.csproj b/test/TestAssets/OutOfProcDataCollector/OutOfProcDataCollector.csproj index 586f788a0c..ffa1e79bd9 100644 --- a/test/TestAssets/OutOfProcDataCollector/OutOfProcDataCollector.csproj +++ b/test/TestAssets/OutOfProcDataCollector/OutOfProcDataCollector.csproj @@ -1,12 +1,15 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + netstandard2.0 15.0.0.0 - 1.6.0 false + latest + enable - + + true + diff --git a/test/TestAssets/OutputtingTestProject/OutputtingTestProject.csproj b/test/TestAssets/OutputtingTestProject/OutputtingTestProject.csproj index 9d5a70e0fa..36e129c892 100644 --- a/test/TestAssets/OutputtingTestProject/OutputtingTestProject.csproj +++ b/test/TestAssets/OutputtingTestProject/OutputtingTestProject.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe x64 @@ -12,14 +12,5 @@ - - - - - - - - - - + diff --git a/test/TestAssets/ParametrizedTestProject/ParametrizedTestProject.csproj b/test/TestAssets/ParametrizedTestProject/ParametrizedTestProject.csproj index 0999951177..ff21f18b70 100644 --- a/test/TestAssets/ParametrizedTestProject/ParametrizedTestProject.csproj +++ b/test/TestAssets/ParametrizedTestProject/ParametrizedTestProject.csproj @@ -3,7 +3,7 @@ ParametrizedTestProject - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -17,12 +17,5 @@ $(PackageVersion) - - - - - - - - + diff --git a/test/TestAssets/PerfTestProject/PerfTestProject.csproj b/test/TestAssets/PerfTestProject/PerfTestProject.csproj index 1601a6e3d3..f86334f7aa 100644 --- a/test/TestAssets/PerfTestProject/PerfTestProject.csproj +++ b/test/TestAssets/PerfTestProject/PerfTestProject.csproj @@ -5,19 +5,16 @@ true - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe PerfTestProject - + portable - + full - - - diff --git a/test/TestAssets/ProjectFileRunSettingsTestProject/ProjectFileRunSettingsTestProject.csproj b/test/TestAssets/ProjectFileRunSettingsTestProject/ProjectFileRunSettingsTestProject.csproj index c44b84fd01..3402c17d45 100644 --- a/test/TestAssets/ProjectFileRunSettingsTestProject/ProjectFileRunSettingsTestProject.csproj +++ b/test/TestAssets/ProjectFileRunSettingsTestProject/ProjectFileRunSettingsTestProject.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) x64 fail.runsettings @@ -11,11 +11,4 @@ - - - - - - - diff --git a/test/TestAssets/QualityToolsAssets/BingWebTest/BingWebTest.csproj b/test/TestAssets/QualityToolsAssets/BingWebTest/BingWebTest.csproj index e9c0056a3f..248b264e3e 100644 --- a/test/TestAssets/QualityToolsAssets/BingWebTest/BingWebTest.csproj +++ b/test/TestAssets/QualityToolsAssets/BingWebTest/BingWebTest.csproj @@ -17,7 +17,6 @@ 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False true @@ -54,24 +53,6 @@ PreserveNewest - - - - - False - - - False - - - False - - - False - - - - - win10-x64 + win-x64 Exe diff --git a/test/TestAssets/SerializeTestRunTestProject/SerializeTestRunTestProject.csproj b/test/TestAssets/SerializeTestRunTestProject/SerializeTestRunTestProject.csproj index 02776870c2..d8d7def7ef 100644 --- a/test/TestAssets/SerializeTestRunTestProject/SerializeTestRunTestProject.csproj +++ b/test/TestAssets/SerializeTestRunTestProject/SerializeTestRunTestProject.csproj @@ -1,7 +1,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -15,10 +15,4 @@ $(PackageVersion) - - - - - - diff --git a/test/TestAssets/SimpleClassLibrary/SimpleClassLibrary.csproj b/test/TestAssets/SimpleClassLibrary/SimpleClassLibrary.csproj index 1501208e37..d70c903ffc 100644 --- a/test/TestAssets/SimpleClassLibrary/SimpleClassLibrary.csproj +++ b/test/TestAssets/SimpleClassLibrary/SimpleClassLibrary.csproj @@ -1,22 +1,7 @@ + - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) - - - - - - full - - - portable - - - full - - - portable - diff --git a/test/TestAssets/SimpleDataCollector/Class1.cs b/test/TestAssets/SimpleDataCollector/Class1.cs index 580e986450..094f0341ac 100644 --- a/test/TestAssets/SimpleDataCollector/Class1.cs +++ b/test/TestAssets/SimpleDataCollector/Class1.cs @@ -40,10 +40,8 @@ public void TestSessionStart(TestSessionStartArgs testSessionStartArgs) { Console.WriteLine(testSessionStartArgs.Configuration); File.WriteAllText(_fileName, "TestSessionStart : " + testSessionStartArgs.Configuration + "\r\n"); -#if NETFRAMEWORK var appDomainFilePath = Environment.GetEnvironmentVariable("TEST_ASSET_APPDOMAIN_COLLECTOR_PATH") ?? Path.Combine(Path.GetTempPath(), "appdomain_datacollector.txt"); File.WriteAllText(appDomainFilePath, "AppDomain FriendlyName: " + AppDomain.CurrentDomain.FriendlyName); -#endif } /// diff --git a/test/TestAssets/SimpleDataCollector/SimpleDataCollector.csproj b/test/TestAssets/SimpleDataCollector/SimpleDataCollector.csproj index 7f7262516d..effa009d16 100644 --- a/test/TestAssets/SimpleDataCollector/SimpleDataCollector.csproj +++ b/test/TestAssets/SimpleDataCollector/SimpleDataCollector.csproj @@ -4,7 +4,7 @@ 15.0.0.0 15.0.0.0 15.0.0.0 - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + netstandard2.0 true SimpleDataCollector TITestDllKey.snk @@ -14,9 +14,6 @@ false - - true - true diff --git a/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.cs b/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.cs new file mode 100644 index 0000000000..c8f281521c --- /dev/null +++ b/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace SimpleTestAdapter; + +/// +/// A minimal test adapter with no external dependencies beyond Microsoft.TestPlatform.ObjectModel. +/// Test methods are discovered by scanning for the [SimpleTest] attribute. This avoids MSTest +/// version compatibility issues when testing with older vstest.console versions. +/// +[FileExtension(".dll")] +[DefaultExecutorUri(ExecutorUri)] +[ExtensionUri(ExecutorUri)] +public class SimpleTestAdapter : ITestExecutor, ITestDiscoverer +{ + public const string ExecutorUri = "executor://simple.testadapter"; + + public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, + IMessageLogger logger, ITestCaseDiscoverySink discoverySink) + { + foreach (var source in sources) + { + foreach (var testCase in GetTestCases(source)) + { + discoverySink.SendTestCase(testCase); + } + } + } + + public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + foreach (var test in tests) + { + var result = RunTest(test); + frameworkHandle.RecordResult(result); + } + } + + public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle) + { + foreach (var source in sources) + { + foreach (var testCase in GetTestCases(source)) + { + var result = RunTest(testCase); + frameworkHandle.RecordResult(result); + } + } + } + + public void Cancel() + { + } + + private static IEnumerable GetTestCases(string source) + { + Assembly assembly; + try + { + assembly = Assembly.LoadFrom(source); + } + catch + { + yield break; + } + + foreach (var type in assembly.GetTypes()) + { + foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)) + { + var attr = method.GetCustomAttributes().FirstOrDefault(a => a.GetType().Name == nameof(SimpleTestAttribute)); + if (attr is null) + { + continue; + } + + yield return new TestCase($"{type.FullName}.{method.Name}", new Uri(ExecutorUri), source) + { + DisplayName = method.Name, + CodeFilePath = source, + }; + } + } + } + + private static TestResult RunTest(TestCase testCase) + { + try + { + var source = testCase.Source; + var assembly = Assembly.LoadFrom(source); + var fqn = testCase.FullyQualifiedName; + var lastDot = fqn.LastIndexOf('.'); + var typeName = fqn.Substring(0, lastDot); + var methodName = fqn.Substring(lastDot + 1); + + var type = assembly.GetType(typeName); + if (type is null) + { + return new TestResult(testCase) { Outcome = TestOutcome.NotFound }; + } + + var method = type.GetMethod(methodName); + if (method is null) + { + return new TestResult(testCase) { Outcome = TestOutcome.NotFound }; + } + + var instance = Activator.CreateInstance(type); + method.Invoke(instance, null); + return new TestResult(testCase) { Outcome = TestOutcome.Passed }; + } + catch (TargetInvocationException ex) when (ex.InnerException is SimpleTestFailException failEx) + { + return new TestResult(testCase) + { + Outcome = TestOutcome.Failed, + ErrorMessage = failEx.Message, + ErrorStackTrace = failEx.StackTrace, + }; + } + catch (Exception ex) + { + return new TestResult(testCase) + { + Outcome = TestOutcome.Failed, + ErrorMessage = ex.Message, + ErrorStackTrace = ex.StackTrace, + }; + } + } +} + +/// +/// Marks a method as a test method for . +/// +[AttributeUsage(AttributeTargets.Method)] +public class SimpleTestAttribute : Attribute +{ +} + +/// +/// Marks a test as expected to fail. +/// +public static class SimpleAssert +{ + public static void Fail(string message = "Test failed.") + => throw new SimpleTestFailException(message); +} + +/// +/// Exception thrown by to indicate an intentional test failure. +/// +public class SimpleTestFailException : Exception +{ + public SimpleTestFailException(string message) : base(message) { } +} diff --git a/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.csproj b/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.csproj new file mode 100644 index 0000000000..9f585892a3 --- /dev/null +++ b/test/TestAssets/SimpleTestAdapter/SimpleTestAdapter.csproj @@ -0,0 +1,11 @@ + + + + + netstandard2.0 + + + + + + diff --git a/test/TestAssets/SimpleTestProject/SimpleTestProject.csproj b/test/TestAssets/SimpleTestProject/SimpleTestProject.csproj index bfd19d5549..48471e9714 100644 --- a/test/TestAssets/SimpleTestProject/SimpleTestProject.csproj +++ b/test/TestAssets/SimpleTestProject/SimpleTestProject.csproj @@ -17,12 +17,5 @@ $(PackageVersion) - - - - - - - - + diff --git a/test/TestAssets/SimpleTestProject/UnitTest1.cs b/test/TestAssets/SimpleTestProject/UnitTest1.cs index 1496002aec..65452bdf94 100644 --- a/test/TestAssets/SimpleTestProject/UnitTest1.cs +++ b/test/TestAssets/SimpleTestProject/UnitTest1.cs @@ -22,7 +22,6 @@ public class UnitTest1 [TestMethod] public void PassingTest() { - Assert.AreEqual(2, 2); } /// @@ -38,7 +37,7 @@ public void FailingTest() var appDomainFilePath = Environment.GetEnvironmentVariable("TEST_ASSET_APPDOMAIN_TEST_PATH") ?? Path.Combine(Path.GetTempPath(), "appdomain_test.txt"); File.WriteAllText(appDomainFilePath, "AppDomain FriendlyName: " + AppDomain.CurrentDomain.FriendlyName); #endif - Assert.AreEqual(2, 3); + Assert.Fail(); } /// diff --git a/test/TestAssets/SimpleTestProject2/SimpleTestProject2.csproj b/test/TestAssets/SimpleTestProject2/SimpleTestProject2.csproj index 5747451e98..7cb2d1d5f5 100644 --- a/test/TestAssets/SimpleTestProject2/SimpleTestProject2.csproj +++ b/test/TestAssets/SimpleTestProject2/SimpleTestProject2.csproj @@ -8,7 +8,7 @@ SimpleTestProject2 - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe @@ -18,13 +18,11 @@ - - - + portable - + full diff --git a/test/TestAssets/SimpleTestProject2/UnitTest1.cs b/test/TestAssets/SimpleTestProject2/UnitTest1.cs index 4b790665a2..1f2e536319 100644 --- a/test/TestAssets/SimpleTestProject2/UnitTest1.cs +++ b/test/TestAssets/SimpleTestProject2/UnitTest1.cs @@ -17,7 +17,6 @@ public class UnitTest1 [TestMethod] public void PassingTest2() { - Assert.AreEqual(2, 2); } /// @@ -26,7 +25,7 @@ public void PassingTest2() [TestMethod] public void FailingTest2() { - Assert.AreEqual(2, 3); + Assert.Fail(); } /// diff --git a/test/TestAssets/SimpleTestProject3/SimpleTestProject3.csproj b/test/TestAssets/SimpleTestProject3/SimpleTestProject3.csproj index 9d5a70e0fa..43ac0b1e11 100644 --- a/test/TestAssets/SimpleTestProject3/SimpleTestProject3.csproj +++ b/test/TestAssets/SimpleTestProject3/SimpleTestProject3.csproj @@ -2,7 +2,7 @@ - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe x64 @@ -12,14 +12,7 @@ - - + - - - - - - diff --git a/test/TestAssets/SimpleTestProject4/SimpleTestProject4.csproj b/test/TestAssets/SimpleTestProject4/SimpleTestProject4.csproj new file mode 100644 index 0000000000..0cd6475d16 --- /dev/null +++ b/test/TestAssets/SimpleTestProject4/SimpleTestProject4.csproj @@ -0,0 +1,17 @@ + + + + + $(TestProjectTargetFrameworks) + + + + + $(PackageVersion) + + + + + + + diff --git a/test/TestAssets/SimpleTestProject4/UnitTest1.cs b/test/TestAssets/SimpleTestProject4/UnitTest1.cs new file mode 100644 index 0000000000..a747bb319d --- /dev/null +++ b/test/TestAssets/SimpleTestProject4/UnitTest1.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using SimpleTestAdapter; + +namespace SimpleTestProject4; + +public class UnitTest1 +{ + [SimpleTest] + public void PassingTest() + { + } + + [SimpleTest] + public void FailingTest() + { + SimpleAssert.Fail(); + } + + [SimpleTest] + public void AnotherPassingTest() + { + } +} diff --git a/test/TestAssets/SimpleTestProjectARM/SimpleTestProjectARM.csproj b/test/TestAssets/SimpleTestProjectARM64/SimpleTestProjectARM64.csproj similarity index 77% rename from test/TestAssets/SimpleTestProjectARM/SimpleTestProjectARM.csproj rename to test/TestAssets/SimpleTestProjectARM64/SimpleTestProjectARM64.csproj index e2696f70d3..ec15c8b2ce 100644 --- a/test/TestAssets/SimpleTestProjectARM/SimpleTestProjectARM.csproj +++ b/test/TestAssets/SimpleTestProjectARM64/SimpleTestProjectARM64.csproj @@ -4,12 +4,11 @@ true true - ARM + ARM64 - SimpleTestProjectARM - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe diff --git a/test/TestAssets/SimpleTestProjectARM/UnitTest1.cs b/test/TestAssets/SimpleTestProjectARM64/UnitTest1.cs similarity index 100% rename from test/TestAssets/SimpleTestProjectARM/UnitTest1.cs rename to test/TestAssets/SimpleTestProjectARM64/UnitTest1.cs diff --git a/test/TestAssets/SimpleTestProjectx86/SimpleTestProjectx86.csproj b/test/TestAssets/SimpleTestProjectx86/SimpleTestProjectx86.csproj index 398c3d908d..d075e21033 100644 --- a/test/TestAssets/SimpleTestProjectx86/SimpleTestProjectx86.csproj +++ b/test/TestAssets/SimpleTestProjectx86/SimpleTestProjectx86.csproj @@ -9,7 +9,7 @@ SimpleTestProjectx86 - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) Exe diff --git a/test/TestAssets/SimpleTestProjectx86/UnitTest1.cs b/test/TestAssets/SimpleTestProjectx86/UnitTest1.cs index 4e405dace5..64086ca053 100644 --- a/test/TestAssets/SimpleTestProjectx86/UnitTest1.cs +++ b/test/TestAssets/SimpleTestProjectx86/UnitTest1.cs @@ -17,7 +17,6 @@ public class UnitTest1 [TestMethod] public void PassingTestx86() { - Assert.AreEqual(2, 2); } } } diff --git a/test/TestAssets/SpecialCharOutputProject/SpecialCharOutputProject.csproj b/test/TestAssets/SpecialCharOutputProject/SpecialCharOutputProject.csproj new file mode 100644 index 0000000000..36e129c892 --- /dev/null +++ b/test/TestAssets/SpecialCharOutputProject/SpecialCharOutputProject.csproj @@ -0,0 +1,16 @@ + + + + + $(TestProjectTargetFrameworks) + Exe + x64 + + + + + + + + + diff --git a/test/TestAssets/SpecialCharOutputProject/UnitTest1.cs b/test/TestAssets/SpecialCharOutputProject/UnitTest1.cs new file mode 100644 index 0000000000..d60015e860 --- /dev/null +++ b/test/TestAssets/SpecialCharOutputProject/UnitTest1.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SpecialCharOutputProject; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestWithSpecialCharOutput() + { + // Write invalid XML character U+FFFF to test output. Before the GH-3136 fix, + // the HTML logger would throw XmlException when processing this character. + using StreamWriter writer = new StreamWriter(Console.OpenStandardOutput()); + writer.WriteLine("Special: \uFFFE \uFFFF"); + } +} diff --git a/test/TestAssets/StderrOutputProject/StderrOutputProject.csproj b/test/TestAssets/StderrOutputProject/StderrOutputProject.csproj new file mode 100644 index 0000000000..36e129c892 --- /dev/null +++ b/test/TestAssets/StderrOutputProject/StderrOutputProject.csproj @@ -0,0 +1,16 @@ + + + + + $(TestProjectTargetFrameworks) + Exe + x64 + + + + + + + + + diff --git a/test/TestAssets/StderrOutputProject/UnitTest1.cs b/test/TestAssets/StderrOutputProject/UnitTest1.cs new file mode 100644 index 0000000000..475d25d9c0 --- /dev/null +++ b/test/TestAssets/StderrOutputProject/UnitTest1.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace StderrOutputProject; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void PassingTestThatWritesToStderr() + { + Console.Error.WriteLine("debug info on stderr"); + + // Use a non-constant expression so the MSTEST0032 analyzer does not flag this. + var result = "pass"; + Assert.AreEqual("pass", result); + } +} diff --git a/test/TestAssets/TerminalLoggerTestProject/TerminalLoggerTestProject.csproj b/test/TestAssets/TerminalLoggerTestProject/TerminalLoggerTestProject.csproj index bfd19d5549..f7b27bdfb2 100644 --- a/test/TestAssets/TerminalLoggerTestProject/TerminalLoggerTestProject.csproj +++ b/test/TestAssets/TerminalLoggerTestProject/TerminalLoggerTestProject.csproj @@ -3,7 +3,7 @@ SimpleTestProject - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) @@ -17,12 +17,4 @@ $(PackageVersion) - - - - - - - - diff --git a/test/TestAssets/TerminalLoggerTestProject/UnitTest1.cs b/test/TestAssets/TerminalLoggerTestProject/UnitTest1.cs index 1d4b51287a..80c9d6e06d 100644 --- a/test/TestAssets/TerminalLoggerTestProject/UnitTest1.cs +++ b/test/TestAssets/TerminalLoggerTestProject/UnitTest1.cs @@ -21,7 +21,6 @@ public class UnitTest1 [TestMethod] public void PassingTest() { - Assert.AreEqual(2, 2); } /// @@ -31,7 +30,9 @@ public void PassingTest() public void FailingTest() { // test characters taken from https://pages.ucsd.edu/~dkjordan/chin/unitestuni.html +#pragma warning disable MSTEST0025 // Use 'Assert.Fail' instead of an always-failing assert Assert.AreEqual("ğğğ𦮙我們剛才從𓋴𓅓𓏏𓇏𓇌𓀀", "not the same"); +#pragma warning restore MSTEST0025 // Use 'Assert.Fail' instead of an always-failing assert } /// diff --git a/test/TestAssets/TestAssets.sln b/test/TestAssets/TestAssets.sln deleted file mode 100644 index 7556f991e1..0000000000 --- a/test/TestAssets/TestAssets.sln +++ /dev/null @@ -1,417 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32024.52 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit10kPassing", "performance\NUnit10kPassing\NUnit10kPassing.csproj", "{F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnit10kPassing", "performance\XUnit10kPassing\XUnit10kPassing.csproj", "{C1A621CF-8FA8-437C-98E8-C6C6418FEBEF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest10kPassing", "performance\MSTest10kPassing\MSTest10kPassing.csproj", "{52CAF89F-2309-4597-B531-79D6A96902BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUTestProject", "NUTestProject\NUTestProject.csproj", "{1ADE5795-2365-4790-8ACB-2EF0C2613D61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlameUnitTestProject", "BlameUnitTestProject\BlameUnitTestProject.csproj", "{29294E06-3998-4FF4-910F-EE93A915C3A1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUTestProject", "XUTestProject\XUTestProject.csproj", "{C8AB532E-28E9-4C5E-9F2D-06B6690FCB72}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProject", "SimpleTestProject\SimpleTestProject.csproj", "{7E79BDC2-49BA-403A-BE07-212C463A279B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleDataCollector", "SimpleDataCollector\SimpleDataCollector.csproj", "{E0042DCD-0C90-4736-B673-BC6CBDA04834}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProject2", "SimpleTestProject2\SimpleTestProject2.csproj", "{2B0F911C-5864-4EF7-A1F4-6923F7963D74}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutOfProcDataCollector", "OutOfProcDataCollector\OutOfProcDataCollector.csproj", "{1266AB9D-94D9-496D-8AE5-73612D097A09}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleRunners", "ConsoleRunners\ConsoleRunners.csproj", "{FBDB7A61-C1DB-4B24-B568-DA96AAD0DB8A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MstestV1UnitTestProject", "MstestV1UnitTestProject\MstestV1UnitTestProject.csproj", "{E28DD78A-E4C1-48C1-B027-88576F669C73}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProjectx86", "SimpleTestProjectx86\SimpleTestProjectx86.csproj", "{C4369F97-5D81-4D1A-BAE1-2AE3B2408D44}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProjectARM", "SimpleTestProjectARM\SimpleTestProjectARM.csproj", "{7C865EAA-C6C2-4CAF-A6AD-D9CF29577A36}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUITTestProject", "CUITTestProject\CUITTestProject.csproj", "{CF46C8A0-E9FA-40E9-96CA-DCD3797546D8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisableAppdomainTest1", "DisableAppdomainTest1\DisableAppdomainTest1.csproj", "{36C7990F-0A36-47CE-8E10-7887D24E2F9A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisableAppdomainTest2", "DisableAppdomainTest2\DisableAppdomainTest2.csproj", "{A09B21CC-F726-413A-B185-3AE1172BAED0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleManagedApp", "ConsoleManagedApp\ConsoleManagedApp.csproj", "{132E4690-DE43-4684-BA05-6942155EEAB5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LegacySettingsUnitTestProject", "LegacySettingsUnitTestProject\LegacySettingsUnitTestProject.csproj", "{8C38E692-FBE3-41A4-A008-4CA79B203985}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewtonSoftDependency", "NewtonSoftDependency\NewtonSoftDependency.csproj", "{79EDA259-5EA0-45F0-990A-F078427E198A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDomainGetAssembliesTestProject", "AppDomainGetAssembliesTestProject\AppDomainGetAssembliesTestProject.csproj", "{BF090BCE-CC7D-4359-93E2-30F2B454F751}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariablesTestProject", "EnvironmentVariablesTestProject\EnvironmentVariablesTestProject.csproj", "{BA53C202-55D6-4BBC-A24A-444B2D5F6309}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectFileRunSettingsTestProject", "ProjectFileRunSettingsTestProject\ProjectFileRunSettingsTestProject.csproj", "{A0113409-56D3-4060-BD94-A4BA4739712D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CrashingOnDebugAssertTestProject", "CrashingOnDebugAssertTestProject\CrashingOnDebugAssertTestProject.csproj", "{7A04F7AC-09E4-426C-A599-110DFA693200}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DCD7C4DA-B8CC-46D0-AA21-1340DD1EB5ED}" - ProjectSection(SolutionItems) = preProject - NuGet.config = NuGet.config - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProject3", "SimpleTestProject3\SimpleTestProject3.csproj", "{1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTestProject", "DiscoveryTestProject\DiscoveryTestProject.csproj", "{4BAA0E81-FB31-4BAB-BD75-0CA315245BD8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventLogUnitTestProject", "EventLogUnitTestProject\EventLogUnitTestProject.csproj", "{40DA6965-C3C3-46BC-BA56-6D457C097F3C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleClassLibrary", "SimpleClassLibrary\SimpleClassLibrary.csproj", "{F37144D7-2C6D-42AF-9E85-EF10E5244A7B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfContainedAppTestProject", "SelfContainedAppTestProject\SelfContainedAppTestProject.csproj", "{7F85E9D0-7BCE-431D-B468-4F6104E52DFA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoverletCoverageTestProject", "CoverletCoverageTestProject\CoverletCoverageTestProject.csproj", "{0D4D59D7-C52F-4858-A220-EAC7484E3827}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeCoverageTest", "CodeCoverageTest\CodeCoverageTest.csproj", "{23790570-66D2-4CD5-9CD0-F8787C5B61BF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultitargetedNetFrameworkProject", "MultitargetedNetFrameworkProject\MultitargetedNetFrameworkProject.csproj", "{E2F1E03B-002A-4A3E-BAFC-8F2F0AB5B86F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "child-crash", "child-crash\child-crash.csproj", "{A716ED69-FBC8-4E3F-B728-589873A1540D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "child-hang", "child-hang\child-hang.csproj", "{F262678B-7CD8-43DD-B752-4E761BE9A0A3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "crash", "crash\crash.csproj", "{CA159C14-FCE9-4D65-815C-F5D7D79583DE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hanging-child", "hanging-child\hanging-child.csproj", "{4393D63A-0C36-42F3-98A9-81440F213640}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "timeout", "timeout\timeout.csproj", "{135B06DB-1A0E-4DC3-93DA-8649AE3BDE19}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "problematic-child", "problematic-child\problematic-child.csproj", "{5FB9DFB8-2453-40EA-AAAD-D44677BCDF50}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParametrizedTestProject", "ParametrizedTestProject\ParametrizedTestProject.csproj", "{CA82D2B3-CCF7-4F68-8840-286BA2D5B23B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttachmentProcessorDataCollector", "AttachmentProcessorDataCollector\AttachmentProcessorDataCollector.csproj", "{16F51720-29D0-472A-93FA-2604D61991B7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchitectureSwitch", "ArchitectureSwitch\ArchitectureSwitch.csproj", "{452352E1-71CA-436E-8165-F284EE36C924}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProjectMessedUpTargetFramework", "SimpleTestProjectMessedUpTargetFramework\SimpleTestProjectMessedUpTargetFramework.csproj", "{08C44607-EB80-4EE5-927D-08C34AA277AF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{0C9CA869-32FD-4A9E-8885-E2E19786C746}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest1Passing", "performance\MSTest1Passing\MSTest1Passing.csproj", "{3F23276F-F7D6-4E50-AA43-8DDB3EF1F680}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest100Passing", "performance\MSTest100Passing\MSTest100Passing.csproj", "{027CADBF-7071-482A-8D86-CA03AC31FC4D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest1000Passing", "performance\MSTest1000Passing\MSTest1000Passing.csproj", "{B0ECF78C-71A7-4281-B81E-7BD09649E5EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit1Passing", "performance\NUnit1Passing\NUnit1Passing.csproj", "{EF157AEA-43B8-4923-9E86-BB134F17F2F0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit100Passing", "performance\NUnit100Passing\NUnit100Passing.csproj", "{76EFDDEC-4AD2-4B12-8EC9-440190B89418}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit1000Passing", "performance\NUnit1000Passing\NUnit1000Passing.csproj", "{75A46D17-91D6-430C-A8A8-C2D2E8C9943C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnit1Passing", "performance\XUnit1Passing\XUnit1Passing.csproj", "{B2F74D8D-9D16-493C-A716-B58426074075}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnit100Passing", "performance\XUnit100Passing\XUnit100Passing.csproj", "{06724523-611A-4054-A1AC-29DA2E87DD87}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnit1000Passing", "performance\XUnit1000Passing\XUnit1000Passing.csproj", "{15DC879D-1A60-4745-8DF4-EBC36EE12339}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTestProject2", "MSTestProject2\MSTestProject2.csproj", "{10AA955C-B412-41A8-899F-8609AAE19F61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTestProject1", "MSTestProject1\MSTestProject1.csproj", "{E166D337-4033-4209-863F-8F77675EAEE8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic", "basic", "{2633D125-64A7-456C-AD37-F8A6B56C2403}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tools", "Tools\Tools.csproj", "{85F9F2A8-D2A5-4EA1-8BE0-06CCE141EC7A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perfy.TestAdapter", "performance\Perfy.TestAdapter\Perfy.TestAdapter.csproj", "{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecursiveResourceLookupCrash", "RecursiveResourceLookupCrash\RecursiveResourceLookupCrash.csproj", "{9B5BDF7D-5816-47C6-8CFD-E45B152CA35F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerializeTestRunTestProject", "SerializeTestRunTestProject\SerializeTestRunTestProject.csproj", "{64C4C4CD-2319-4033-9F33-53AAA8FECEFA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiHostTestExecutionProject", "MultiHostTestExecutionProject\MultiHostTestExecutionProject.csproj", "{CE6673DA-B50F-46DF-99A3-8A7C54DE9B61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonDll.TestAdapter", "NonDll.TestAdapter\NonDll.TestAdapter.csproj", "{429552A4-4C18-4355-94C5-80DC88C48405}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandard2Library", "NetStandard2Library\NetStandard2Library.csproj", "{080F0AD2-E7AE-42C8-B867-56D78576735D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalLoggerTestProject", "TerminalLoggerTestProject\TerminalLoggerTestProject.csproj", "{C69BEEA4-BE37-4155-8DD0-130C62D8BF6D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutputtingTestProject", "OutputtingTestProject\OutputtingTestProject.csproj", "{A83CE873-2536-4795-B601-5816294ED0B8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4}.Release|Any CPU.Build.0 = Release|Any CPU - {C1A621CF-8FA8-437C-98E8-C6C6418FEBEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1A621CF-8FA8-437C-98E8-C6C6418FEBEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1A621CF-8FA8-437C-98E8-C6C6418FEBEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1A621CF-8FA8-437C-98E8-C6C6418FEBEF}.Release|Any CPU.Build.0 = Release|Any CPU - {52CAF89F-2309-4597-B531-79D6A96902BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52CAF89F-2309-4597-B531-79D6A96902BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52CAF89F-2309-4597-B531-79D6A96902BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52CAF89F-2309-4597-B531-79D6A96902BE}.Release|Any CPU.Build.0 = Release|Any CPU - {1ADE5795-2365-4790-8ACB-2EF0C2613D61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1ADE5795-2365-4790-8ACB-2EF0C2613D61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1ADE5795-2365-4790-8ACB-2EF0C2613D61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1ADE5795-2365-4790-8ACB-2EF0C2613D61}.Release|Any CPU.Build.0 = Release|Any CPU - {29294E06-3998-4FF4-910F-EE93A915C3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29294E06-3998-4FF4-910F-EE93A915C3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29294E06-3998-4FF4-910F-EE93A915C3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29294E06-3998-4FF4-910F-EE93A915C3A1}.Release|Any CPU.Build.0 = Release|Any CPU - {C8AB532E-28E9-4C5E-9F2D-06B6690FCB72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C8AB532E-28E9-4C5E-9F2D-06B6690FCB72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C8AB532E-28E9-4C5E-9F2D-06B6690FCB72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C8AB532E-28E9-4C5E-9F2D-06B6690FCB72}.Release|Any CPU.Build.0 = Release|Any CPU - {7E79BDC2-49BA-403A-BE07-212C463A279B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E79BDC2-49BA-403A-BE07-212C463A279B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E79BDC2-49BA-403A-BE07-212C463A279B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E79BDC2-49BA-403A-BE07-212C463A279B}.Release|Any CPU.Build.0 = Release|Any CPU - {E0042DCD-0C90-4736-B673-BC6CBDA04834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0042DCD-0C90-4736-B673-BC6CBDA04834}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0042DCD-0C90-4736-B673-BC6CBDA04834}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0042DCD-0C90-4736-B673-BC6CBDA04834}.Release|Any CPU.Build.0 = Release|Any CPU - {2B0F911C-5864-4EF7-A1F4-6923F7963D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B0F911C-5864-4EF7-A1F4-6923F7963D74}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B0F911C-5864-4EF7-A1F4-6923F7963D74}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B0F911C-5864-4EF7-A1F4-6923F7963D74}.Release|Any CPU.Build.0 = Release|Any CPU - {1266AB9D-94D9-496D-8AE5-73612D097A09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1266AB9D-94D9-496D-8AE5-73612D097A09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1266AB9D-94D9-496D-8AE5-73612D097A09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1266AB9D-94D9-496D-8AE5-73612D097A09}.Release|Any CPU.Build.0 = Release|Any CPU - {FBDB7A61-C1DB-4B24-B568-DA96AAD0DB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBDB7A61-C1DB-4B24-B568-DA96AAD0DB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBDB7A61-C1DB-4B24-B568-DA96AAD0DB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBDB7A61-C1DB-4B24-B568-DA96AAD0DB8A}.Release|Any CPU.Build.0 = Release|Any CPU - {E28DD78A-E4C1-48C1-B027-88576F669C73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E28DD78A-E4C1-48C1-B027-88576F669C73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E28DD78A-E4C1-48C1-B027-88576F669C73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E28DD78A-E4C1-48C1-B027-88576F669C73}.Release|Any CPU.Build.0 = Release|Any CPU - {C4369F97-5D81-4D1A-BAE1-2AE3B2408D44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4369F97-5D81-4D1A-BAE1-2AE3B2408D44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4369F97-5D81-4D1A-BAE1-2AE3B2408D44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4369F97-5D81-4D1A-BAE1-2AE3B2408D44}.Release|Any CPU.Build.0 = Release|Any CPU - {7C865EAA-C6C2-4CAF-A6AD-D9CF29577A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C865EAA-C6C2-4CAF-A6AD-D9CF29577A36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C865EAA-C6C2-4CAF-A6AD-D9CF29577A36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C865EAA-C6C2-4CAF-A6AD-D9CF29577A36}.Release|Any CPU.Build.0 = Release|Any CPU - {CF46C8A0-E9FA-40E9-96CA-DCD3797546D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF46C8A0-E9FA-40E9-96CA-DCD3797546D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF46C8A0-E9FA-40E9-96CA-DCD3797546D8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF46C8A0-E9FA-40E9-96CA-DCD3797546D8}.Release|Any CPU.Build.0 = Release|Any CPU - {36C7990F-0A36-47CE-8E10-7887D24E2F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {36C7990F-0A36-47CE-8E10-7887D24E2F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {36C7990F-0A36-47CE-8E10-7887D24E2F9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {36C7990F-0A36-47CE-8E10-7887D24E2F9A}.Release|Any CPU.Build.0 = Release|Any CPU - {A09B21CC-F726-413A-B185-3AE1172BAED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A09B21CC-F726-413A-B185-3AE1172BAED0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A09B21CC-F726-413A-B185-3AE1172BAED0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A09B21CC-F726-413A-B185-3AE1172BAED0}.Release|Any CPU.Build.0 = Release|Any CPU - {132E4690-DE43-4684-BA05-6942155EEAB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {132E4690-DE43-4684-BA05-6942155EEAB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {132E4690-DE43-4684-BA05-6942155EEAB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {132E4690-DE43-4684-BA05-6942155EEAB5}.Release|Any CPU.Build.0 = Release|Any CPU - {8C38E692-FBE3-41A4-A008-4CA79B203985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C38E692-FBE3-41A4-A008-4CA79B203985}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C38E692-FBE3-41A4-A008-4CA79B203985}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C38E692-FBE3-41A4-A008-4CA79B203985}.Release|Any CPU.Build.0 = Release|Any CPU - {79EDA259-5EA0-45F0-990A-F078427E198A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79EDA259-5EA0-45F0-990A-F078427E198A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79EDA259-5EA0-45F0-990A-F078427E198A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79EDA259-5EA0-45F0-990A-F078427E198A}.Release|Any CPU.Build.0 = Release|Any CPU - {BF090BCE-CC7D-4359-93E2-30F2B454F751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF090BCE-CC7D-4359-93E2-30F2B454F751}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF090BCE-CC7D-4359-93E2-30F2B454F751}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF090BCE-CC7D-4359-93E2-30F2B454F751}.Release|Any CPU.Build.0 = Release|Any CPU - {BA53C202-55D6-4BBC-A24A-444B2D5F6309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BA53C202-55D6-4BBC-A24A-444B2D5F6309}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BA53C202-55D6-4BBC-A24A-444B2D5F6309}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BA53C202-55D6-4BBC-A24A-444B2D5F6309}.Release|Any CPU.Build.0 = Release|Any CPU - {A0113409-56D3-4060-BD94-A4BA4739712D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0113409-56D3-4060-BD94-A4BA4739712D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0113409-56D3-4060-BD94-A4BA4739712D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0113409-56D3-4060-BD94-A4BA4739712D}.Release|Any CPU.Build.0 = Release|Any CPU - {7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A04F7AC-09E4-426C-A599-110DFA693200}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A04F7AC-09E4-426C-A599-110DFA693200}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A04F7AC-09E4-426C-A599-110DFA693200}.Release|Any CPU.Build.0 = Release|Any CPU - {1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FF723F6-3A09-41F6-B85C-C4BE9C4374F0}.Release|Any CPU.Build.0 = Release|Any CPU - {4BAA0E81-FB31-4BAB-BD75-0CA315245BD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BAA0E81-FB31-4BAB-BD75-0CA315245BD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BAA0E81-FB31-4BAB-BD75-0CA315245BD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BAA0E81-FB31-4BAB-BD75-0CA315245BD8}.Release|Any CPU.Build.0 = Release|Any CPU - {40DA6965-C3C3-46BC-BA56-6D457C097F3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40DA6965-C3C3-46BC-BA56-6D457C097F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40DA6965-C3C3-46BC-BA56-6D457C097F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40DA6965-C3C3-46BC-BA56-6D457C097F3C}.Release|Any CPU.Build.0 = Release|Any CPU - {F37144D7-2C6D-42AF-9E85-EF10E5244A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F37144D7-2C6D-42AF-9E85-EF10E5244A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F37144D7-2C6D-42AF-9E85-EF10E5244A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F37144D7-2C6D-42AF-9E85-EF10E5244A7B}.Release|Any CPU.Build.0 = Release|Any CPU - {7F85E9D0-7BCE-431D-B468-4F6104E52DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F85E9D0-7BCE-431D-B468-4F6104E52DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F85E9D0-7BCE-431D-B468-4F6104E52DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F85E9D0-7BCE-431D-B468-4F6104E52DFA}.Release|Any CPU.Build.0 = Release|Any CPU - {0D4D59D7-C52F-4858-A220-EAC7484E3827}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D4D59D7-C52F-4858-A220-EAC7484E3827}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D4D59D7-C52F-4858-A220-EAC7484E3827}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D4D59D7-C52F-4858-A220-EAC7484E3827}.Release|Any CPU.Build.0 = Release|Any CPU - {23790570-66D2-4CD5-9CD0-F8787C5B61BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23790570-66D2-4CD5-9CD0-F8787C5B61BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23790570-66D2-4CD5-9CD0-F8787C5B61BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23790570-66D2-4CD5-9CD0-F8787C5B61BF}.Release|Any CPU.Build.0 = Release|Any CPU - {E2F1E03B-002A-4A3E-BAFC-8F2F0AB5B86F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2F1E03B-002A-4A3E-BAFC-8F2F0AB5B86F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2F1E03B-002A-4A3E-BAFC-8F2F0AB5B86F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2F1E03B-002A-4A3E-BAFC-8F2F0AB5B86F}.Release|Any CPU.Build.0 = Release|Any CPU - {A716ED69-FBC8-4E3F-B728-589873A1540D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A716ED69-FBC8-4E3F-B728-589873A1540D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A716ED69-FBC8-4E3F-B728-589873A1540D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A716ED69-FBC8-4E3F-B728-589873A1540D}.Release|Any CPU.Build.0 = Release|Any CPU - {F262678B-7CD8-43DD-B752-4E761BE9A0A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F262678B-7CD8-43DD-B752-4E761BE9A0A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F262678B-7CD8-43DD-B752-4E761BE9A0A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F262678B-7CD8-43DD-B752-4E761BE9A0A3}.Release|Any CPU.Build.0 = Release|Any CPU - {CA159C14-FCE9-4D65-815C-F5D7D79583DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA159C14-FCE9-4D65-815C-F5D7D79583DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA159C14-FCE9-4D65-815C-F5D7D79583DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA159C14-FCE9-4D65-815C-F5D7D79583DE}.Release|Any CPU.Build.0 = Release|Any CPU - {4393D63A-0C36-42F3-98A9-81440F213640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4393D63A-0C36-42F3-98A9-81440F213640}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4393D63A-0C36-42F3-98A9-81440F213640}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4393D63A-0C36-42F3-98A9-81440F213640}.Release|Any CPU.Build.0 = Release|Any CPU - {135B06DB-1A0E-4DC3-93DA-8649AE3BDE19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {135B06DB-1A0E-4DC3-93DA-8649AE3BDE19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {135B06DB-1A0E-4DC3-93DA-8649AE3BDE19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {135B06DB-1A0E-4DC3-93DA-8649AE3BDE19}.Release|Any CPU.Build.0 = Release|Any CPU - {5FB9DFB8-2453-40EA-AAAD-D44677BCDF50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FB9DFB8-2453-40EA-AAAD-D44677BCDF50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FB9DFB8-2453-40EA-AAAD-D44677BCDF50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FB9DFB8-2453-40EA-AAAD-D44677BCDF50}.Release|Any CPU.Build.0 = Release|Any CPU - {CA82D2B3-CCF7-4F68-8840-286BA2D5B23B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA82D2B3-CCF7-4F68-8840-286BA2D5B23B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA82D2B3-CCF7-4F68-8840-286BA2D5B23B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA82D2B3-CCF7-4F68-8840-286BA2D5B23B}.Release|Any CPU.Build.0 = Release|Any CPU - {16F51720-29D0-472A-93FA-2604D61991B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {16F51720-29D0-472A-93FA-2604D61991B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {16F51720-29D0-472A-93FA-2604D61991B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16F51720-29D0-472A-93FA-2604D61991B7}.Release|Any CPU.Build.0 = Release|Any CPU - {452352E1-71CA-436E-8165-F284EE36C924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {452352E1-71CA-436E-8165-F284EE36C924}.Debug|Any CPU.Build.0 = Debug|Any CPU - {452352E1-71CA-436E-8165-F284EE36C924}.Release|Any CPU.ActiveCfg = Release|Any CPU - {452352E1-71CA-436E-8165-F284EE36C924}.Release|Any CPU.Build.0 = Release|Any CPU - {08C44607-EB80-4EE5-927D-08C34AA277AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08C44607-EB80-4EE5-927D-08C34AA277AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08C44607-EB80-4EE5-927D-08C34AA277AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08C44607-EB80-4EE5-927D-08C34AA277AF}.Release|Any CPU.Build.0 = Release|Any CPU - {3F23276F-F7D6-4E50-AA43-8DDB3EF1F680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F23276F-F7D6-4E50-AA43-8DDB3EF1F680}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F23276F-F7D6-4E50-AA43-8DDB3EF1F680}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F23276F-F7D6-4E50-AA43-8DDB3EF1F680}.Release|Any CPU.Build.0 = Release|Any CPU - {027CADBF-7071-482A-8D86-CA03AC31FC4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {027CADBF-7071-482A-8D86-CA03AC31FC4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {027CADBF-7071-482A-8D86-CA03AC31FC4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {027CADBF-7071-482A-8D86-CA03AC31FC4D}.Release|Any CPU.Build.0 = Release|Any CPU - {B0ECF78C-71A7-4281-B81E-7BD09649E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0ECF78C-71A7-4281-B81E-7BD09649E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0ECF78C-71A7-4281-B81E-7BD09649E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0ECF78C-71A7-4281-B81E-7BD09649E5EE}.Release|Any CPU.Build.0 = Release|Any CPU - {EF157AEA-43B8-4923-9E86-BB134F17F2F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF157AEA-43B8-4923-9E86-BB134F17F2F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF157AEA-43B8-4923-9E86-BB134F17F2F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF157AEA-43B8-4923-9E86-BB134F17F2F0}.Release|Any CPU.Build.0 = Release|Any CPU - {76EFDDEC-4AD2-4B12-8EC9-440190B89418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76EFDDEC-4AD2-4B12-8EC9-440190B89418}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76EFDDEC-4AD2-4B12-8EC9-440190B89418}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76EFDDEC-4AD2-4B12-8EC9-440190B89418}.Release|Any CPU.Build.0 = Release|Any CPU - {75A46D17-91D6-430C-A8A8-C2D2E8C9943C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75A46D17-91D6-430C-A8A8-C2D2E8C9943C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75A46D17-91D6-430C-A8A8-C2D2E8C9943C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75A46D17-91D6-430C-A8A8-C2D2E8C9943C}.Release|Any CPU.Build.0 = Release|Any CPU - {B2F74D8D-9D16-493C-A716-B58426074075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2F74D8D-9D16-493C-A716-B58426074075}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2F74D8D-9D16-493C-A716-B58426074075}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2F74D8D-9D16-493C-A716-B58426074075}.Release|Any CPU.Build.0 = Release|Any CPU - {06724523-611A-4054-A1AC-29DA2E87DD87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06724523-611A-4054-A1AC-29DA2E87DD87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06724523-611A-4054-A1AC-29DA2E87DD87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06724523-611A-4054-A1AC-29DA2E87DD87}.Release|Any CPU.Build.0 = Release|Any CPU - {15DC879D-1A60-4745-8DF4-EBC36EE12339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15DC879D-1A60-4745-8DF4-EBC36EE12339}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15DC879D-1A60-4745-8DF4-EBC36EE12339}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15DC879D-1A60-4745-8DF4-EBC36EE12339}.Release|Any CPU.Build.0 = Release|Any CPU - {10AA955C-B412-41A8-899F-8609AAE19F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10AA955C-B412-41A8-899F-8609AAE19F61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10AA955C-B412-41A8-899F-8609AAE19F61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10AA955C-B412-41A8-899F-8609AAE19F61}.Release|Any CPU.Build.0 = Release|Any CPU - {E166D337-4033-4209-863F-8F77675EAEE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E166D337-4033-4209-863F-8F77675EAEE8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E166D337-4033-4209-863F-8F77675EAEE8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E166D337-4033-4209-863F-8F77675EAEE8}.Release|Any CPU.Build.0 = Release|Any CPU - {85F9F2A8-D2A5-4EA1-8BE0-06CCE141EC7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85F9F2A8-D2A5-4EA1-8BE0-06CCE141EC7A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85F9F2A8-D2A5-4EA1-8BE0-06CCE141EC7A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85F9F2A8-D2A5-4EA1-8BE0-06CCE141EC7A}.Release|Any CPU.Build.0 = Release|Any CPU - {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|Any CPU.Build.0 = Release|Any CPU - {9B5BDF7D-5816-47C6-8CFD-E45B152CA35F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B5BDF7D-5816-47C6-8CFD-E45B152CA35F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B5BDF7D-5816-47C6-8CFD-E45B152CA35F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B5BDF7D-5816-47C6-8CFD-E45B152CA35F}.Release|Any CPU.Build.0 = Release|Any CPU - {64C4C4CD-2319-4033-9F33-53AAA8FECEFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64C4C4CD-2319-4033-9F33-53AAA8FECEFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64C4C4CD-2319-4033-9F33-53AAA8FECEFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64C4C4CD-2319-4033-9F33-53AAA8FECEFA}.Release|Any CPU.Build.0 = Release|Any CPU - {CE6673DA-B50F-46DF-99A3-8A7C54DE9B61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE6673DA-B50F-46DF-99A3-8A7C54DE9B61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE6673DA-B50F-46DF-99A3-8A7C54DE9B61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE6673DA-B50F-46DF-99A3-8A7C54DE9B61}.Release|Any CPU.Build.0 = Release|Any CPU - {429552A4-4C18-4355-94C5-80DC88C48405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {429552A4-4C18-4355-94C5-80DC88C48405}.Debug|Any CPU.Build.0 = Debug|Any CPU - {429552A4-4C18-4355-94C5-80DC88C48405}.Release|Any CPU.ActiveCfg = Release|Any CPU - {429552A4-4C18-4355-94C5-80DC88C48405}.Release|Any CPU.Build.0 = Release|Any CPU - {080F0AD2-E7AE-42C8-B867-56D78576735D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {080F0AD2-E7AE-42C8-B867-56D78576735D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {080F0AD2-E7AE-42C8-B867-56D78576735D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {080F0AD2-E7AE-42C8-B867-56D78576735D}.Release|Any CPU.Build.0 = Release|Any CPU - {C69BEEA4-BE37-4155-8DD0-130C62D8BF6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C69BEEA4-BE37-4155-8DD0-130C62D8BF6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C69BEEA4-BE37-4155-8DD0-130C62D8BF6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C69BEEA4-BE37-4155-8DD0-130C62D8BF6D}.Release|Any CPU.Build.0 = Release|Any CPU - {A83CE873-2536-4795-B601-5816294ED0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A83CE873-2536-4795-B601-5816294ED0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A83CE873-2536-4795-B601-5816294ED0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A83CE873-2536-4795-B601-5816294ED0B8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {F22A8D65-0581-4CC7-9C1C-9BC9F9E80DA4} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {C1A621CF-8FA8-437C-98E8-C6C6418FEBEF} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {52CAF89F-2309-4597-B531-79D6A96902BE} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {3F23276F-F7D6-4E50-AA43-8DDB3EF1F680} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {027CADBF-7071-482A-8D86-CA03AC31FC4D} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {B0ECF78C-71A7-4281-B81E-7BD09649E5EE} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {EF157AEA-43B8-4923-9E86-BB134F17F2F0} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {76EFDDEC-4AD2-4B12-8EC9-440190B89418} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {75A46D17-91D6-430C-A8A8-C2D2E8C9943C} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {B2F74D8D-9D16-493C-A716-B58426074075} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {06724523-611A-4054-A1AC-29DA2E87DD87} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {15DC879D-1A60-4745-8DF4-EBC36EE12339} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - {10AA955C-B412-41A8-899F-8609AAE19F61} = {2633D125-64A7-456C-AD37-F8A6B56C2403} - {E166D337-4033-4209-863F-8F77675EAEE8} = {2633D125-64A7-456C-AD37-F8A6B56C2403} - {71BF7EC9-7BEE-4038-8F4E-87032FA4E995} = {0C9CA869-32FD-4A9E-8885-E2E19786C746} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D2334DAA-F7B2-450E-ABA4-FBC185152500} - EndGlobalSection -EndGlobal diff --git a/test/TestAssets/TestAssets.slnx b/test/TestAssets/TestAssets.slnx new file mode 100644 index 0000000000..514bebba48 --- /dev/null +++ b/test/TestAssets/TestAssets.slnx @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/TestAssets/Tools/Tools.csproj b/test/TestAssets/Tools/Tools.csproj index b56dbd9b07..7c13e8ec53 100644 --- a/test/TestAssets/Tools/Tools.csproj +++ b/test/TestAssets/Tools/Tools.csproj @@ -6,7 +6,7 @@ - net7.0;net6.0;net5.0 + net8.0;net9.0 Exe hanging_child diff --git a/test/TestAssets/XUTestProject/XUTestProject.csproj b/test/TestAssets/XUTestProject/XUTestProject.csproj index 5e91dc7e9d..96540e54bf 100644 --- a/test/TestAssets/XUTestProject/XUTestProject.csproj +++ b/test/TestAssets/XUTestProject/XUTestProject.csproj @@ -2,14 +2,12 @@ XUTestProject - $(NetFrameworkMinimum);$(NetCoreAppMinimum) + $(TestProjectTargetFrameworks) - - - + diff --git a/test/TestAssets/child-crash/UnitTest1.cs b/test/TestAssets/child-crash/UnitTest1.cs index 9262bc9bcb..65c868c00a 100644 --- a/test/TestAssets/child-crash/UnitTest1.cs +++ b/test/TestAssets/child-crash/UnitTest1.cs @@ -23,19 +23,19 @@ public void TestMethod1() #else var directory = "Release"; #endif - // wait for two children to crash - var childProcess = Path.GetFullPath($@"../../../problematic-child/{directory}/net5.0/problematic-child{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ".dll")}"); + // wait for child to crash + var childProcess = Path.GetFullPath($@"../../../problematic-child/{directory}/net9.0/problematic-child{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ".dll")}"); if (!File.Exists(childProcess)) { throw new FileNotFoundException(childProcess); } - // 2 chidren, that is 3 crashing processes - (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Process.Start(childProcess, "2") : Process.Start("dotnet", $"{childProcess} 2")).WaitForExit(); + // 1 child, that is 2 crashing processes + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Process.Start(childProcess, "1") : Process.Start("dotnet", $"{childProcess} 1")).WaitForExit(); // then crash self with stack overflow (+1 crash) Span s = stackalloc byte[int.MaxValue]; - // we should get 4 crash dumps in total from this test + // we should get 3 crash dumps in total from this test } } } diff --git a/test/TestAssets/child-crash/child-crash.csproj b/test/TestAssets/child-crash/child-crash.csproj index 20b4a41dae..77e1b70f57 100644 --- a/test/TestAssets/child-crash/child-crash.csproj +++ b/test/TestAssets/child-crash/child-crash.csproj @@ -6,7 +6,7 @@ - net7.0;net6.0;net5.0 + net8.0;net9.0 child_crash false @@ -16,7 +16,6 @@ - diff --git a/test/TestAssets/child-hang/UnitTest1.cs b/test/TestAssets/child-hang/UnitTest1.cs index 9e7e7679ac..32528adef9 100644 --- a/test/TestAssets/child-hang/UnitTest1.cs +++ b/test/TestAssets/child-hang/UnitTest1.cs @@ -24,16 +24,16 @@ public void TestMethod1() #else var directory = "Release"; #endif - // wait for two children to crash - var childProcess = Path.GetFullPath($@"../../../hanging-child/{directory}/net5.0/hanging-child{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ".dll")}"); - // 2 chidren, that is 3 hanging processes - var process = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Process.Start(childProcess, "2") : Process.Start(GetFullPath("dotnet"), $"{childProcess} 2")); + // wait for child to hang + var childProcess = Path.GetFullPath($@"../../../hanging-child/{directory}/net9.0/hanging-child{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ".dll")}"); + // 1 child, that is 2 hanging processes + var process = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Process.Start(childProcess, "1") : Process.Start(GetFullPath("dotnet"), $"{childProcess} 1")); process.WaitForExit(); // then hang self (+1 hang) Thread.Sleep(30_000); - // we should get 4 hang dumps in total from this test + // we should get 3 hang dumps in total from this test } public static string GetFullPath(string fileName) diff --git a/test/TestAssets/child-hang/child-hang.csproj b/test/TestAssets/child-hang/child-hang.csproj index 30f4c676a5..10d52a7577 100644 --- a/test/TestAssets/child-hang/child-hang.csproj +++ b/test/TestAssets/child-hang/child-hang.csproj @@ -5,7 +5,7 @@ - $(NetFrameworkMinimum);net472;net48;$(NetCoreAppMinimum);net7.0;net6.0;net5.0 + $(TestProjectTargetFrameworks) child_hang false @@ -15,7 +15,6 @@ - diff --git a/test/TestAssets/crash/crash.csproj b/test/TestAssets/crash/crash.csproj index 4c23615afb..dd6c0426e9 100644 --- a/test/TestAssets/crash/crash.csproj +++ b/test/TestAssets/crash/crash.csproj @@ -5,7 +5,7 @@ - $(NetFrameworkMinimum);net472;net48;$(NetCoreAppMinimum);net7.0;net6.0;net5.0 + $(TestProjectTargetFrameworks) false @@ -14,7 +14,6 @@ - diff --git a/test/TestAssets/hanging-child/hanging-child.csproj b/test/TestAssets/hanging-child/hanging-child.csproj index 787e5d84cd..2c46cb554d 100644 --- a/test/TestAssets/hanging-child/hanging-child.csproj +++ b/test/TestAssets/hanging-child/hanging-child.csproj @@ -6,7 +6,7 @@ - net7.0;net6.0;net5.0 + net8.0;net9.0 Exe hanging_child diff --git a/test/TestAssets/performance/MSTest1000Passing/MSTest1000Passing.csproj b/test/TestAssets/performance/MSTest1000Passing/MSTest1000Passing.csproj index b5aad4d303..003be862b0 100644 --- a/test/TestAssets/performance/MSTest1000Passing/MSTest1000Passing.csproj +++ b/test/TestAssets/performance/MSTest1000Passing/MSTest1000Passing.csproj @@ -1,7 +1,7 @@ - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/MSTest100Passing/MSTest100Passing.csproj b/test/TestAssets/performance/MSTest100Passing/MSTest100Passing.csproj index b5aad4d303..003be862b0 100644 --- a/test/TestAssets/performance/MSTest100Passing/MSTest100Passing.csproj +++ b/test/TestAssets/performance/MSTest100Passing/MSTest100Passing.csproj @@ -1,7 +1,7 @@ - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/MSTest10kPassing/MSTest10kPassing.csproj b/test/TestAssets/performance/MSTest10kPassing/MSTest10kPassing.csproj index df21b00d98..38220eb26e 100644 --- a/test/TestAssets/performance/MSTest10kPassing/MSTest10kPassing.csproj +++ b/test/TestAssets/performance/MSTest10kPassing/MSTest10kPassing.csproj @@ -1,7 +1,7 @@ - net6.0;net48;net472 + net8.0;net48;net472 false diff --git a/test/TestAssets/performance/MSTest1Passing/MSTest1Passing.csproj b/test/TestAssets/performance/MSTest1Passing/MSTest1Passing.csproj index f2a1a66533..b81e9fb18d 100644 --- a/test/TestAssets/performance/MSTest1Passing/MSTest1Passing.csproj +++ b/test/TestAssets/performance/MSTest1Passing/MSTest1Passing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/NUnit1000Passing/NUnit1000Passing.csproj b/test/TestAssets/performance/NUnit1000Passing/NUnit1000Passing.csproj index 62a772053e..2b873ab980 100644 --- a/test/TestAssets/performance/NUnit1000Passing/NUnit1000Passing.csproj +++ b/test/TestAssets/performance/NUnit1000Passing/NUnit1000Passing.csproj @@ -1,7 +1,7 @@ - net6.0;net48 + net9.0;net48 diff --git a/test/TestAssets/performance/NUnit100Passing/NUnit100Passing.csproj b/test/TestAssets/performance/NUnit100Passing/NUnit100Passing.csproj index e15c0b0c66..4b0d0e5b87 100644 --- a/test/TestAssets/performance/NUnit100Passing/NUnit100Passing.csproj +++ b/test/TestAssets/performance/NUnit100Passing/NUnit100Passing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 diff --git a/test/TestAssets/performance/NUnit10kPassing/NUnit10kPassing.csproj b/test/TestAssets/performance/NUnit10kPassing/NUnit10kPassing.csproj index e15c0b0c66..4b0d0e5b87 100644 --- a/test/TestAssets/performance/NUnit10kPassing/NUnit10kPassing.csproj +++ b/test/TestAssets/performance/NUnit10kPassing/NUnit10kPassing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 diff --git a/test/TestAssets/performance/NUnit1Passing/NUnit1Passing.csproj b/test/TestAssets/performance/NUnit1Passing/NUnit1Passing.csproj index e15c0b0c66..4b0d0e5b87 100644 --- a/test/TestAssets/performance/NUnit1Passing/NUnit1Passing.csproj +++ b/test/TestAssets/performance/NUnit1Passing/NUnit1Passing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 diff --git a/test/TestAssets/performance/Perfy.TestAdapter/Perfy.TestAdapter.csproj b/test/TestAssets/performance/Perfy.TestAdapter/Perfy.TestAdapter.csproj index e370f3059c..0ab7c46aa0 100644 --- a/test/TestAssets/performance/Perfy.TestAdapter/Perfy.TestAdapter.csproj +++ b/test/TestAssets/performance/Perfy.TestAdapter/Perfy.TestAdapter.csproj @@ -1,7 +1,7 @@ - net7.0;net6.0;net48;net472;net471;net5.0 + net48;net472;net471;net9.0;net8.0 false diff --git a/test/TestAssets/performance/XUnit1000Passing/XUnit1000Passing.csproj b/test/TestAssets/performance/XUnit1000Passing/XUnit1000Passing.csproj index 77eebc9bcd..119a479786 100644 --- a/test/TestAssets/performance/XUnit1000Passing/XUnit1000Passing.csproj +++ b/test/TestAssets/performance/XUnit1000Passing/XUnit1000Passing.csproj @@ -1,7 +1,7 @@ - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/XUnit100Passing/XUnit100Passing.csproj b/test/TestAssets/performance/XUnit100Passing/XUnit100Passing.csproj index 77eebc9bcd..119a479786 100644 --- a/test/TestAssets/performance/XUnit100Passing/XUnit100Passing.csproj +++ b/test/TestAssets/performance/XUnit100Passing/XUnit100Passing.csproj @@ -1,7 +1,7 @@ - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/XUnit10kPassing/XUnit10kPassing.csproj b/test/TestAssets/performance/XUnit10kPassing/XUnit10kPassing.csproj index 66cfa47bed..393eb3be00 100644 --- a/test/TestAssets/performance/XUnit10kPassing/XUnit10kPassing.csproj +++ b/test/TestAssets/performance/XUnit10kPassing/XUnit10kPassing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/performance/XUnit1Passing/XUnit1Passing.csproj b/test/TestAssets/performance/XUnit1Passing/XUnit1Passing.csproj index 66cfa47bed..393eb3be00 100644 --- a/test/TestAssets/performance/XUnit1Passing/XUnit1Passing.csproj +++ b/test/TestAssets/performance/XUnit1Passing/XUnit1Passing.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net9.0;net48 false diff --git a/test/TestAssets/problematic-child/problematic-child.csproj b/test/TestAssets/problematic-child/problematic-child.csproj index 3d9286d46f..f00b791029 100644 --- a/test/TestAssets/problematic-child/problematic-child.csproj +++ b/test/TestAssets/problematic-child/problematic-child.csproj @@ -6,7 +6,7 @@ - net7.0;net6.0;net5.0 + net8.0;net9.0 Exe problematic_child diff --git a/test/TestAssets/timeout/UnitTest1.cs b/test/TestAssets/timeout/UnitTest1.cs index 609ccd7e0a..fdbb338f6a 100644 --- a/test/TestAssets/timeout/UnitTest1.cs +++ b/test/TestAssets/timeout/UnitTest1.cs @@ -15,7 +15,7 @@ public class UnitTest1 [TestMethod] public void TestMethod1() { - Thread.Sleep(10_000); + Thread.Sleep(30_000); } } } diff --git a/test/TestAssets/timeout/timeout.csproj b/test/TestAssets/timeout/timeout.csproj index 294bebab1c..369c5af645 100644 --- a/test/TestAssets/timeout/timeout.csproj +++ b/test/TestAssets/timeout/timeout.csproj @@ -6,7 +6,7 @@ - $(NetFrameworkMinimum);net472;net48;$(NetCoreAppMinimum);net7.0;net6.0;net5.0 + $(TestProjectTargetFrameworks) false diff --git a/test/TranslationLayer.UnitTests/ConsoleParametersTests.cs b/test/TranslationLayer.UnitTests/ConsoleParametersTests.cs index eeb1dcc17c..c6fa2ba453 100644 --- a/test/TranslationLayer.UnitTests/ConsoleParametersTests.cs +++ b/test/TranslationLayer.UnitTests/ConsoleParametersTests.cs @@ -26,13 +26,13 @@ public void LogFilePathShouldEnsureDoubleQuote() string result = sut.LogFilePath; - Assert.IsTrue(result.StartsWith("\"")); + Assert.StartsWith("\"", result); } [TestMethod] public void TraceLevelShouldHaveVerboseAsDefaultValue() { var consoleParameters = new ConsoleParameters(new FileHelper()); - Assert.AreEqual(consoleParameters.TraceLevel, TraceLevel.Verbose); + Assert.AreEqual(TraceLevel.Verbose, consoleParameters.TraceLevel); } } diff --git a/test/TranslationLayer.UnitTests/DiscoveryEventsHandleConverterTests.cs b/test/TranslationLayer.UnitTests/DiscoveryEventsHandleConverterTests.cs index 78301f4901..8032aecc76 100644 --- a/test/TranslationLayer.UnitTests/DiscoveryEventsHandleConverterTests.cs +++ b/test/TranslationLayer.UnitTests/DiscoveryEventsHandleConverterTests.cs @@ -24,7 +24,7 @@ public DiscoveryEventsHandleConverterTests() [TestMethod] public void ConstructorShouldThrowArgumentExceptionIfTestDiscoveryEventHandlerIsNull() { - Assert.ThrowsException(() => new DiscoveryEventsHandleConverter(null!)); + Assert.ThrowsExactly(() => new DiscoveryEventsHandleConverter(null!)); } [TestMethod] diff --git a/test/TranslationLayer.UnitTests/Program.cs b/test/TranslationLayer.UnitTests/Program.cs deleted file mode 100644 index 02d1eccf01..0000000000 --- a/test/TranslationLayer.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/TranslationLayer.UnitTests/TranslationLayer.UnitTests.csproj b/test/TranslationLayer.UnitTests/TranslationLayer.UnitTests.csproj index 5afb1d461e..ed7d4d359d 100644 --- a/test/TranslationLayer.UnitTests/TranslationLayer.UnitTests.csproj +++ b/test/TranslationLayer.UnitTests/TranslationLayer.UnitTests.csproj @@ -6,19 +6,13 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe TranslationLayer.UnitTests - - - - - - diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs index b2d496d208..06b4317610 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs @@ -23,6 +23,8 @@ using Moq; +#pragma warning disable MSTEST0049 // Use 'TestContext.CancellationToken' - suppressed for Moq setup patterns + using Newtonsoft.Json.Linq; using Payloads = Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; @@ -40,6 +42,8 @@ public class VsTestConsoleRequestSenderTests private readonly IDataSerializer _serializer = JsonDataSerializer.Instance; private readonly Mock _telemetryHandler; + public TestContext TestContext { get; set; } = null!; + public VsTestConsoleRequestSenderTests() { _mockCommunicationManager = new Mock(); @@ -82,7 +86,7 @@ public void InitializeCommunicationShouldReturnInvalidPortNumberIfHostServerFail _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)); var portOutput = _requestSender.InitializeCommunication(); - Assert.IsTrue(portOutput < 0, "Negative port number must be returned if Hosting Server fails."); + Assert.IsLessThan(0, portOutput, "Negative port number must be returned if Hosting Server fails."); var connectionSuccess = _requestSender.WaitForRequestHandlerConnection(_waitTimeout); Assert.IsFalse(connectionSuccess, "Connection must fail as server failed to host."); @@ -100,7 +104,7 @@ public async Task InitializeCommunicationAsyncShouldReturnInvalidPortNumberIfHos _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)); var portOutput = await _requestSender.InitializeCommunicationAsync(_waitTimeout); - Assert.IsTrue(portOutput < 0, "Negative port number must be returned if Hosting Server fails."); + Assert.IsLessThan(0, portOutput, "Negative port number must be returned if Hosting Server fails."); _mockCommunicationManager.Verify(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); _mockCommunicationManager.Verify(cm => cm.AcceptClientAsync(), Times.Never); @@ -114,7 +118,7 @@ public void InitializeCommunicationShouldFailConnectionIfMessageReceiveFailed() _mockCommunicationManager.Setup(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, dummyPortInput)); _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); _mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) - .Callback((int timeout) => Task.Delay(200).Wait()); + .Callback((int timeout) => Task.Delay(200, TestContext.CancellationToken).Wait()); _mockCommunicationManager.Setup(cm => cm.ReceiveMessage()).Throws(new Exception("Fail")); var portOutput = _requestSender.InitializeCommunication(); @@ -153,7 +157,7 @@ public void InitializeCommunicationShouldFailConnectionIfSessionConnectedDidNotC _mockCommunicationManager.Setup(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, dummyPortInput)); _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); _mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) - .Callback((int timeout) => Task.Delay(200).Wait()); + .Callback((int timeout) => Task.Delay(200, TestContext.CancellationToken).Wait()); var discoveryMessage = new Message() { MessageType = MessageType.StartDiscovery }; @@ -201,7 +205,7 @@ public void InitializeCommunicationShouldFailConnectionIfSendMessageFailed() _mockCommunicationManager.Setup(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, dummyPortInput)); _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); _mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) - .Callback((int timeout) => Task.Delay(200).Wait()); + .Callback((int timeout) => Task.Delay(200, TestContext.CancellationToken).Wait()); var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; @@ -252,7 +256,7 @@ public void InitializeCommunicationShouldFailConnectionIfProtocolIsNotCompatible _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); _mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) - .Callback((int timeout) => Task.Delay(200).Wait()); + .Callback((int timeout) => Task.Delay(200, TestContext.CancellationToken).Wait()); var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; @@ -465,7 +469,7 @@ public void DiscoverTestsShouldCompleteWithSingleFullyDiscoveredSource() mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); Assert.IsNotNull(receivedDiscoveryCompleteEventArgs!.FullyDiscoveredSources); - Assert.AreEqual(1, receivedDiscoveryCompleteEventArgs.FullyDiscoveredSources.Count); + Assert.HasCount(1, receivedDiscoveryCompleteEventArgs.FullyDiscoveredSources); } [TestMethod] @@ -508,9 +512,9 @@ public void DiscoverTestsShouldCompleteWithCorrectAbortedValuesIfAbortingWasRequ // Assert mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); Assert.IsNotNull(receivedDiscoveryCompleteEventArgs!.FullyDiscoveredSources); - Assert.AreEqual(1, receivedDiscoveryCompleteEventArgs.FullyDiscoveredSources.Count); + Assert.HasCount(1, receivedDiscoveryCompleteEventArgs.FullyDiscoveredSources); Assert.AreEqual(-1, receivedDiscoveryCompleteEventArgs.TotalCount); - Assert.AreEqual(true, receivedDiscoveryCompleteEventArgs.IsAborted); + Assert.IsTrue(receivedDiscoveryCompleteEventArgs.IsAborted); } [TestMethod] @@ -542,7 +546,7 @@ public void DiscoverTestsShouldReportBackTestsWithTraitsInTestsFoundMessage() _requestSender.DiscoverTests(new List() { "1.dll" }, null, new TestPlatformOptions(), null, mockHandler.Object); Assert.IsNotNull(receivedTestCases); - Assert.AreEqual(1, receivedTestCases.Count); + Assert.HasCount(1, receivedTestCases); // Verify that the traits are passed through properly. var traits = receivedTestCases.ToArray()[0].Traits; @@ -580,7 +584,7 @@ public async Task DiscoverTestsAsyncShouldReportBackTestsWithTraitsInTestsFoundM await _requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), null, mockHandler.Object); Assert.IsNotNull(receivedTestCases); - Assert.AreEqual(1, receivedTestCases.Count); + Assert.HasCount(1, receivedTestCases); // Verify that the traits are passed through properly. var traits = receivedTestCases.ToArray()[0].Traits; @@ -613,7 +617,7 @@ public void DiscoverTestsShouldReportBackTestsWithTraitsInDiscoveryCompleteMessa _requestSender.DiscoverTests(new List() { "1.dll" }, null, new TestPlatformOptions(), null, mockHandler.Object); Assert.IsNotNull(receivedTestCases); - Assert.AreEqual(1, receivedTestCases.Count); + Assert.HasCount(1, receivedTestCases); // Verify that the traits are passed through properly. var traits = receivedTestCases.ToArray()[0].Traits; @@ -646,7 +650,7 @@ public async Task DiscoverTestsAsyncShouldReportBackTestsWithTraitsInDiscoveryCo await _requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), null, mockHandler.Object); Assert.IsNotNull(receivedTestCases); - Assert.AreEqual(1, receivedTestCases.Count); + Assert.HasCount(1, receivedTestCases); // Verify that the traits are passed through properly. var traits = receivedTestCases.ToArray()[0].Traits; @@ -2552,7 +2556,7 @@ public void StartTestSessionWithTesthostLauncherShouldSucceed() MessageType.CustomTestHostLaunchCallback, It.IsAny(), _protocolVersion)) - .Callback((string messageType, object payload, int version) => Assert.AreEqual(((CustomHostLaunchAckPayload)payload).HostProcessId, TesthostPid)); + .Callback((string messageType, object payload, int version) => Assert.AreEqual(TesthostPid, ((CustomHostLaunchAckPayload)payload).HostProcessId)); _mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())) .Returns(Task.FromResult(launchMessage)) .Callback(reconfigureAction); @@ -2627,7 +2631,7 @@ public async Task StartTestSessionAsyncWithTesthostLauncherShouldSucceed() MessageType.CustomTestHostLaunchCallback, It.IsAny(), _protocolVersion)) - .Callback((string messageType, object payload, int version) => Assert.AreEqual(((CustomHostLaunchAckPayload)payload).HostProcessId, TesthostPid)); + .Callback((string messageType, object payload, int version) => Assert.AreEqual(TesthostPid, ((CustomHostLaunchAckPayload)payload).HostProcessId)); _mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())) .Returns(Task.FromResult(launchMessage)) .Callback(reconfigureAction); @@ -2822,7 +2826,7 @@ private void InitializeCommunication(int protocolVersion) _mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); _mockCommunicationManager.Setup(cm => cm.WaitForClientConnection(Timeout.Infinite)) - .Callback((int timeout) => Task.Delay(200).Wait()); + .Callback((int timeout) => Task.Delay(200, TestContext.CancellationToken).Wait()); var sessionConnected = new Message() { MessageType = MessageType.SessionConnected }; var versionCheck = new Message() { MessageType = MessageType.VersionCheck, Payload = protocolVersion }; @@ -2887,3 +2891,4 @@ private async Task InitializeCommunicationAsync(int protocolVersion) #endregion } +#pragma warning restore MSTEST0049 diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs index e3ba446662..00bd5b3117 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs @@ -84,7 +84,7 @@ public void StartSessionShouldThrowExceptionOnBadPort() { _mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(-1); - Assert.ThrowsException(() => _consoleWrapper.StartSession()); + Assert.ThrowsExactly(() => _consoleWrapper.StartSession()); } [TestMethod] @@ -271,9 +271,15 @@ public void InitializeExtensionsShouldThrowExceptionOnBadConnection() { _mockProcessHelper.Setup(x => x.GetCurrentProcessFileName()).Returns("DummyProcess"); _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); - - var exception = Assert.ThrowsException(() => _consoleWrapper.InitializeExtensions(new List { "Hello", "World" })); - Assert.AreEqual("DummyProcess process failed to connect to vstest.console process after 90 seconds. This may occur due to machine slowness, please set environment variable VSTEST_CONNECTION_TIMEOUT to increase timeout.", exception.Message); + _mockProcessManager.Setup(pm => pm.ProcessId).Returns(1234); + _mockProcessManager.Setup(pm => pm.ProcessName).Returns("vstest.console"); + _mockProcessManager.Setup(pm => pm.ExitCode).Returns(1); + _mockProcessManager.Setup(pm => pm.ErrorOutput).Returns("some error"); + + var exception = Assert.ThrowsExactly(() => _consoleWrapper.InitializeExtensions(new List { "Hello", "World" })); + Assert.Contains("1234", exception.Message); + Assert.Contains("exitCode 1", exception.Message); + Assert.Contains("some error", exception.Message); _mockRequestSender.Verify(rs => rs.InitializeExtensions(It.IsAny>()), Times.Never); } @@ -329,9 +335,14 @@ public void DiscoverTestsShouldThrowExceptionOnBadConnection() { _mockProcessHelper.Setup(x => x.GetCurrentProcessFileName()).Returns("DummyProcess"); _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); - - var exception = Assert.ThrowsException(() => _consoleWrapper.DiscoverTests(new List { "Hello", "World" }, null, null, new Mock().Object)); - Assert.AreEqual("DummyProcess process failed to connect to vstest.console process after 90 seconds. This may occur due to machine slowness, please set environment variable VSTEST_CONNECTION_TIMEOUT to increase timeout.", exception.Message); + _mockProcessManager.Setup(pm => pm.ProcessId).Returns((int?)0); + _mockProcessManager.Setup(pm => pm.ProcessName).Returns("vstest.console"); + _mockProcessManager.Setup(pm => pm.ExitCode).Returns((int?)0); + _mockProcessManager.Setup(pm => pm.ErrorOutput).Returns(""); + + var exception = Assert.ThrowsExactly(() => _consoleWrapper.DiscoverTests(new List { "Hello", "World" }, null, null, new Mock().Object)); + Assert.Contains("DummyProcess", exception.Message); + Assert.Contains("exitCode 0", exception.Message); _mockRequestSender.Verify(rs => rs.DiscoverTests(It.IsAny>(), It.IsAny(), null, null, It.IsAny()), Times.Never); } @@ -698,4 +709,55 @@ public void EndSessionShouldSucceed() _mockRequestSender.Verify(rs => rs.Close(), Times.Once); _mockProcessManager.Verify(x => x.ShutdownProcess(), Times.Once); } + + [TestMethod] + public void StartSessionShouldReportProcessCrashDetailsOnTimeout() + { + _mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(100); + _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); + _mockProcessManager.Setup(pm => pm.ProcessId).Returns((int?)5432); + _mockProcessManager.Setup(pm => pm.ProcessName).Returns("vstest.console"); + _mockProcessManager.Setup(pm => pm.ExitCode).Returns((int?)1); + _mockProcessManager.Setup(pm => pm.ErrorOutput).Returns("Unhandled exception: System.OutOfMemoryException"); + _mockProcessHelper.Setup(x => x.GetCurrentProcessFileName()).Returns("TestRunner"); + + var ex = Assert.ThrowsExactly(() => _consoleWrapper.InitializeExtensions(new[] { "path" })); + + Assert.Contains("5432", ex.Message); + Assert.Contains("exitCode 1", ex.Message); + Assert.Contains("OutOfMemoryException", ex.Message); + } + + [TestMethod] + public void StartSessionShouldReportHangingProcessDetailsOnTimeout() + { + _mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(100); + _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); + _mockProcessManager.Setup(pm => pm.ProcessId).Returns((int?)5432); + _mockProcessManager.Setup(pm => pm.ProcessName).Returns("vstest.console"); + _mockProcessManager.Setup(pm => pm.ExitCode).Returns((int?)null); + _mockProcessManager.Setup(pm => pm.ErrorOutput).Returns(""); + _mockProcessHelper.Setup(x => x.GetCurrentProcessFileName()).Returns("TestRunner"); + + var ex = Assert.ThrowsExactly(() => _consoleWrapper.InitializeExtensions(new[] { "path" })); + + Assert.Contains("5432", ex.Message); + Assert.Contains("still running", ex.Message); + } + + [TestMethod] + public void StartSessionShouldReportNotStartedProcessOnTimeout() + { + _mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(100); + _mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(false); + _mockProcessManager.Setup(pm => pm.ProcessId).Returns((int?)null); + _mockProcessManager.Setup(pm => pm.ProcessName).Returns((string?)null); + _mockProcessManager.Setup(pm => pm.ExitCode).Returns((int?)null); + _mockProcessManager.Setup(pm => pm.ErrorOutput).Returns((string?)null); + _mockProcessHelper.Setup(x => x.GetCurrentProcessFileName()).Returns("TestRunner"); + + var ex = Assert.ThrowsExactly(() => _consoleWrapper.InitializeExtensions(new[] { "path" })); + + Assert.Contains("failed to start", ex.Message); + } } diff --git a/test/coverlet.collector/coverlet.collector.csproj b/test/coverlet.collector/coverlet.collector.csproj index 719388ac58..169286672b 100644 --- a/test/coverlet.collector/coverlet.collector.csproj +++ b/test/coverlet.collector/coverlet.collector.csproj @@ -1,6 +1,6 @@ - net6.0;net48 + net9.0;net48 false 9.9.9.9 diff --git a/test/datacollector.PlatformTests/CommunicationLayerIntegrationTests.cs b/test/datacollector.PlatformTests/CommunicationLayerIntegrationTests.cs index 27b26d83c5..67a68cb78a 100644 --- a/test/datacollector.PlatformTests/CommunicationLayerIntegrationTests.cs +++ b/test/datacollector.PlatformTests/CommunicationLayerIntegrationTests.cs @@ -71,7 +71,7 @@ public void AfterTestRunShouldSendGetAttachments() Assert.AreEqual("CustomDataCollector", dataCollectionResult.Attachments![0].DisplayName); Assert.AreEqual("my://custom/datacollector", dataCollectionResult.Attachments[0].Uri.ToString()); - Assert.IsTrue(dataCollectionResult.Attachments[0].Attachments[0].Uri.ToString().Contains("filename.txt")); + Assert.Contains("filename.txt", dataCollectionResult.Attachments[0].Attachments[0].Uri.ToString()); } [TestMethod] diff --git a/test/datacollector.PlatformTests/Program.cs b/test/datacollector.PlatformTests/Program.cs deleted file mode 100644 index b1c48820be..0000000000 --- a/test/datacollector.PlatformTests/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.DataCollector.ComponentTests; - -/// -/// Main entry point for the command line runner. -/// -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj b/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj index d97d26cf66..c55879c711 100644 --- a/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj +++ b/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj @@ -8,8 +8,8 @@ Microsoft.VisualStudio.TestPlatform.DataCollector.PlatformTests - net6.0;net48 - Exe + net9.0;net48 + Exe datacollector.PlatformTests @@ -19,11 +19,4 @@ PreserveNewest - - - - - - - diff --git a/test/datacollector.UnitTests/DataCollectionAttachmentManagerTests.cs b/test/datacollector.UnitTests/DataCollectionAttachmentManagerTests.cs index 76f16762b3..eb84270bbf 100644 --- a/test/datacollector.UnitTests/DataCollectionAttachmentManagerTests.cs +++ b/test/datacollector.UnitTests/DataCollectionAttachmentManagerTests.cs @@ -27,6 +27,8 @@ public class DataCollectionAttachmentManagerTests private readonly SessionId _sessionId; private static readonly string TempDirectoryPath = Path.GetTempPath(); + public TestContext TestContext { get; set; } + public DataCollectionAttachmentManagerTests() { _attachmentManager = new DataCollectionAttachmentManager(); @@ -69,10 +71,10 @@ public void ParallelAccessShouldNotBreak() } _ = TestCaseEvent($"test_{Guid.NewGuid()}"); } - })); + }, TestContext.CancellationToken)); } - Task.WaitAll(parallelTasks.ToArray()); + Task.WaitAll(parallelTasks.ToArray(), TestContext.CancellationToken); } finally { @@ -95,13 +97,13 @@ List TestCaseEvent(string uri) [TestMethod] public void InitializeShouldThrowExceptionIfSessionIdIsNull() { - Assert.ThrowsException(() => _attachmentManager.Initialize(null!, string.Empty, _messageSink.Object)); + Assert.ThrowsExactly(() => _attachmentManager.Initialize(null!, string.Empty, _messageSink.Object)); } [TestMethod] public void InitializeShouldThrowExceptionIfMessageSinkIsNull() { - Assert.ThrowsException(() => _attachmentManager.Initialize(_sessionId, string.Empty, null!)); + Assert.ThrowsExactly(() => _attachmentManager.Initialize(_sessionId, string.Empty, null!)); } [TestMethod] @@ -134,7 +136,7 @@ public void AddAttachmentShouldNotAddNewFileTransferIfSessionIsNotConfigured() _attachmentManager.AddAttachment(dataCollectorDataMessage, null, uri, friendlyName); - Assert.AreEqual(0, _attachmentManager.AttachmentSets.Count); + Assert.IsEmpty(_attachmentManager.AttachmentSets); } [TestMethod] @@ -162,7 +164,7 @@ public void AddAttachmentShouldAddNewFileTransferAndCopyFileToOutputDirectoryIfD Assert.IsTrue(File.Exists(Path.Combine(TempDirectoryPath, filename))); Assert.IsTrue(File.Exists(Path.Combine(TempDirectoryPath, _sessionId.Id.ToString(), filename))); - Assert.AreEqual(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments.Count); + Assert.HasCount(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments); } [TestMethod] @@ -196,8 +198,8 @@ public void AddAttachmentsShouldAddFilesCorrespondingToDifferentDataCollectors() // Wait for file operations to complete waitHandle.WaitOne(Timeout); - Assert.AreEqual(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments.Count); - Assert.AreEqual(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri1].Attachments.Count); + Assert.HasCount(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments); + Assert.HasCount(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri1].Attachments); } [TestMethod] @@ -222,7 +224,7 @@ public void AddAttachmentShouldAddNewFileTransferAndMoveFileToOutputDirectoryIfD // Wait for file operations to complete waitHandle.WaitOne(Timeout); - Assert.AreEqual(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments.Count); + Assert.HasCount(1, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments); Assert.IsTrue(File.Exists(Path.Combine(TempDirectoryPath, _sessionId.Id.ToString(), filename))); Assert.IsFalse(File.Exists(Path.Combine(TempDirectoryPath, filename))); } @@ -257,13 +259,13 @@ public void AddAttachmentShouldAddMultipleAttachmentsForSameDc() // Wait for file operations to complete waitHandle.WaitOne(Timeout); - Assert.AreEqual(2, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments.Count); + Assert.HasCount(2, _attachmentManager.AttachmentSets[datacollectioncontext][uri].Attachments); } [TestMethod] public void AddAttachmentShouldNotAddNewFileTransferIfNullIsPassed() { - Assert.ThrowsException(() => _attachmentManager.AddAttachment(null!, null, null!, null!)); + Assert.ThrowsExactly(() => _attachmentManager.AddAttachment(null!, null, null!, null!)); } [TestMethod] @@ -282,14 +284,14 @@ public void GetAttachmentsShouldReturnAllAttachments() _attachmentManager.AddAttachment(dataCollectorDataMessage, null, uri, friendlyName); - Assert.AreEqual(1, _attachmentManager.AttachmentSets.Count); + Assert.HasCount(1, _attachmentManager.AttachmentSets); var result = _attachmentManager.GetAttachments(datacollectioncontext); - Assert.AreEqual(0, _attachmentManager.AttachmentSets.Count); - Assert.AreEqual(1, result.Count); + Assert.IsEmpty(_attachmentManager.AttachmentSets); + Assert.HasCount(1, result); Assert.AreEqual(friendlyName, result[0].DisplayName); Assert.AreEqual(uri, result[0].Uri); - Assert.AreEqual(1, result[0].Attachments.Count); + Assert.HasCount(1, result[0].Attachments); } [TestMethod] @@ -300,7 +302,7 @@ public void GetAttachmentsShouldNotReturnAnyDataWhenActiveFileTransferAreNotPres var datacollectioncontext = new DataCollectionContext(_sessionId); var result = _attachmentManager.GetAttachments(datacollectioncontext); - Assert.AreEqual(0, result.Count); + Assert.IsEmpty(result); } [TestMethod] @@ -326,7 +328,7 @@ public void GetAttachmentsShouldNotReturnAttachmentsAfterCancelled() // Wait for the attachment transfer tasks to complete var result = testableAttachmentManager.GetAttachments(datacollectioncontext); - Assert.AreEqual(0, result[0].Attachments.Count); + Assert.IsEmpty(result[0].Attachments); } private class TestableDataCollectionAttachmentManager : DataCollectionAttachmentManager diff --git a/test/datacollector.UnitTests/DataCollectionEnvironmentVariableTests.cs b/test/datacollector.UnitTests/DataCollectionEnvironmentVariableTests.cs index 9f3a0c881d..b1208ace14 100644 --- a/test/datacollector.UnitTests/DataCollectionEnvironmentVariableTests.cs +++ b/test/datacollector.UnitTests/DataCollectionEnvironmentVariableTests.cs @@ -14,7 +14,7 @@ public class DataCollectionEnvironmentVariableTests [TestMethod] public void ConstructorShouldThrowExceptionIfKeyValueIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => { var envvariable = new DataCollectionEnvironmentVariable(default, null!); diff --git a/test/datacollector.UnitTests/DataCollectionManagerTests.cs b/test/datacollector.UnitTests/DataCollectionManagerTests.cs index 7cf8a2a93a..ca6407d98e 100644 --- a/test/datacollector.UnitTests/DataCollectionManagerTests.cs +++ b/test/datacollector.UnitTests/DataCollectionManagerTests.cs @@ -60,7 +60,7 @@ public DataCollectionManagerTests() [TestMethod] public void InitializeDataCollectorsShouldThrowExceptionIfSettingsXmlIsNull() { - Assert.ThrowsException(() => _dataCollectionManager.InitializeDataCollectors(null!)); + Assert.ThrowsExactly(() => _dataCollectionManager.InitializeDataCollectors(null!)); } [TestMethod] @@ -69,7 +69,7 @@ public void InitializeDataCollectorsShouldReturnEmptyDictionaryIfDataCollectorsA var runSettings = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Empty); _dataCollectionManager.InitializeDataCollectors(runSettings); - Assert.AreEqual(0, _dataCollectionManager.RunDataCollectors.Count); + Assert.IsEmpty(_dataCollectionManager.RunDataCollectors); } [TestMethod] @@ -80,7 +80,7 @@ public void InitializeDataCollectorsShouldLoadDataCollector() Assert.IsTrue(_dataCollectionManager.RunDataCollectors.ContainsKey(_mockDataCollector.Object.GetType())); Assert.AreEqual(typeof(AttachmentProcessorDataCollector2), _dataCollectionManager.RunDataCollectors[_mockDataCollector.Object.GetType()].DataCollectorConfig.AttachmentsProcessorType); - Assert.IsTrue(_dataCollectionManager.RunDataCollectors[_mockDataCollector.Object.GetType()].DataCollectorConfig.Metadata.Contains(true)); + Assert.Contains(true, _dataCollectionManager.RunDataCollectors[_mockDataCollector.Object.GetType()].DataCollectorConfig.Metadata); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -90,7 +90,7 @@ public void InitializeShouldNotAddDataCollectorIfItIsDisabled() var dataCollectorSettingsDisabled = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, _friendlyName, _uri, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, "enabled=\"false\"")); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsDisabled); - Assert.AreEqual(0, _dataCollectionManager.RunDataCollectors.Count); + Assert.IsEmpty(_dataCollectionManager.RunDataCollectors); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -111,7 +111,7 @@ public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsNotCo var dataCollectorSettingsWithWrongFriendlyName = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, "anyFriendlyName", _uri, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty)); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithWrongFriendlyName); - Assert.AreEqual(1, _dataCollectionManager.RunDataCollectors.Count); + Assert.HasCount(1, _dataCollectionManager.RunDataCollectors); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockDataCollector.Verify(x => x.Initialize(_mockTelemetryReporter.Object), Times.Once); } @@ -122,7 +122,7 @@ public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsCorre var dataCollectorSettingsWithWrongUri = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, _friendlyName, "my://custom/WrongDatacollector", _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty)); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithWrongUri); - Assert.AreEqual(1, _dataCollectionManager.RunDataCollectors.Count); + Assert.HasCount(1, _dataCollectionManager.RunDataCollectors); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockDataCollector.Verify(x => x.Initialize(_mockTelemetryReporter.Object), Times.Once); } @@ -133,7 +133,7 @@ public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsNullA var dataCollectorSettingsWithNullFriendlyName = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, string.Empty, _uri, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty).Replace("friendlyName=\"\"", string.Empty)); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithNullFriendlyName); - Assert.AreEqual(1, _dataCollectionManager.RunDataCollectors.Count); + Assert.HasCount(1, _dataCollectionManager.RunDataCollectors); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockDataCollector.Verify(x => x.Initialize(_mockTelemetryReporter.Object), Times.Once); } @@ -142,14 +142,14 @@ public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsNullA public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsCorrectAndUriIsEmpty() { var dataCollectorSettingsWithEmptyUri = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, _friendlyName, string.Empty, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty)); - Assert.ThrowsException(() => _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithEmptyUri)); + Assert.ThrowsExactly(() => _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithEmptyUri)); } [TestMethod] public void InitializeDataCollectorsShouldLoadDataCollectorIfFriendlyNameIsEmptyAndUriIsCorrect() { var dataCollectorSettingsWithEmptyFriendlyName = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, _friendlyName, string.Empty, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty)); - Assert.ThrowsException(() => _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithEmptyFriendlyName)); + Assert.ThrowsExactly(() => _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithEmptyFriendlyName)); } [TestMethod] @@ -158,7 +158,7 @@ public void InitializeDataCollectorsShouldNotLoadDataCollectorIfFriendlyNameIsNo var dataCollectorSettingsWithWrongFriendlyNameAndWrongUri = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, "anyFriendlyName", "datacollector://data", _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty)); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithWrongFriendlyNameAndWrongUri); - Assert.AreEqual(0, _dataCollectionManager.RunDataCollectors.Count); + Assert.IsEmpty(_dataCollectionManager.RunDataCollectors); _mockDataCollector.Verify(x => x.Initialize(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -200,7 +200,7 @@ public void InitializeDataCollectorsShouldLogExceptionToMessageSinkIfInitializat _dataCollectionManager.InitializeDataCollectors(_dataCollectorSettings); - Assert.AreEqual(0, _dataCollectionManager.RunDataCollectors.Count); + Assert.IsEmpty(_dataCollectionManager.RunDataCollectors); _mockMessageSink.Verify(x => x.SendMessage(It.IsAny()), Times.Once); } @@ -211,7 +211,7 @@ public void InitializeDataCollectorsShouldLogExceptionToMessageSinkIfSetEnvironm _dataCollectionManager.InitializeDataCollectors(_dataCollectorSettings); - Assert.AreEqual(0, _dataCollectionManager.RunDataCollectors.Count); + Assert.IsEmpty(_dataCollectionManager.RunDataCollectors); _mockMessageSink.Verify(x => x.SendMessage(It.IsAny()), Times.Once); } @@ -244,7 +244,7 @@ public void InitializeDataCollectorsShouldReturnOtherThanCodeCoverageEnvironment var result = _dataCollectionManager.InitializeDataCollectors(_dataCollectorSettings); - Assert.AreEqual(3, result.Count); + Assert.HasCount(3, result); Assert.AreEqual("clrie", result["cor_profiler"]); Assert.AreEqual("path", result["clrie_profiler_vanguard"]); Assert.AreEqual("same_value", result["same_key"]); @@ -340,7 +340,7 @@ public void SessionEndedShouldReturnEmptyCollectionIfDataCollectionIsNotEnabled( var result = _dataCollectionManager.SessionEnded(); - Assert.AreEqual(0, result.Count); + Assert.IsEmpty(result); } [TestMethod] @@ -349,7 +349,7 @@ public void GetInvokedDataCollectorsShouldReturnDataCollector() var dataCollectorSettingsWithNullFriendlyName = string.Format(CultureInfo.InvariantCulture, _defaultRunSettings, string.Format(CultureInfo.InvariantCulture, _defaultDataCollectionSettings, string.Empty, _uri, _mockDataCollector.Object.GetType().AssemblyQualifiedName, typeof(DataCollectionManagerTests).Assembly.Location, string.Empty).Replace("friendlyName=\"\"", string.Empty)); _dataCollectionManager.InitializeDataCollectors(dataCollectorSettingsWithNullFriendlyName); var invokedDataCollector = _dataCollectionManager.GetInvokedDataCollectors(); - Assert.AreEqual(1, invokedDataCollector.Count); + Assert.HasCount(1, invokedDataCollector); Assert.IsTrue(invokedDataCollector[0].HasAttachmentProcessor); } @@ -367,7 +367,7 @@ public void SessionEndedShouldReturnAttachments() var result = _dataCollectionManager.SessionEnded(); - Assert.IsTrue(result[0].Attachments[0].Uri.ToString().Contains("filename.txt")); + Assert.Contains("filename.txt", result[0].Attachments[0].Uri.ToString()); } [TestMethod] @@ -378,7 +378,7 @@ public void SessionEndedShouldNotReturnAttachmentsIfExceptionIsThrownWhileGettin var result = _dataCollectionManager.SessionEnded(); - Assert.AreEqual(0, result.Count); + Assert.IsEmpty(result); } [TestMethod] @@ -402,7 +402,7 @@ public void SessionEndedShouldContinueDataCollectionIfExceptionIsThrownWhileSend var result = _dataCollectionManager.SessionEnded(); - Assert.AreEqual(1, result.Count); + Assert.HasCount(1, result); } [TestMethod] diff --git a/test/datacollector.UnitTests/DataCollectorConfigTests.cs b/test/datacollector.UnitTests/DataCollectorConfigTests.cs index 05ff4fdbfe..d90a4389d5 100644 --- a/test/datacollector.UnitTests/DataCollectorConfigTests.cs +++ b/test/datacollector.UnitTests/DataCollectorConfigTests.cs @@ -23,7 +23,7 @@ public void ConstructorShouldSetCorrectFriendlyNameAndUri() [TestMethod] public void ConstructorShouldThrowExceptionIfTypeIsNull() { - Assert.ThrowsException( + Assert.ThrowsExactly( () => new DataCollectorConfig(null!)); } diff --git a/test/datacollector.UnitTests/DataCollectorMainTests.cs b/test/datacollector.UnitTests/DataCollectorMainTests.cs index 59103a2949..04c671bb32 100644 --- a/test/datacollector.UnitTests/DataCollectorMainTests.cs +++ b/test/datacollector.UnitTests/DataCollectorMainTests.cs @@ -114,7 +114,7 @@ public void RunShouldInitializeTraceWithCorrectVerboseTraceLevel() public void RunShouldThrowIfTimeoutOccured() { _mockDataCollectionRequestHandler.Setup(rh => rh.WaitForRequestSenderConnection(It.IsAny())).Returns(false); - var message = Assert.ThrowsException(() => _dataCollectorMain.Run(_args)).Message; + var message = Assert.ThrowsExactly(() => _dataCollectorMain.Run(_args)).Message; Assert.AreEqual(TimeoutErrorMessage, message); } diff --git a/test/datacollector.UnitTests/Program.cs b/test/datacollector.UnitTests/Program.cs deleted file mode 100644 index b41676bbbf..0000000000 --- a/test/datacollector.UnitTests/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollector.UnitTests; - -/// -/// Main entry point for the command line runner. -/// -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/datacollector.UnitTests/TestPlatformDataCollectionEventsTests.cs b/test/datacollector.UnitTests/TestPlatformDataCollectionEventsTests.cs index 13b56f7cf5..6aacd5c2d2 100644 --- a/test/datacollector.UnitTests/TestPlatformDataCollectionEventsTests.cs +++ b/test/datacollector.UnitTests/TestPlatformDataCollectionEventsTests.cs @@ -26,7 +26,7 @@ public TestPlatformDataCollectionEventsTests() [TestMethod] public void RaiseEventsShouldThrowExceptionIfEventArgsIsNull() { - Assert.ThrowsException(() => _events.RaiseEvent(null!)); + Assert.ThrowsExactly(() => _events.RaiseEvent(null!)); } [TestMethod] diff --git a/test/datacollector.UnitTests/TestPlatformDataCollectionLoggerTests.cs b/test/datacollector.UnitTests/TestPlatformDataCollectionLoggerTests.cs index dd6e706cd5..af766dadac 100644 --- a/test/datacollector.UnitTests/TestPlatformDataCollectionLoggerTests.cs +++ b/test/datacollector.UnitTests/TestPlatformDataCollectionLoggerTests.cs @@ -35,27 +35,27 @@ public TestPlatformDataCollectionLoggerTests() [TestMethod] public void LogErrorShouldThrowExceptionIfContextIsNull() { - Assert.ThrowsException(() => _logger.LogError(null!, string.Empty)); + Assert.ThrowsExactly(() => _logger.LogError(null!, string.Empty)); - Assert.ThrowsException(() => _logger.LogError(null!, new Exception())); + Assert.ThrowsExactly(() => _logger.LogError(null!, new Exception())); - Assert.ThrowsException(() => _logger.LogError(null!, string.Empty, new Exception())); + Assert.ThrowsExactly(() => _logger.LogError(null!, string.Empty, new Exception())); } [TestMethod] public void LogErrorShouldThrowExceptionIfTextIsNull() { - Assert.ThrowsException(() => _logger.LogError(_context, (string)null!)); + Assert.ThrowsExactly(() => _logger.LogError(_context, (string)null!)); - Assert.ThrowsException(() => _logger.LogError(_context, null!, new Exception())); + Assert.ThrowsExactly(() => _logger.LogError(_context, null!, new Exception())); } [TestMethod] public void LogErrorShouldThrowExceptionIfExceptionIsNull() { - Assert.ThrowsException(() => _logger.LogError(_context, (Exception?)null!)); + Assert.ThrowsExactly(() => _logger.LogError(_context, (Exception?)null!)); - Assert.ThrowsException(() => _logger.LogError(_context, string.Empty, null!)); + Assert.ThrowsExactly(() => _logger.LogError(_context, string.Empty, null!)); } [TestMethod] diff --git a/test/datacollector.UnitTests/TestPlatformDataCollectionSinkTests.cs b/test/datacollector.UnitTests/TestPlatformDataCollectionSinkTests.cs index 11f6c3fe23..9b7403d522 100644 --- a/test/datacollector.UnitTests/TestPlatformDataCollectionSinkTests.cs +++ b/test/datacollector.UnitTests/TestPlatformDataCollectionSinkTests.cs @@ -41,7 +41,7 @@ public void Cleanup() [TestMethod] public void SendFileAsyncShouldThrowExceptionIfFileTransferInformationIsNull() { - Assert.ThrowsException(() => _dataCollectionSink.SendFileAsync(default!)); + Assert.ThrowsExactly(() => _dataCollectionSink.SendFileAsync(default!)); } [TestMethod] diff --git a/test/datacollector.UnitTests/datacollector.UnitTests.csproj b/test/datacollector.UnitTests/datacollector.UnitTests.csproj index 9af01796fe..1d8cc619ae 100644 --- a/test/datacollector.UnitTests/datacollector.UnitTests.csproj +++ b/test/datacollector.UnitTests/datacollector.UnitTests.csproj @@ -9,18 +9,11 @@ Microsoft.VisualStudio.TestPlatform.DataCollector.UnitTests - Exe - net6.0;net48 + Exe + net9.0;net48 datacollector.UnitTests - - - - - - - diff --git a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs index 81b1b9da2c..b715544586 100644 --- a/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs +++ b/test/testhost.UnitTests/AppDomainEngineInvokerTests.cs @@ -102,7 +102,7 @@ public void AppDomainEngineInvokerShouldUseTestHostStartupConfigAndRuntimeAfterM Assert.AreEqual(legacyUnhandledEleExpectedXml, runtimeEle.Descendants("legacyUnhandledExceptionPolicy").First()?.ToString(), "legacyUnhandledExceptionPolicy element must be of the TestHost one."); - Assert.IsFalse(runtimeEle.ToString().Contains("probing"), "Probing element of TestHost must not be present."); + Assert.DoesNotContain("probing", runtimeEle.ToString(), "Probing element of TestHost must not be present."); var assemblyBindingXName = XName.Get("assemblyBinding", XmlNamespace); var mergedDocAssemblyBindingNodes = runtimeEle.Descendants(assemblyBindingXName); @@ -111,7 +111,7 @@ public void AppDomainEngineInvokerShouldUseTestHostStartupConfigAndRuntimeAfterM var dependentAssemblyXName = XName.Get("dependentAssembly", XmlNamespace); var dependentAssemblyNodes = mergedDocAssemblyBindingNodes.First().Descendants(dependentAssemblyXName); Assert.AreEqual(1, dependentAssemblyNodes.Count(), "AssemblyRedirect of TestHost must be present."); - Assert.IsTrue(dependentAssemblyNodes.First().ToString().Contains("Microsoft.VisualStudio.TestPlatform.ObjectModel"), "Correct AssemblyRedirect must be present."); + Assert.Contains("Microsoft.VisualStudio.TestPlatform.ObjectModel", dependentAssemblyNodes.First().ToString(), "Correct AssemblyRedirect must be present."); var diagEle = doc.Descendants("system.diagnostics").FirstOrDefault(); var appSettingsEle = doc.Descendants("appSettings").FirstOrDefault(); @@ -155,8 +155,8 @@ public void AppDomainEngineInvokerShouldOnlyMergeAssemblyRedirectionsFromTestHos var dependentAssemblyNodes = mergedDocAssemblyBindingNodes.First().Descendants(dependentAssemblyXName); Assert.AreEqual(2, dependentAssemblyNodes.Count(), "AssemblyBinding of TestHost must be present."); - Assert.IsTrue(dependentAssemblyNodes.ElementAt(0).ToString().Contains("Microsoft.VisualStudio.UnitTests"), "First AssemblyRedirect must be of UserConfig."); - Assert.IsTrue(dependentAssemblyNodes.ElementAt(1).ToString().Contains("Microsoft.VisualStudio.TestPlatform.ObjectModel"), "Second AssemblyRedirect must be from TestHost Node."); + Assert.Contains("Microsoft.VisualStudio.UnitTests", dependentAssemblyNodes.ElementAt(0).ToString(), "First AssemblyRedirect must be of UserConfig."); + Assert.Contains("Microsoft.VisualStudio.TestPlatform.ObjectModel", dependentAssemblyNodes.ElementAt(1).ToString(), "Second AssemblyRedirect must be from TestHost Node."); var diagEle = doc.Descendants("system.diagnostics").FirstOrDefault(); var appSettingsEle = doc.Descendants("appSettings").FirstOrDefault(); diff --git a/test/testhost.UnitTests/DefaultEngineInvokerTests.cs b/test/testhost.UnitTests/DefaultEngineInvokerTests.cs index ce72b43937..79d17a3bca 100644 --- a/test/testhost.UnitTests/DefaultEngineInvokerTests.cs +++ b/test/testhost.UnitTests/DefaultEngineInvokerTests.cs @@ -81,7 +81,7 @@ public void InvokeShouldWaitBasedOnTimeoutEnvVariableDuringDataCollectorConnecti public void InvokeShouldThrowExceptionIfDataCollectorConnection() { _mockDataCollectionTestCaseEventSender.Setup(s => s.WaitForRequestSenderConnection(It.IsAny())).Returns(false); - var message = Assert.ThrowsException(() => _engineInvoker.Invoke(ArgsDictionary)).Message; + var message = Assert.ThrowsExactly(() => _engineInvoker.Invoke(ArgsDictionary)).Message; Assert.AreEqual(message, TimeoutErrorMessage); } diff --git a/test/testhost.UnitTests/Program.cs b/test/testhost.UnitTests/Program.cs deleted file mode 100644 index f451d5c15b..0000000000 --- a/test/testhost.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace testhost.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/testhost.UnitTests/TestHostTraceListenerTests.cs b/test/testhost.UnitTests/TestHostTraceListenerTests.cs index ef2624f54a..8bcd1d6663 100644 --- a/test/testhost.UnitTests/TestHostTraceListenerTests.cs +++ b/test/testhost.UnitTests/TestHostTraceListenerTests.cs @@ -42,31 +42,27 @@ public void Cleanup() } [TestMethod] - [ExpectedException(typeof(DebugAssertException))] public void DebugAssertThrowsDebugAssertException() { - Debug.Assert(false); + Assert.ThrowsExactly(() => Debug.Assert(false)); } [TestMethod] - [ExpectedException(typeof(DebugAssertException))] public void DebugFailThrowsDebugAssertException() { - Debug.Fail("fail"); + Assert.ThrowsExactly(() => Debug.Fail("fail")); } [TestMethod] - [ExpectedException(typeof(DebugAssertException))] public void TraceAssertThrowsDebugAssertException() { - Trace.Assert(false); + Assert.ThrowsExactly(() => Trace.Assert(false)); } [TestMethod] - [ExpectedException(typeof(DebugAssertException))] public void TraceFailThrowsDebugAssertException() { - Trace.Fail("fail"); + Assert.ThrowsExactly(() => Trace.Fail("fail")); } [TestMethod] diff --git a/test/testhost.UnitTests/UnitTestClientTests.cs b/test/testhost.UnitTests/UnitTestClientTests.cs index e3c43ac440..2a78255e18 100644 --- a/test/testhost.UnitTests/UnitTestClientTests.cs +++ b/test/testhost.UnitTests/UnitTestClientTests.cs @@ -22,7 +22,7 @@ public void SplitArgumentsShouldHonorDoubleQuotes() var argument = "--port 8080 --endpoint 127.0.0.1:8020 --diag \"abc txt\""; string[] argsArr = UnitTestClient.SplitArguments(argument); - Assert.AreEqual(6, argsArr.Length); + Assert.HasCount(6, argsArr); CollectionAssert.AreEqual(argsArr, expected); } @@ -33,7 +33,7 @@ public void SplitArgumentsShouldHonorSingleQuotes() var argument = "--port 8080 --endpoint 127.0.0.1:8020 --diag \'abc txt\'"; string[] argsArr = UnitTestClient.SplitArguments(argument); - Assert.AreEqual(6, argsArr.Length); + Assert.HasCount(6, argsArr); CollectionAssert.AreEqual(expected, argsArr); } @@ -44,7 +44,7 @@ public void SplitArgumentsShouldSplitAtSpacesOutsideOfQuotes() var argument = "--port 8080 --endpoint 127.0.0.1:8020 --diag abc txt"; string[] argsArr = UnitTestClient.SplitArguments(argument); - Assert.AreEqual(7, argsArr.Length); + Assert.HasCount(7, argsArr); CollectionAssert.AreEqual(expected, argsArr); } @@ -59,7 +59,7 @@ public void RunWhenCliUiLanguageIsSetChangesCultureAndFlowsOverride() bool threadCultureWasSet = false; // Act - We have an exception because we are not passing the right args but that's ok for our test - Assert.ThrowsException(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = lang.Equals(culture)))); + Assert.ThrowsExactly(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = lang.Equals(culture)))); // Assert Assert.IsTrue(threadCultureWasSet, "DefaultThreadCurrentUICulture was not set"); @@ -81,7 +81,7 @@ public void RunWhenVsLangIsSetChangesCultureAndFlowsOverride() bool threadCultureWasSet = false; // Act - We have an exception because we are not passing the right args but that's ok for our test - Assert.ThrowsException(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = lang.Equals(culture)))); + Assert.ThrowsExactly(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = lang.Equals(culture)))); // Assert Assert.IsTrue(threadCultureWasSet, "DefaultThreadCurrentUICulture was not set"); @@ -102,7 +102,7 @@ public void RunWhenNoCultureEnvVarSetDoesNotChangeCultureNorFlowsOverride() bool threadCultureWasSet = false; // Act - We have an exception because we are not passing the right args but that's ok for our test - Assert.ThrowsException(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = true))); + Assert.ThrowsExactly(() => Program.Run(null, new(envVarMock.Object, lang => threadCultureWasSet = true))); // Assert Assert.IsFalse(threadCultureWasSet, "DefaultThreadCurrentUICulture was set"); diff --git a/test/testhost.UnitTests/testhost.UnitTests.csproj b/test/testhost.UnitTests/testhost.UnitTests.csproj index 74724c5158..fd26e4ce51 100644 --- a/test/testhost.UnitTests/testhost.UnitTests.csproj +++ b/test/testhost.UnitTests/testhost.UnitTests.csproj @@ -6,18 +6,11 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe testhost.UnitTests - x64 + x64 - - - - - - - diff --git a/test/vstest.ProgrammerTests/BasicRunAndDiscovery.cs b/test/vstest.ProgrammerTests/BasicRunAndDiscovery.cs index 79f3926154..cd3cd07998 100644 --- a/test/vstest.ProgrammerTests/BasicRunAndDiscovery.cs +++ b/test/vstest.ProgrammerTests/BasicRunAndDiscovery.cs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using FluentAssertions; +using System.Diagnostics.CodeAnalysis; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using vstest.ProgrammerTests.Fakes; +using FluentAssertions; + using Intent; -using System.Diagnostics.CodeAnalysis; + +using vstest.ProgrammerTests.Fakes; #pragma warning disable IDE1006 // Naming Styles namespace vstest.ProgrammerTests; diff --git a/test/vstest.ProgrammerTests/Fakes/FakeProcessHelper.cs b/test/vstest.ProgrammerTests/Fakes/FakeProcessHelper.cs index 4729970c96..5c5eb6f1e2 100644 --- a/test/vstest.ProgrammerTests/Fakes/FakeProcessHelper.cs +++ b/test/vstest.ProgrammerTests/Fakes/FakeProcessHelper.cs @@ -81,6 +81,9 @@ public string GetTestEngineDirectory() } public object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? environmentVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallback) + => LaunchProcess(processPath, arguments, workingDirectory, environmentVariables, errorCallback, exitCallBack, outputCallback, createNoNewWindow: true); + + public object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? environmentVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallback, bool createNoNewWindow) { // TODO: Throw if setting says we can't start new processes; var process = new FakeProcess(FakeErrorAggregator, processPath, arguments, workingDirectory, environmentVariables, errorCallback, exitCallBack, outputCallback); diff --git a/test/vstest.ProgrammerTests/Fakes/FakeTestHost.cs b/test/vstest.ProgrammerTests/Fakes/FakeTestHost.cs index 3a1cb11ab3..5be76c1006 100644 --- a/test/vstest.ProgrammerTests/Fakes/FakeTestHost.cs +++ b/test/vstest.ProgrammerTests/Fakes/FakeTestHost.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace vstest.ProgrammerTests.Fakes; + internal class FakeTestHostFixture : IDisposable { public int Id { get; } diff --git a/test/vstest.ProgrammerTests/MultiTFMRunAndDiscovery.cs b/test/vstest.ProgrammerTests/MultiTFMRunAndDiscovery.cs index 36c9822d57..6814b22ee0 100644 --- a/test/vstest.ProgrammerTests/MultiTFMRunAndDiscovery.cs +++ b/test/vstest.ProgrammerTests/MultiTFMRunAndDiscovery.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Intent; -using FluentAssertions; -using vstest.ProgrammerTests.Fakes; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -11,8 +10,12 @@ using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; -using System.Reflection; -using System.Diagnostics.CodeAnalysis; + +using FluentAssertions; + +using Intent; + +using vstest.ProgrammerTests.Fakes; namespace vstest.ProgrammerTests; diff --git a/test/vstest.ProgrammerTests/vstest.ProgrammerTests.csproj b/test/vstest.ProgrammerTests/vstest.ProgrammerTests.csproj index 5eab35bed3..8239cfe76f 100644 --- a/test/vstest.ProgrammerTests/vstest.ProgrammerTests.csproj +++ b/test/vstest.ProgrammerTests/vstest.ProgrammerTests.csproj @@ -8,12 +8,11 @@ enable true - net6.0 - Exe + net8.0 Exe - + @@ -26,7 +25,7 @@ - + diff --git a/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs index a45cdeebc6..92c11edcf4 100644 --- a/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs +++ b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs @@ -42,7 +42,9 @@ public void CommandLineOptionsDefaultBatchSizeIsTen() [TestMethod] public void CommandLineOptionsDiscoveryDefaultBatchSizeIsThousand() { +#pragma warning disable MSTEST0025 // Use 'Assert.Fail' instead of an always-failing assert Assert.AreEqual(1000, CommandLineOptions.DefaultDiscoveryBatchSize); +#pragma warning restore MSTEST0025 // Use 'Assert.Fail' instead of an always-failing assert } [TestMethod] @@ -71,7 +73,7 @@ public void CommandLineOptionsGetForHasPhoneContextPropertyIfTargetDeviceIsSetRe [TestMethod] public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForNullSource() { - Assert.ThrowsException(() => CommandLineOptions.Instance.AddSource(null!)); + Assert.ThrowsExactly(() => CommandLineOptions.Instance.AddSource(null!)); } [TestMethod] @@ -89,7 +91,7 @@ public void CommandLineOptionsAddSourceShouldConvertRelativePathToAbsolutePath() [TestMethod] public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForInvalidSource() { - Assert.ThrowsException(() => CommandLineOptions.Instance.AddSource("DummySource")); + Assert.ThrowsExactly(() => CommandLineOptions.Instance.AddSource("DummySource")); } [TestMethod] diff --git a/test/vstest.console.UnitTests/CommandLine/InferHelperTests.cs b/test/vstest.console.UnitTests/CommandLine/InferHelperTests.cs index c03e9bc02a..b35684b3a9 100644 --- a/test/vstest.console.UnitTests/CommandLine/InferHelperTests.cs +++ b/test/vstest.console.UnitTests/CommandLine/InferHelperTests.cs @@ -130,7 +130,7 @@ public void AutoDetectArchitectureShouldPopulateSourceArchitectureDictionary() .Returns(Architecture.AnyCPU).Returns(Architecture.X64).Returns(Architecture.X86); Assert.AreEqual(_defaultArchitecture, _inferHelper.AutoDetectArchitecture(new List() { "AnyCPU1.dll", "x64.exe", "x86.dll" }, _defaultArchitecture, out var sourceArchitectures)); - Assert.AreEqual(3, sourceArchitectures.Count); + Assert.HasCount(3, sourceArchitectures); Assert.AreEqual(_defaultArchitecture, sourceArchitectures["AnyCPU1.dll"]); Assert.AreEqual(Architecture.X64, sourceArchitectures["x64.exe"]); Assert.AreEqual(Architecture.X86, sourceArchitectures["x86.dll"]); @@ -240,7 +240,7 @@ public void AutoDetectFrameworkShouldPopulatetheDictionaryForAllTheSources() Assert.AreEqual(_frameworkNet47.Name, _inferHelper.AutoDetectFramework(new List() { "net46.dll", "net47.exe", "net45.dll" }, out var sourceFrameworks).Name); - Assert.AreEqual(3, sourceFrameworks.Count); + Assert.HasCount(3, sourceFrameworks); Assert.AreEqual(_frameworkNet46.Name, sourceFrameworks["net46.dll"].Name); Assert.AreEqual(_frameworkNet47.Name, sourceFrameworks["net47.exe"].Name); Assert.AreEqual(_frameworkNet45.Name, sourceFrameworks["net45.dll"].Name); diff --git a/test/vstest.console.UnitTests/ExceptionUtilities.cs b/test/vstest.console.UnitTests/ExceptionUtilities.cs deleted file mode 100644 index 196abb3c4f..0000000000 --- a/test/vstest.console.UnitTests/ExceptionUtilities.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Globalization; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; - -/// -/// This only exists because there is an issue with MSTest v2 and ThrowsException with a message API. -/// Move to Assert.ThrowException() with a message once the bug is fixed. -/// -public static class ExceptionUtilities -{ - public static void ThrowsException(Action action, string format, params string[] args) - { - var isExceptionThrown = false; - - try - { - action(); - } - catch (Exception ex) - { - Assert.AreEqual(typeof(T), ex.GetType()); - isExceptionThrown = true; - var message = string.Format(CultureInfo.CurrentCulture, format, args); - StringAssert.Contains(ex.Message, message); - } - - Assert.IsTrue(isExceptionThrown, "No Exception Thrown"); - } -} diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 313ec26a25..c659fb07ed 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -24,6 +24,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; [TestClass] +// Because runsettings tests use the instance of RunSettingsManager which is static. +[DoNotParallelize] public class ExecutorUnitTests { private readonly Mock _mockTestPlatformEventSource; @@ -46,16 +48,16 @@ public void ExecutorPrintsSplashScreenTest() Assert.AreEqual(1, exitCode, "Exit code must be One for bad arguments"); // Verify that messages exist - Assert.IsTrue(mockOutput.Messages.Count > 0, "Executor must print at least copyright info"); + Assert.IsNotEmpty(mockOutput.Messages, "Executor must print at least copyright info"); Assert.IsNotNull(mockOutput.Messages.First().Message, "First Printed Message cannot be null or empty"); - StringAssert.Contains(mockOutput.Messages.First().Message, - CommandLineResources.MicrosoftCommandLineTitle.Split(['{'], 2)[0]); + Assert.Contains(CommandLineResources.MicrosoftCommandLineTitle.Split(['{'], 2)[0], + mockOutput.Messages.First().Message!); var suffixIndex = assemblyVersion.IndexOf("-"); var version = suffixIndex == -1 ? assemblyVersion : assemblyVersion.Substring(0, suffixIndex); - StringAssert.Contains(mockOutput.Messages.First().Message, - version); + Assert.Contains(version, + mockOutput.Messages.First().Message!); } [TestMethod] @@ -67,12 +69,13 @@ public void ExecutorShouldNotPrintsSplashScreenIfNoLogoPassed() Assert.AreEqual(1, exitCode, "Exit code must be One for bad arguments"); // Verify that messages exist - Assert.IsTrue(mockOutput.Messages.Count == 1, "Executor should not print no valid arguments provided"); + Assert.HasCount(1, mockOutput.Messages); // Check the part of message before the actual version because that is variable. - Assert.IsFalse( + Assert.DoesNotContain( + CommandLineResources.MicrosoftCommandLineTitle.Split(['{'], 2)[0], mockOutput.Messages.First() - .Message!.Contains(CommandLineResources.MicrosoftCommandLineTitle.Split(['{'], 2)[0])); + .Message!); } [TestMethod] @@ -83,7 +86,7 @@ public void ExecutorShouldSanitizeNoLogoInput() Assert.AreEqual(1, exitCode, "Exit code must be One when no arguments are provided."); - Assert.IsTrue(mockOutput.Messages.Any(message => message.Message!.Contains(CommandLineResources.NoArgumentsProvided))); + Assert.Contains(message => message.Message!.Contains(CommandLineResources.NoArgumentsProvided), mockOutput.Messages); } /// @@ -97,7 +100,7 @@ public void ExecutorEmptyArgsPrintsErrorAndHelpMessage() Assert.AreEqual(1, exitCode, "Exit code must be One when no arguments are provided."); - Assert.IsTrue(mockOutput.Messages.Any(message => message.Message!.Contains(CommandLineResources.NoArgumentsProvided))); + Assert.Contains(message => message.Message!.Contains(CommandLineResources.NoArgumentsProvided), mockOutput.Messages); } [TestMethod] @@ -109,7 +112,7 @@ public void ExecutorWithInvalidArgsShouldPrintErrorMessage() Assert.AreEqual(1, exitCode, "Exit code must be One when no arguments are provided."); - Assert.IsTrue(mockOutput.Messages.Any(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)))); + Assert.Contains(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)), mockOutput.Messages); } [TestMethod] @@ -121,7 +124,7 @@ public void ExecutorWithInvalidArgsShouldPrintHowToUseHelpOption() Assert.AreEqual(1, exitCode, "Exit code must be One when no arguments are provided."); - Assert.IsTrue(mockOutput.Messages.Any(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)))); + Assert.Contains(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)), mockOutput.Messages); } [TestMethod] @@ -133,7 +136,7 @@ public void ExecutorWithInvalidArgsAndValueShouldPrintErrorMessage() Assert.AreEqual(1, exitCode, "Exit code must be One when no arguments are provided."); - Assert.IsTrue(mockOutput.Messages.Any(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)))); + Assert.Contains(message => message.Message!.Contains(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidArgument, badArg)), mockOutput.Messages); } /// @@ -197,7 +200,7 @@ public void ExecutorShouldPrintDotnetVSTestDeprecationMessage(string commandLine new Executor(mockOutput, _mockTestPlatformEventSource.Object, processHelper.Object, environment.Object).Execute(commandLine); - Assert.AreEqual(5, mockOutput.Messages.Count); + Assert.HasCount(5, mockOutput.Messages); Assert.AreEqual(OutputLevel.Warning, mockOutput.Messages[2].Level); Assert.AreEqual("The dotnet vstest command is superseded by dotnet test, which can now be used to run assemblies. See https://aka.ms/dotnet-test.", mockOutput.Messages[2].Message); } @@ -330,7 +333,7 @@ public void ExecutorShouldPrintWarningIfRunningEmulatedOnARM64() var exitCode = new Executor(mockOutput, _mockTestPlatformEventSource.Object, processHelper.Object, environment.Object).Execute(); var assemblyVersion = typeof(Executor).Assembly.GetCustomAttribute()!.InformationalVersion; - Assert.AreEqual(4, mockOutput.Messages.Count); + Assert.HasCount(4, mockOutput.Messages); Assert.AreEqual("vstest.console.exe is running in emulated mode as x64. For better performance, please consider using the native runner vstest.console.arm64.exe.", mockOutput.Messages[1].Message); Assert.AreEqual(OutputLevel.Warning, @@ -350,9 +353,9 @@ public void ExecutorShouldPrintRunnerArchitecture() var exitCode = new Executor(mockOutput, _mockTestPlatformEventSource.Object, processHelper.Object, environment.Object).Execute(); var assemblyVersion = typeof(Executor).Assembly.GetCustomAttribute()!.InformationalVersion; - Assert.AreEqual(3, mockOutput.Messages.Count); - Assert.IsTrue(Regex.IsMatch(mockOutput.Messages[0].Message!, @"VSTest version .* \(x64\)")); - Assert.IsFalse(mockOutput.Messages.Any(message => message.Message!.Contains("vstest.console.exe is running in emulated mode"))); + Assert.HasCount(3, mockOutput.Messages); + Assert.MatchesRegex(@"VSTest version .* \(x64\)", mockOutput.Messages[0].Message!); + Assert.DoesNotContain(message => message.Message!.Contains("vstest.console.exe is running in emulated mode"), mockOutput.Messages); } private class MockOutput : IOutput diff --git a/test/vstest.console.UnitTests/InProcessVsTestConsoleWrapperTests.cs b/test/vstest.console.UnitTests/InProcessVsTestConsoleWrapperTests.cs index ec0f6f778b..57eacd4918 100644 --- a/test/vstest.console.UnitTests/InProcessVsTestConsoleWrapperTests.cs +++ b/test/vstest.console.UnitTests/InProcessVsTestConsoleWrapperTests.cs @@ -87,7 +87,7 @@ public void InProcessWrapperConstructorShouldThrowIfPortIsInvalid() { _mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(-1); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => new InProcessVsTestConsoleWrapper( new ConsoleParameters(), _mockEnvironmentVariableHelper.Object, @@ -116,9 +116,9 @@ public void InProcessWrapperConstructorShouldSetEnvironmentVariablesReceivedAsCo new Mock().Object, new()); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.Count == 1); + Assert.AreEqual(1, ProcessHelper.ExternalEnvironmentVariables?.Count); Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.ContainsKey(environmentVariableName)); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName] == "1"); + Assert.AreEqual("1", ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName]); } [TestMethod] @@ -147,13 +147,13 @@ public void InProcessWrapperConstructorShouldSetEnvironmentVariablesReceivedAsCo new Mock().Object, new()); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.Count == 3); + Assert.AreEqual(3, ProcessHelper.ExternalEnvironmentVariables?.Count); Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.ContainsKey(environmentVariableName1)); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName1] == "1"); + Assert.AreEqual("1", ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName1]); Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.ContainsKey(environmentVariableName2)); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName2] == "1"); + Assert.AreEqual("1", ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName2]); Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?.ContainsKey(environmentVariableName3)); - Assert.IsTrue(ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName3] == "1"); + Assert.AreEqual("1", ProcessHelper.ExternalEnvironmentVariables?[environmentVariableName3]); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index bc9d7cece8..786663f931 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -61,7 +61,7 @@ public ConsoleLoggerTests() [TestMethod] public void InitializeShouldThrowExceptionIfEventsIsNull() { - Assert.ThrowsException(() => _consoleLogger.Initialize(null!, string.Empty)); + Assert.ThrowsExactly(() => _consoleLogger.Initialize(null!, string.Empty)); } [TestMethod] @@ -78,19 +78,19 @@ public void InitializeWithParametersShouldThrowExceptionIfEventsIsNull() { "param1", "value" }, }; - Assert.ThrowsException(() => _consoleLogger.Initialize(null!, parameters)); + Assert.ThrowsExactly(() => _consoleLogger.Initialize(null!, parameters)); } [TestMethod] public void InitializeWithParametersShouldThrowExceptionIfParametersIsEmpty() { - Assert.ThrowsException(() => _consoleLogger.Initialize(new Mock().Object, new Dictionary())); + Assert.ThrowsExactly(() => _consoleLogger.Initialize(new Mock().Object, new Dictionary())); } [TestMethod] public void InitializeWithParametersShouldThrowExceptionIfParametersIsNull() { - Assert.ThrowsException(() => _consoleLogger.Initialize(new Mock().Object, (Dictionary)null!)); + Assert.ThrowsExactly(() => _consoleLogger.Initialize(new Mock().Object, (Dictionary)null!)); } [TestMethod] @@ -159,7 +159,7 @@ public void TestMessageHandlerShouldThrowExceptionIfEventArgsIsNull() var loggerEvents = new InternalTestLoggerEvents(TestSessionMessageLogger.Instance); loggerEvents.EnableEvents(); - Assert.ThrowsException(() => loggerEvents.RaiseTestRunMessage(default!)); + Assert.ThrowsExactly(() => loggerEvents.RaiseTestRunMessage(default!)); } [TestMethod] @@ -230,7 +230,7 @@ public void TestResultHandlerShouldThowExceptionIfEventArgsIsNull() var loggerEvents = new InternalTestLoggerEvents(TestSessionMessageLogger.Instance); loggerEvents.EnableEvents(); - Assert.ThrowsException(() => loggerEvents.RaiseTestResult(default!)); + Assert.ThrowsExactly(() => loggerEvents.RaiseTestResult(default!)); } [TestMethod] @@ -615,24 +615,24 @@ public void TestResultHandlerShouldShowFailedTestsAndPassedTestsForQuietVerbosit loggerEvents.WaitForEventCompletion(); _mockOutput.Verify(o => o.Write(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, - (CommandLineResources.PassedTestIndicator + "!").PadRight(8), + new[] { (CommandLineResources.PassedTestIndicator + "!").PadRight(8), 0.ToString(CultureInfo.InvariantCulture).PadLeft(5), 1.ToString(CultureInfo.InvariantCulture).PadLeft(5), 1.ToString(CultureInfo.InvariantCulture).PadLeft(5), 2.ToString(CultureInfo.InvariantCulture).PadLeft(5), - "1 m 2 s"), OutputLevel.Information), Times.Once); + "1 m 2 s"}), OutputLevel.Information), Times.Once); _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryAssemblyAndFramework, "TestSourcePassed", expectedFramework), OutputLevel.Information), Times.Once); _mockOutput.Verify(o => o.Write(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, - (CommandLineResources.FailedTestIndicator + "!").PadRight(8), + new[] { (CommandLineResources.FailedTestIndicator + "!").PadRight(8), 1.ToString(CultureInfo.InvariantCulture).PadLeft(5), 1.ToString(CultureInfo.InvariantCulture).PadLeft(5), 1.ToString(CultureInfo.InvariantCulture).PadLeft(5), 3.ToString(CultureInfo.InvariantCulture).PadLeft(5), - "1 h 2 m"), OutputLevel.Information), Times.Once); + "1 h 2 m" }), OutputLevel.Information), Times.Once); _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryAssemblyAndFramework, "TestSource", @@ -666,8 +666,8 @@ public void TestResultHandlerShouldNotShowformattedFailedTestsAndPassedTestsForO loggerEvents.RaiseTestRunComplete(new TestRunCompleteEventArgs(new Mock().Object, false, false, null, new Collection(), new Collection(), TimeSpan.FromSeconds(1))); loggerEvents.WaitForEventCompletion(); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, CommandLineResources.PassedTestIndicator, 2, 1, 0, 1, "1 m 2 s", "TestSourcePassed", "(net462)"), OutputLevel.Information), Times.Never); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, CommandLineResources.FailedTestIndicator, 5, 1, 1, 1, "1 h 6 m", "TestSource", "(net462)"), OutputLevel.Information), Times.Never); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, new object[] { CommandLineResources.PassedTestIndicator, 2, 1, 0, 1, "1 m 2 s", "TestSourcePassed", "(net462)" }), OutputLevel.Information), Times.Never); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummary, new object[] { CommandLineResources.FailedTestIndicator, 5, 1, 1, 1, "1 h 6 m", "TestSource", "(net462)" }), OutputLevel.Information), Times.Never); } [TestMethod] @@ -781,7 +781,7 @@ public void TestResultHandlerShouldWriteToNoTestResultForQuietVerbosity() [DataRow("[1 h]", new int[5] { 0, 1, 0, 5, 78 })] [DataRow("[5 m]", new int[5] { 0, 0, 5, 0, 78 })] [DataRow("[4 s]", new int[5] { 0, 0, 0, 4, 0 })] - [DataTestMethod] + [TestMethod] public void TestResultHandlerForTestResultWithDurationShouldPrintDurationInfo(string expectedDuration, int[] timeSpanArgs) { var loggerEvents = new InternalTestLoggerEvents(TestSessionMessageLogger.Instance); @@ -804,7 +804,7 @@ public void TestResultHandlerForTestResultWithDurationShouldPrintDurationInfo(st _mockOutput.Verify(o => o.WriteLine("TestName " + expectedDuration, OutputLevel.Information), Times.Once()); } - [DataTestMethod] + [TestMethod] public void TestResultHandlerForTestResultWithDurationLessThanOneMsShouldPrintDurationInfo() { var loggerEvents = new InternalTestLoggerEvents(TestSessionMessageLogger.Instance); @@ -940,7 +940,7 @@ public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsCanceled() } loggerEvents.CompleteTestRun(null, true, false, null, null, null, new TimeSpan(1, 0, 0, 0)); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryForCanceledOrAbortedRun), OutputLevel.Information), Times.Once()); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryForCanceledOrAbortedRun, Array.Empty()), OutputLevel.Information), Times.Once()); _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryFailedTests, 1), OutputLevel.Information), Times.Once()); _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryPassedTests, 0), OutputLevel.Information), Times.Never()); _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummarySkippedTests, 0), OutputLevel.Information), Times.Never()); @@ -972,6 +972,7 @@ public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsAborted() { { "verbosity", "normal" } }; + _consoleLogger.Initialize(loggerEvents, parameters); foreach (var testResult in GetTestResultObject(TestOutcome.Failed)) @@ -980,7 +981,7 @@ public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsAborted() } loggerEvents.CompleteTestRun(null, false, true, null, null, null, new TimeSpan(1, 0, 0, 0)); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryForCanceledOrAbortedRun), OutputLevel.Information), Times.Once()); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestRunSummaryForCanceledOrAbortedRun, Array.Empty()), OutputLevel.Information), Times.Once()); _mockOutput.Verify(o => o.WriteLine(CommandLineResources.TestRunAborted, OutputLevel.Error), Times.Once()); } diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTimingRegressionTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTimingRegressionTests.cs new file mode 100644 index 0000000000..58523187d1 --- /dev/null +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTimingRegressionTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Internal; + +/// +/// Regression tests for ConsoleLogger MinimalTestResult timing fix. +/// +[TestClass] +public class ConsoleLoggerTimingRegressionTests +{ + // Regression test for #5143 — Fix timing in simple log + // When the test framework (e.g. xUnit 2.x.x) does not report proper start/end times, + // the Duration is non-zero but EndTime - StartTime could be near zero. + // The fix adjusts StartTime = EndTime - Duration in that case. + [TestMethod] + public void TestResult_WhenDurationExceedsTimeSpan_StartTimeShouldBeAdjustable() + { + var testCase = new TestCase("Test.Method1", new Uri("executor://test"), "test.dll"); + var now = DateTimeOffset.UtcNow; + + var testResult = new TestResult(testCase) + { + // Simulate xUnit behavior: StartTime ≈ EndTime (both set to "now") + // but Duration is meaningful + StartTime = now, + EndTime = now, + Duration = TimeSpan.FromSeconds(5), + Outcome = TestOutcome.Passed + }; + + // The fix checks: if EndTime - StartTime < Duration, then StartTime = EndTime - Duration + // Verify this invariant + if (testResult.EndTime - testResult.StartTime < testResult.Duration) + { + var adjustedStartTime = testResult.EndTime - testResult.Duration; + Assert.IsLessThan(testResult.EndTime, adjustedStartTime); + Assert.AreEqual(testResult.Duration, testResult.EndTime - adjustedStartTime); + } + } + + // Regression test for #5143 + [TestMethod] + public void TestResult_WhenStartEndTimeCorrect_NoAdjustmentNeeded() + { + var testCase = new TestCase("Test.Method2", new Uri("executor://test"), "test.dll"); + var start = DateTimeOffset.UtcNow; + var end = start + TimeSpan.FromSeconds(3); + + var testResult = new TestResult(testCase) + { + StartTime = start, + EndTime = end, + Duration = TimeSpan.FromSeconds(3), + Outcome = TestOutcome.Passed + }; + + // EndTime - StartTime matches Duration, no adjustment needed + Assert.AreEqual(testResult.Duration, testResult.EndTime - testResult.StartTime); + } + + // Regression test for #5143 + [TestMethod] + public void TestResult_ZeroDuration_ShouldNotCauseIssues() + { + var testCase = new TestCase("Test.Method3", new Uri("executor://test"), "test.dll"); + var now = DateTimeOffset.UtcNow; + + var testResult = new TestResult(testCase) + { + StartTime = now, + EndTime = now, + Duration = TimeSpan.Zero, + Outcome = TestOutcome.Passed + }; + + // With zero duration, no adjustment should be needed + Assert.AreEqual(TimeSpan.Zero, testResult.Duration); + } +} diff --git a/test/vstest.console.UnitTests/Internal/FilePatternParserTests.cs b/test/vstest.console.UnitTests/Internal/FilePatternParserTests.cs index 571936d859..ed65d73e99 100644 --- a/test/vstest.console.UnitTests/Internal/FilePatternParserTests.cs +++ b/test/vstest.console.UnitTests/Internal/FilePatternParserTests.cs @@ -101,7 +101,7 @@ public void FilePatternParserShouldCheckIfFileExistsIfFullPathGiven() // Assert _mockFileHelper.Verify(x => x.Exists(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"))); - Assert.IsTrue(matchingFiles.Contains(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"))); + Assert.Contains(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"), matchingFiles); } [TestMethod] @@ -111,7 +111,62 @@ public void FilePatternParserShouldThrowCommandLineExceptionIfFileDoesNotExist() _mockFileHelper.Setup(x => x.Exists(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"))).Returns(false); _mockMatcherHelper.Setup(x => x.Execute(It.IsAny())).Returns(patternMatchingResult); - Assert.ThrowsException(() => _filePatternParser.GetMatchingFiles(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"))); + Assert.ThrowsExactly(() => _filePatternParser.GetMatchingFiles(TranslatePath(@"E:\path\to\project\tests\Blame.Tests\\abc.Tests.dll"))); + } + + [TestMethod] + // only on windows because we don't translate the path to be valid linux / mac path + [OSCondition(OperatingSystems.Windows)] + public void FilePatternParserShouldCorrectlySplitPatternAndDirectoryWithForwardSlashes() + { + var patternMatchingResult = new PatternMatchingResult(new List()); + _mockMatcherHelper.Setup(x => x.Execute(It.IsAny())).Returns(patternMatchingResult); + + // Test with forward slashes - this should work on all platforms + // This specifically tests the fix for issue #14993 + _filePatternParser.GetMatchingFiles("C:/Users/someUser/Desktop/a/c/*bc.dll"); + + // Assert that the pattern is parsed correctly + _mockMatcherHelper.Verify(x => x.AddInclude("*bc.dll")); + // On Windows, the path may be normalized, so we verify the key components are present + _mockMatcherHelper.Verify(x => x.Execute(It.Is(y => + y.FullName.Contains("someUser") && y.FullName.Contains("Desktop") && + y.FullName.Contains("a") && y.FullName.EndsWith("c")))); + } + + [TestMethod] + // only on windows because we don't translate the path to be valid linux / mac path + [OSCondition(OperatingSystems.Windows)] + public void FilePatternParserShouldCorrectlySplitWithArbitraryDirectoryDepthWithForwardSlashes() + { + var patternMatchingResult = new PatternMatchingResult(new List()); + _mockMatcherHelper.Setup(x => x.Execute(It.IsAny())).Returns(patternMatchingResult); + + // Test with forward slashes and recursive patterns + _filePatternParser.GetMatchingFiles("C:/Users/someUser/**/c/*bc.txt"); + + // Assert + _mockMatcherHelper.Verify(x => x.AddInclude("**/c/*bc.txt")); + _mockMatcherHelper.Verify(x => x.Execute(It.Is(y => + y.FullName.Contains("someUser")))); + } + + [TestMethod] + // only on windows because we don't translate the path to be valid linux / mac path + [OSCondition(OperatingSystems.Windows)] + public void FilePatternParserShouldHandleForwardSlashesWithoutThrowingException() + { + var patternMatchingResult = new PatternMatchingResult(new List()); + _mockMatcherHelper.Setup(x => x.Execute(It.IsAny())).Returns(patternMatchingResult); + + // This is the specific case from the original bug report that was throwing ArgumentOutOfRangeException + // Before the fix: System.ArgumentOutOfRangeException: length ('-1') must be a non-negative value + _filePatternParser.GetMatchingFiles("C:/path/to/my/tests/*_Tests.dll"); + + // Assert that we successfully parse without throwing and get the expected pattern + _mockMatcherHelper.Verify(x => x.AddInclude("*_Tests.dll")); + _mockMatcherHelper.Verify(x => x.Execute(It.Is(y => + y.FullName.Contains("path") && y.FullName.Contains("tests")))); } private static string TranslatePath(string path) diff --git a/test/vstest.console.UnitTests/Internal/ProgressIndicatorTests.cs b/test/vstest.console.UnitTests/Internal/ProgressIndicatorTests.cs index e4dc72df7e..8c15b5d78d 100644 --- a/test/vstest.console.UnitTests/Internal/ProgressIndicatorTests.cs +++ b/test/vstest.console.UnitTests/Internal/ProgressIndicatorTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Internal; [TestClass] public class ProgressIndicatorTests { + private readonly SteppableTimer _steppableTimer; private readonly ProgressIndicator _indicator; private readonly Mock _consoleOutput; private readonly Mock _consoleHelper; @@ -22,7 +23,8 @@ public ProgressIndicatorTests() _consoleHelper = new Mock(); _consoleHelper.Setup(c => c.WindowWidth).Returns(100); _consoleHelper.Setup(c => c.CursorTop).Returns(20); - _indicator = new ProgressIndicator(_consoleOutput.Object, _consoleHelper.Object); + _steppableTimer = new SteppableTimer(); + _indicator = new ProgressIndicator(_consoleOutput.Object, _consoleHelper.Object, _steppableTimer); } [TestCleanup] @@ -40,16 +42,23 @@ public void StartShouldStartPrintingProgressMessage() } [TestMethod] - public void StartShouldShowProgressMessage() + public void StartShouldShowProgressMessageAndDotForEveryTimeATimerFires() { - _indicator.Start(); - _consoleHelper.Setup(c => c.CursorLeft).Returns(30); - System.Threading.Thread.Sleep(1500); + _indicator.Start(); Assert.IsTrue(_indicator.IsRunning); _consoleOutput.Verify(m => m.Write("Test run in progress.", OutputLevel.Information), Times.Once); + + _steppableTimer.Step(); + _consoleOutput.Verify(m => m.Write(".", OutputLevel.Information), Times.Once); + + _steppableTimer.Step(); + + _consoleOutput.Verify(m => m.Write(".", OutputLevel.Information), Times.Exactly(2)); + + _indicator.Stop(); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Internal/SteppableTimer.cs b/test/vstest.console.UnitTests/Internal/SteppableTimer.cs new file mode 100644 index 0000000000..ba5f62ba81 --- /dev/null +++ b/test/vstest.console.UnitTests/Internal/SteppableTimer.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Timers; + +using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Internal; + +internal class SteppableTimer : ISystemTimersTimer +{ + private bool _isStarted; + + public void Step() + { + if (_isStarted) + { + // Some craziness with no constructor here. +#if NET + var elapsed = new System.Timers.ElapsedEventArgs(DateTime.UtcNow); +#else + var elapsed = ElapsedEventArgs.Empty as ElapsedEventArgs; +#endif + Elapsed?.Invoke(this, elapsed); + } + } + + private event ElapsedEventHandler? Elapsed = delegate { }; + + event ElapsedEventHandler? ISystemTimersTimer.Elapsed + { + add + { + Elapsed += value; + } + + remove + { + Elapsed -= value; + } + } + + void IDisposable.Dispose() + { + } + + void ISystemTimersTimer.Start() + { + _isStarted = true; + } + + void ISystemTimersTimer.Stop() + { + _isStarted = false; + } +} diff --git a/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs index bca0bd8f0f..c8b1645c71 100644 --- a/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs +++ b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs @@ -36,7 +36,9 @@ public AeDebuggerArgumentProcessorTest() [TestMethod] public void AeDebuggerArgumentProcessorCommandName() { +#pragma warning disable MSTEST0032 // Assertion condition is always true Assert.AreEqual("/AeDebugger", AeDebuggerArgumentProcessor.CommandName); +#pragma warning restore MSTEST0032 // Assertion condition is always true } [TestMethod] @@ -59,11 +61,11 @@ public void AeDebuggerArgumentProcessorReturnsCorrectTypes() [TestMethod] public void AeDebuggerArgumentExecutor_InvalidCtor() { - Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, _output.Object, null!)); - Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, null!, _environmentVariableHelper.Object)); - Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, null!, _output.Object, _environmentVariableHelper.Object)); - Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(_environment.Object, null!, _processHelper.Object, _output.Object, _environmentVariableHelper.Object)); - Assert.ThrowsException(() => new AeDebuggerArgumentExecutor(null!, _fileHelper.Object, _processHelper.Object, _output.Object, _environmentVariableHelper.Object)); + Assert.ThrowsExactly(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, _output.Object, null!)); + Assert.ThrowsExactly(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, _processHelper.Object, null!, _environmentVariableHelper.Object)); + Assert.ThrowsExactly(() => new AeDebuggerArgumentExecutor(_environment.Object, _fileHelper.Object, null!, _output.Object, _environmentVariableHelper.Object)); + Assert.ThrowsExactly(() => new AeDebuggerArgumentExecutor(_environment.Object, null!, _processHelper.Object, _output.Object, _environmentVariableHelper.Object)); + Assert.ThrowsExactly(() => new AeDebuggerArgumentExecutor(null!, _fileHelper.Object, _processHelper.Object, _output.Object, _environmentVariableHelper.Object)); } [TestMethod] @@ -80,7 +82,7 @@ public void AeDebuggerArgumentExecutor_NullArgument() public void AeDebuggerArgumentExecutor_WrongInstallUnistallCommand(string wrongCommand) { _executor.Initialize(wrongCommand); - Assert.ThrowsException(() => _executor.Execute()); + Assert.ThrowsExactly(() => _executor.Execute()); } [TestMethod] @@ -135,8 +137,9 @@ public void AeDebuggerArgumentExecutor_ProcdumpArgument(string command, bool ins null, It.IsAny>(), It.IsAny>(), - It.IsAny>())) - .Returns((string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack) => + It.IsAny>(), + It.IsAny())) + .Returns((string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack, bool createNoNewWindow) => { Assert.IsTrue(install ? arguments == "-ma -i" : arguments == "-u"); return new object(); diff --git a/test/vstest.console.UnitTests/Processors/ArtifactProcessingCollectModeProcessorTest.cs b/test/vstest.console.UnitTests/Processors/ArtifactProcessingCollectModeProcessorTest.cs index 0c730b207d..533852b3d0 100644 --- a/test/vstest.console.UnitTests/Processors/ArtifactProcessingCollectModeProcessorTest.cs +++ b/test/vstest.console.UnitTests/Processors/ArtifactProcessingCollectModeProcessorTest.cs @@ -14,7 +14,7 @@ public class ArtifactProcessingCollectModeProcessorTest { [TestMethod] public void ProcessorExecutorInitialize_ShouldFailIfNullCommandOption() => - Assert.ThrowsException(() => new ArtifactProcessingCollectModeProcessorExecutor(null!)); + Assert.ThrowsExactly(() => new ArtifactProcessingCollectModeProcessorExecutor(null!)); [TestMethod] public void ProcessorExecutorInitialize_ShouldNotFailIfNullArg() diff --git a/test/vstest.console.UnitTests/Processors/ArtifactProcessingPostProcessModeProcessorTest.cs b/test/vstest.console.UnitTests/Processors/ArtifactProcessingPostProcessModeProcessorTest.cs index 78fea1b91f..d8d2cd30d7 100644 --- a/test/vstest.console.UnitTests/Processors/ArtifactProcessingPostProcessModeProcessorTest.cs +++ b/test/vstest.console.UnitTests/Processors/ArtifactProcessingPostProcessModeProcessorTest.cs @@ -21,8 +21,8 @@ public class ArtifactProcessingPostProcessModeProcessorTest [TestMethod] public void ProcessorExecutorInitialize_ShouldFailIfNullCtor() { - Assert.ThrowsException(() => new ArtifactProcessingPostProcessModeProcessorExecutor(null!, _artifactProcessingManagerMock.Object)); - Assert.ThrowsException(() => new ArtifactProcessingPostProcessModeProcessorExecutor(new CommandLineOptions(), null!)); + Assert.ThrowsExactly(() => new ArtifactProcessingPostProcessModeProcessorExecutor(null!, _artifactProcessingManagerMock.Object)); + Assert.ThrowsExactly(() => new ArtifactProcessingPostProcessModeProcessorExecutor(new CommandLineOptions(), null!)); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs index 3a40e52bfb..181517cfa5 100644 --- a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs @@ -7,9 +7,9 @@ using Microsoft.VisualStudio.TestPlatform.CommandLine; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; -using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; @@ -157,7 +157,7 @@ public void InitializeShouldThrowErrorIfArgumentIsInValid(string arg) var args = new string[] { arg }; var str = CommandLineResources.MalformedRunSettingsKey; - CommandLineException ex = Assert.ThrowsException(() => _executor.Initialize(args)); + CommandLineException ex = Assert.ThrowsExactly(() => _executor.Initialize(args)); Assert.AreEqual(str, ex.Message); } @@ -178,11 +178,8 @@ public void InitializeShouldIgnoreThrowExceptionIfKeyHasWhiteSpace() { var args = new string[] { "MST est.DeploymentEnabled=False" }; - Action action = () => _executor.Initialize(args); - - ExceptionUtilities.ThrowsException( - action, - "One or more runsettings provided contain invalid token"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(args)); + Assert.Contains("One or more runsettings provided contain invalid token", ex.Message); } [TestMethod] @@ -315,8 +312,8 @@ public void InitializeShouldNotUpdateCommandLineOptionsArchitectureAndFxIfNotPro Assert.IsFalse(_commandLineOptions.FrameworkVersionSpecified); } - [DynamicData(nameof(TestRunParameterArgValidTestCases), DynamicDataSourceType.Method)] - [DataTestMethod] + [DynamicData(nameof(TestRunParameterArgValidTestCases))] + [TestMethod] public void InitializeShouldValidateTestRunParameter(string arg, string runSettingsWithTestRunParameters) { var args = new string[] { arg }; @@ -327,14 +324,14 @@ public void InitializeShouldValidateTestRunParameter(string arg, string runSetti Assert.AreEqual(runSettingsWithTestRunParameters, _settingsProvider.ActiveRunSettings.SettingsXml); } - [DynamicData(nameof(TestRunParameterArgInvalidTestCases), DynamicDataSourceType.Method)] - [DataTestMethod] + [DynamicData(nameof(TestRunParameterArgInvalidTestCases))] + [TestMethod] public void InitializeShouldThrowErrorIfTestRunParameterNodeIsInValid(string arg) { var args = new string[] { arg }; var str = string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidTestRunParameterArgument, arg); - CommandLineException ex = Assert.ThrowsException(() => _executor.Initialize(args)); + CommandLineException ex = Assert.ThrowsExactly(() => _executor.Initialize(args)); Assert.AreEqual(str, ex.Message); } diff --git a/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs index 09613d6c82..57f2d0afce 100644 --- a/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs @@ -86,19 +86,19 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowIfArgumentIsNull() { - Assert.ThrowsException(() => _executor.Initialize(null)); + Assert.ThrowsExactly(() => _executor.Initialize(null)); } [TestMethod] public void InitializeShouldNotThrowIfArgumentIsEmpty() { - Assert.ThrowsException(() => _executor.Initialize(string.Empty)); + Assert.ThrowsExactly(() => _executor.Initialize(string.Empty)); } [TestMethod] public void InitializeShouldNotThrowIfArgumentIsWhiteSpace() { - Assert.ThrowsException(() => _executor.Initialize(" ")); + Assert.ThrowsExactly(() => _executor.Initialize(" ")); } [TestMethod] @@ -114,21 +114,10 @@ public void InitializeShouldThrowExceptionWhenTestSettingsIsEnabled() runsettings.LoadSettingsXml(runsettingsString); _settingsProvider.SetActiveRunSettings(runsettings); - bool exceptionThrown = false; - - try - { - _executor.Initialize("MyDataCollector"); - } - catch (SettingsException ex) - { - exceptionThrown = true; - Assert.AreEqual( - "--Collect|/Collect:\"MyDataCollector\" is not supported if test run is configured using testsettings.", - ex.Message); - } - - Assert.IsTrue(exceptionThrown, "Initialize should throw exception"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("MyDataCollector")); + Assert.AreEqual( + "--Collect|/Collect:\"MyDataCollector\" is not supported if test run is configured using testsettings.", + ex.Message); } [TestMethod] @@ -597,7 +586,7 @@ public void InitializeShouldThrowExceptionWhenInvalidCollectorNameProvided() runsettings.LoadSettingsXml(runsettingsString); _settingsProvider.SetActiveRunSettings(runsettings); - Assert.ThrowsException(() => _executor.Initialize("MyDataCollector=SomeSetting")); + Assert.ThrowsExactly(() => _executor.Initialize("MyDataCollector=SomeSetting")); } [TestMethod] @@ -608,7 +597,7 @@ public void InitializeShouldThrowExceptionWhenInvalidConfigurationsProvided() runsettings.LoadSettingsXml(runsettingsString); _settingsProvider.SetActiveRunSettings(runsettings); - Assert.ThrowsException(() => _executor.Initialize("MyDataCollector;SomeSetting")); + Assert.ThrowsExactly(() => _executor.Initialize("MyDataCollector;SomeSetting")); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs index ca33ec99fe..1e934f454f 100644 --- a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs @@ -34,14 +34,14 @@ public void DisableAutoFakesArgumentProcessorMetadataShouldProvideAppropriateCap [TestMethod] public void DisableAutoFakesArgumentProcessorExecutorShouldThrowIfArgumentIsNullOrEmpty() { - Assert.ThrowsException(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize(string.Empty)); - Assert.ThrowsException(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize(" ")); + Assert.ThrowsExactly(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize(string.Empty)); + Assert.ThrowsExactly(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize(" ")); } [TestMethod] public void DisableAutoFakesArgumentProcessorExecutorShouldThrowIfArgumentIsNotBooleanString() { - Assert.ThrowsException(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize("DisableAutoFakes")); + Assert.ThrowsExactly(() => _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize("DisableAutoFakes")); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index 2a09e0145b..bed998f47f 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -211,7 +211,6 @@ public void InitializeShouldWarnIfIncorrectParameterIsSpecifiedForCollectDumpOpt } [TestMethod] - [ExpectedException(typeof(CommandLineException))] public void InitializeShouldThrowIfInvalidParameterFormatIsSpecifiedForCollectDumpOption() { var invalidString = "CollectDump;sdf=sdg;;as;a="; @@ -225,32 +224,7 @@ public void InitializeShouldThrowIfInvalidParameterFormatIsSpecifiedForCollectDu _mockEnvronment.Setup(x => x.Architecture) .Returns(PlatformArchitecture.X64); - _executor.Initialize(invalidString); - _mockOutput.Verify(x => x.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidBlameArgument, invalidString), OutputLevel.Warning)); - - Assert.IsNotNull(_settingsProvider.ActiveRunSettings); - Assert.AreEqual(string.Join(Environment.NewLine, - "", - "", - " ", - " ", - " ", - " ", - " C:\\dir\\TestResults", - " ", - " ", - " ", - " ", - " ", - " C:\\dir\\TestResults", - " ", - " ", - " ", - " ", - " ", - " ", - ""), - _settingsProvider.ActiveRunSettings.SettingsXml); + Assert.ThrowsExactly(() => _executor.Initialize(invalidString)); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs index 405fee6e19..a541cbdcc6 100644 --- a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs @@ -101,7 +101,7 @@ public void InitializeShouldCreateEntryForCodeCoverageInRunSettingsIfNotAlreadyP var dataCollectorsFriendlyNames = XmlRunSettingsUtilities.GetDataCollectorsFriendlyName(_settingsProvider.ActiveRunSettings .SettingsXml!); - Assert.IsTrue(dataCollectorsFriendlyNames.Contains("Code Coverage"), + Assert.Contains("Code Coverage", dataCollectorsFriendlyNames, "Code coverage setting in not available in runsettings"); } diff --git a/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs index 52f1bce87b..e6766808e3 100644 --- a/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableDiagArgumentProcessorTests.cs @@ -118,14 +118,7 @@ public void EnableDiagArgumentProcessorExecutorShouldThrowIfInvalidArgument(stri [DataRow("log.log")] public void EnableDiagArgumentProcessorExecutorShouldNotThrowIfValidArgument(string argument) { - try - { - _diagProcessor.Executor!.Value.Initialize(argument); - } - catch (Exception ex) - { - Assert.Fail("Expected no exception, but got: " + ex.Message); - } + _diagProcessor.Executor!.Value.Initialize(argument); } [TestMethod] @@ -178,7 +171,7 @@ public TestableEnableDiagArgumentProcessor(IFileHelper fileHelper) private void EnableDiagArgumentProcessorExecutorShouldThrowIfInvalidArgument(string argument, string exceptionMessage) { - var e = Assert.ThrowsException(() => _diagProcessor.Executor!.Value.Initialize(argument)); - StringAssert.Contains(e.Message, exceptionMessage); + var e = Assert.ThrowsExactly(() => _diagProcessor.Executor!.Value.Initialize(argument)); + Assert.Contains(exceptionMessage, e.Message); } } diff --git a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs index 40ef827514..bda4a022a6 100644 --- a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs @@ -13,6 +13,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] +// Because runsettings tests use the instance of RunSettingsManager which is static. +[DoNotParallelize] public class EnableLoggersArgumentProcessorTests { [TestInitialize] @@ -62,16 +64,10 @@ public void CapabilitiesShouldAppropriateProperties() public void ExectorInitializeShouldThrowExceptionIfInvalidArgumentIsPassed(string argument) { var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); - try - { - executor.Initialize(argument); - } - catch (Exception e) - { - string exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.LoggerUriInvalid, argument); - Assert.IsTrue(e.GetType().Equals(typeof(CommandLineException))); - Assert.IsTrue(e.Message.Contains(exceptionMessage)); - } + var e = Assert.ThrowsExactly(() => executor.Initialize(argument)); + string exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.LoggerUriInvalid, argument); + Assert.IsInstanceOfType(e); + Assert.Contains(exceptionMessage, e.Message); } [TestMethod] @@ -241,7 +237,7 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerWhenRunSettingsNotPassed() "; - Assert.IsTrue(RunSettingsManager.Instance.ActiveRunSettings!.SettingsXml!.Contains(expectedSettingsXml)); + Assert.Contains(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings!.SettingsXml!); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs index dd19bc886f..4c827f9d77 100644 --- a/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs @@ -76,7 +76,7 @@ public void AppendsEnvironmentVariableToRunSettings() Assert.IsNotNull(inIsolation, "Isolation must be forced, an InIsolation entry was missing!"); var variables = environmentVariables.Elements().ToArray(); - Assert.AreEqual(1, variables.Length, "Environment variable count mismatched!"); + Assert.HasCount(1, variables); Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is not set to true."); Assert.AreEqual("VARIABLE", variables[0].Name.LocalName); @@ -107,7 +107,7 @@ public void AppendsMultipleEnvironmentVariablesToRunSettings() Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is not set to true."); var variables = environmentVariables.Elements().ToArray(); - Assert.AreEqual(3, variables.Length, "Environment variable count mismatched!"); + Assert.HasCount(3, variables); Assert.AreEqual("VARIABLE_ONE", variables[0].Name.LocalName); Assert.AreEqual("VALUE", variables[0].Value); @@ -140,7 +140,7 @@ public void InIsolationValueShouldBeOverriden() Assert.AreEqual("true", inIsolation.Value, "Isolation must be forced, InIsolation is overriden to true."); var variables = environmentVariables.Elements().ToArray(); - Assert.AreEqual(1, variables.Length, "Environment variable count mismatched!"); + Assert.HasCount(1, variables); Assert.AreEqual("VARIABLE", variables[0].Name.LocalName); Assert.AreEqual("VALUE", variables[0].Value); diff --git a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs index 69d8e91974..35a7c8396e 100644 --- a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Globalization; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -48,7 +50,7 @@ public void CapabilitiesShouldReturnAppropriateProperties() { var capabilities = new FrameworkArgumentProcessorCapabilities(); Assert.AreEqual("/Framework", capabilities.CommandName); - StringAssert.Contains(capabilities.HelpContentResourceName, "Valid values are \".NETFramework,Version=v4.5.1\", \".NETCoreApp,Version=v1.0\""); + Assert.Contains("Valid values are \".NETFramework,Version=v4.5.1\", \".NETCoreApp,Version=v1.0\"", capabilities.HelpContentResourceName); Assert.AreEqual(HelpContentPriority.FrameworkArgumentProcessorHelpPriority, capabilities.HelpPriority); Assert.IsFalse(capabilities.IsAction); @@ -67,26 +69,22 @@ public void CapabilitiesShouldReturnAppropriateProperties() public void InitializeShouldThrowIfArgumentIsNull() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize(null), - "The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:\".NETFramework,Version=v4.5.1\""); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(null)); + Assert.Contains("The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:\".NETFramework,Version=v4.5.1\"", ex.Message); } [TestMethod] public void InitializeShouldThrowIfArgumentIsEmpty() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize(" "), - "The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:\".NETFramework,Version=v4.5.1\""); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(" ")); + Assert.Contains("The /Framework argument requires the target .Net Framework version for the test run. Example: /Framework:\".NETFramework,Version=v4.5.1\"", ex.Message); } [TestMethod] public void InitializeShouldThrowIfArgumentIsInvalid() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize("foo"), - "Invalid .Net Framework version:{0}. Please give the fullname of the TargetFramework(Example: .NETCoreApp,Version=v2.0). Other supported .Net Framework versions are Framework40, Framework45, FrameworkCore10 and FrameworkUap10.", - "foo"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("foo")); + Assert.Contains(string.Format(CultureInfo.CurrentCulture, "Invalid .Net Framework version:{0}. Please give the fullname of the TargetFramework(Example: .NETCoreApp,Version=v2.0). Other supported .Net Framework versions are Framework40, Framework45, FrameworkCore10 and FrameworkUap10.", "foo"), ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs index 333d6ece1c..e39f6feedd 100644 --- a/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/HelpArgumentProcessorTests.cs @@ -69,11 +69,11 @@ public void ExecuterExecuteWritesAppropriateDataToConsole() var output = new DummyConsoleOutput(); executor.Output = output; _ = executor.Execute(); - Assert.IsTrue(output.Lines.Contains("Usage: vstest.console.exe [Arguments] [Options] [[--] ...]]")); - Assert.IsTrue(output.Lines.Contains("Arguments:")); - Assert.IsTrue(output.Lines.Contains("Options:")); - Assert.IsTrue(output.Lines.Contains("Description: Runs tests from the specified files.")); - Assert.IsTrue(output.Lines.Contains(" To run tests:" + Environment.NewLine + " >vstest.console.exe tests.dll " + Environment.NewLine + " To run tests with additional settings such as data collectors:" + Environment.NewLine + " >vstest.console.exe tests.dll /Settings:Local.RunSettings")); + Assert.Contains("Usage: vstest.console.exe [Arguments] [Options] [[--] ...]]", output.Lines); + Assert.Contains("Arguments:", output.Lines); + Assert.Contains("Options:", output.Lines); + Assert.Contains("Description: Runs tests from the specified files.", output.Lines); + Assert.Contains(" To run tests:" + Environment.NewLine + " >vstest.console.exe tests.dll " + Environment.NewLine + " To run tests with additional settings such as data collectors:" + Environment.NewLine + " >vstest.console.exe tests.dll /Settings:Local.RunSettings", output.Lines); } } diff --git a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs index 2889fe8e0f..49a4f07346 100644 --- a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs @@ -62,9 +62,8 @@ public void InIsolationArgumentProcessorMetadataShouldProvideAppropriateCapabili public void InIsolationArgumentProcessorExecutorShouldThrowIfArgumentIsProvided() { // InProcess should not have any values or arguments - ExceptionUtilities.ThrowsException( - () => _executor.Initialize("true"), - "Argument true is not expected in the 'InIsolation' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /InIsolation) and try again."); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("true")); + Assert.Contains("Argument true is not expected in the 'InIsolation' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /InIsolation) and try again.", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index 810aad5463..83159cdf58 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -148,7 +148,7 @@ public void ExecutorExecuteForNoSourcesShouldReturnFail() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -166,7 +166,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -183,7 +183,7 @@ public void ExecutorExecuteShouldThrowSettingsException() var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); + Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); } [TestMethod] @@ -202,7 +202,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); + Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); } [TestMethod] @@ -220,7 +220,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -234,9 +234,9 @@ public void ExecutorExecuteShouldOutputDiscoveredTestsAndReturnSuccess() mockDiscoveryRequest.Verify(dr => dr.DiscoverAsync(), Times.Once); var fileOutput = File.ReadAllLines(_dummyFilePath); - Assert.IsTrue(fileOutput.Length == 2); - Assert.IsTrue(fileOutput.Contains("Test1")); - Assert.IsTrue(fileOutput.Contains("Test2")); + Assert.HasCount(2, fileOutput); + Assert.Contains("Test1", fileOutput); + Assert.Contains("Test2", fileOutput); } [TestMethod] @@ -250,19 +250,18 @@ public void DiscoveryShouldFilterCategoryTestsAndReturnSuccess() mockDiscoveryRequest.Verify(dr => dr.DiscoverAsync(), Times.Once); var fileOutput = File.ReadAllLines(_dummyFilePath); - Assert.IsTrue(fileOutput.Length == 1); - Assert.IsTrue(fileOutput.Contains("Test1")); - Assert.IsFalse(fileOutput.Contains("Test2")); + Assert.HasCount(1, fileOutput); + Assert.Contains("Test1", fileOutput); + Assert.DoesNotContain("Test2", fileOutput); } - [ExpectedException(typeof(CommandLineException))] [TestMethod] public void ExecutorExecuteShouldThrowWhenListFullyQualifiedTestsTargetPathIsEmpty() { var mockDiscoveryRequest = new Mock(); var mockConsoleOutput = new Mock(); - RunListFullyQualifiedTestArgumentProcessorExecuteWithMockSetup(mockDiscoveryRequest, mockConsoleOutput, false); + Assert.ThrowsExactly(() => RunListFullyQualifiedTestArgumentProcessorExecuteWithMockSetup(mockDiscoveryRequest, mockConsoleOutput, false)); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs index 381095ee58..fad84601b1 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -151,7 +151,7 @@ public void ExecutorExecuteForNoSourcesShouldReturnFail() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -168,7 +168,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -185,7 +185,7 @@ public void ExecutorExecuteShouldThrowSettingsException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); + Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); } [TestMethod] @@ -202,7 +202,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); + Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); } [TestMethod] @@ -219,7 +219,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs index b98f2db49c..9ed9f22bb6 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs @@ -49,15 +49,8 @@ public void ExecutorInitializeWithNullOrEmptyListTestsTargetPathShouldThrowComma var options = CommandLineOptions.Instance; ListTestsTargetPathArgumentExecutor executor = new(options); - try - { - executor.Initialize(null); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - StringAssert.Contains(ex.Message, "ListTestsTargetPath is required with ListFullyQualifiedTests!"); - } + var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); + Assert.Contains("ListTestsTargetPath is required with ListFullyQualifiedTests!", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs index 9e0580aa63..4d264812a5 100644 --- a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs @@ -68,9 +68,8 @@ public void InitializeShouldThrowIfArgumentIsNonNull() { // Parallel should not have any values or arguments - ExceptionUtilities.ThrowsException( - () => _executor.Initialize("123"), - "Argument " + 123 + " is not expected in the 'Parallel' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /Parallel) and try again."); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("123")); + Assert.Contains("Argument " + 123 + " is not expected in the 'Parallel' command. Specify the command without the argument (Example: vstest.console.exe myTests.dll /Parallel) and try again.", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs index 7624740e17..08ab0db5ad 100644 --- a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs @@ -30,10 +30,9 @@ public void GetExecutorShouldReturnParentProcessIdArgumentProcessorCapabilities( public void CapabilitiesShouldHaveHigherPriorityThanPortCapabilities() { var parentProcessIdCapabilities = new ParentProcessIdArgumentProcessorCapabilities(); - var portCapabilities = new PortArgumentProcessorCapabilities(); // Less the number, high the priority - Assert.IsTrue(parentProcessIdCapabilities.Priority == portCapabilities.Priority, "ParentProcessId must have higher priority than Port."); + Assert.AreEqual(ArgumentProcessorPriority.DesignMode, parentProcessIdCapabilities.Priority, "ParentProcessId must have higher priority than Port."); } [TestMethod] @@ -57,30 +56,16 @@ public void CapabilitiesShouldReturnAppropriateProperties() public void ExecutorInitializeWithNullOrEmptyParentProcessIdShouldThrowCommandLineException() { var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); - try - { - executor.Initialize(null); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); - } + var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); + Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); } [TestMethod] public void ExecutorInitializeWithInvalidParentProcessIdShouldThrowCommandLineException() { var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); - try - { - executor.Initialize("Foo"); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); - } + var ex = Assert.ThrowsExactly(() => executor.Initialize("Foo")); + Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs index a0119490ac..73f38ca8cd 100644 --- a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Globalization; + using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -67,35 +69,29 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowIfArgumentIsNull() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize(null), - "The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(null)); + Assert.Contains("The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86", ex.Message); } [TestMethod] public void InitializeShouldThrowIfArgumentIsEmpty() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize(" "), - "The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(" ")); + Assert.Contains("The /Platform argument requires the target platform type for the test run to be provided. Example: /Platform:x86", ex.Message); } [TestMethod] public void InitializeShouldThrowIfArgumentIsNotAnArchitecture() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize("foo"), - "Invalid platform type: {0}. Valid platform types are X86, X64, ARM, ARM64, S390x, Ppc64le, RiscV64.", - "foo"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("foo")); + Assert.Contains(string.Format(CultureInfo.CurrentCulture, "Invalid platform type: {0}. Valid platform types are X86, X64, ARM, ARM64, S390x, Ppc64le, RiscV64, LoongArch64.", "foo"), ex.Message); } [TestMethod] public void InitializeShouldThrowIfArgumentIsNotASupportedArchitecture() { - ExceptionUtilities.ThrowsException( - () => _executor.Initialize("AnyCPU"), - "Invalid platform type: {0}. Valid platform types are X86, X64, ARM, ARM64, S390x, Ppc64le, RiscV64.", - "AnyCPU"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize("AnyCPU")); + Assert.Contains(string.Format(CultureInfo.CurrentCulture, "Invalid platform type: {0}. Valid platform types are X86, X64, ARM, ARM64, S390x, Ppc64le, RiscV64, LoongArch64.", "AnyCPU"), ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index d7a4db5a34..09e78728de 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -70,29 +70,15 @@ public void CapabilitiesShouldAppropriateProperties() [TestMethod] public void ExecutorInitializeWithNullOrEmptyPortShouldThrowCommandLineException() { - try - { - _executor.Initialize(null); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual("The --Port|/Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); - } + var ex = Assert.ThrowsExactly(() => _executor.Initialize(null)); + Assert.AreEqual("The --Port|/Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); } [TestMethod] public void ExecutorInitializeWithInvalidPortShouldThrowCommandLineException() { - try - { - _executor.Initialize("Foo"); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual("The --Port|/Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); - } + var ex = Assert.ThrowsExactly(() => _executor.Initialize("Foo")); + Assert.AreEqual("The --Port|/Port argument requires the port number which is an integer. Specify the port for socket connection and receiving the event messages.", ex.Message); } [TestMethod] @@ -164,7 +150,7 @@ public void ExecutorExecuteForFailedConnectionShouldThrowCommandLineException() int port = 2345; _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); - Assert.ThrowsException(() => _executor.Execute()); + Assert.ThrowsExactly(() => _executor.Execute()); _testDesignModeClient.Verify(td => td.ConnectToClientAndProcessRequests(port, _testRequestManager.Object), Times.Once); } diff --git a/test/vstest.console.UnitTests/Processors/RegressionBugFixTests.cs b/test/vstest.console.UnitTests/Processors/RegressionBugFixTests.cs new file mode 100644 index 0000000000..f790c7d3b4 --- /dev/null +++ b/test/vstest.console.UnitTests/Processors/RegressionBugFixTests.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Globalization; +using System.Reflection; + +using Microsoft.VisualStudio.TestPlatform.CommandLine; +using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; +using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace vstest.console.UnitTests.Processors; + +/// +/// Regression test for GH-813 / PR #1390: +/// The code coverage default settings template must exclude auto-generated code +/// by including exclusion patterns in the Attributes section. PR #1390 added the +/// TestSDKAutoGeneratedCode attribute to auto-generated code and added corresponding +/// exclusion patterns to the default code coverage settings template. +/// If the fix is reverted, the template would not contain these exclusion patterns, +/// and auto-generated code would be included in code coverage results. +/// +[TestClass] +public class RegressionBugFixTests +{ + public TestContext TestContext { get; set; } = null!; + + [TestMethod] + public void CodeCoverageTemplate_MustExcludeAutoGeneratedCodeAttributes() + { + // GH-813: The fix added attribute exclusion patterns to the default code coverage + // settings template. Access the private field via reflection to verify the patterns. + var field = typeof(EnableCodeCoverageArgumentExecutor) + .GetField("CodeCoverageCollectorSettingsTemplate", + BindingFlags.NonPublic | BindingFlags.Static); + + Assert.IsNotNull(field, + "GH-813: CodeCoverageCollectorSettingsTemplate field must exist."); + + var template = (string)field.GetValue(null)!; + Assert.IsNotNull(template, + "GH-813: CodeCoverageCollectorSettingsTemplate must not be null."); + + // The template must contain the TestSDKAutoGeneratedCode attribute exclusion pattern + // that was added by PR #1390 to exclude auto-generated code from coverage. + Assert.Contains("TestSDKAutoGeneratedCode", template, + "GH-813: Code coverage template must exclude TestSDKAutoGeneratedCode-attributed code."); + } + + [TestMethod] + public void Initialize_CodeCoverage_ResultingRunSettingsMustContainAutoGeneratedCodeExclusion() + { + // GH-813: When code coverage is enabled via Initialize, the resulting runsettings + // must contain attribute exclusion patterns for auto-generated code. + var settingsProvider = new TestableRunSettingsProvider(); + var executor = new EnableCodeCoverageArgumentExecutor( + CommandLineOptions.Instance, settingsProvider, new Mock().Object); + CollectArgumentExecutor.EnabledDataCollectors.Clear(); + + var runsettingsString = string.Format(CultureInfo.CurrentCulture, + string.Join(Environment.NewLine, + "", + "", + " ", + " {0}", + " ", + ""), + ""); + var runsettings = new RunSettings(); + runsettings.LoadSettingsXml(runsettingsString); + settingsProvider.SetActiveRunSettings(runsettings); + + // Act + executor.Initialize(string.Empty); + + // Assert: the resulting runsettings must contain the auto-generated code exclusion + var resultXml = settingsProvider.ActiveRunSettings!.SettingsXml!; + Assert.Contains("TestSDKAutoGeneratedCode", resultXml, + "GH-813: Resulting runsettings must contain auto-generated code attribute exclusion."); + Assert.Contains("", resultXml, + "GH-813: Resulting runsettings must contain Attribute exclusion elements."); + } +} diff --git a/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs index 3c433aefe7..7f948c8aab 100644 --- a/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs @@ -36,7 +36,7 @@ public void CapabilitiesShouldReturnAppropriateProperties() { var capabilities = new ResponseFileArgumentProcessorCapabilities(); Assert.AreEqual("@", capabilities.CommandName); - StringAssert.Contains(capabilities.HelpContentResourceName, "Read response file for more options"); + Assert.Contains("Read response file for more options", capabilities.HelpContentResourceName); Assert.AreEqual(HelpContentPriority.ResponseFileArgumentProcessorHelpPriority, capabilities.HelpPriority); Assert.IsFalse(capabilities.IsAction); diff --git a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs index bc5d345804..c1c9cdb2c9 100644 --- a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs @@ -108,20 +108,8 @@ public void InitializeShouldThrowIfGivenPathIsIllegal() private void InitializeExceptionTestTemplate(string? folder, string message) { - var isExceptionThrown = false; - - try - { - _executor.Initialize(folder); - } - catch (Exception ex) - { - isExceptionThrown = true; - Assert.IsTrue(ex is CommandLineException, "ex is CommandLineException"); - StringAssert.StartsWith(ex.Message, message); - } - - Assert.IsTrue(isExceptionThrown, "isExceptionThrown"); + var ex = Assert.ThrowsExactly(() => _executor.Initialize(folder)); + Assert.StartsWith(message, ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs index d32df8d4c1..be82e2687b 100644 --- a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -76,21 +77,15 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsNull() { - Action action = () => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(null); - - ExceptionUtilities.ThrowsException( - action, - "The /Settings parameter requires a settings file to be provided."); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(null)); + Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsWhiteSpace() { - Action action = () => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(" "); - - ExceptionUtilities.ThrowsException( - action, - "The /Settings parameter requires a settings file to be provided."); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(" ")); + Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } [TestMethod] @@ -104,10 +99,8 @@ public void InitializeShouldThrowExceptionIfFileDoesNotExist() executor.FileHelper = mockFileHelper.Object; - ExceptionUtilities.ThrowsException( - () => executor.Initialize(fileName), - "The Settings file '{0}' could not be found.", - fileName); + var ex = Assert.ThrowsExactly(() => executor.Initialize(fileName)); + Assert.Contains(string.Format(CultureInfo.CurrentCulture, "The Settings file '{0}' could not be found.", fileName), ex.Message); } [TestMethod] @@ -129,9 +122,8 @@ public void InitializeShouldThrowIfRunSettingsSchemaDoesNotMatch() executor.FileHelper = mockFileHelper.Object; // Act and Assert. - ExceptionUtilities.ThrowsException( - () => executor.Initialize(fileName), - "Settings file provided does not conform to required format."); + var ex = Assert.ThrowsExactly(() => executor.Initialize(fileName)); + Assert.Contains("Settings file provided does not conform to required format.", ex.Message); } [TestMethod] @@ -253,7 +245,7 @@ public void InitializeShouldSetActiveRunSettingsForTestSettingsFiles() $" ", $" ", $""); - StringAssert.Contains(_settingsProvider.ActiveRunSettings.SettingsXml, expected); + Assert.Contains(expected, _settingsProvider.ActiveRunSettings.SettingsXml!); } @@ -323,7 +315,7 @@ public void InitializeShouldPreserveActualJapaneseString() null); executor.Initialize(runsettingsFile); - Assert.IsTrue(_settingsProvider.ActiveRunSettings!.SettingsXml!.Contains(@"C:\新しいフォルダー")); + Assert.Contains(@"C:\新しいフォルダー", _settingsProvider.ActiveRunSettings!.SettingsXml!); File.Delete(runsettingsFile); } diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index eaffa017fc..6462c6a940 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -101,8 +101,8 @@ public void CapabilitiesShouldReturnAppropriateProperties() { RunSpecificTestsArgumentProcessorCapabilities capabilities = new(); Assert.AreEqual("/Tests", capabilities.CommandName); - StringAssert.Contains(capabilities.HelpContentResourceName.NormalizeLineEndings(), - "/Tests:\r\n Run tests with names that match the provided values.".NormalizeLineEndings()); + Assert.Contains("/Tests:\r\n Run tests with names that match the provided values.".NormalizeLineEndings(), + capabilities.HelpContentResourceName.NormalizeLineEndings()); Assert.AreEqual(HelpContentPriority.RunSpecificTestsArgumentProcessorHelpPriority, capabilities.HelpPriority); Assert.IsTrue(capabilities.IsAction); @@ -124,7 +124,7 @@ public void InitializeShouldThrowIfArgumentIsNull() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Initialize(null)); + Assert.ThrowsExactly(() => executor.Initialize(null)); } [TestMethod] @@ -135,7 +135,7 @@ public void InitializeShouldThrowIfArgumentIsEmpty() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Initialize(string.Empty)); + Assert.ThrowsExactly(() => executor.Initialize(string.Empty)); } [TestMethod] @@ -146,7 +146,7 @@ public void InitializeShouldThrowIfArgumentIsWhiteSpace() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Initialize(" ")); + Assert.ThrowsExactly(() => executor.Initialize(" ")); } [TestMethod] @@ -157,7 +157,7 @@ public void InitializeShouldThrowIfArgumentsAreEmpty() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Initialize(" , ")); + Assert.ThrowsExactly(() => executor.Initialize(" , ")); } [TestMethod] @@ -168,7 +168,7 @@ public void ExecutorShouldSplitTestsSeparatedByComma() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -179,7 +179,7 @@ public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -229,7 +229,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringDiscovery var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -247,7 +247,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringDisco var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -265,7 +265,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringDiscovery() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -292,7 +292,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringExecution executor.Initialize("Test1"); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -319,7 +319,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringExecution() executor.Initialize("Test1"); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -347,7 +347,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringExecu executor.Initialize("Test1"); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index 0c31c47d9d..bb540babf1 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -133,7 +133,7 @@ public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } private RunTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager) @@ -163,7 +163,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -179,7 +179,7 @@ public void ExecutorExecuteShouldThrowSettingsException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -195,7 +195,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] @@ -211,7 +211,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - Assert.ThrowsException(() => executor.Execute()); + Assert.ThrowsExactly(() => executor.Execute()); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs index ce33c73f8a..59537431ab 100644 --- a/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs @@ -13,7 +13,9 @@ public class ShowDeprecateDotnetVStestMessageArgumentProcessorTests [TestMethod] public void ShowDeprecateDotnetVStestMessageProcessorCommandName() { +#pragma warning disable MSTEST0032 // Assertion condition is always true Assert.AreEqual("/ShowDeprecateDotnetVSTestMessage", ShowDeprecateDotnetVStestMessageArgumentProcessor.CommandName); +#pragma warning restore MSTEST0032 // Assertion condition is always true } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs index f60d0ffb8d..daeee438f3 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] +// Because runsettings tests use the instance of RunSettingsManager which is static. +[DoNotParallelize] public class TestAdapterLoadingStrategyArgumentProcessorTests { private readonly RunSettings _currentActiveSetting; @@ -73,19 +75,8 @@ public void InitializeShouldAddRightAdapterPathInErrorMessage() var message = "The path 'd:\\users' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; - var isExceptionThrown = false; - try - { - executor.Initialize(nameof(TestAdapterLoadingStrategy.Default)); - } - catch (Exception ex) - { - isExceptionThrown = true; - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual(message, ex.Message); - } - - Assert.IsTrue(isExceptionThrown); + var ex = Assert.ThrowsExactly(() => executor.Initialize(nameof(TestAdapterLoadingStrategy.Default))); + Assert.AreEqual(message, ex.Message); } @@ -104,19 +95,7 @@ public void InitializeShouldThrowIfPathDoesNotExist() var message = $"The path '{folder}' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; - var isExceptionThrown = false; - - try - { - executor.Initialize(nameof(TestAdapterLoadingStrategy.Default)); - } - catch (Exception ex) - { - isExceptionThrown = true; - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual(message, ex.Message); - } - - Assert.IsTrue(isExceptionThrown); + var ex2 = Assert.ThrowsExactly(() => executor.Initialize(nameof(TestAdapterLoadingStrategy.Default))); + Assert.AreEqual(message, ex2.Message); } } diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs index 8dd96a60ad..dbaa81af15 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -20,6 +20,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] +// Because runsettings tests use the instance of RunSettingsManager which is static. +[DoNotParallelize] public class TestAdapterPathArgumentProcessorTests { private readonly RunSettings _currentActiveSetting; @@ -83,20 +85,8 @@ public void InitializeShouldThrowIfArgumentIsNull() var message = @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; - var isExceptionThrown = false; - - try - { - executor.Initialize(null); - } - catch (Exception ex) - { - isExceptionThrown = true; - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual(message, ex.Message); - } - - Assert.IsTrue(isExceptionThrown); + var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); + Assert.AreEqual(message, ex.Message); } [TestMethod] @@ -109,20 +99,8 @@ public void InitializeShouldThrowIfArgumentIsAWhiteSpace() var message = @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; - var isExceptionThrown = false; - - try - { - executor.Initialize(" "); - } - catch (Exception ex) - { - isExceptionThrown = true; - Assert.IsTrue(ex is CommandLineException); - Assert.AreEqual(message, ex.Message); - } - - Assert.IsTrue(isExceptionThrown); + var ex2 = Assert.ThrowsExactly(() => executor.Initialize(" ")); + Assert.AreEqual(message, ex2.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs index f16d22e52c..52231ae264 100644 --- a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs @@ -32,7 +32,7 @@ public void CapabilitiesShouldAppropriateProperties() { TestCaseFilterArgumentProcessorCapabilities capabilities = new(); Assert.AreEqual("/TestCaseFilter", capabilities.CommandName); - StringAssert.Contains(capabilities.HelpContentResourceName, "/TestCaseFilter:" + Environment.NewLine + " Run tests that match the given expression." + Environment.NewLine + " is of the format Operator[|&]"); + Assert.Contains("/TestCaseFilter:" + Environment.NewLine + " Run tests that match the given expression." + Environment.NewLine + " is of the format Operator[|&]", capabilities.HelpContentResourceName); Assert.AreEqual(HelpContentPriority.TestCaseFilterArgumentProcessorHelpPriority, capabilities.HelpPriority); Assert.IsFalse(capabilities.IsAction); @@ -51,15 +51,8 @@ public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldThrowCommandLin var options = CommandLineOptions.Instance; TestCaseFilterArgumentExecutor executor = new(options); - try - { - executor.Initialize(null); - } - catch (Exception ex) - { - Assert.IsTrue(ex is CommandLineException); - StringAssert.Contains(ex.Message, @"The /TestCaseFilter argument requires the filter value."); - } + var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); + Assert.Contains(@"The /TestCaseFilter argument requires the filter value.", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/TestSessionCorrelationIdProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestSessionCorrelationIdProcessorTests.cs index 89d57df89b..41c0b81d7c 100644 --- a/test/vstest.console.UnitTests/Processors/TestSessionCorrelationIdProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestSessionCorrelationIdProcessorTests.cs @@ -14,13 +14,13 @@ public class TestSessionCorrelationIdProcessorTests { [TestMethod] public void ProcessorExecutorInitialize_ShouldFailIfNullCommandOption() => - Assert.ThrowsException(() => new TestSessionCorrelationIdProcessorModeProcessorExecutor(null!)); + Assert.ThrowsExactly(() => new TestSessionCorrelationIdProcessorModeProcessorExecutor(null!)); [TestMethod] public void ProcessorExecutorInitialize_ShouldFailIfNullSession() { TestSessionCorrelationIdProcessorModeProcessorExecutor testSessionCorrelationIdProcessor = new(new CommandLineOptions()); - Assert.ThrowsException(() => testSessionCorrelationIdProcessor.Initialize(null)); + Assert.ThrowsExactly(() => testSessionCorrelationIdProcessor.Initialize(null)); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs index 9f4533c153..e75b58aa40 100644 --- a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs @@ -74,16 +74,9 @@ public void ExecuterInitializeWithInvalidSourceShouldThrowCommandLineException() // This path is invalid string testFilePath = "TestFile.txt"; - try - { - executor.Initialize(testFilePath); - } - catch (Exception ex) - { - Assert.IsTrue(ex is TestSourceException); - StringAssert.StartsWith(ex.Message, "The test source file \""); - StringAssert.EndsWith(ex.Message, testFilePath + "\" provided was not found."); - } + var ex = Assert.ThrowsExactly(() => executor.Initialize(testFilePath)); + Assert.StartsWith("The test source file \"", ex.Message); + Assert.EndsWith(testFilePath + "\" provided was not found.", ex.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs index 2813d6a157..08988f689b 100644 --- a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs @@ -70,7 +70,7 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsNull() { - var message = Assert.ThrowsException(() => _executor.Initialize(null)).Message; + var message = Assert.ThrowsExactly(() => _executor.Initialize(null)).Message; Assert.AreEqual(@"The /UseVsixExtensions parameter requires a value. If 'true', the installed VSIX extensions (if any) will be used in the test run. If false, they will be ignored. Example: /UseVsixExtensions:true", message); } @@ -79,7 +79,7 @@ public void InitializeShouldThrowExceptionIfArgumentIsInvalid() { var invalidArg = "Foo"; - var message = Assert.ThrowsException(() => _executor.Initialize(invalidArg)).Message; + var message = Assert.ThrowsExactly(() => _executor.Initialize(invalidArg)).Message; Assert.AreEqual(@"Argument Foo is not expected in the 'UseVsixExtensions' command. Specify the command indicating whether the vsix extensions should be used or skipped (Example: vstest.console.exe myTests.dll /UseVsixExtensions:true) and try again.", message); } diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 01f9acf5f0..48f971b4a3 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -70,11 +70,8 @@ public void CreateArgumentProcessorShouldReturnThrowExceptionIfArgumentsIsNull() var command = "--"; ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(); - Action action = () => factory.CreateArgumentProcessor(command, null!); - - ExceptionUtilities.ThrowsException( - action, - "Cannot be null or empty", "argument"); + var ex = Assert.ThrowsExactly(() => factory.CreateArgumentProcessor(command, null!)); + Assert.Contains("Cannot be null or empty", ex.Message); } [TestMethod] @@ -168,7 +165,7 @@ private static IEnumerable GetArgumentProcessors(bool specia foreach (var processor in allProcessors) { var instance = Activator.CreateInstance(processor) as IArgumentProcessor; - Assert.IsNotNull(instance, "Unable to instantiate processor: {0}", processor); + Assert.IsNotNull(instance, $"Unable to instantiate processor: {processor}"); var specialProcessor = instance.Metadata.Value.IsSpecialCommand; if ((specialCommandFilter && specialProcessor) || (!specialCommandFilter && !specialProcessor)) diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorUtilitiesTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorUtilitiesTests.cs index a597af523d..5edc10c0c7 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorUtilitiesTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorUtilitiesTests.cs @@ -15,19 +15,13 @@ public class ArgumentProcessorUtilitiesTests { [TestMethod] [DataRow("")] - [DataRow(" ")] [DataRow(";;;;")] public void GetArgumentListShouldThrowErrorOnInvalidArgument(string argument) { - try - { - ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, "test exception."); - } - catch (Exception e) - { - Assert.IsTrue(e.GetType().Equals(typeof(CommandLineException))); - Assert.IsTrue(e.Message.Contains("test exception.")); - } + var e = Assert.ThrowsExactly(() => + ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, "test exception.")); + Assert.IsInstanceOfType(e); + Assert.Contains("test exception.", e.Message); } [TestMethod] @@ -43,15 +37,10 @@ public void GetArgumentListShouldReturnCorrectArgumentList(string argument) [DataRow(["key1=value1", "invalidPair", "key2=value2"])] public void GetArgumentParametersShouldThrowErrorOnInvalidParameters(string[] parameterArgs) { - try - { - ArgumentProcessorUtilities.GetArgumentParameters(parameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, "test exception."); - } - catch (Exception e) - { - Assert.IsTrue(e.GetType().Equals(typeof(CommandLineException))); - Assert.IsTrue(e.Message.Contains("test exception.")); - } + var e = Assert.ThrowsExactly(() => + ArgumentProcessorUtilities.GetArgumentParameters(parameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, "test exception.")); + Assert.IsInstanceOfType(e); + Assert.Contains("test exception.", e.Message); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Program.cs b/test/vstest.console.UnitTests/Program.cs deleted file mode 100644 index ecb0d60707..0000000000 --- a/test/vstest.console.UnitTests/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; - -public static class Program -{ - public static void Main() - { - } -} diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index d8f8f94a8c..b6484c8ebe 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -45,6 +45,8 @@ namespace vstest.console.UnitTests.TestPlatformHelpers; [TestClass] public class TestRequestManagerTests { + public TestContext TestContext { get; set; } = null!; + private DummyLoggerEvents _mockLoggerEvents; private readonly CommandLineOptions _commandLineOptions; private readonly Mock _mockTestPlatform; @@ -331,7 +333,7 @@ public void DiscoverTestsShouldCollectMetrics() Assert.AreEqual("Other", targetDevice); Assert.AreEqual(2, maxcount); Assert.AreEqual("X86", targetPlatform.ToString()); - Assert.AreEqual(true, disableAppDomain); + Assert.IsTrue((bool)disableAppDomain); } [TestMethod] @@ -519,11 +521,11 @@ public void DiscoverTestsShouldCollectCommands() var commandLineArray = commandLineSwitches.ToString(); - Assert.IsTrue(commandLineArray!.Contains("/Parallel")); - Assert.IsTrue(commandLineArray.Contains("/EnableCodeCoverage")); - Assert.IsTrue(commandLineArray.Contains("/InIsolation")); - Assert.IsTrue(commandLineArray.Contains("/UseVsixExtensions")); - Assert.IsTrue(commandLineArray.Contains("/settings//.RunSettings")); + Assert.Contains("/Parallel", commandLineArray!); + Assert.Contains("/EnableCodeCoverage", commandLineArray!); + Assert.Contains("/InIsolation", commandLineArray!); + Assert.Contains("/UseVsixExtensions", commandLineArray!); + Assert.Contains("/settings//.RunSettings", commandLineArray!); } [TestMethod] @@ -571,7 +573,7 @@ public void DiscoverTestsShouldCollectTestSettings() Assert.IsTrue(actualRequestData!.MetricsCollection.Metrics.TryGetValue(TelemetryDataConstants.CommandLineSwitches, out var commandLineSwitches)); var commandLineArray = commandLineSwitches.ToString(); - Assert.IsTrue(commandLineArray!.Contains("/settings//.TestSettings")); + Assert.Contains("/settings//.TestSettings", commandLineArray!); } [TestMethod] @@ -619,7 +621,7 @@ public void DiscoverTestsShouldCollectVsmdiFile() Assert.IsTrue(actualRequestData!.MetricsCollection.Metrics.TryGetValue(TelemetryDataConstants.CommandLineSwitches, out var commandLineSwitches)); var commandLineArray = commandLineSwitches.ToString(); - Assert.IsTrue(commandLineArray!.Contains("/settings//.vsmdi")); + Assert.Contains("/settings//.vsmdi", commandLineArray!); } [TestMethod] @@ -667,7 +669,7 @@ public void DiscoverTestsShouldCollectTestRunConfigFile() Assert.IsTrue(actualRequestData!.MetricsCollection.Metrics.TryGetValue(TelemetryDataConstants.CommandLineSwitches, out var commandLineSwitches)); var commandLineArray = commandLineSwitches.ToString(); - Assert.IsTrue(commandLineArray!.Contains("/settings//.testrunConfig")); + Assert.Contains("/settings//.testrunConfig", commandLineArray!); } [TestMethod] @@ -698,8 +700,8 @@ public void DiscoverTestsShouldUpdateFrameworkAndPlatformIfNotSpecifiedInDesignM _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny())); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny())); - Assert.IsTrue(actualDiscoveryCriteria!.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualDiscoveryCriteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualDiscoveryCriteria!.RunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualDiscoveryCriteria.RunSettings!); } [TestMethod] @@ -732,8 +734,8 @@ public void DiscoverTestsShouldNotUpdateFrameworkAndPlatformIfSpecifiedInDesignM _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny()), Times.Never); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny()), Times.Never); - Assert.IsTrue(actualDiscoveryCriteria!.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualDiscoveryCriteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualDiscoveryCriteria!.RunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualDiscoveryCriteria.RunSettings!); } [TestMethod] @@ -763,8 +765,8 @@ public void DiscoverTestsShouldUpdateFrameworkAndPlatformInCommandLineScenariosI _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny())); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny())); - Assert.IsTrue(actualDiscoveryCriteria!.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualDiscoveryCriteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualDiscoveryCriteria!.RunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualDiscoveryCriteria.RunSettings!); } [TestMethod] @@ -804,8 +806,8 @@ public void DiscoverTestsShouldNotInferAndUpdateFrameworkAndPlatformInCommandLin _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny()), Times.Once); // but we don't update the settings, to keep what user specified - Assert.IsFalse(actualDiscoveryCriteria!.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsFalse(actualDiscoveryCriteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.DoesNotContain(Constants.DotNetFramework46, actualDiscoveryCriteria!.RunSettings!); + Assert.DoesNotContain(nameof(Architecture.ARM), actualDiscoveryCriteria.RunSettings!); } [TestMethod] @@ -976,11 +978,11 @@ public void RunTestsShouldCollectCommands() var commandLineArray = commandLineSwitches.ToString(); - Assert.IsTrue(commandLineArray!.Contains("/Parallel")); - Assert.IsTrue(commandLineArray.Contains("/EnableCodeCoverage")); - Assert.IsTrue(commandLineArray.Contains("/InIsolation")); - Assert.IsTrue(commandLineArray.Contains("/UseVsixExtensions")); - Assert.IsTrue(commandLineArray.Contains("/settings//.RunSettings")); + Assert.Contains("/Parallel", commandLineArray!); + Assert.Contains("/EnableCodeCoverage", commandLineArray!); + Assert.Contains("/InIsolation", commandLineArray!); + Assert.Contains("/UseVsixExtensions", commandLineArray!); + Assert.Contains("/settings//.RunSettings", commandLineArray!); } [TestMethod] @@ -1135,7 +1137,7 @@ public void RunTestsShouldCollectMetrics() Assert.AreEqual("Other", targetDevice); Assert.AreEqual(2, maxcount); Assert.AreEqual("X86", targetPlatform.ToString()); - Assert.AreEqual(true, disableAppDomain); + Assert.IsTrue((bool)disableAppDomain); } [TestMethod] @@ -1249,10 +1251,10 @@ public void RunTestsMultipleCallsShouldNotRunInParallel() }); var mockCustomlauncher = new Mock(); - var task1 = Task.Run(() => _testRequestManager.RunTests(payload1, mockCustomlauncher.Object, mockRunEventsRegistrar1.Object, _protocolConfig)); - var task2 = Task.Run(() => _testRequestManager.RunTests(payload2, mockCustomlauncher.Object, mockRunEventsRegistrar2.Object, _protocolConfig)); + var task1 = Task.Run(() => _testRequestManager.RunTests(payload1, mockCustomlauncher.Object, mockRunEventsRegistrar1.Object, _protocolConfig), TestContext.CancellationToken); + var task2 = Task.Run(() => _testRequestManager.RunTests(payload2, mockCustomlauncher.Object, mockRunEventsRegistrar2.Object, _protocolConfig), TestContext.CancellationToken); - Task.WaitAll(task1, task2); + Task.WaitAll([task1, task2], TestContext.CancellationToken); if (run1Start < run2Start) { @@ -1290,52 +1292,52 @@ public void RunTestsShouldPublishMetrics() [TestMethod] public void RunTestsIfThrowsTestPlatformExceptionShouldThrowOut() { - Assert.ThrowsException(() => RunTestsIfThrowsExceptionShouldThrowOut(new TestPlatformException("HelloWorld"))); + Assert.ThrowsExactly(() => RunTestsIfThrowsExceptionShouldThrowOut(new TestPlatformException("HelloWorld"))); } [TestMethod] public void RunTestsIfThrowsSettingsExceptionShouldThrowOut() { - Assert.ThrowsException(() => RunTestsIfThrowsExceptionShouldThrowOut(new SettingsException("HelloWorld"))); + Assert.ThrowsExactly(() => RunTestsIfThrowsExceptionShouldThrowOut(new SettingsException("HelloWorld"))); } [TestMethod] public void RunTestsIfThrowsInvalidOperationExceptionShouldThrowOut() { - Assert.ThrowsException(() => RunTestsIfThrowsExceptionShouldThrowOut(new InvalidOperationException("HelloWorld"))); + Assert.ThrowsExactly(() => RunTestsIfThrowsExceptionShouldThrowOut(new InvalidOperationException("HelloWorld"))); } [TestMethod] public void RunTestsIfThrowsExceptionShouldThrowOut() { - Assert.ThrowsException(() => RunTestsIfThrowsExceptionShouldThrowOut(new NotImplementedException("HelloWorld"))); + Assert.ThrowsExactly(() => RunTestsIfThrowsExceptionShouldThrowOut(new NotImplementedException("HelloWorld"))); } [TestMethod] public void DiscoverTestsIfThrowsTestPlatformExceptionShouldThrowOut() { - Assert.ThrowsException(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new TestPlatformException("HelloWorld"))); + Assert.ThrowsExactly(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new TestPlatformException("HelloWorld"))); } [TestMethod] public void DiscoverTestsIfThrowsSettingsExceptionShouldThrowOut() { - Assert.ThrowsException(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new SettingsException("HelloWorld"))); + Assert.ThrowsExactly(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new SettingsException("HelloWorld"))); } [TestMethod] public void DiscoverTestsIfThrowsInvalidOperationExceptionShouldThrowOut() { - Assert.ThrowsException(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new InvalidOperationException("HelloWorld"))); + Assert.ThrowsExactly(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new InvalidOperationException("HelloWorld"))); } [TestMethod] public void DiscoverTestsIfThrowsExceptionShouldThrowOut() { - Assert.ThrowsException(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new NotImplementedException("HelloWorld"))); + Assert.ThrowsExactly(() => DiscoverTestsIfThrowsExceptionShouldThrowOut(new NotImplementedException("HelloWorld"))); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void DiscoverTestsShouldUpdateDesignModeAndCollectSourceInformation(bool designModeValue) @@ -1369,7 +1371,7 @@ public void DiscoverTestsShouldNotUpdateDesignModeIfUserHasSetDesignModeInRunSet tp => tp.CreateDiscoveryRequest(It.IsAny(), It.Is(dc => dc.RunSettings!.Contains(designmode)), It.IsAny(), It.IsAny>(), It.IsAny())); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void RunTestsShouldUpdateDesignModeIfRunnerIsInDesignMode(bool designModeValue) @@ -1389,7 +1391,7 @@ public void RunTestsShouldUpdateDesignModeIfRunnerIsInDesignMode(bool designMode _mockTestPlatform.Verify(tp => tp.CreateTestRunRequest(It.IsAny(), It.Is(rc => rc.TestRunSettings!.Contains(designmode)), It.IsAny(), It.IsAny>(), It.IsAny())); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void DiscoverTestsShouldNotUpdateCollectSourceInformationIfUserHasSetItInRunSettings(bool val) @@ -1433,8 +1435,8 @@ public void RunTestsShouldShouldUpdateFrameworkAndPlatformIfNotSpecifiedInDesign _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny())); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny())); - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualTestRunCriteria.TestRunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualTestRunCriteria!.TestRunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualTestRunCriteria.TestRunSettings!); } @@ -1472,8 +1474,8 @@ public void RunTestsShouldNotUpdateFrameworkAndPlatformIfSpecifiedInDesignMode() _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny()), Times.Once); // but don't update runsettings because we want to keep what user specified - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualTestRunCriteria!.TestRunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualTestRunCriteria!.TestRunSettings!); } [TestMethod] @@ -1513,7 +1515,7 @@ public void RunTestsShouldNotUpdatePlatformIfSpecifiedInDesignMode(string target _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny()), Times.Once); // don't update it in runsettings to keep what user provided - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings!.Contains(targetPlatform)); + Assert.Contains(targetPlatform, actualTestRunCriteria!.TestRunSettings!); } [TestMethod] @@ -1545,8 +1547,8 @@ public void RunTestsShouldUpdateFrameworkAndPlatformInCommandLineScenarios() _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny())); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny())); - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualTestRunCriteria.TestRunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualTestRunCriteria!.TestRunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualTestRunCriteria.TestRunSettings!); } [TestMethod] @@ -1585,8 +1587,8 @@ public void RunTestsShouldNotpdateFrameworkAndPlatformInRunsettingsIfSpecifiedBy _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny()), Times.Once); // but don't update them in runsettings so we keep what user specified - Assert.IsFalse(actualTestRunCriteria!.TestRunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsFalse(actualTestRunCriteria.TestRunSettings.Contains(nameof(Architecture.ARM))); + Assert.DoesNotContain(Constants.DotNetFramework46, actualTestRunCriteria!.TestRunSettings!); + Assert.DoesNotContain(nameof(Architecture.ARM), actualTestRunCriteria.TestRunSettings!); } [TestMethod] @@ -1626,8 +1628,8 @@ public void RunTestsWithTestCasesShouldUpdateFrameworkAndPlatformIfNotSpecifiedI _mockAssemblyMetadataProvider.Verify(a => a.GetArchitecture(It.IsAny())); _mockAssemblyMetadataProvider.Verify(a => a.GetFrameworkName(It.IsAny())); - Assert.IsTrue(actualTestRunCriteria!.TestRunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(actualTestRunCriteria.TestRunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, actualTestRunCriteria!.TestRunSettings!); + Assert.Contains(nameof(Architecture.ARM), actualTestRunCriteria.TestRunSettings!); CollectionAssert.AreEqual(actualSources, archSources); CollectionAssert.AreEqual(actualSources, fxSources); } @@ -1657,19 +1659,10 @@ public void RunTestShouldThrowExceptionIfRunSettingWithDcHasTestSettingsInIt() }; _commandLineOptions.EnableCodeCoverage = false; - bool exceptionThrown = false; - try - { - _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); - } - catch (SettingsException ex) - { - exceptionThrown = true; - Assert.IsTrue(ex.Message.Contains(@"C:\temp.testsettings"), ex.Message); - } - - Assert.IsTrue(exceptionThrown, "Initialize should throw exception"); + var ex = Assert.ThrowsExactly(() => + _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig)); + Assert.Contains(@"C:\temp.testsettings", ex.Message); } [TestMethod] @@ -1697,19 +1690,10 @@ public void RunTestShouldThrowExceptionIfRunSettingWithDcHasTestSettingsAndEnabl }; _commandLineOptions.EnableCodeCoverage = true; - bool exceptionThrown = false; - try - { - _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); - } - catch (SettingsException ex) - { - exceptionThrown = true; - Assert.IsTrue(ex.Message.Contains(@"C:\temp.testsettings"), ex.Message); - } - - Assert.IsTrue(exceptionThrown, "Initialize should throw exception"); + var ex = Assert.ThrowsExactly(() => + _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig)); + Assert.Contains(@"C:\temp.testsettings", ex.Message); } [TestMethod] @@ -1759,7 +1743,7 @@ public void RunTestsShouldAddConsoleLoggerInRunSettingsInNonDesignMode() _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualTestRunCriteria!.TestRunSettings)!.LoggerSettingsList; - Assert.AreEqual(1, loggerSettingsList.Count); + Assert.HasCount(1, loggerSettingsList); Assert.AreEqual("Console", loggerSettingsList[0].FriendlyName); Assert.IsNotNull(loggerSettingsList[0].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[0].CodeBase); @@ -1797,7 +1781,7 @@ public void RunTestsShouldAddConsoleLoggerInRunSettingsIfDesignModeSetFalseInRun _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualTestRunCriteria!.TestRunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("Console", loggerSettingsList[1].FriendlyName); @@ -1840,7 +1824,7 @@ public void DiscoverTestsShouldAddConsoleLoggerInRunSettingsIfDesignModeSetFalse new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualDiscoveryCriteria!.RunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("Console", loggerSettingsList[1].FriendlyName); @@ -1870,7 +1854,7 @@ public void RunTestsShouldNotAddConsoleLoggerInRunSettingsInDesignMode() (IRequestData requestData, TestRunCriteria runCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualTestRunCriteria = runCriteria).Returns(mockTestRunRequest.Object); _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); - Assert.IsFalse(actualTestRunCriteria!.TestRunSettings!.Contains("LoggerRunSettings")); + Assert.DoesNotContain("LoggerRunSettings", actualTestRunCriteria!.TestRunSettings!); } [TestMethod] @@ -1898,7 +1882,7 @@ public void DiscoverTestsShouldAddConsoleLoggerInRunSettingsInNonDesignMode() new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualDiscoveryCriteria!.RunSettings)!.LoggerSettingsList; - Assert.AreEqual(1, loggerSettingsList.Count); + Assert.HasCount(1, loggerSettingsList); Assert.AreEqual("Console", loggerSettingsList[0].FriendlyName); Assert.IsNotNull(loggerSettingsList[0].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[0].CodeBase); @@ -1929,7 +1913,7 @@ public void DiscoverTestsShouldNotAddConsoleLoggerInRunSettingsInDesignMode() _testRequestManager.DiscoverTests(payload, new Mock().Object, _protocolConfig); - Assert.IsFalse(actualDiscoveryCriteria!.RunSettings!.Contains("LoggerRunSettings")); + Assert.DoesNotContain("LoggerRunSettings", actualDiscoveryCriteria!.RunSettings!); } [TestMethod] @@ -1969,14 +1953,14 @@ public void RunTestsShouldOverrideOnlyAssemblyNameIfConsoleLoggerAlreadyPresentI _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualTestRunCriteria!.TestRunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("console", loggerSettingsList[1].FriendlyName); Assert.AreEqual(new Uri("logger://tempconsoleUri").ToString(), loggerSettingsList[1].Uri!.ToString()); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].AssemblyQualifiedName); Assert.AreNotEqual("tempCodeBase", loggerSettingsList[1].CodeBase); - Assert.IsTrue(loggerSettingsList[1].Configuration!.InnerXml.Contains("Value1")); + Assert.Contains("Value1", loggerSettingsList[1].Configuration!.InnerXml); Assert.IsNotNull(loggerSettingsList[1].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[1].CodeBase); } @@ -2021,14 +2005,14 @@ public void DiscoverTestsShouldOverrideOnlyAssemblyNameIfConsoleLoggerAlreadyPre new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualDiscoveryCriteria!.RunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("consoleTemp", loggerSettingsList[1].FriendlyName); Assert.AreEqual(new Uri("logger://Microsoft/TestPlatform/ConsoleLogger/v1").ToString(), loggerSettingsList[1].Uri!.ToString()); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].AssemblyQualifiedName); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].CodeBase); - Assert.IsTrue(loggerSettingsList[1].Configuration!.InnerXml.Contains("Value1")); + Assert.Contains("Value1", loggerSettingsList[1].Configuration!.InnerXml); Assert.IsNotNull(loggerSettingsList[1].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[1].CodeBase); } @@ -2070,14 +2054,14 @@ public void RunTestsShouldOverrideOnlyAssemblyNameIfConsoleLoggerAlreadyPresentI _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualTestRunCriteria!.TestRunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("console", loggerSettingsList[1].FriendlyName); Assert.AreEqual(new Uri("logger://tempconsoleUri").ToString(), loggerSettingsList[1].Uri!.ToString()); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].AssemblyQualifiedName); Assert.AreNotEqual("tempCodeBase", loggerSettingsList[1].CodeBase); - Assert.IsTrue(loggerSettingsList[1].Configuration!.InnerXml.Contains("Value1")); + Assert.Contains("Value1", loggerSettingsList[1].Configuration!.InnerXml); Assert.IsNotNull(loggerSettingsList[1].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[1].CodeBase); } @@ -2122,14 +2106,14 @@ public void DiscoverTestsShouldOverrideOnlyAssemblyNameIfConsoleLoggerAlreadyPre new Mock().Object, _protocolConfig); var loggerSettingsList = XmlRunSettingsUtilities.GetLoggerRunSettings(actualDiscoveryCriteria!.RunSettings)!.LoggerSettingsList; - Assert.AreEqual(2, loggerSettingsList.Count); + Assert.HasCount(2, loggerSettingsList); Assert.IsNotNull(loggerSettingsList[0].Configuration); Assert.AreEqual("blabla", loggerSettingsList[0].FriendlyName); Assert.AreEqual("consoleTemp", loggerSettingsList[1].FriendlyName); Assert.AreEqual(new Uri("logger://Microsoft/TestPlatform/ConsoleLogger/v1").ToString(), loggerSettingsList[1].Uri!.ToString()); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].AssemblyQualifiedName); Assert.AreNotEqual("tempAssemblyName", loggerSettingsList[1].CodeBase); - Assert.IsTrue(loggerSettingsList[1].Configuration!.InnerXml.Contains("Value1")); + Assert.Contains("Value1", loggerSettingsList[1].Configuration!.InnerXml); Assert.IsNotNull(loggerSettingsList[1].AssemblyQualifiedName); Assert.IsNotNull(loggerSettingsList[1].CodeBase); } @@ -2161,7 +2145,7 @@ public void ProcessTestRunAttachmentsShouldSucceedWithTelemetryEnabled() _mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStop()); _mockMetricsPublisher.Verify(p => p.PublishMetrics(TelemetryDataConstants.TestAttachmentsProcessingCompleteEvent, - It.Is>(m => + It.Is>(m => m.Count == 2 && m.ContainsKey(TelemetryDataConstants.NumberOfAttachmentsSentForProcessing) && (int)m[TelemetryDataConstants.NumberOfAttachmentsSentForProcessing]! == 5 @@ -2205,7 +2189,9 @@ public async Task CancelTestRunAttachmentsProcessingShouldSucceedIfRequestInProg { i++; Console.WriteLine($"Iteration {i}"); +#pragma warning disable MSTEST0049 // Intentionally not using CancellationToken - the mock must poll without throwing Task.Delay(5).Wait(); +#pragma warning restore MSTEST0049 } r.MetricsCollection.Add(TelemetryDataConstants.AttachmentsProcessingState, "Canceled"); @@ -2219,8 +2205,8 @@ public async Task CancelTestRunAttachmentsProcessingShouldSucceedIfRequestInProg CollectMetrics = true }; - Task task = Task.Run(() => _testRequestManager.ProcessTestRunAttachments(payload, mockEventsHandler.Object, _protocolConfig)); - await Task.Delay(50); + Task task = Task.Run(() => _testRequestManager.ProcessTestRunAttachments(payload, mockEventsHandler.Object, _protocolConfig), TestContext.CancellationToken); + await Task.Delay(50, TestContext.CancellationToken); _testRequestManager.CancelTestRunAttachmentsProcessing(); await task; @@ -2230,7 +2216,7 @@ public async Task CancelTestRunAttachmentsProcessingShouldSucceedIfRequestInProg _mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStop()); _mockMetricsPublisher.Verify(p => p.PublishMetrics(TelemetryDataConstants.TestAttachmentsProcessingCompleteEvent, - It.Is>(m => + It.Is>(m => m.Count == 1 && m.ContainsKey(TelemetryDataConstants.AttachmentsProcessingState) && (string?)m[TelemetryDataConstants.AttachmentsProcessingState] == "Canceled"))); @@ -2303,8 +2289,8 @@ public void StartTestSessionShouldUpdateSettings() .Callback( (IRequestData _, StartTestSessionCriteria criteria, ITestSessionEventsHandler _, Dictionary _, IWarningLogger _) => { - Assert.IsTrue(criteria.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(criteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, criteria.RunSettings!); + Assert.Contains(nameof(Architecture.ARM), criteria.RunSettings!); }); _testRequestManager.StartTestSession( @@ -2352,8 +2338,8 @@ public void StartTestSessionShouldSendCompletedEventIfTestPlatformReturnsFalse() .Callback( (IRequestData _, StartTestSessionCriteria criteria, ITestSessionEventsHandler _, Dictionary _, IWarningLogger _) => { - Assert.IsTrue(criteria.RunSettings!.Contains(Constants.DotNetFramework46)); - Assert.IsTrue(criteria.RunSettings.Contains(nameof(Architecture.ARM))); + Assert.Contains(Constants.DotNetFramework46, criteria.RunSettings!); + Assert.Contains(nameof(Architecture.ARM), criteria.RunSettings!); }); _testRequestManager.StartTestSession( @@ -2392,23 +2378,14 @@ public void StartTestSessionShouldThrowSettingsExceptionWhenFindingIncompatibleD }; _commandLineOptions.EnableCodeCoverage = false; - bool exceptionThrown = false; - try - { + var ex = Assert.ThrowsExactly(() => _testRequestManager.StartTestSession( payload, new Mock().Object, new Mock().Object, - _protocolConfig); - } - catch (SettingsException ex) - { - exceptionThrown = true; - Assert.IsTrue(ex.Message.Contains(@"C:\temp.testsettings"), ex.Message); - } - - Assert.IsTrue(exceptionThrown, "Initialize should throw exception"); + _protocolConfig)); + Assert.Contains(@"C:\temp.testsettings", ex.Message); } [TestMethod] @@ -2561,10 +2538,10 @@ public void StopTestSessionShouldPropagateExceptionWhenKillSessionThrows() Assert.IsNotNull(eventArgs.TestSessionInfo); Assert.IsNotNull(eventArgs.Metrics); Assert.AreEqual(eventArgs.TestSessionInfo, testSessionInfo); - Assert.AreEqual(eventArgs.IsStopped, false); + Assert.IsFalse(eventArgs.IsStopped); }); - Assert.ThrowsException(() => + Assert.ThrowsExactly(() => _testRequestManager.StopTestSession( new() { @@ -2663,6 +2640,147 @@ public void AddOrUpdateBatchSizeSetsRunConfigurationAndBatchSize() Assert.AreEqual("1000", xmlDocument.OuterXml); } + [TestMethod] + public void UpdateCodeCoverageSettings_SetEnableDynamicNativeInstrumentationToFalse_WhenNotPresent() + { + // Arrange + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(""" + + + + + + + All + All + Verbose + %LOGS_DIR% + + %LOGS_DIR% + All + False + False + + + + + + + """); + var configuration = new RunConfiguration(); + + // Act + var result = TestRequestManager.UpdateCollectCoverageSettings(xmlDocument, configuration); + + // Assert + Assert.IsTrue(result); + Assert.Contains("FalseFalse", xmlDocument.OuterXml); + } + + [TestMethod] + public void UpdateCodeCoverageSettings_SetEnableDynamicNativeInstrumentationToFalse_WhenNotPresentAndParentDetailsOfConfigurationAreAlsoNotPresent() + { + // Arrange + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(""" + + + + + + + + + + """); + var configuration = new RunConfiguration(); + + // Act + var result = TestRequestManager.UpdateCollectCoverageSettings(xmlDocument, configuration); + + // Assert + Assert.IsTrue(result); + Assert.Contains($"False", xmlDocument.OuterXml); + } + + [TestMethod] + [DataRow("True")] + [DataRow("False")] + public void UpdateCodeCoverageSettings_DontSetEnableDynamicNativeInstrumentationToFalse_WhenAlreadyPresent(string setting) + { + // Arrange + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml($""" + + + + + + + All + All + Verbose + %LOGS_DIR% + + {setting} + + + + + + + """); + var configuration = new RunConfiguration(); + + // Act + var result = TestRequestManager.UpdateCollectCoverageSettings(xmlDocument, configuration); + + // Assert + // No matter what user has set, we don't override it. + Assert.IsFalse(result); + Assert.Contains($"{setting}", xmlDocument.OuterXml); + } + + [TestMethod] + [DataRow("friendlyName=\"Code Coverage\"")] + [DataRow("friendlyName=\"code coverage\"")] + [DataRow("uri=\"datacollector://Microsoft/CodeCoverage/2.0\"")] + [DataRow("uri=\"datacollector://microsoft/codecoverage/2.0\"")] + [DataRow("assemblyQualifiedName=\"Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\"")] + public void UpdateCodeCoverageSettings_SetEnableDynamicNativeInstrumentationToFalse_WhenUserUsesImperfectNamesForCollector(string collector) + { + // Arrange + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml($""" + + + + + + + All + All + Verbose + %LOGS_DIR% + + + + + + + + """); + var configuration = new RunConfiguration(); + + // Act + var result = TestRequestManager.UpdateCollectCoverageSettings(xmlDocument, configuration); + + // Assert + Assert.IsTrue(result); + Assert.Contains($"False", xmlDocument.OuterXml); + } + private static DiscoveryRequestPayload CreateDiscoveryPayload(string runsettings) { var discoveryPayload = new DiscoveryRequestPayload diff --git a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj index 6655577c90..1d5e926f41 100644 --- a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj +++ b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj @@ -6,8 +6,8 @@ - net6.0;net48 - Exe + net9.0;net48 + Exe vstest.console.UnitTests @@ -22,14 +22,6 @@ - + - - - - - - - -