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..979cc3e 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; @@ -42,20 +43,51 @@ 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(); 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..47aab52 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,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, this) { Navigation = Navigation})), o => !Running); + ViewAllResultsCommand = new Command( async o => await Navigation.PushAsync(new ResultsView(new ResultsViewModel(_results.GetTestResults(), true))), o => !HasResults); @@ -123,6 +127,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 +143,7 @@ async Task ExecuteTestsAync() { Running = true; Results = null; + TestRunResult results = await _testPackage.ExecuteTests(); ResultSummary summary = new ResultSummary(results); @@ -155,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 new file mode 100644 index 0000000..7833588 --- /dev/null +++ b/src/nunit.xamarin/ViewModel/TestDescriptionViewModel.cs @@ -0,0 +1,73 @@ +// *********************************************************************** +// 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; + Name = StringOrUnknown(_test.Name); + 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 Name { get; } + 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..bb879ca --- /dev/null +++ b/src/nunit.xamarin/ViewModel/TestExplorerViewModel.cs @@ -0,0 +1,100 @@ +// *********************************************************************** +// 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 System.Windows.Input; +using Xamarin.Forms; + +namespace NUnit.Runner.ViewModel +{ + class TestExplorerViewModel : BaseViewModel + { + readonly TestPackage _testPackage; + readonly SummaryViewModel _summaryViewModel; + bool _busy; + + 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() + { + var groupedTests = (await _testPackage.EnumerateTestsAsync()).GroupBy(t => t.Test.ClassName); + Device.BeginInvokeOnMainThread(() => + { + Busy = true; + + TestList.Clear(); + + foreach (var testClass in groupedTests) + { + TestList.Add(new GroupedTestsViewModel(testClass.Key, testClass.Select(t => new TestDescriptionViewModel(t.Test, t.Assembly)))); + } + + Busy = false; + }); + } + + public ObservableCollection TestList { get; } + + public ICommand RunTestCommand { set; get; } + + /// + /// True if tests are being loaded or executed + /// + public bool Busy + { + get { return _busy; } + set + { + if (value.Equals(_busy)) return; + _busy = 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 +