Skip to content

Add analyzer for OS platform conditions - #11044

Open
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/implement-issue-11018
Open

Add analyzer for OS platform conditions#11044
Amaury Levé (Evangelink) wants to merge 1 commit into
mainfrom
dev/amauryleve/implement-issue-11018

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

  • add MSTEST0084 to detect missing or inconsistent OSCondition attributes on tests using SupportedOSPlatformAttribute or UnsupportedOSPlatformAttribute
  • add a safe C# code fix that combines same-mode platforms, preserves named arguments, and updates conditions across partial declarations
  • avoid code fixes when mixed modes, platform versions, or unsupported platform names cannot be represented exactly
  • add C# and Visual Basic analyzer coverage and regenerate localized resources

Testing

  • analyzer unit test project builds with 0 warnings
  • 12 focused MSTEST0084 tests pass on net8.0 and net472
  • all 1,820 analyzer unit tests pass on net8.0

Fixes #11018

Add MSTEST0084 to keep platform compatibility attributes aligned with MSTest OSCondition attributes, including safe code fixes and coverage for multi-document and malformed syntax cases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2e19749e-0880-43e6-9e79-66116fe6ea89
Copilot AI balanced review requested due to automatic review settings September 4, 2026 14:41
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #11044

Parallelization

Test assembly Scope Workers Analyzer coverage
MSTest.Analyzers.UnitTests MethodLevel CPU count (Workers = 0) coverable once the parallel-safety analyzers ship (attribute-based opt-in)

Findings: A (global-state) 0 · B (paths) 0 · C (declaration) 0 · D (over-serialization) 0 — by severity: Critical 0 · High 0 · Warning 0 · Info 0.

The only test file this PR touches is a newly added file,
test/UnitTests/MSTest.Analyzers.UnitTests/OSPlatformAttributesShouldBeConsistentAnalyzerTests.cs
(420 lines added, no deletions). It adds 12 [TestMethod]s, all pure in-memory Roslyn
analyzer/code-fix verification via VerifyCS/VerifyVB (CSharpCodeFixVerifier /
VisualBasicCodeFixVerifier) plus one method that builds an AdhocWorkspace and an
in-memory Document. None of the added methods touch environment variables, the
current working directory, console state, culture, the registry, AppContext/AppDomain
state, mutable statics, or the filesystem — all inputs/outputs are in-memory strings and
Roslyn workspace objects. No lifecycle members ([TestInitialize]/[TestCleanup]/
[ClassInitialize]/[AssemblyInitialize], constructor, Dispose), and no
[ResourceLock] / [DoNotParallelize] / [Parallelize] declarations were added,
removed, or changed anywhere in this PR.

Nothing to flag for parallel-safety.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 86.5 AIC · ⌖ 5.11 AIC · ⊞ 24.8K · [◷]( · )

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Important no-fix, mode-equivalence, and FreeBSD branches lack effective regression coverage.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Low severity

New issues introduced by this change (1)
Severity Finding
Low severity src/​Analyzers/​MSTest.Analyzers/​OSPlatformAttributesShouldBeConsistentAnalyzer.cs — The tests for mixed modes and versioned platforms only call VerifyAnalyzerAsync, while the no-fix…
What changed in this PR

Adds MSTEST0084 to align platform compatibility attributes with MSTest runtime conditions, including a safe C# fixer and localized diagnostics.

Changes:

  • Detects missing or inconsistent OSCondition attributes.
  • Adds fixes for representable platform combinations and partial declarations.
  • Adds C#/VB tests, release metadata, and localization resources.
