Skip to content

Add test coverage for platform attribute behaviours#5204

Merged
stevenaw merged 5 commits into
nunit:mainfrom
stevenaw:5168-platform--dontmergeprops-tests
Apr 1, 2026
Merged

Add test coverage for platform attribute behaviours#5204
stevenaw merged 5 commits into
nunit:mainfrom
stevenaw:5168-platform--dontmergeprops-tests

Conversation

@stevenaw
Copy link
Copy Markdown
Member

@stevenaw stevenaw commented Mar 30, 2026

Fixes #5168

A doc update PR will also be required before both can be reviewed

@stevenaw stevenaw marked this pull request as draft March 30, 2026 20:44
@stevenaw stevenaw force-pushed the 5168-platform--dontmergeprops-tests branch from 4916196 to 00400de Compare March 30, 2026 20:45
Copy link
Copy Markdown
Member

@manfred-brands manfred-brands left a comment

Choose a reason for hiding this comment

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

@stevenaw What you test is the behaviour of your ObservableAttribute, not the PlatformAttribute. The latter doesn't use properties to checking on the properties at the fixture and test level tests how NUnit deals with properties there, not how the PlatformAttribute works.

Your ChildTestWithoutAttribute_DoesntExplicitlyInheritPlatforms is wrong.
PlatformAttribute on a (base) Fixture mark the test as non-runnable.

The actual unit test needs to check if the test is runnable based upon the platform passed in.
You can either change the expected result based upon the real platform the test is running on, e.g. if running on Linux, the Windows test should be NotRunnable and vice versa.

Alternatively, to bypass the actual Platform in the PlatformHelper, we need to allow a unit test to change the IsPlatformSupported method behaviour to a controlled one supplied by the test.

One way with your new dictionary is to add a method that adds dummy entries to the PlatformChecks dictionary which you then use in your tests.

@stevenaw
Copy link
Copy Markdown
Member Author

stevenaw commented Mar 31, 2026

Thanks @manfred-brands

My original comment in #5166 and intent here was not to test the run/skip check itself, but rather the relationship between a fixture and tests with regards to this attribute. In particular, that the configuration applied to a fixture is completely independent of that applied to a test method. The run/skip check is then, of course, derived from that however we already have quite good coverage of those checks here: https://github.com/nunit/nunit/blob/main/src/NUnitFramework/tests/Internal/PlatformDetectionTests.cs That's also why I felt it important to write ChildTestWithoutAttribute_DoesntExplicitlyInheritPlatforms as it captures the case where the platform attribute is only present on the fixture. The ObservablePlatformAttribute was then created as a way to see this more directly as a simple "skip/run" doesn't really capture the nuance of why it was skipped or runnable without being able to compare the Include/Exclude properties which factored into the calculation. They were exposed as properties of the test for visibility as the original attribute instance is short-lived

I can try your suggestion to update the tests to probe more directly for the run/skip check by substituting dummy values into the static PlatformChecks field, however modifying a static field would risk leaking state and potentially make the tests more brittle too

@stevenaw
Copy link
Copy Markdown
Member Author

stevenaw commented Mar 31, 2026

@manfred-brands I've pushed a commit which I think makes the changes you were asking for. It required disabling parallelization of these tests as well making changes to NUnit itself to remove the readonly modifier on the field so that the copy of it I saved prior to mutating it could be safely restored back into the field at the end

@stevenaw stevenaw marked this pull request as ready for review March 31, 2026 23:07

private static readonly Dictionary<string, Func<OSPlatform, bool>> PlatformChecks =
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable IDE1006 // Naming Styles
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've left these suppressions in like this temporarily until we confirm if we want to go in this direction. If we went ahead then I'd fix the underlying formatting issue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No I wasn't thinking of completely replacing the dictionary.
More of adding non-standard entries.

Copy link
Copy Markdown
Member

@manfred-brands manfred-brands left a comment

Choose a reason for hiding this comment

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

I have added 3 commits.

  1. Using test specific platforms.
  2. Add test on real platforms.
  3. Add tests using Excludes

Only thing left is the test naming.
Even though the attribute is separately applied to the fixture and the test, if the fixture is not runnable, the test will not be run, regardless of the test attributes.
So the attribute on the fixture is implicitly inherited.


private static readonly Dictionary<string, Func<OSPlatform, bool>> PlatformChecks =
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable IDE1006 // Naming Styles
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No I wasn't thinking of completely replacing the dictionary.
More of adding non-standard entries.

namespace NUnit.TestData
{
[ObservablePlatform(Includes = [PlatformNames.DotNET])]
[Platform(Includes = [PlatformNames.Win])]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was thinking of using non-standard platforms not affecting any other test.

[OneTimeSetUp]
public void OneTimeSetUp()
{
_platformField = typeof(PlatformHelper).GetField("PlatformChecks", BindingFlags.Static | BindingFlags.NonPublic)!;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we make the field internal we don't have to resort to reflection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for your change. I admit I usually try to avoid modifying the system under test just to expose things for testing, but I'm alright with this approach.

Comment on lines +53 to +54
Assert.That(testMethod.RunState, Is.EqualTo(RunState.Runnable));
Assert.That(fixture.RunState == RunState.Runnable, Is.EqualTo(allowOS));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

General question: Why would a testMethod be Runnable when the fixture is not?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've always understood this to be an implementation detail. TestSuite and TestMethod both implement ITest, and the IApplyToTest interface in turn applies only to the ITest which it has been decorated with. I understand the framework should then account for these varying RunStates when attempting to run a test method where the suite is "Skipped" though I admit I haven't explicitly checked that condition.

There is a bit more discussion on the nuance in #2936 as it relates to properties of tests. The best single comment I could find there to explain is Charlie's comment here: #2936 (comment)

@stevenaw
Copy link
Copy Markdown
Member Author

stevenaw commented Apr 1, 2026

Thanks for your changes and approval here @manfred-brands . No concerns from me with what you've pushed either.

@stevenaw stevenaw merged commit 9daf3ed into nunit:main Apr 1, 2026
5 checks passed
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.

Check tests and documentation for the Platform attribute

2 participants