From 7c0d0c3aee2bc7598693ece172a49c097efaee82 Mon Sep 17 00:00:00 2001 From: khaledmousa Date: Wed, 20 Apr 2022 01:42:01 +0100 Subject: [PATCH 1/4] Added TestExplorer view - introduce new TestExplorerView - introduce associated ViewModels - added button to Summary View to open the Test Explorer --- .../Extensions/XamarinExtensions.cs | 13 +++ src/nunit.xamarin/Helpers/TestPackage.cs | 26 ++++++ src/nunit.xamarin/View/SummaryView.xaml | 23 +++-- src/nunit.xamarin/View/TestExplorerView.xaml | 36 ++++++++ .../View/TestExplorerView.xaml.cs | 43 ++++++++++ .../ViewModel/GroupedTestsViewModel.cs | 43 ++++++++++ .../ViewModel/SummaryViewModel.cs | 4 + .../ViewModel/TestDescriptionViewModel.cs | 71 ++++++++++++++++ .../ViewModel/TestExplorerViewModel.cs | 83 +++++++++++++++++++ src/nunit.xamarin/nunit.xamarin.csproj | 9 ++ 10 files changed, 344 insertions(+), 7 deletions(-) create mode 100644 src/nunit.xamarin/View/TestExplorerView.xaml create mode 100644 src/nunit.xamarin/View/TestExplorerView.xaml.cs create mode 100644 src/nunit.xamarin/ViewModel/GroupedTestsViewModel.cs create mode 100644 src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs create mode 100644 src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs diff --git a/src/nunit.xamarin/Extensions/XamarinExtensions.cs b/src/nunit.xamarin/Extensions/XamarinExtensions.cs index 02f54b1..c1ed987 100644 --- a/src/nunit.xamarin/Extensions/XamarinExtensions.cs +++ b/src/nunit.xamarin/Extensions/XamarinExtensions.cs @@ -22,6 +22,9 @@ // *********************************************************************** using NUnit.Framework.Interfaces; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using Xamarin.Forms; namespace NUnit.Runner.Extensions @@ -54,5 +57,15 @@ public static Color Color(this ResultState result) return Xamarin.Forms.Color.Gray; } } + + public static IEnumerable PropertyValues(this ITest test, string propertyName) + { + if (!test.Properties.ContainsKey(propertyName)) yield break; + else + { + for (int i = 0; i < test.Properties[propertyName].Count; i++) + yield return test.Properties[propertyName][i]?.ToString(); + } + } } } \ No newline at end of file diff --git a/src/nunit.xamarin/Helpers/TestPackage.cs b/src/nunit.xamarin/Helpers/TestPackage.cs index 0e4fd0e..097daf3 100644 --- a/src/nunit.xamarin/Helpers/TestPackage.cs +++ b/src/nunit.xamarin/Helpers/TestPackage.cs @@ -22,6 +22,7 @@ // *********************************************************************** using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using NUnit.Framework.Api; @@ -56,6 +57,31 @@ public async Task ExecuteTests() return resultPackage; } + public async Task> EnumerateTestsAsync() + { + var resultTests = new List<(ITest Test, string Assembly)>(); + foreach (var (assembly, options) in _testAssemblies) + { + var runner = await LoadTestAssemblyAsync(assembly, options).ConfigureAwait(false); + var assemblyTests = await Task.Run(() => runner.ExploreTests(TestFilter.Empty)); + resultTests.AddRange(EnumerateTestsInternal(assemblyTests).Select(t => (t, assembly.GetName().Name))); + } + return resultTests; + } + + private IEnumerable EnumerateTestsInternal(ITest parent) + { + var resultTests = new List(); + + foreach (var test in parent.Tests) + { + if (test.HasChildren) resultTests.AddRange(EnumerateTestsInternal(test)); + else resultTests.Add(test); + } + + return resultTests; + } + private static async Task LoadTestAssemblyAsync(Assembly assembly, Dictionary options) { var runner = new NUnitTestAssemblyRunner(new DefaultTestAssemblyBuilder()); diff --git a/src/nunit.xamarin/View/SummaryView.xaml b/src/nunit.xamarin/View/SummaryView.xaml index 5472195..5668706 100644 --- a/src/nunit.xamarin/View/SummaryView.xaml +++ b/src/nunit.xamarin/View/SummaryView.xaml @@ -11,13 +11,22 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nunit.xamarin/View/TestExplorerView.xaml.cs b/src/nunit.xamarin/View/TestExplorerView.xaml.cs new file mode 100644 index 0000000..2f17dbe --- /dev/null +++ b/src/nunit.xamarin/View/TestExplorerView.xaml.cs @@ -0,0 +1,43 @@ +using NUnit.Runner.Messages; +using NUnit.Runner.ViewModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace NUnit.Runner.View +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class TestExplorerView : ContentPage + { + TestExplorerViewModel _model; + + internal TestExplorerView(TestExplorerViewModel model) + { + _model = model; + _model.Navigation = Navigation; + BindingContext = _model; + InitializeComponent(); + + MessagingCenter.Subscribe(this, ErrorMessage.Name, error => + { + Device.BeginInvokeOnMainThread(async () => await DisplayAlert("Error", error.Message, "OK")); + }); + } + + /// + /// Called when the view is appearing + /// + protected override void OnAppearing() + { + base.OnAppearing(); +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + _model.LoadTestsAsync(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + } +} \ No newline at end of file diff --git a/src/nunit.xamarin/ViewModel/GroupedTestsViewModel.cs b/src/nunit.xamarin/ViewModel/GroupedTestsViewModel.cs new file mode 100644 index 0000000..9d1abe1 --- /dev/null +++ b/src/nunit.xamarin/ViewModel/GroupedTestsViewModel.cs @@ -0,0 +1,43 @@ +// *********************************************************************** +// Copyright (c) 2015 NUnit Project +// +// 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.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace NUnit.Runner.ViewModel +{ + class GroupedTestsViewModel : ObservableCollection + { + public GroupedTestsViewModel(string groupName, IEnumerable tests) + { + GroupName = groupName; + foreach (var test in (tests ?? Enumerable.Empty())) + Items.Add(test); + } + + public string GroupName { get; } + } +} diff --git a/src/nunit.xamarin/ViewModel/SummaryViewModel.cs b/src/nunit.xamarin/ViewModel/SummaryViewModel.cs index e9f34af..da3134a 100644 --- a/src/nunit.xamarin/ViewModel/SummaryViewModel.cs +++ b/src/nunit.xamarin/ViewModel/SummaryViewModel.cs @@ -31,6 +31,7 @@ using NUnit.Runner.Services; using Xamarin.Forms; +using System.Linq; namespace NUnit.Runner.ViewModel { @@ -45,6 +46,7 @@ public SummaryViewModel() { _testPackage = new TestPackage(); RunTestsCommand = new Command(async o => await ExecuteTestsAync(), o => !Running); + ExploreTestsCommand = new Command(async o => await Navigation.PushAsync(new TestExplorerView(new TestExplorerViewModel(_testPackage))), o => !Running); ViewAllResultsCommand = new Command( async o => await Navigation.PushAsync(new ResultsView(new ResultsViewModel(_results.GetTestResults(), true))), o => !HasResults); @@ -123,6 +125,7 @@ public bool Running public ICommand RunTestsCommand { set; get; } public ICommand ViewAllResultsCommand { set; get; } public ICommand ViewFailedResultsCommand { set; get; } + public ICommand ExploreTestsCommand { get; set; } /// /// Adds an assembly to be tested. @@ -138,6 +141,7 @@ async Task ExecuteTestsAync() { Running = true; Results = null; + TestRunResult results = await _testPackage.ExecuteTests(); ResultSummary summary = new ResultSummary(results); diff --git a/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs b/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs new file mode 100644 index 0000000..4348d4e --- /dev/null +++ b/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs @@ -0,0 +1,71 @@ +// *********************************************************************** +// Copyright (c) 2015 NUnit Project +// +// 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.Interfaces; +using NUnit.Runner.Extensions; +using NUnit.Runner.ViewModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NUnit.Runner.ViewModel +{ + class TestDescriptionViewModel : BaseViewModel + { + public TestDescriptionViewModel(ITest test, string assembly) + { + _test = test; + MethodName = StringOrUnknown(_test.MethodName); + ClassName = StringOrUnknown(_test.ClassName); + FullName = StringOrUnknown(_test.FullName); + Assembly = assembly; + Categories = _test.PropertyValues("Category").ToArray(); + } + + private readonly ITest _test; + public string MethodName { get; } + public string ClassName { get; } + public string FullName { get; } + public string Assembly { get; } + public string[] Categories { get; } + + private bool _selected; + + /// + /// True if test is selected + /// + public bool Selected + { + get { return _selected; } + set + { + if (value.Equals(_selected)) return; + _selected = value; + OnPropertyChanged(); + } + } + + private string StringOrUnknown(string str) => string.IsNullOrWhiteSpace(str) ? "" : str; + } +} diff --git a/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs b/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs new file mode 100644 index 0000000..7020e97 --- /dev/null +++ b/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs @@ -0,0 +1,83 @@ +// *********************************************************************** +// Copyright (c) 2015 NUnit Project +// +// 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.Runner.Helpers; +using NUnit.Runner.ViewModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace NUnit.Runner.ViewModel +{ + class TestExplorerViewModel : BaseViewModel + { + readonly TestPackage _testPackage; + bool _loading; + + public TestExplorerViewModel(TestPackage testPackage) + { + _testPackage = testPackage; + TestList = new ObservableCollection(); + } + + public async Task LoadTestsAsync() + { + var groupedTests = (await _testPackage.EnumerateTestsAsync()).GroupBy(t => t.Test.ClassName); + Device.BeginInvokeOnMainThread(() => + { + Loading = true; + + TestList.Clear(); + + foreach (var testClass in groupedTests) + { + TestList.Add(new GroupedTestsViewModel(testClass.Key, testClass.Select(t => new TestDescriptionViewModel(t.Test, t.Assembly)))); + } + + Loading = false; + }); + } + + public ObservableCollection TestList { get; } + + /// + /// True if tests are being loaded or grouped + /// + public bool Loading + { + get { return _loading; } + set + { + if (value.Equals(_loading)) return; + _loading = value; + OnPropertyChanged(); + } + } + + + } +} diff --git a/src/nunit.xamarin/nunit.xamarin.csproj b/src/nunit.xamarin/nunit.xamarin.csproj index a4bff86..fe74e2c 100644 --- a/src/nunit.xamarin/nunit.xamarin.csproj +++ b/src/nunit.xamarin/nunit.xamarin.csproj @@ -36,5 +36,14 @@ Supported Xamarin platforms: *.xaml + + TestExplorerView.xaml + + + + + + MSBuild:UpdateDesignTimeXaml + From 5516f32b958a3c07615b2c0c7e87e0426a2e7813 Mon Sep 17 00:00:00 2001 From: khaledmousa Date: Wed, 20 Apr 2022 02:20:09 +0100 Subject: [PATCH 2/4] Allow double tapping on a test to run it --- src/nunit.xamarin/Helpers/TestPackage.cs | 12 +++++-- src/nunit.xamarin/View/TestExplorerView.xaml | 6 +++- .../ViewModel/SummaryViewModel.cs | 19 +++++++++- .../ViewModel/TestDescriptionViewModel.cs | 2 ++ .../ViewModel/TestExplorerViewModel.cs | 35 ++++++++++++++----- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/nunit.xamarin/Helpers/TestPackage.cs b/src/nunit.xamarin/Helpers/TestPackage.cs index 097daf3..979cc3e 100644 --- a/src/nunit.xamarin/Helpers/TestPackage.cs +++ b/src/nunit.xamarin/Helpers/TestPackage.cs @@ -43,14 +43,20 @@ public void AddAssembly(Assembly testAssembly, Dictionary options _testAssemblies.Add( (testAssembly, options) ); } - public async Task ExecuteTests() + public async Task ExecuteTests() => await ExecuteTests(Enumerable.Empty()); + + public async Task ExecuteTests(IEnumerable testNames) { + var testFilter = testNames?.Any() == true ? + (TestFilter.FromXml($"{string.Join("", testNames.Select(n => $"{n}"))}")) : + TestFilter.Empty; + var resultPackage = new TestRunResult(); - foreach (var (assembly,options) in _testAssemblies) + foreach (var (assembly, options) in _testAssemblies) { NUnitTestAssemblyRunner runner = await LoadTestAssemblyAsync(assembly, options).ConfigureAwait(false); - ITestResult result = await Task.Run(() => runner.Run(TestListener.NULL, TestFilter.Empty)).ConfigureAwait(false); + ITestResult result = await Task.Run(() => runner.Run(TestListener.NULL, testFilter)).ConfigureAwait(false); resultPackage.AddResult(result); } resultPackage.CompleteTestRun(); diff --git a/src/nunit.xamarin/View/TestExplorerView.xaml b/src/nunit.xamarin/View/TestExplorerView.xaml index 90bcdc3..9f2f282 100644 --- a/src/nunit.xamarin/View/TestExplorerView.xaml +++ b/src/nunit.xamarin/View/TestExplorerView.xaml @@ -4,9 +4,10 @@ Title="Test Explorer" Padding="10" BackgroundColor="{DynamicResource defaultBackground}" + x:Name="testExplorerPage" x:Class="NUnit.Runner.View.TestExplorerView"> - + @@ -17,6 +18,9 @@ diff --git a/src/nunit.xamarin/ViewModel/SummaryViewModel.cs b/src/nunit.xamarin/ViewModel/SummaryViewModel.cs index da3134a..47aab52 100644 --- a/src/nunit.xamarin/ViewModel/SummaryViewModel.cs +++ b/src/nunit.xamarin/ViewModel/SummaryViewModel.cs @@ -46,7 +46,9 @@ public SummaryViewModel() { _testPackage = new TestPackage(); RunTestsCommand = new Command(async o => await ExecuteTestsAync(), o => !Running); - ExploreTestsCommand = new Command(async o => await Navigation.PushAsync(new TestExplorerView(new TestExplorerViewModel(_testPackage))), o => !Running); + + ExploreTestsCommand = new Command(async o => await Navigation.PushAsync(new TestExplorerView(new TestExplorerViewModel(_testPackage, this) { Navigation = Navigation})), o => !Running); + ViewAllResultsCommand = new Command( async o => await Navigation.PushAsync(new ResultsView(new ResultsViewModel(_results.GetTestResults(), true))), o => !HasResults); @@ -159,6 +161,21 @@ async Task ExecuteTestsAync() }); } + internal async Task DisplayResults(TestRunResult results) + { + ResultSummary summary = new ResultSummary(results); + + _resultProcessor = TestResultProcessor.BuildChainOfResponsability(Options); + await _resultProcessor.Process(summary).ConfigureAwait(false); + + Device.BeginInvokeOnMainThread( + () => + { + Results = summary; + Running = false; + }); + } + public static void TerminateWithSuccess() { #if __IOS__ diff --git a/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs b/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs index 4348d4e..7833588 100644 --- a/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs +++ b/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs @@ -36,6 +36,7 @@ class TestDescriptionViewModel : BaseViewModel public TestDescriptionViewModel(ITest test, string assembly) { _test = test; + Name = StringOrUnknown(_test.Name); MethodName = StringOrUnknown(_test.MethodName); ClassName = StringOrUnknown(_test.ClassName); FullName = StringOrUnknown(_test.FullName); @@ -44,6 +45,7 @@ public TestDescriptionViewModel(ITest test, string assembly) } private readonly ITest _test; + public string Name { get; } public string MethodName { get; } public string ClassName { get; } public string FullName { get; } diff --git a/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs b/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs index 7020e97..bb879ca 100644 --- a/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs +++ b/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs @@ -29,6 +29,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; using Xamarin.Forms; namespace NUnit.Runner.ViewModel @@ -36,12 +37,26 @@ namespace NUnit.Runner.ViewModel class TestExplorerViewModel : BaseViewModel { readonly TestPackage _testPackage; - bool _loading; + readonly SummaryViewModel _summaryViewModel; + bool _busy; - public TestExplorerViewModel(TestPackage testPackage) + public TestExplorerViewModel(TestPackage testPackage, SummaryViewModel summaryViewModel) { _testPackage = testPackage; + _summaryViewModel = summaryViewModel; TestList = new ObservableCollection(); + + RunTestCommand = new Command( + async o => + { + if(o is TestDescriptionViewModel testVm) + { + Busy = true; + var results = await _testPackage.ExecuteTests(new[] { testVm.FullName }); + await _summaryViewModel.DisplayResults(results); + await Navigation.PopAsync(); + } + }); } public async Task LoadTestsAsync() @@ -49,7 +64,7 @@ public async Task LoadTestsAsync() var groupedTests = (await _testPackage.EnumerateTestsAsync()).GroupBy(t => t.Test.ClassName); Device.BeginInvokeOnMainThread(() => { - Loading = true; + Busy = true; TestList.Clear(); @@ -58,22 +73,24 @@ public async Task LoadTestsAsync() TestList.Add(new GroupedTestsViewModel(testClass.Key, testClass.Select(t => new TestDescriptionViewModel(t.Test, t.Assembly)))); } - Loading = false; + Busy = false; }); } public ObservableCollection TestList { get; } + public ICommand RunTestCommand { set; get; } + /// - /// True if tests are being loaded or grouped + /// True if tests are being loaded or executed /// - public bool Loading + public bool Busy { - get { return _loading; } + get { return _busy; } set { - if (value.Equals(_loading)) return; - _loading = value; + if (value.Equals(_busy)) return; + _busy = value; OnPropertyChanged(); } } From 17999c3ba1dce6878784bc8a67424e1eb8d81df2 Mon Sep 17 00:00:00 2001 From: khaledmousa Date: Wed, 20 Apr 2022 02:47:48 +0100 Subject: [PATCH 3/4] minor UI cleanup --- src/nunit.xamarin/View/TestExplorerView.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nunit.xamarin/View/TestExplorerView.xaml b/src/nunit.xamarin/View/TestExplorerView.xaml index 9f2f282..8b3945a 100644 --- a/src/nunit.xamarin/View/TestExplorerView.xaml +++ b/src/nunit.xamarin/View/TestExplorerView.xaml @@ -16,7 +16,7 @@ - +