File Description
test/​UnitTests/​MSTest.Analyzers.UnitTests/​OSPlatformAttributesShouldBeConsistentAnalyzerTests.cs Tests analyzer and fixer behavior.
src/​Analyzers/​MSTest.Analyzers/​OSPlatformAttributesShouldBeConsistentAnalyzer.cs Implements MSTEST0084.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​OSPlatformAttributesShouldBeConsistentFixer.cs Implements the C# code fix.
src/​Analyzers/​MSTest.Analyzers/​Helpers/​WellKnownTypeNames.cs Adds platform attribute metadata names.
src/​Analyzers/​MSTest.Analyzers/​Helpers/​DiagnosticIds.cs Registers the MSTEST0084 identifier.
src/​Analyzers/​MSTest.Analyzers/​AnalyzerReleases.Unshipped.md Records the new analyzer rule.
src/​Analyzers/​MSTest.Analyzers/​Resources.resx Adds diagnostic strings.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​CodeFixResources.resx Adds the code-fix title.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.cs.xlf Adds Czech localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.de.xlf Adds German localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.es.xlf Adds Spanish localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.fr.xlf Adds French localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.it.xlf Adds Italian localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.ja.xlf Adds Japanese localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.ko.xlf Adds Korean localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.pl.xlf Adds Polish localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.pt-BR.xlf Adds Portuguese localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.ru.xlf Adds Russian localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.tr.xlf Adds Turkish localization entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.zh-Hans.xlf Adds Simplified Chinese entries.
src/​Analyzers/​MSTest.Analyzers/​xlf/​Resources.zh-Hant.xlf Adds Traditional Chinese entries.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.cs.xlf Adds Czech code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.de.xlf Adds German code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.es.xlf Adds Spanish code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.fr.xlf Adds French code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.it.xlf Adds Italian code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.ja.xlf Adds Japanese code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.ko.xlf Adds Korean code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.pl.xlf Adds Polish code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.pt-BR.xlf Adds Portuguese code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.ru.xlf Adds Russian code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.tr.xlf Adds Turkish code-fix localization.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.zh-Hans.xlf Adds Simplified Chinese code-fix entries.
src/​Analyzers/​MSTest.Analyzers.CodeFixes/​xlf/​CodeFixResources.zh-Hant.xlf Adds Traditional Chinese code-fix entries.
Suppressed comments (2)

src/Analyzers/MSTest.Analyzers/OSPlatformAttributesShouldBeConsistentAnalyzer.cs:192

  • The two-argument equivalence branch is untested: the equivalent-condition test covers only the one-argument include constructor, and the unsupported-platform test only adds a missing condition. A regression that reverses or ignores ConditionMode would therefore survive. Add equivalent explicit-Include and Exclude cases, plus a mismatched-mode case that expects a diagnostic.
            [{ Value: int actualMode }, { Value: int actualOperatingSystems }]
                => (actualMode == 0) == includeMode && actualOperatingSystems == operatingSystems,

src/Analyzers/MSTest.Analyzers/OSPlatformAttributesShouldBeConsistentAnalyzer.cs:154

  • FreeBSD is a newly supported mapping, but no test exercises this arm or verifies generation of OperatingSystems.FreeBSD. A wrong bit or emitted name here would leave the current suite green. Add a FreeBSD fix or equivalence case.
            "FREEBSD" => 1 << 3,

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +103 to +107
ImmutableDictionary<string, string?> properties = canFix
? ImmutableDictionary<string, string?>.Empty
.Add(ConditionModeKey, includeMode ? "Include" : "Exclude")
.Add(OperatingSystemsKey, operatingSystemsExpression)
: ImmutableDictionary<string, string?>.Empty;
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #11044

GradeTestMutationNotesHow to improve
B (80–89) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenSupportedPlatformHasNoOSConditionInVisualBasic_
Diagnostic
2/3 killed Verifies diagnostic location only; unlike its C# counterpart it never asserts the reported message argument (method name). Assert the diagnostic explicitly with VerifyVB.Diagnostic().WithLocation(0).WithArguments("TestMethod").
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenSupportedPlatformHasNoOSCondition_
AddsIncludeCondition
4/4 killed Fully verifies diagnostic location, message argument, and generated fix.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenUnsupportedPlatformsHaveNoOSCondition_
AddsCombinedExcludeCondition
4/4 killed Covers combined multi-platform exclude mode with class-level attribute and verifies the exact fix.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenOSConditionIsEquivalent_
NoDiagnostic
3/3 killed Confirms the no-op path when an existing OSCondition already matches, guarding against false positives.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenOSConditionIsInconsistent_
UpdatesCondition
4/4 killed Verifies the update path replacing a stale OSCondition value with the correct one.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenOSConditionIsInconsistent_
PreservesNamedArguments
4/4 killed Confirms named arguments (e.g. IgnoreMessage) survive the code fix rewrite.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenOSConditionIsOnAnotherPartialDeclaration_
UpdatesItsDocument
4/4 killed Exercises the cross-document fix path for a partial class split across files.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenExistingOSConditionConstructorIsMalformed_
UpdatesAttributeWithoutDuplicatingIt
3/3 killed Targets the branch where an existing parenthesis-less [OSCondition] attribute must be replaced, not duplicated.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenDiagnosticHasNoSafeFix_
DoesNotRegisterCodeFix
2/2 killed White-box test of the fixer's early return when the diagnostic lacks the required properties.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenPlatformsUseMixedModes_
DiagnosticWithoutFix
3/3 killed Confirms mixed supported/unsupported attributes report a diagnostic but register no fix.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenPlatformHasVersion_
DiagnosticWithoutFix
2/2 killed Confirms versioned platform strings (e.g. windows10.0) are unmapped and yield a fixless diagnostic.
A (90–100) new OSPlatformAttributesShouldBeConsistentAnalyzerTests.
WhenPlatformAttributeIsOnNonTest_
NoDiagnostic
2/2 killed Verifies the analyzer ignores platform attributes on non-test members entirely.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 126 AIC · ⌖ 2.79 AIC · ⊞ 16.9K · [◷]( · )

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 126 AIC · ⌖ 2.79 AIC · ⊞ 16.9K ·

