Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/xwt.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLluis Sanchez <llsan@microsoft.com>2017-10-02 17:20:25 +0300
committerLluis Sanchez <llsan@microsoft.com>2017-10-02 17:20:25 +0300
commitf04a0fdd5e85b3761362cb30bb3648fe524d56a0 (patch)
treea27b52b49ccb72fee7e77d1fb821b7dcd57fd337 /Testing/CoreTests
parent249e0be72ddc2821cfdf54e22b40e5fd38c8ef67 (diff)
Fix some context switching issues
Make sure that the original sync context is restored after invoking on a specific toolkit. Also added some unit tests.
Diffstat (limited to 'Testing/CoreTests')
-rw-r--r--Testing/CoreTests/ContextSwitchTests.cs295
-rw-r--r--Testing/CoreTests/CoreTests.csproj47
-rw-r--r--Testing/CoreTests/FakeToolkit.cs186
3 files changed, 528 insertions, 0 deletions
diff --git a/Testing/CoreTests/ContextSwitchTests.cs b/Testing/CoreTests/ContextSwitchTests.cs
new file mode 100644
index 00000000..f63ecefc
--- /dev/null
+++ b/Testing/CoreTests/ContextSwitchTests.cs
@@ -0,0 +1,295 @@
+//
+// Test.cs
+//
+// Author:
+// Lluis Sanchez <llsan@microsoft.com>
+//
+// Copyright (c) 2017 Microsoft
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using NUnit.Framework;
+using System;
+using Xwt;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace CoreTests
+{
+ [TestFixture ()]
+ public class ContextSwitchTests
+ {
+ Toolkit mainToolkit;
+ Toolkit secToolkit;
+
+ [Test]
+ public void AsyncContextSwitch ()
+ {
+ Exception error = null;
+ Application.UnhandledException += (object sender, ExceptionEventArgs e) => {
+ error = e.ErrorException;
+ // If somethig goes wrong, make sure the main loop is exited
+ Application.Exit ();
+ };
+
+ // Load the main toolkit
+
+ Application.Initialize (typeof (MainFakeToolkit).AssemblyQualifiedName);
+ mainToolkit = Toolkit.CurrentEngine;
+
+ // Load a second toolkit
+
+ secToolkit = Toolkit.Load (typeof (SecondaryFakeToolkit).AssemblyQualifiedName);
+
+ // The main toolkit is the default toolkit by default
+
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ // Run the tests in the simulated ui loop
+ var task = AsyncContextSwitchLoop ();
+
+ Application.Run ();
+ task.Wait ();
+
+ if (error != null) {
+ Console.WriteLine (error);
+ throw error;
+ }
+
+ Application.Dispose ();
+ }
+
+ async Task AsyncContextSwitchLoop ()
+ {
+ // We are in the main toolkit
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ bool callbackInvoked = false;
+
+ var t = secToolkit.Invoke (async delegate {
+
+ // Current engine should now be the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+
+ await Task.Delay (200);
+
+ // Current engine should still the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+ Console.WriteLine ("Done");
+ callbackInvoked = true;
+ });
+
+ // Invocation on the second toolkit should not change the
+ // current engine
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ await Task.Delay (2000);
+
+ // During the wait, code in the context of the secondary context
+ // has been called, but the main toolkit must have been restored
+
+ Assert.IsTrue (callbackInvoked);
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ Application.Exit ();
+ }
+
+ [Test]
+ public void AppInvokeCapturesToolkit ()
+ {
+ Exception error = null;
+ Application.UnhandledException += (object sender, ExceptionEventArgs e) => {
+ error = e.ErrorException;
+ // If somethig goes wrong, make sure the main loop is exited
+ Application.Exit ();
+ };
+
+ // Load the main toolkit
+ Application.Initialize (typeof (MainFakeToolkit).AssemblyQualifiedName);
+ mainToolkit = Toolkit.CurrentEngine;
+
+ // Load a second toolkit
+ secToolkit = Toolkit.Load (typeof (SecondaryFakeToolkit).AssemblyQualifiedName);
+
+ // The main toolkit is the default toolkit by default
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ secToolkit.Invoke (delegate {
+ // Current engine should now be the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+ Application.TimeoutInvoke (100, () => {
+ // Current toolkit should still be the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+ Application.Exit ();
+ return false;
+ });
+ });
+
+ Application.Run ();
+
+ if (error != null) {
+ Console.WriteLine (error);
+ throw error;
+ }
+
+ Application.Dispose ();
+ }
+
+ [Test]
+ public void TaskWaitRestoresMainToolkit ()
+ {
+ Exception error = null;
+ Application.UnhandledException += (object sender, ExceptionEventArgs e) => {
+ error = e.ErrorException;
+ // If somethig goes wrong, make sure the main loop is exited
+ Application.Exit ();
+ };
+
+ // Load the main toolkit
+
+ Application.Initialize (typeof (MainFakeToolkit).AssemblyQualifiedName);
+ mainToolkit = Toolkit.CurrentEngine;
+
+ // Load a second toolkit
+
+ secToolkit = Toolkit.Load (typeof (SecondaryFakeToolkit).AssemblyQualifiedName);
+
+ // We are in the main toolkit
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ bool noXwtCallback = false;
+
+ // Enqueue an event that will be executed directly by the
+ // event loop, out of the XWT context
+ EventQueue.MainEventQueue.Enqueue (delegate {
+ // When outside the XWT context, the default toolkit should be
+ // the main toolkit
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+ noXwtCallback = true;
+ return false;
+ }, TimeSpan.FromMilliseconds (50));
+
+ var t = secToolkit.Invoke (async delegate {
+
+ // Current engine should now be the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+
+ Assert.IsFalse (noXwtCallback);
+
+ await Task.Delay (200);
+
+ // Out of band event was executed during the wait
+ Assert.IsTrue (noXwtCallback);
+
+ // Current engine should still the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+ Console.WriteLine ("Done");
+ Application.Exit ();
+ });
+
+ // Invocation on the second toolkit should not change the
+ // current engine
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ Application.Run ();
+
+ if (error != null) {
+ Console.WriteLine (error);
+ throw error;
+ }
+
+ Application.Dispose ();
+ }
+
+
+ [Test]
+ public void EventLoopPumpInMainToolkit ()
+ {
+ Exception error = null;
+ Application.UnhandledException += (object sender, ExceptionEventArgs e) => {
+ error = e.ErrorException;
+ // If somethig goes wrong, make sure the main loop is exited
+ Application.Exit ();
+ };
+
+ // Load the main toolkit
+
+ Application.Initialize (typeof (MainFakeToolkit).AssemblyQualifiedName);
+ mainToolkit = Toolkit.CurrentEngine;
+
+ // Load a second toolkit
+
+ secToolkit = Toolkit.Load (typeof (SecondaryFakeToolkit).AssemblyQualifiedName);
+
+ // We are in the main toolkit
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ bool noXwtCallback = false;
+
+ // Enqueue an event that will be executed directly by the
+ // event loop, out of the XWT context
+ EventQueue.MainEventQueue.Enqueue (delegate {
+ // When outside the XWT context, the default toolkit should be
+ // the main toolkit
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+ noXwtCallback = true;
+ });
+
+ secToolkit.Invoke (delegate {
+
+ // Current engine should now be the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+
+ Assert.IsFalse (noXwtCallback);
+
+ Application.MainLoop.DispatchPendingEvents ();
+
+ // Out of band event must have been dispatched
+ Assert.IsTrue (noXwtCallback);
+
+ // Current engine should still the second toolkit
+ Assert.AreSame (secToolkit, Toolkit.CurrentEngine);
+ Console.WriteLine ("Done");
+ Application.Exit ();
+ });
+
+ // Invocation on the second toolkit should not change the
+ // current engine
+ Assert.AreEqual (mainToolkit, Toolkit.CurrentEngine);
+
+ Application.Run ();
+
+ if (error != null) {
+ Console.WriteLine (error);
+ throw error;
+ }
+
+ Application.Dispose ();
+ }
+ }
+
+ class MainFakeToolkit : FakeToolkit
+ {
+ }
+
+ class SecondaryFakeToolkit : FakeToolkit
+ {
+
+ }
+}
diff --git a/Testing/CoreTests/CoreTests.csproj b/Testing/CoreTests/CoreTests.csproj
new file mode 100644
index 00000000..99839e81
--- /dev/null
+++ b/Testing/CoreTests/CoreTests.csproj
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{F0C03C12-F08A-4378-958D-86DD4CFE966F}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>CoreTests</RootNamespace>
+ <AssemblyName>CoreTests</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="nunit.framework">
+ <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ContextSwitchTests.cs" />
+ <Compile Include="FakeToolkit.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\Xwt\Xwt.csproj">
+ <Project>{92494904-35FA-4DC9-BDE9-3A3E87AC49D3}</Project>
+ <Name>Xwt</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/Testing/CoreTests/FakeToolkit.cs b/Testing/CoreTests/FakeToolkit.cs
new file mode 100644
index 00000000..344e5ba2
--- /dev/null
+++ b/Testing/CoreTests/FakeToolkit.cs
@@ -0,0 +1,186 @@
+//
+// FakeToolkit.cs
+//
+// Author:
+// Lluis Sanchez <llsan@microsoft.com>
+//
+// Copyright (c) 2017 Microsoft
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Threading;
+using Xwt;
+using Xwt.Backends;
+using System.Linq;
+
+namespace CoreTests
+{
+ public class FakeToolkit : ToolkitEngineBackend
+ {
+ public FakeToolkit ()
+ {
+ }
+
+ public override void InitializeApplication ()
+ {
+ base.InitializeApplication ();
+ EventQueue.MainEventQueue.Reset ();
+ }
+
+ public override void DispatchPendingEvents ()
+ {
+ EventQueue.MainEventQueue.DispatchPendingEvents (false);
+ }
+
+ public override void ExitApplication ()
+ {
+ EventQueue.MainEventQueue.Exit ();
+ }
+
+ public override IWindowFrameBackend GetBackendForWindow (object nativeWindow)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override object GetNativeWidget (Widget w)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override object GetNativeWindow (IWindowFrameBackend backend)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool HasNativeParent (Widget w)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void InvokeAsync (Action action)
+ {
+ EventQueue.MainEventQueue.Enqueue (delegate {
+ ApplicationContext.InvokeUserCode (action);
+ });
+ }
+
+ public override void RunApplication ()
+ {
+ EventQueue.MainEventQueue.DispatchPendingEvents (true);
+ }
+
+ public override object TimerInvoke (Func<bool> action, TimeSpan timeSpan)
+ {
+ return EventQueue.MainEventQueue.Enqueue (delegate {
+ bool result = false;
+ ApplicationContext.InvokeUserCode (delegate {
+ result = action ();
+ });
+ return result;
+ }, timeSpan);
+ }
+
+ void ScheduleTimerInvoke (CancellationTokenSource cts, Func<bool> action, TimeSpan timeSpan)
+ {
+ Task.Delay (timeSpan, cts.Token).ContinueWith (t => {
+ InvokeAsync (delegate {
+ if (action ())
+ ScheduleTimerInvoke (cts, action, timeSpan);
+ });
+ DispatchPendingEvents ();
+ }, TaskContinuationOptions.NotOnCanceled);
+ }
+
+ public override void CancelTimerInvoke (object id)
+ {
+ ((CancellationTokenSource)id).Cancel ();
+ }
+ }
+
+ public class EventQueue
+ {
+ Queue<Action> events = new Queue<Action> ();
+ bool exit;
+
+ public static EventQueue MainEventQueue { get; } = new EventQueue ();
+
+ public void Reset ()
+ {
+ exit = false;
+ }
+
+ public void DispatchPendingEvents (bool keepWaiting)
+ {
+ do {
+ List<Action> list;
+ lock (events) {
+ list = events.ToList ();
+ events.Clear ();
+ }
+
+ foreach (var e in list)
+ e ();
+
+ if (!keepWaiting)
+ return;
+
+ lock (events) {
+ if (events.Count == 0 && !exit)
+ Monitor.Wait (events);
+ }
+ } while (!exit);
+ }
+
+ public void Exit ()
+ {
+ lock (events) {
+ exit = true;
+ Monitor.PulseAll (events);
+ }
+ }
+
+ public void Enqueue (Action action)
+ {
+ lock (events) {
+ events.Enqueue (action);
+ Monitor.PulseAll (events);
+ }
+ }
+
+ public CancellationTokenSource Enqueue (Func<bool> action, TimeSpan timeSpan)
+ {
+ CancellationTokenSource cts = new CancellationTokenSource ();
+ ScheduleTimerInvoke (cts, action, timeSpan);
+ return cts;
+ }
+
+ void ScheduleTimerInvoke (CancellationTokenSource cts, Func<bool> action, TimeSpan timeSpan)
+ {
+ Task.Delay (timeSpan, cts.Token).ContinueWith (t => {
+ Enqueue (delegate {
+ if (action ())
+ ScheduleTimerInvoke (cts, action, timeSpan);
+ });
+ }, TaskContinuationOptions.NotOnCanceled);
+ }
+ }
+}