diff options
author | Miguel Costa <miguel.costa@qt.io> | 2021-04-30 13:00:15 +0200 |
---|---|---|
committer | Miguel Costa <miguel.costa@qt.io> | 2021-06-14 09:18:49 +0000 |
commit | dc0e672865ef795b2ac2853586e9fe8a466a2903 (patch) | |
tree | e03b91d806c9f890b8b9a88bc11336a18a1a3b41 /src | |
parent | 4b49853cadfeb03a65e1b22987cbe11f418e3940 (diff) |
Rework QtVSTest project
Made the following changes to the QtVSTest project:
* Reworked handling of global variables;
* Can now use result functions (e.g. MACRO_OK) in macro code;
* Macros will now return MACRO_OK by default;
* Removed unnecessary macro functions (e.g. MACRO_ASSERT_OK);
* Fixed an issue where generation of empty macro code would fail;
* 'var' statement now assumes 'object' type by default;
* Changed 'ui context' statement parameter 'VS' to 'VSROOT';
* Added the 'DESKTOP' parameter to the 'ui context' statement;
* 'ui context' statement now has a 3 second timeout by default.
Change-Id: I99f5d3d5923f096c207437dee3c679c3b0311ee0
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/qtvstest/Macro.cs | 110 | ||||
-rw-r--r-- | src/qtvstest/MacroClient.h | 35 | ||||
-rw-r--r-- | src/qtvstest/csmacro.tmLanguage_TT | 6 | ||||
-rw-r--r-- | src/tests/CoreFeatures/main.cpp | 70 | ||||
-rw-r--r-- | src/tests/macros/Test_CreateGuiApp.csmacro | 3 | ||||
-rw-r--r-- | src/tests/macros/Test_DebugGuiApp.csmacro | 7 | ||||
-rw-r--r-- | src/tests/macros/Test_ImportProFile.csmacro | 5 | ||||
-rw-r--r-- | src/tests/macros/Test_QtVsToolsLoaded.csmacro | 1 | ||||
-rw-r--r-- | src/tests/macros/Test_RebuildSolution.csmacro | 2 |
9 files changed, 147 insertions, 92 deletions
diff --git a/src/qtvstest/Macro.cs b/src/qtvstest/Macro.cs index a264036d..4f2e2acf 100644 --- a/src/qtvstest/Macro.cs +++ b/src/qtvstest/Macro.cs @@ -107,6 +107,8 @@ namespace QtVsTest.Macros AsyncPackage Package { get; set; } EnvDTE80.DTE2 Dte { get; set; } + AutomationElement UiRoot => AutomationElement.RootElement; + AutomationElement _UiVsRoot; AutomationElement UiVsRoot { @@ -184,6 +186,9 @@ namespace QtVsTest.Macros static ConcurrentDictionary<string, Macro> Macros = new ConcurrentDictionary<string, Macro>(); + static ConcurrentDictionary<string, object> Globals + = new ConcurrentDictionary<string, object>(); + /// <summary> /// Macro constructor /// </summary> @@ -240,17 +245,17 @@ namespace QtVsTest.Macros if (!Ok) return; - if (string.IsNullOrEmpty(CSharpMethodCode)) - return; - try { - InitGlobalVars(); + InitGlobals(); await Run(); await SwitchToWorkerThreadAsync(); Result = ResultField.GetValue(null) as string; + if (string.IsNullOrEmpty(Result)) + Result = MACRO_OK; } catch (Exception e) { ErrorException(e); } + UpdateGlobals(); } /// <summary> @@ -400,10 +405,16 @@ namespace QtVsTest.Macros break; case StatementType.Var: - if (s.Args.Count < 2) + if (s.Args.Count < 1) return ErrorMsg("Missing args for #var"); - var typeName = s.Args[0]; - var varName = s.Args[1]; + string typeName, varName; + if (s.Args.Count == 1) { + typeName = "object"; + varName = s.Args[0]; + } else { + typeName = s.Args[0]; + varName = s.Args[1]; + } var initValue = s.Code; if (varName.Where(c => char.IsWhiteSpace(c)).Any()) return ErrorMsg("Wrong var name"); @@ -503,15 +514,16 @@ namespace QtVsTest.Macros var uiIterator = uiContext; foreach (var item in path) { var itemType = item.GetType(); + var scope = (uiIterator == UiRoot) ? TreeScope.Children : TreeScope.Subtree; if (itemType.IsAssignableFrom(typeof(string))) { // Find element by name var name = (string)item; - uiIterator = uiIterator.FindFirst(TreeScope.Subtree, + uiIterator = uiIterator.FindFirst(scope, new PropertyCondition(AutomationElement.NameProperty, name)); } else if (itemType.IsAssignableFrom(typeof(string[]))) { // Find element by name and type var itemParams = (string[])item; - uiIterator = uiIterator.FindFirst(TreeScope.Subtree, + uiIterator = uiIterator.FindFirst(scope, new AndCondition(itemParams.Select((x, i) => (i == 0) ? new PropertyCondition( AutomationElement.NameProperty, x) : @@ -539,6 +551,7 @@ namespace QtVsTest.Macros { csharp.Append(@" public static Func<AutomationElement, object[], AutomationElement> UiFind; + public static AutomationElement UiRoot; public static AutomationElement UiVsRoot; public static AutomationElement UiContext;"); return true; @@ -552,6 +565,9 @@ namespace QtVsTest.Macros MacroClass.GetField("UiFind", PUBLIC_STATIC) .SetValue(null, new Func<AutomationElement, object[], AutomationElement>(UiFind)); + MacroClass.GetField("UiRoot", PUBLIC_STATIC) + .SetValue(null, UiRoot); + MacroClass.GetField("UiVsRoot", PUBLIC_STATIC) .SetValue(null, UiVsRoot); @@ -567,36 +583,34 @@ namespace QtVsTest.Macros return ErrorMsg("Invalid #ui statement"); if (s.Args[0].Equals("context", IGNORE_CASE)) { - //# ui context [ VS ] [_int_] => _string_ [, _string_, ... ] + //# ui context [ VSROOT | DESKTOP ] [_int_] => _string_ [, _string_, ... ] //# ui context HWND [_int_] => _int_ if (s.Args.Count > 3 || string.IsNullOrEmpty(s.Code)) return ErrorMsg("Invalid #ui statement"); - bool uiVsRoot = (s.Args.Count > 1 && s.Args[1] == "VS"); + bool uiVsRoot = (s.Args.Count > 1 && s.Args[1] == "VSROOT"); + bool uiDesktop = (s.Args.Count > 1 && s.Args[1] == "DESKTOP"); bool uiHwnd = (s.Args.Count > 1 && s.Args[1] == "HWND"); string context; if (uiVsRoot) context = string.Format("UiFind(UiVsRoot, new object[] {{ {0} }})", s.Code); + else if (uiDesktop) + context = string.Format("UiFind(UiRoot, new object[] {{ {0} }})", s.Code); else if (uiHwnd) context = string.Format("AutomationElement.FromHandle((IntPtr)({0}))", s.Code); else context = string.Format("UiFind(UiContext, new object[] {{ {0} }})", s.Code); - int timeout = 0; - if (s.Args.Count > 1 && !uiVsRoot && !uiHwnd) + int timeout = 3000; + if (s.Args.Count > 1 && !uiVsRoot && !uiDesktop && !uiHwnd) timeout = int.Parse(s.Args[1]); else if (s.Args.Count > 2) timeout = int.Parse(s.Args[2]); - if (timeout > 0) { - csharp.AppendFormat(@" + csharp.AppendFormat(@" await WaitExpr({0}, () => UiContext = {1});", timeout, context); - } else { - csharp.AppendFormat(@" - UiContext = {0};", context); - } } else if (s.Args[0].Equals("pattern", IGNORE_CASE)) { //# ui pattern <_TypeName_> <_VarName_> [ => _string_ [, _string_, ... ] ] @@ -702,6 +716,13 @@ namespace QtVsTest.Macros csharp.Append( /** BEGIN generate code **/ @" + static string MACRO_OK { get { return ""(ok)""; } } + static string MACRO_ERROR { get { return ""(error)""; } } + static string MACRO_WARN { get { return ""(warn)""; } } + static string MACRO_ERROR_MSG(string msg) + { return string.Format(""{0}\r\n{1}"", MACRO_ERROR, msg); } + static string MACRO_WARN_MSG(string msg) + { return string.Format(""{0}\r\n{1}"", MACRO_WARN, msg); } public static Func<string, Assembly> GetAssembly; public static Func<Task> SwitchToUIThread; public static Func<Task> SwitchToWorkerThread; @@ -852,17 +873,12 @@ namespace QtVsTest.Macros if (callee == null) throw new FileNotFoundException("Unknown macro"); - callee.InitGlobalVars(); - - CopyGlobalVars( - from: GlobalVars.Values.Where(x => !x.IsCallOutput), - to: callee.GlobalVars.Values); - + callee.InitGlobals(); await callee.Run(); + callee.UpdateGlobals(); - CopyGlobalVars( - from: callee.GlobalVars.Values, - to: GlobalVars.Values); + // Refresh caller local copies of globals + InitGlobals(); } public async Task WaitExprAsync(int timeout, Func<object> expr) @@ -902,26 +918,31 @@ namespace QtVsTest.Macros return false; } - void InitGlobalVars() + void InitGlobals() { - var globalVarsInit = GlobalVars.Values - .Where(x => x.FieldInfo != null && !string.IsNullOrEmpty(x.InitialValueExpr)); - foreach (var globalVar in globalVarsInit) - globalVar.FieldInfo.SetValue(null, globalVar.InitInfo.GetValue(null)); + foreach (var globalVar in GlobalVars.Values) { + string varName = globalVar.Name; + Type varType = globalVar.FieldInfo.FieldType; + object value; + if (Globals.TryGetValue(varName, out value)) { + Type valueType = value.GetType(); + if (!varType.IsAssignableFrom(valueType)) { + throw new InvalidCastException(string.Format( + "Global variable '{0}': cannot assign '{1}' from '{2}'", + varName, varType.Name, valueType.Name)); + } + globalVar.FieldInfo.SetValue(null, value); + } else { + globalVar.FieldInfo.SetValue(null, globalVar.InitInfo.GetValue(null)); + } + } } - static void CopyGlobalVars(IEnumerable<GlobalVar> from, IEnumerable<GlobalVar> to) + void UpdateGlobals() { - var globalVars = to.Join(from, - DstVar => DstVar.Name, SrcVar => SrcVar.Name, - (DstVar, SrcVar) => new { DstVar, SrcVar }) - .Where(x => x.SrcVar.FieldInfo != null && x.DstVar.FieldInfo != null - && x.DstVar.FieldInfo.FieldType - .IsAssignableFrom(x.SrcVar.FieldInfo.FieldType)); - - foreach (var globalVar in globalVars) { - globalVar.DstVar.FieldInfo - .SetValue(null, globalVar.SrcVar.FieldInfo.GetValue(null)); + foreach (var globalVar in GlobalVars.Values) { + object value = globalVar.FieldInfo.GetValue(null); + Globals.AddOrUpdate(globalVar.Name, value, (key, oldValue) => value); } } @@ -952,6 +973,7 @@ namespace QtVsTest.Macros public static void Reset() { Macros.Clear(); + Globals.Clear(); } bool GenerateResultFuncs(StringBuilder csharp) diff --git a/src/qtvstest/MacroClient.h b/src/qtvstest/MacroClient.h index 16d34c20..ba3b2fa2 100644 --- a/src/qtvstest/MacroClient.h +++ b/src/qtvstest/MacroClient.h @@ -40,25 +40,11 @@ #include <process.h> -#define MACRO_OK QStringLiteral("(ok)") -#define MACRO_ERROR QStringLiteral("(error)") -#define MACRO_ERROR_MSG(msg) QStringLiteral("(error)\r\n" msg) - -#define MACRO_ASSERT_OK(result) QCOMPARE(result, MACRO_OK) - -#define MACRO_GLOBALS(globalVars) "//# macro Globals\r\n" globalVars -#define MACRO_GLOBAL_VAR(varName, varValue) "//# var string " varName " => " varValue "\r\n" - - -inline bool macroResultOk(QString result) -{ - return result == MACRO_OK; -} - -inline bool macroResultError(QString result) -{ - return result.startsWith(MACRO_ERROR); -} +#define MACRO_OK QStringLiteral("(ok)") +#define MACRO_ERROR QStringLiteral("(error)") +#define MACRO_ERROR_MSG(msg) QStringLiteral("(error)\r\n" msg) +#define MACRO_WARN QStringLiteral("(warn)") +#define MACRO_WARN_MSG(msg) QStringLiteral("(warn)\r\n" msg) class MacroClient { @@ -160,13 +146,19 @@ public: return loadAndRunMacro(macroFile); } - QString loadMacro(QFile ¯oFile, QString macroName) + QString storeMacro(QString macroName, QString macroCode) + { + return runMacro(QString() % "//#macro " % macroName % "\r\n" % macroCode); + } + + QString storeMacro(QString macroName, QFile ¯oFile) { if (macroName.isNull() || macroName.isEmpty()) return MACRO_ERROR_MSG("Invalid macro name"); return loadAndRunMacro(macroFile, QString("//#macro %1").arg(macroName)); } +private: QString loadAndRunMacro(QFile ¯oFile, QString macroHeader = QString()) { if (!macroFile.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -176,12 +168,11 @@ public: if (macroCode.isEmpty()) return MACRO_ERROR_MSG("Macro load failed"); if (!macroHeader.isNull()) - return runMacro(macroHeader + "\r\n" + macroCode); + return runMacro(macroHeader % "\r\n" % macroCode); else return runMacro(macroCode); } -private: class QDetachableProcess : public QProcess { public: diff --git a/src/qtvstest/csmacro.tmLanguage_TT b/src/qtvstest/csmacro.tmLanguage_TT index 86b361fb..c3b7c292 100644 --- a/src/qtvstest/csmacro.tmLanguage_TT +++ b/src/qtvstest/csmacro.tmLanguage_TT @@ -355,12 +355,12 @@ </dict> <!-- ///////////////////////////////////////////////////////////////////////////////////////////// - //# ui context [ VS [timeout] ] => _string_ [, _string_, ... ] - //# ui context HWND => _int_ + //# ui context [ VSROOT | DESKTOP [timeout] ] => _string_ [, _string_, ... ] + //# ui context HWND [timeout] => _int_ ///////////////////////////////////////////////////////////////////////////////////////// --> <dict> <key>begin</key> - <string>(//#\s*ui)\s+(context)\s+(?:(?:(?:(VS)(?:\s+(\d+))?)|(HWND))\s+)?(=>)</string> + <string>(//#\s*ui)\s+(context)\s+(?:(?:(?:(VSROOT|DESKTOP)(?:\s+(\d+))?)|(HWND))\s+)?(=>)</string> <key>beginCaptures</key> <dict> <key>1</key> diff --git a/src/tests/CoreFeatures/main.cpp b/src/tests/CoreFeatures/main.cpp index 28d27cfb..462e74e3 100644 --- a/src/tests/CoreFeatures/main.cpp +++ b/src/tests/CoreFeatures/main.cpp @@ -42,18 +42,67 @@ private slots: { qint64 pid = 0; QVERIFY(client.connect(&pid)); - MACRO_ASSERT_OK(client.runMacro(QFile(":/QtVsToolsLoaded"))); - MACRO_ASSERT_OK(client.runMacro( - MACRO_GLOBALS( - MACRO_GLOBAL_VAR("QtConfPath", "@\"" QT_CONF_PATH "\"")))); + client.runMacro(QString() % "//# var QtConfPath => @\"" % QT_CONF_PATH % "\""); + QCOMPARE(client.runMacro(QFile(":/QtVsToolsLoaded")), MACRO_OK); + } + + void tutorial01TestCase() + { + QSKIP("tutorial"); + QCOMPARE(client.runMacro(QString() + % "//# using System.Windows.Forms\r\n" + % "MessageBox.Show(\"Hello from Visual Studio!!\");"), + MACRO_OK); + } + + void tutorial02TestCase() + { + QSKIP("tutorial"); + QCOMPARE(client.runMacro(QString() + % "//# using System.Windows.Forms\r\n" + % "var task = Task.Run(() => MessageBox.Show(\"Hello, close this in 15 secs!!\"));\r\n" + % "//# wait 15000 => task.IsCompleted"), + MACRO_OK); + } + + void tutorial03TestCase() + { + QSKIP("tutorial"); + QCOMPARE(client.runMacro( + "Result = Environment.CurrentDirectory.Replace(\"\\\\\", \"/\");"), + QDir::currentPath()); + } + + void tutorial04TestCase() + { + QSKIP("tutorial"); + QCOMPARE(client.runMacro("//# var InitTime => DateTime.Now"), + MACRO_OK); + + QCOMPARE(client.runMacro(QString() + % "//# using System.Windows.Forms\r\n" + % "//# var InitTime\r\n" + % "MessageBox.Show(\"Test started at \" + InitTime);"), + MACRO_OK); + } + + void tutorial05TestCase() + { + QSKIP("tutorial"); + QCOMPARE(client.runMacro(QString() + % "//# using System.Windows.Forms\r\n" + % "Task.Run(() => MessageBox.Show(\"Press OK to close.\", \"Hello\"));\r\n" + % "//# ui context DESKTOP => \"Hello\", \"OK\"\r\n" + % "UiContext.SetFocus();\r\n"), + MACRO_OK); } void guiAppCreate_Rebuild_Debug() { client.runMacro("//# wait 5000 => !Dte.Solution.IsOpen"); - MACRO_ASSERT_OK(client.runMacro(QFile(":/CreateGuiApp"))); - MACRO_ASSERT_OK(client.runMacro(QFile(":/RebuildSolution"))); - MACRO_ASSERT_OK(client.runMacro(QFile(":/DebugGuiApp"))); + QCOMPARE(client.runMacro(QFile(":/CreateGuiApp")), MACRO_OK); + QCOMPARE(client.runMacro(QFile(":/RebuildSolution")), MACRO_OK); + QCOMPARE(client.runMacro(QFile(":/DebugGuiApp")), MACRO_OK); client.runMacro( "Dte.Solution.Close(false);" "\r\n" "//# wait 15000 => !Dte.Solution.IsOpen" "\r\n"); @@ -61,9 +110,10 @@ private slots: void importProFile_Rebuild_Debug() { - MACRO_ASSERT_OK(client.runMacro(QFile(":/ImportProFile"))); - MACRO_ASSERT_OK(client.runMacro(QFile(":/RebuildSolution"))); - MACRO_ASSERT_OK(client.runMacro(QFile(":/DebugGuiApp"))); + QSKIP("foo"); + QCOMPARE(client.runMacro(QFile(":/ImportProFile")), MACRO_OK); + QCOMPARE(client.runMacro(QFile(":/RebuildSolution")), MACRO_OK); + QCOMPARE(client.runMacro(QFile(":/DebugGuiApp")), MACRO_OK); client.runMacro( "Dte.Solution.Close(false);" "\r\n" "//# wait 15000 => !Dte.Solution.IsOpen" "\r\n"); diff --git a/src/tests/macros/Test_CreateGuiApp.csmacro b/src/tests/macros/Test_CreateGuiApp.csmacro index cba81c3e..4c717f85 100644 --- a/src/tests/macros/Test_CreateGuiApp.csmacro +++ b/src/tests/macros/Test_CreateGuiApp.csmacro @@ -41,10 +41,9 @@ solution.Create(tempDir, solutionName); solution.SaveAs(Path.Combine(solutionDir, solutionFileName)); var templateGuiApp = solution.GetProjectTemplate("Qt Widgets Application", "VC"); var taskAddProj = Task.Run(() => solution.AddFromTemplate(templateGuiApp, projectDir, "MyGuiApp")); -//# ui context VS => "Qt Widgets Application Wizard" +//# ui context VSROOT 15000 => "Qt Widgets Application Wizard" //# ui pattern Invoke => "Next >" //# ui pattern Invoke => "Next >" //# ui pattern Invoke => "Finish" if (!taskAddProj.Wait(15000)) throw new Exception("Timeout: Solution2.AddFromTemplate"); -Result = "(ok)"; diff --git a/src/tests/macros/Test_DebugGuiApp.csmacro b/src/tests/macros/Test_DebugGuiApp.csmacro index 8369758a..bd8e72a6 100644 --- a/src/tests/macros/Test_DebugGuiApp.csmacro +++ b/src/tests/macros/Test_DebugGuiApp.csmacro @@ -26,17 +26,12 @@ ** ****************************************************************************/ -//# using "Process = System.Diagnostics.Process" - var solution = Dte.Solution as Solution2; var solutionBuild = solution.SolutionBuild as SolutionBuild2; var project = solution.Projects.Cast<Project>().First(); - solutionBuild.Debug(); //# wait 15000 => Dte.Debugger.CurrentMode == dbgDebugMode.dbgRunMode -//# wait 15000 IntPtr hWnd => Process.GetProcesses().Where(p => p.ProcessName.Equals(project.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault().MainWindowHandle -//# ui context HWND => hWnd +//# ui context DESKTOP 15000 => project.Name //# ui pattern Window mainWindow mainWindow.Close(); //# wait 15000 => Dte.Debugger.CurrentMode == dbgDebugMode.dbgDesignMode -Result = "(ok)"; diff --git a/src/tests/macros/Test_ImportProFile.csmacro b/src/tests/macros/Test_ImportProFile.csmacro index 35745152..49e71306 100644 --- a/src/tests/macros/Test_ImportProFile.csmacro +++ b/src/tests/macros/Test_ImportProFile.csmacro @@ -28,7 +28,7 @@ //# using System.IO -//# call Globals +//# var string QtConfPath var qtConf = File.ReadAllLines(QtConfPath); var prefix = qtConf @@ -59,11 +59,10 @@ Directory.EnumerateFiles(wigglyPath) var taskOpenPro = Task.Run(() => Dte.ExecuteCommand("QtVSTools.OpenQtProjectFile.pro")); taskOpenPro.Wait(0); -//# ui context VS 3000 => "Select a Qt Project to Add to the Solution" +//# ui context VSROOT 3000 => "Select a Qt Project to Add to the Solution" //# ui pattern Value proFilePath => new[] { "File name:", "combo box" }, new[] { "File name:", "edit" } proFilePath.SetValue(projectFile); //# ui pattern Invoke => new[] { "Open", "button", "1" } if (!taskOpenPro.Wait(15000)) throw new Exception("Timeout: QtVSTools.OpenQtProjectFile.pro"); -Result = "(ok)"; diff --git a/src/tests/macros/Test_QtVsToolsLoaded.csmacro b/src/tests/macros/Test_QtVsToolsLoaded.csmacro index 83998ec1..f4a1aaab 100644 --- a/src/tests/macros/Test_QtVsToolsLoaded.csmacro +++ b/src/tests/macros/Test_QtVsToolsLoaded.csmacro @@ -33,4 +33,3 @@ var VsixType = QtVsTools.GetType("QtVsTools.Vsix"); var VsixInstance = VsixType.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); //# wait 15000 => VsixInstance.GetValue(null) -Result = "(ok)"; diff --git a/src/tests/macros/Test_RebuildSolution.csmacro b/src/tests/macros/Test_RebuildSolution.csmacro index 3234816d..005cabba 100644 --- a/src/tests/macros/Test_RebuildSolution.csmacro +++ b/src/tests/macros/Test_RebuildSolution.csmacro @@ -38,4 +38,4 @@ var taskRebuild = Task.Run(() => }); bool buildOk = (taskRebuild.Wait(15000) && solutionBuild.LastBuildInfo == 0); -Result = buildOk ? "(ok)" : "(error)\r\nBuild failed."; +Result = buildOk ? MACRO_OK : MACRO_ERROR_MSG("Build failed."); |