using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using NUnit.Framework; using NUnit.Framework.Interfaces; using NUnit.Framework.Internal; using NUnit.Framework.Internal.Commands; using NUnit.Framework.Internal.Execution; using UnityEngine.TestTools.Logging; using UnityEngine.TestTools.TestRunner; using CountdownEvent = System.Threading.CountdownEvent; namespace UnityEngine.TestRunner.NUnitExtensions.Runner { internal class CompositeWorkItem : UnityWorkItem { private readonly TestSuite _suite; private readonly TestSuiteResult _suiteResult; private readonly ITestFilter _childFilter; private TestCommand _setupCommand; private TestCommand _teardownCommand; public List Children { get; private set; } private int _countOrder; private CountdownEvent _childTestCountdown; public CompositeWorkItem(TestSuite suite, ITestFilter childFilter, WorkItemFactory factory) : base(suite, factory) { _suite = suite; _suiteResult = Result as TestSuiteResult; _childFilter = childFilter; _countOrder = 0; } protected override IEnumerable PerformWork() { InitializeSetUpAndTearDownCommands(); if (UnityTestExecutionContext.CurrentContext != null && m_DontRunRestoringResult && EditModeTestCallbacks.RestoringTestContext != null) { EditModeTestCallbacks.RestoringTestContext(); } if (!CheckForCancellation()) if (Test.RunState == RunState.Explicit && !_childFilter.IsExplicitMatch(Test)) SkipFixture(ResultState.Explicit, GetSkipReason(), null); else switch (Test.RunState) { default: case RunState.Runnable: case RunState.Explicit: Result.SetResult(ResultState.Success); CreateChildWorkItems(); if (Children.Count > 0) { if (!m_DontRunRestoringResult) { //This is needed to give the editor a chance to go out of playmode if needed before creating objects. //If we do not, the objects could be automatically destroyed when exiting playmode and could result in errors later on yield return null; PerformOneTimeSetUp(); } if (!CheckForCancellation()) { switch (Result.ResultState.Status) { case TestStatus.Passed: foreach (var child in RunChildren()) { if (CheckForCancellation()) { yield break; } yield return child; } break; case TestStatus.Skipped: case TestStatus.Inconclusive: case TestStatus.Failed: SkipChildren(_suite, Result.ResultState.WithSite(FailureSite.Parent), "OneTimeSetUp: " + Result.Message); break; } } if (Context.ExecutionStatus != TestExecutionStatus.AbortRequested && !m_DontRunRestoringResult) { PerformOneTimeTearDown(); } } break; case RunState.Skipped: SkipFixture(ResultState.Skipped, GetSkipReason(), null); break; case RunState.Ignored: SkipFixture(ResultState.Ignored, GetSkipReason(), null); break; case RunState.NotRunnable: SkipFixture(ResultState.NotRunnable, GetSkipReason(), GetProviderStackTrace()); break; } if (!ResultedInDomainReload) { WorkItemComplete(); } } private bool CheckForCancellation() { if (Context.ExecutionStatus != TestExecutionStatus.Running) { Result.SetResult(ResultState.Cancelled, "Test cancelled by user"); return true; } return false; } private void InitializeSetUpAndTearDownCommands() { List setUpTearDownItems = _suite.TypeInfo != null ? CommandBuilder.BuildSetUpTearDownList(_suite.TypeInfo.Type, typeof(OneTimeSetUpAttribute), typeof(OneTimeTearDownAttribute)) : new List(); var actionItems = new List(); foreach (ITestAction action in Actions) { bool applyToSuite = (action.Targets & ActionTargets.Suite) == ActionTargets.Suite || action.Targets == ActionTargets.Default && !(Test is ParameterizedMethodSuite); bool applyToTest = (action.Targets & ActionTargets.Test) == ActionTargets.Test && !(Test is ParameterizedMethodSuite); if (applyToSuite) actionItems.Add(new TestActionItem(action)); if (applyToTest) Context.UpstreamActions.Add(action); } _setupCommand = CommandBuilder.MakeOneTimeSetUpCommand(_suite, setUpTearDownItems, actionItems); _teardownCommand = CommandBuilder.MakeOneTimeTearDownCommand(_suite, setUpTearDownItems, actionItems); } private void PerformOneTimeSetUp() { var logScope = new LogScope(); try { _setupCommand.Execute(Context); } catch (Exception ex) { if (ex is NUnitException || ex is TargetInvocationException) ex = ex.InnerException; Result.RecordException(ex, FailureSite.SetUp); } if (logScope.AnyFailingLogs()) { Result.RecordException(new UnhandledLogMessageException(logScope.FailingLogs.First())); } logScope.Dispose(); } private IEnumerable RunChildren() { int childCount = Children.Count; if (childCount == 0) throw new InvalidOperationException("RunChildren called but item has no children"); _childTestCountdown = new CountdownEvent(childCount); foreach (UnityWorkItem child in Children) { if (CheckForCancellation()) { yield break; } var unityTestExecutionContext = new UnityTestExecutionContext(Context); child.InitializeContext(unityTestExecutionContext); var enumerable = child.Execute().GetEnumerator(); while (true) { if (!enumerable.MoveNext()) { break; } ResultedInDomainReload |= child.ResultedInDomainReload; yield return enumerable.Current; } _suiteResult.AddResult(child.Result); childCount--; } if (childCount > 0) { while (childCount-- > 0) CountDownChildTest(); } } private void CreateChildWorkItems() { Children = new List(); var testSuite = _suite; foreach (ITest test in testSuite.Tests) { if (_childFilter.Pass(test)) { var child = m_Factory.Create(test, _childFilter); if (test.Properties.ContainsKey(PropertyNames.Order)) { Children.Insert(0, child); _countOrder++; } else { Children.Add(child); } } } if (_countOrder != 0) SortChildren(); } private class UnityWorkItemOrderComparer : IComparer { public int Compare(UnityWorkItem x, UnityWorkItem y) { var xKey = int.MaxValue; var yKey = int.MaxValue; if (x.Test.Properties.ContainsKey(PropertyNames.Order)) xKey = (int)x.Test.Properties[PropertyNames.Order][0]; if (y.Test.Properties.ContainsKey(PropertyNames.Order)) yKey = (int)y.Test.Properties[PropertyNames.Order][0]; return xKey.CompareTo(yKey); } } private void SortChildren() { Children.Sort(0, _countOrder, new UnityWorkItemOrderComparer()); } private void SkipFixture(ResultState resultState, string message, string stackTrace) { Result.SetResult(resultState.WithSite(FailureSite.SetUp), message, StackFilter.Filter(stackTrace)); SkipChildren(_suite, resultState.WithSite(FailureSite.Parent), "OneTimeSetUp: " + message); } private void SkipChildren(TestSuite suite, ResultState resultState, string message) { foreach (Test child in suite.Tests) { if (_childFilter.Pass(child)) { Context.Listener.TestStarted(child); TestResult childResult = child.MakeTestResult(); childResult.SetResult(resultState, message); _suiteResult.AddResult(childResult); if (child.IsSuite) SkipChildren((TestSuite)child, resultState, message); Context.Listener.TestFinished(childResult); } } } private void PerformOneTimeTearDown() { var logScope = new LogScope(); try { _teardownCommand.Execute(Context); } catch (Exception ex) { if (ex is NUnitException || ex is TargetInvocationException) ex = ex.InnerException; Result.RecordException(ex, FailureSite.SetUp); } if (logScope.AnyFailingLogs()) { Result.RecordException(new UnhandledLogMessageException(logScope.FailingLogs.First())); } logScope.Dispose(); } private string GetSkipReason() { return (string)Test.Properties.Get(PropertyNames.SkipReason); } private string GetProviderStackTrace() { return (string)Test.Properties.Get(PropertyNames.ProviderStackTrace); } private void CountDownChildTest() { _childTestCountdown.Signal(); if (_childTestCountdown.CurrentCount == 0) { if (Context.ExecutionStatus != TestExecutionStatus.AbortRequested) PerformOneTimeTearDown(); foreach (var childResult in _suiteResult.Children) if (childResult.ResultState == ResultState.Cancelled) { this.Result.SetResult(ResultState.Cancelled, "Cancelled by user"); break; } WorkItemComplete(); } } public override void Cancel(bool force) { if (Children == null) return; foreach (var child in Children) { var ctx = child.Context; if (ctx != null) ctx.ExecutionStatus = force ? TestExecutionStatus.AbortRequested : TestExecutionStatus.StopRequested; if (child.State == WorkItemState.Running) child.Cancel(force); } } } }