using System; using System.Collections.Generic; using System.Linq; namespace OpenTap { /// /// Mixins can be used with EmbedPropertiesAttribute to give extra functionality to a test step. /// public interface IMixin : ITapPlugin { } abstract class MixinEvent where T2: IMixin { static readonly TraceSource log = Log.CreateSource("Mixin"); protected static T1 Invoke(object target, Action f, T1 arg) => Invoke(target, f, null, arg, out _); protected static T1 Invoke(object target, Action f, Func ordering, T1 arg, out bool anyInvoked) { anyInvoked = false; var emb = TypeData.GetTypeData(target).GetBaseType(); if (emb == null) return arg; var embeddingMembers = emb.GetEmbeddingMembers(); List objects = null; foreach (var mem in embeddingMembers) { if (!mem.TypeDescriptor.DescendsTo(typeof(T2))) continue; if (mem.Readable == false) continue; if (mem.GetValue(target) is T2 mixin) { (objects ??= []).Add(mixin); } } if (objects != null) { IEnumerable orderedObjects = ordering != null ? objects.OrderBy(ordering) : objects; foreach (var mixin in orderedObjects) { try { f(mixin, arg); } catch (Exception e) when (e is not OperationCanceledException) { log.Error("Caught error in mixin: {0}", e.Message); log.Debug(e); } } anyInvoked = true; } return arg; } } class TestStepPreRunEvent : MixinEvent { public static TestStepPreRunEventArgs Invoke(ITestStep step) { var eventArg = new TestStepPreRunEventArgs(step); Invoke(step, static (v, arg) => v.OnPreRun(arg), static v => (v as ITestStepPreRunMixinOrder)?.GetPreRunOrder() ?? 0.0, eventArg, out bool anyInvoked); eventArg.AnyPrerunsInvoked = anyInvoked; return eventArg; } } /// Event args for ITestStepPreRun mixin. public sealed class TestStepPreRunEventArgs { /// The step for which the event happens. public ITestStep TestStep { get; } /// Can be set to true to skip the step public bool SkipStep { get; set; } /// Indicates whether any prerun mixins were invoked. internal bool AnyPrerunsInvoked { get; set; } internal TestStepPreRunEventArgs(ITestStep step) => TestStep = step; } class TestPlanPreRunEvent : MixinEvent { public static TestPlanPreRunEventArgs Invoke(TestPlan plan) => Invoke(plan, (v, arg) => v.OnPreRun(arg), new TestPlanPreRunEventArgs(plan)); } /// Event args for ITestStepPreRun mixin. public sealed class TestPlanPreRunEventArgs { /// The step for which the event happens. public TestPlan TestPlan { get; } internal TestPlanPreRunEventArgs(TestPlan step) => TestPlan = step; } class TestStepPostRunEvent : MixinEvent { public static void Invoke(ITestStep step) => Invoke(step, static (mixin, args) => mixin.OnPostRun(args), static v => (v as ITestStepPostRunMixinOrder)?.GetPostRunOrder() ?? 0, new TestStepPostRunEventArgs(step), out _); } /// Event args for ITestStepPostRun mixin. public sealed class TestStepPostRunEventArgs { /// The step for which the event happens. public ITestStep TestStep { get; } internal TestStepPostRunEventArgs(ITestStep step) => TestStep = step; } class ResourcePreOpenEvent: MixinEvent { public static void Invoke(IResource resource) => Invoke(resource, static (mixin, args) => mixin.OnPreOpen(args), new ResourcePreOpenEventArgs(resource)); } /// Event args for IResourcePreOpenMixin mixin. public class ResourcePreOpenEventArgs { /// The resource for which the event happens. public IResource Resource { get; } internal ResourcePreOpenEventArgs(IResource resource) => Resource = resource; } /// This mixin is activated just after a step has been executed. It allows modifying the test step run. public interface ITestStepPostRunMixin : IMixin { /// Invoked after test step run. void OnPostRun(TestStepPostRunEventArgs eventArgs); } /// Defines an ordering for test step post run mixins. public interface ITestStepPostRunMixinOrder : ITestStepPostRunMixin { /// /// Gets the order. Default value is 0. The ordering is ascending. /// double GetPostRunOrder(); } /// This mixin is activated just before a step is executed. It allows modifying the test step run. public interface ITestStepPreRunMixin : IMixin { /// Invoked before test step run. void OnPreRun(TestStepPreRunEventArgs eventArgs); } /// Defines an ordering for test step pre run mixins. public interface ITestStepPreRunMixinOrder : ITestStepPreRunMixin { /// Gets the order. Default value is 0. The ordering is ascending. double GetPreRunOrder(); } /// This mixin is activated just before a resource opens. public interface IResourcePreOpenMixin : IMixin { /// Invoked just before IResource.Open is called. void OnPreOpen(ResourcePreOpenEventArgs eventArgs); } /// This mixin is activated just before a step is executed. It allows modifying the test step run. public interface ITestPlanPreRunMixin : IMixin { /// Invoked before test step run. void OnPreRun(TestPlanPreRunEventArgs eventArgs); } }