{"payload":{"feedbackUrl":"https://github.com/orgs/community/discussions/53140","repo":{"id":15797463,"defaultBranch":"master","name":"Testing","ownerLogin":"LeapingGorillaLTD","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2014-01-10T12:23:14.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/6359527?v=4","public":true,"private":false,"isOrgOwned":true},"refInfo":{"name":"","listCacheKey":"v0:1663932494.4945412","currentOid":""},"activityList":{"items":[{"before":"03ef9e4e4095b188fc206c40ac72ae844c6c3c86","after":"a6008945156beb2e69228e474cf9723346b447e3","ref":"refs/heads/master","pushedAt":"2024-04-09T06:19:58.000Z","pushType":"pr_merge","commitsCount":9,"pusher":{"login":"gary-lg","name":"Gary H","path":"/gary-lg","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/17553415?s=80&v=4"},"commit":{"message":"Merge pull request #7 from dolbz/xunit-improvements\n\nImplements two improvements for xUnit\r\n# Remove blocking .Wait() call during test setup\r\n\r\n## Background\r\n\r\nxUnit uses a SynchronisationContext that uses a fixed number of threads which by default matches the number of logical processors available on the machine. If any code calls the synchronous .Wait() method on a Task this blocks the executing thread and the rest of the async state machine will execute on one of the other synchronisation context threads.\r\n\r\nThis behaviour can cause a deadlock if there are enough tests with async methods in their setup. As xUnit runs multiple tests in parallel by default, it will run as many tests as there are logical processors in parallel. If all test threads block on .Wait() calls at the same time there will be no threads available to execute the rest of the code being waited on. All running tests will be waiting for a thread to be available and all threads will be blocked so none will become available. When using await properly from end-to-end, threads aren't blocked during an await and are able run to completion.\r\nThe change\r\n\r\nxUnit supports asynchronous setup using the IAsyncLifetime interface on test classes. The base classes used for Leaping Gorilla tests now implement this interface and perform test setup asynchronously. If a test contains only synchronous setup it will still run synchronously as only completed tasks will be returned from the async setup methods.\r\n\r\nThe existing synchronous Setup() method has been retained to avoid a breaking change for consumers but SetupAsync() should now be used instead if custom setup invocation is required. The legacy method still does a synchronous .Wait() so is vulnerable to the deadlock described above.\r\n\r\nℹ️ NUnit code was also updated to use an async method for setup but it isn't as affected by the .Wait() issue like xUnit so it's more of a no-op change.\r\n\r\n# Use one test class instance for each Leaping Gorilla test scenario\r\n\r\n## Background\r\n\r\nxUnit creates a new test class instance for every [Fact] method by design. The Leaping Gorilla [Then] attribute extends [Fact], so the existing behaviour is that a new test class instance is created for every [Then].\r\n\r\nThis isn't a problem behaviourally, but in some test scenarios e.g. integration testing where setup may be more expensive it can be a performance issue. The Leaping Gorilla test structure suits an approach where each concrete test class is constructed once per test run and multiple assertions are made on the state of objects within that instance.\r\nThe change\r\n\r\nThe changes in this PR override the test run behaviour to fit this philosophy, specifically for Leaping Gorilla based tests. If any regular xUnit tests are included in the same test assembly they will execute as normal.\r\n\r\nKey changes:\r\n\r\n Added LeapingGorillaTestCase which:\r\n Identifies a test as a Leaping Gorilla test\r\n Keeps a record of all the [Then] methods that make up the test class\r\n Invokes tests using LeapingGorillaTestCaseRunner\r\n Added LeapingGorillaTestInvoker which is where the code ultimately ends up after being run via LeapingGorillaTestCaseRunner. This class does the following:\r\n Keeps a cache of test class instances and returns the cached instances as required\r\n Records which test methods have executed for each instance\r\n Once all expected [Then] methods have executed, removes the instance from the cache\r\n Uses dynamic proxies to intercept IAsyncLifetime method calls as they are called on every test method invocation and filters them to ensure they get invoked consistently with the class lifetime instead.","shortMessageHtmlLink":"Merge pull request #7 from dolbz/xunit-improvements"}},{"before":"de7e586c694ba945eb2a40343198499f2d4696e2","after":"03ef9e4e4095b188fc206c40ac72ae844c6c3c86","ref":"refs/heads/master","pushedAt":"2024-04-05T09:12:36.000Z","pushType":"push","commitsCount":1,"pusher":{"login":"gary-lg","name":"Gary H","path":"/gary-lg","primaryAvatarUrl":"https://avatars.githubusercontent.com/u/17553415?s=80&v=4"},"commit":{"message":"Update to v6.0.1 with fluent fixes","shortMessageHtmlLink":"Update to v6.0.1 with fluent fixes"}}],"hasNextPage":false,"hasPreviousPage":false,"activityType":"all","actor":null,"timePeriod":"all","sort":"DESC","perPage":30,"cursor":"djE6ks8AAAAEK6YsHQA","startCursor":null,"endCursor":null}},"title":"Activity · LeapingGorillaLTD/Testing"}