Comment on lines +402 to +419
[TestMethod]
public async Task WhenSupportedPlatformHasNoOSConditionInVisualBasic_Diagnostic()
{
string code = """
Imports System.Runtime.Versioning
Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass>
Public Class MyTestClass
<TestMethod>
<[|SupportedOSPlatform("linux")|]>
Public Sub TestMethod()
End Sub
End Class
""";

await VerifyVB.VerifyAnalyzerAsync(code);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Test review · Grade B (80–89) — Verifies diagnostic location only; unlike its C# counterpart it never asserts the reported message argument (method name).

Assert the diagnostic explicitly with WithArguments("TestMethod") so a regression in the message format goes undetected.

Suggested change
[TestMethod]
public async Task WhenSupportedPlatformHasNoOSConditionInVisualBasic_Diagnostic()
{
string code = """
Imports System.Runtime.Versioning
Imports Microsoft.VisualStudio.TestTools.UnitTesting
<TestClass>
Public Class MyTestClass
<TestMethod>
<[|SupportedOSPlatform("linux")|]>
Public Sub TestMethod()
End Sub
End Class
""";
await VerifyVB.VerifyAnalyzerAsync(code);
}
[TestMethod]
public async Task WhenSupportedPlatformHasNoOSConditionInVisualBasic_Diagnostic()
{
string code = """
Imports System.Runtime.Versioning
Imports Microsoft.VisualStudio.TestTools.UnitTesting
<TestClass>
Public Class MyTestClass
<TestMethod>
<[|SupportedOSPlatform("linux")|]>
Public Sub TestMethod()
End Sub
End Class
""";
await VerifyVB.VerifyAnalyzerAsync(
code,
VerifyVB.Diagnostic().WithLocation(0).WithArguments("TestMethod"));
}
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 Automated review by GitHub Copilot. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.

Expert Review — MSTEST0084: OS Platform Conditions Analyzer

PR adds a new analyzer (MSTEST0084) + C# code fix that detects [SupportedOSPlatform]/[UnsupportedOSPlatform] without a matching [OSCondition], and offers to add or update one. Well-structured, focused PR with good test coverage (12 dedicated tests covering include/exclude, partial classes, named argument preservation, mixed-mode bail-out, versioned platforms, and VB diagnostic-only).

Verdict Table

# Dimension Result
1 Algorithmic Correctness ✅ LGTM — Logic is sound; mixed-mode and versioned-platform bail-outs are correct. Minor: macosOSX canonicalization noted inline.
2 Threading & Concurrency ✅ N/A — Analyzer is stateless, concurrent execution enabled correctly.
3 Security & IPC Contract Safety ✅ N/A
4 Public API & Binary Compatibility ✅ LGTM — No new public API; analyzer classes follow existing conventions. Diagnostic ID MSTEST0084 added to AnalyzerReleases.Unshipped.md.
5 Performance & Allocations ✅ LGTM — Code fix path only; no hot-path concern. Minor nit inline.
6 Cross-TFM Compatibility ✅ LGTM — Analyzer targets Roslyn APIs; no TFM-specific APIs used.
7 Resource & IDisposable Management ✅ N/A
8 Defensive Coding at Boundaries ✅ LGTM — Graceful bail-outs when symbol resolution fails, when constructor arguments don't match expected shapes, and when platform names are unmappable.
9 Localization & Resources ✅ LGTM — Strings in .resx, {Locked} markers use bracket/quote form ([OSCondition], SupportedOSPlatformAttribute) — no substring collision risk. XLF files regenerated.
10 Test Isolation ✅ N/A
11 Assertion Quality ✅ LGTM — Uses CSharpCodeFixVerifier / VisualBasicCodeFixVerifier infrastructure.
12 Flakiness Patterns ✅ N/A
13 Test Completeness & Coverage ⚠️ MODERATE — Missing test for unrecognized-but-unversioned platform name (e.g. "android"). See inline comment.
14 Data-Driven Test Coverage ✅ LGTM — Good variety of scenarios.
15 Code Structure & Simplification ✅ LGTM — Clean, well-factored code.
16 Naming & Conventions ✅ LGTM
17 Documentation Accuracy ⚠️ NIT — Bit-position duplication from OperatingSystems enum deserves a sync-requirement comment. See inline.
18 Analyzer & Code Fix Quality ✅ LGTM — Analyzer doesn't throw on malformed input; code fix produces compilable output; severity Info is appropriate.
19 IPC Wire Compatibility ✅ N/A
20 Build Infrastructure & Dependencies ✅ N/A
21 Scope & PR Discipline ✅ LGTM — Single concern, references #11018.
22 PowerShell Scripting Hygiene ✅ N/A

Summary

Solid, well-tested analyzer addition. No blocking issues found. Two minor suggestions inline (add a comment about bit-position sync, add a test case for unrecognized platform names). Overall this is ready for human sign-off.

Comment on lines +147 to +155
private static bool TryMapPlatform(string platformName, out int operatingSystem)
{
operatingSystem = platformName.ToUpperInvariant() switch
{
"LINUX" => 1 << 0,
"OSX" or "MACOS" => 1 << 1,
"WINDOWS" => 1 << 2,
"FREEBSD" => 1 << 3,
_ => 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (Dimension 17 — Documentation): The bit positions here (1 << 0 through 1 << 3) are duplicated from the OperatingSystems enum in TestFramework. Because the analyzer assembly can't reference the framework, the duplication is unavoidable, but a comment noting the sync requirement would help future maintainers:

Suggested change
private static bool TryMapPlatform(string platformName, out int operatingSystem)
{
operatingSystem = platformName.ToUpperInvariant() switch
{
"LINUX" => 1 << 0,
"OSX" or "MACOS" => 1 << 1,
"WINDOWS" => 1 << 2,
"FREEBSD" => 1 << 3,
_ => 0,
operatingSystem = platformName.ToUpperInvariant() switch
{
// Bit positions must match the OperatingSystems enum in
// Microsoft.VisualStudio.TestTools.UnitTesting.
"LINUX" => 1 << 0,
"OSX" or "MACOS" => 1 << 1,
"WINDOWS" => 1 << 2,
"FREEBSD" => 1 << 3,
_ => 0,
};


await VerifyVB.VerifyAnalyzerAsync(code);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (Dimension 13 — Test Completeness): Consider adding a test for a platform name that is valid for [SupportedOSPlatform] but unmappable to OperatingSystems (e.g., "android" or "ios"). This path differs from the versioned-platform test ("windows10.0.19041") because the base name itself is unrecognized, not just versioned. The analyzer should still report the diagnostic but with no code fix offered.


private static void AddNameIfSet(List<string> names, int operatingSystems, int value, string name)
{
if ((operatingSystems & value) != 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (Dimension 1 — Algorithmic Correctness): TryMapPlatform normalises via ToUpperInvariant(), which correctly maps both "OSX" and "MACOS" to the same bit. However, CreateOperatingSystemsExpression always emits "OSX" for the round-tripped name. If the user originally wrote [SupportedOSPlatform("macos")], the code fix will insert OperatingSystems.OSX — which is correct (it's the enum member name) but may surprise a user who expected the code fix to mirror their casing/name choice.

This isn't a bug (the code fix should use the canonical enum name), but it may be worth a brief note in the docs page for MSTEST0084 explaining the macosOSX canonicalization.

.Select(name => SyntaxFactory.ParseExpression($"{MSTestNamespace}.OperatingSystems.{name}")
.WithAdditionalAnnotations(Simplifier.Annotation, Simplifier.AddImportsAnnotation))
.ToArray();
ExpressionSyntax operatingSystemsExpression = operatingSystemExpressions.Aggregate(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (Dimension 5 — Performance): .ToArray() + .Aggregate() allocate intermediary arrays. Since this is code-fix-only (not hot path), it's fine — just noting for completeness that SeparatedList accepts IEnumerable, so the .ToArray() on operatingSystemExpressions could be avoided if desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Analyzer-Idea: Un/SupportedOSPlatformAttribute should be consistent with OSConditionAttribute

2 participants