diff options
author | Lluis Sanchez <slluis.devel@gmail.com> | 2015-09-15 13:35:23 +0300 |
---|---|---|
committer | Lluis Sanchez <slluis.devel@gmail.com> | 2015-09-15 13:35:23 +0300 |
commit | 5bc2c8a85e94579d3c9fdc121b8f4f448b78f116 (patch) | |
tree | 72698d637038f7ecec3ade37e98bc0e617fdceb8 /main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest | |
parent | bc005451c4be06269da39f9d66f70824a68bff97 (diff) | |
parent | f3d3af8d833369490cd26d2fbaa377f0c98ba3c1 (diff) |
Merge pull request #915 from iainx/session-recording
Session recording
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest')
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs | 296 | ||||
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs | 11 |
2 files changed, 290 insertions, 17 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs index 7c58a76780..64b48200c2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs @@ -26,10 +26,16 @@ using System; using MonoDevelop.Components.Commands; +using MonoDevelop.Core; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Remoting; +using System.Diagnostics; using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Text; namespace MonoDevelop.Components.AutoTest { @@ -41,6 +47,11 @@ namespace MonoDevelop.Components.AutoTest get { return manager.currentSession; } } + static SessionRecord currentRecordSession; + public static SessionRecord CurrentRecordSession { + get { return currentRecordSession; } + } + public static void Start (CommandManager commandManager, bool publishServer) { AutoTestService.commandManager = commandManager; @@ -65,12 +76,35 @@ namespace MonoDevelop.Components.AutoTest File.WriteAllText (SessionReferenceFile, sref); } } - + + public static void ReplaySessionFromFile (string filename) + { + currentRecordSession = new SessionRecord (commandManager, filename); + currentRecordSession.ReplayEvents (() => { + currentRecordSession = null; + }); + } + public static SessionRecord StartRecordingSession () { - return new SessionRecord (commandManager); + currentRecordSession = new SessionRecord (commandManager); + return currentRecordSession; } - + + public static void StopRecordingSession (string filename = null) + { + if (currentRecordSession == null) { + return; + } + + currentRecordSession.Pause (); + + if (filename != null) { + currentRecordSession.WriteLogToFile (filename); + } + currentRecordSession = null; + } + internal static string SessionReferenceFile { get { return Path.Combine (Path.GetTempPath (), "monodevelop-autotest-objref"); @@ -146,18 +180,104 @@ namespace MonoDevelop.Components.AutoTest { CommandManager commandManager; List<RecordEvent> events = new List<RecordEvent> (); - bool recording; - - public class RecordEvent + enum State { + Idle, + Recording, + Replaying + }; + State state; + + StringBuilder pendingText; + Gdk.ModifierType pendingModifiers = Gdk.ModifierType.None; + + public abstract class RecordEvent { + public abstract XElement ToXML (); + public abstract void ParseXML (XElement element); + public abstract void Replay (AutoTestSession testSession); } public class KeyPressEvent: RecordEvent { public Gdk.Key Key { get; set; } public Gdk.ModifierType Modifiers { get; set; } + + public override XElement ToXML () + { + return new XElement ("event", new XAttribute ("type", "KeyPressEvent"), + new XElement ("key", Key.ToString ()), + new XElement ("modifier", Modifiers.ToString ())); + } + + public override void ParseXML (XElement element) + { + foreach (var e in element.Elements ()) { + if (e.Name == "key") { + Key = (Gdk.Key)Enum.Parse (typeof (Gdk.Key), e.Value); + } else if (e.Name == "modifier") { + Modifiers = (Gdk.ModifierType)Enum.Parse (typeof (Gdk.ModifierType), e.Value); + } + } + } + + public override void Replay (AutoTestSession testSession) + { + // Select the main window and then we can push key events to it. + AppQuery query = testSession.CreateNewQuery (); + AppResult[] results = query.Window ().Marked ("MonoDevelop.Ide.Gui.DefaultWorkbench").Execute (); + if (results.Length == 0) { + return; + } + + testSession.Select (results[0]); + // We need the GtkWidgetResult for the main window as we only have the keys as a Gdk key + if (results [0] is AutoTest.Results.GtkWidgetResult) { + AutoTest.Results.GtkWidgetResult widgetResult = (AutoTest.Results.GtkWidgetResult) results[0]; + widgetResult.RealTypeKey (Key, Modifiers); + } + } } - + + public class StringEvent: RecordEvent + { + internal string Text; + internal Gdk.ModifierType Modifiers { get; set; } + + public override XElement ToXML () + { + return new XElement ("event", new XAttribute ("type", "StringEvent"), + new XElement ("text", Text), + new XElement ("modifier", Modifiers.ToString ())); + } + + public override void ParseXML (XElement element) + { + foreach (var e in element.Elements ()) { + if (e.Name == "text") { + Text = e.Value; + } else if (e.Name == "modifier") { + Modifiers = (Gdk.ModifierType)Enum.Parse (typeof(Gdk.ModifierType), e.Value); + } + } + } + + public override void Replay (AutoTestSession testSession) + { + AppQuery query = testSession.CreateNewQuery (); + AppResult[] results = query.Window ().Marked ("MonoDevelop.Ide.Gui.DefaultWorkbench").Execute (); + if (results.Length == 0) { + return; + } + + testSession.Select (results [0]); + + if (results [0] is AutoTest.Results.GtkWidgetResult) { + AutoTest.Results.GtkWidgetResult widgetResult = (AutoTest.Results.GtkWidgetResult)results [0]; + widgetResult.EnterText (Text); + } + } + } + public class CommandEvent: RecordEvent { public object CommandId { get; set; } @@ -165,6 +285,29 @@ namespace MonoDevelop.Components.AutoTest public bool IsCommandArray { get { return DataItemIndex != -1; } } + + public override XElement ToXML () + { + return new XElement ("event", new XAttribute ("type", "CommandEvent"), + new XElement ("commandID", CommandId.ToString ()), + new XElement ("dataItemIndex", DataItemIndex)); + } + + public override void ParseXML (XElement element) + { + foreach (var e in element.Elements ()) { + if (e.Name == "commandID") { + CommandId = e.Value; + } else if (e.Name == "dataItemIndex") { + DataItemIndex = Convert.ToInt32 (e.Value); + } + } + } + + public override void Replay (AutoTestSession testSession) + { + testSession.ExecuteCommand (CommandId); + } } internal SessionRecord (CommandManager commandManager) @@ -172,39 +315,56 @@ namespace MonoDevelop.Components.AutoTest this.commandManager = commandManager; Resume (); } + + internal SessionRecord (CommandManager commandManager, string logFile) + { + state = State.Idle; + this.commandManager = commandManager; + LoadFromLogFile (logFile); + } public IEnumerable<RecordEvent> Events { get { - if (recording) + if (state == State.Recording) throw new InvalidOperationException ("The record session must be paused before getting the recorded events."); return events; } } public bool IsPaused { - get { return !recording; } + get { return state == State.Idle; } + } + + public bool IsReplaying { + get { return state == State.Replaying; } } public void Pause () { - if (recording) { + if (state == State.Recording) { commandManager.KeyPressed -= HandleCommandManagerKeyPressed; commandManager.CommandActivated -= HandleCommandManagerCommandActivated; - recording = false; + state = State.Idle; } } public void Resume () { - if (!recording) { + if (state == State.Idle) { commandManager.KeyPressed += HandleCommandManagerKeyPressed; commandManager.CommandActivated += HandleCommandManagerCommandActivated; - recording = true; + state = State.Recording; + LoggingService.LogError ("Starting up session recording"); } } void HandleCommandManagerCommandActivated (object sender, CommandActivationEventArgs e) { + if ((string)e.CommandId == "MonoDevelop.Ide.Commands.ToolCommands.ToggleSessionRecorder" || + (string)e.CommandId == "MonoDevelop.Ide.Commands.ToolCommands.ReplaySession") { + return; + } + CommandEvent cme = new CommandEvent () { CommandId = e.CommandId }; cme.DataItemIndex = -1; @@ -219,9 +379,117 @@ namespace MonoDevelop.Components.AutoTest events.Add (cme); } + void CompleteStringEvent (string s, Gdk.ModifierType modifiers) + { + events.Add (new StringEvent { Text = pendingText.ToString (), Modifiers = pendingModifiers }); + pendingText = null; + pendingModifiers = Gdk.ModifierType.None; + } + void HandleCommandManagerKeyPressed (object sender, KeyPressArgs e) { - events.Add (new KeyPressEvent () { Key = e.Key, Modifiers = e.Modifiers }); + uint unicode = Gdk.Keyval.ToUnicode (e.KeyValue); + if (pendingText != null) { + if (pendingModifiers != e.Modifiers || unicode == 0) { + CompleteStringEvent (pendingText.ToString (), pendingModifiers); + } else { + pendingText.Append ((char)unicode); + return; + } + + // If text event has been completed, then we need to reset the pending events + if (unicode != 0) { + pendingText = new StringBuilder (); + pendingText.Append ((char)unicode); + pendingModifiers = e.Modifiers; + } else { + // Don't have a unicode key, so just issue a standard key event + events.Add (new KeyPressEvent { Key = e.Key, Modifiers = e.Modifiers }); + pendingText = null; + pendingModifiers = Gdk.ModifierType.None; + } + } else { + if (unicode == 0) { + events.Add (new KeyPressEvent () { Key = e.Key, Modifiers = e.Modifiers }); + return; + } + + pendingText = new StringBuilder (); + pendingText.Append ((char)unicode); + pendingModifiers = e.Modifiers; + } + } + + public void WriteLogToFile (string filepath) + { + var doc = new XDocument (new XElement ("xs-event-replay-log", + from ev in events + select ev.ToXML ())); + + using (XmlWriter xw = XmlWriter.Create (filepath, new XmlWriterSettings { Indent = true })) { + doc.Save (xw); + } + } + + public bool LoadFromLogFile (string filepath) + { + XDocument doc = XDocument.Load (filepath); + foreach (XElement element in doc.Element("xs-event-replay-log").Elements ()) { + if (element == null) { + continue; + } + + string evType = element.Attribute ("type").Value; + RecordEvent ev = null; + if (evType == "KeyPressEvent") { + ev = new KeyPressEvent (); + } else if (evType == "CommandEvent") { + ev = new CommandEvent (); + } else if (evType == "StringEvent") { + ev = new StringEvent (); + } + + if (ev == null) { + return false; + } + + ev.ParseXML (element); + events.Add (ev); + } + + return true; + } + + public void ReplayEvents (Action completionHandler = null) + { + AutoTestSession testSession = new AutoTestSession (); + Stopwatch sw = new Stopwatch (); + int eventCount = events.Count; + + state = State.Replaying; + + sw.Start (); + // Each spin of the main loop, remove an event from the queue and replay it. + GLib.Idle.Add (() => { + RecordEvent ev = events[0]; + events.RemoveAt (0); + + ev.Replay (testSession); + + if (events.Count > 0) { + return true; + } + + sw.Stop (); + LoggingService.LogInfo ("Time elapsed to replay {0} events: {1}", eventCount, sw.Elapsed); + state = State.Idle; + + if (completionHandler != null) { + completionHandler (); + } + + return false; + }); } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs index 2d35b53b90..d94f39e2ba 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs @@ -302,14 +302,20 @@ namespace MonoDevelop.Components.AutoTest public void ExecuteOnIdle (Action idleFunc, bool wait = true, int timeout = 20000) { + if (DispatchService.IsGuiThread) { + idleFunc (); + return; + } + if (wait == false) { GLib.Idle.Add (() => { idleFunc (); return false; }); + return; } - + syncEvent.Reset (); GLib.Idle.Add (() => { idleFunc (); @@ -324,7 +330,7 @@ namespace MonoDevelop.Components.AutoTest // Executes the query outside of a syncEvent wait so it is safe to call from // inside an ExecuteOnIdleAndWait - AppResult[] ExecuteQueryNoWait (AppQuery query) + internal AppResult[] ExecuteQueryNoWait (AppQuery query) { AppResult[] resultSet = query.Execute (); Sync (() => { @@ -346,7 +352,6 @@ namespace MonoDevelop.Components.AutoTest } catch (TimeoutException e) { throw new TimeoutException (string.Format ("Timeout while executing ExecuteQuery: {0}", query), e); } - return resultSet; } |