Skip to content

Commit cc08b96

Browse files
authored
Switch to xharness for much more stable CI tests (xamarin#1558)
1 parent 8271472 commit cc08b96

20 files changed

+449
-271
lines changed

DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj

+14-4
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
<RootNamespace>DeviceTests.Droid</RootNamespace>
1010
<AssemblyName>XamarinEssentialsDeviceTestsAndroid</AssemblyName>
1111
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
12+
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
1213
<AndroidApplication>True</AndroidApplication>
13-
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
14+
<AndroidUseIntermediateDesignerFile>true</AndroidUseIntermediateDesignerFile>
1415
<AndroidResgenClass>Resource</AndroidResgenClass>
1516
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
1617
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
1718
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
19+
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
20+
<AndroidUseAapt2>true</AndroidUseAapt2>
21+
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
1822
</PropertyGroup>
1923
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
2024
<DebugSymbols>true</DebugSymbols>
@@ -25,8 +29,14 @@
2529
<ErrorReport>prompt</ErrorReport>
2630
<WarningLevel>4</WarningLevel>
2731
<AndroidLinkMode>None</AndroidLinkMode>
28-
<AndroidSupportedAbis />
32+
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
2933
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
34+
<AotAssemblies>false</AotAssemblies>
35+
<EnableLLVM>false</EnableLLVM>
36+
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
37+
<BundleAssemblies>false</BundleAssemblies>
38+
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
39+
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
3040
</PropertyGroup>
3141
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
3242
<DebugSymbols>true</DebugSymbols>
@@ -53,7 +63,7 @@
5363
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
5464
<PackageReference Include="xunit" Version="2.4.1" />
5565
<PackageReference Include="xunit.runner.devices" Version="2.5.25" />
56-
<PackageReference Include="UnitTests.HeadlessRunner" Version="2.0.0" />
66+
<PackageReference Include="Microsoft.DotNet.XHarness.TestRunners.Xunit" Version="1.0.0-prerelease.20602.1" />
5767
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0.1" />
5868
</ItemGroup>
5969
<ItemGroup>
@@ -68,7 +78,7 @@
6878
</ItemGroup>
6979
<ItemGroup>
7080
<Compile Include="MainActivity.cs" />
71-
<Compile Include="Resources\Resource.Designer.cs" />
81+
<Compile Include="TestInstrumentation.cs" />
7282
<Compile Include="Properties\AssemblyInfo.cs" />
7383
</ItemGroup>
7484
<ItemGroup>

DeviceTests/DeviceTests.Android/MainActivity.cs

+8-40
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,32 @@
1-
using System.Collections.Generic;
2-
using System.Reflection;
3-
using System.Threading.Tasks;
1+
using System.Reflection;
42
using Android.App;
53
using Android.Content.PM;
64
using Android.OS;
7-
using Android.Runtime;
8-
using UnitTests.HeadlessRunner;
95
using Xunit.Runners.UI;
106

117
namespace DeviceTests.Droid
128
{
13-
[Activity(Name="com.xamarin.essentials.devicetests.MainActivity", Label = "@string/app_name", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
9+
[Activity(
10+
Name = "com.xamarin.essentials.devicetests.MainActivity",
11+
Label = "@string/app_name",
12+
Theme = "@style/MainTheme",
13+
MainLauncher = true,
14+
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
1415
public class MainActivity : RunnerActivity
1516
{
1617
protected override void OnCreate(Bundle bundle)
1718
{
1819
Xamarin.Essentials.Platform.Init(this, bundle);
1920

20-
var hostIp = Intent.Extras?.GetString("HOST_IP", null);
21-
var hostPort = Intent.Extras?.GetInt("HOST_PORT", 63559) ?? 63559;
22-
23-
if (!string.IsNullOrEmpty(hostIp))
24-
{
25-
// Run the headless test runner for CI
26-
Task.Run(() =>
27-
{
28-
return Tests.RunAsync(new TestOptions
29-
{
30-
Assemblies = new List<Assembly> { typeof(Battery_Tests).Assembly },
31-
NetworkLogHost = hostIp,
32-
NetworkLogPort = hostPort,
33-
Filters = Traits.GetCommonTraits(),
34-
Format = TestResultsFormat.XunitV2
35-
});
36-
});
37-
}
38-
3921
// tests can be inside the main assembly
4022
AddTestAssembly(Assembly.GetExecutingAssembly());
4123
AddTestAssembly(typeof(Battery_Tests).Assembly);
4224
AddExecutionAssembly(typeof(Battery_Tests).Assembly);
4325

44-
// or in any reference assemblies
45-
// AddTestAssembly(typeof(PortableTests).Assembly);
46-
// or in any assembly that you load (since JIT is available)
47-
48-
#if false
49-
// you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-)
50-
Writer = new TcpTextWriter("10.0.1.2", 16384);
51-
// start running the test suites as soon as the application is loaded
52-
AutoStart = true;
53-
// crash the application (to ensure it's ended) and return to springboard
54-
TerminateAfterExecution = true;
55-
#endif
56-
57-
// you cannot add more assemblies once calling base
5826
base.OnCreate(bundle);
5927
}
6028

61-
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
29+
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
6230
{
6331
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
6432

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.IO;
5+
using System.Reflection;
6+
using Android.App;
7+
using Android.OS;
8+
using Android.Runtime;
9+
using Microsoft.DotNet.XHarness.TestRunners.Common;
10+
using Microsoft.DotNet.XHarness.TestRunners.Xunit;
11+
using Xamarin.Essentials;
12+
13+
namespace DeviceTests.Droid
14+
{
15+
[Instrumentation(Name = "com.xamarin.essentials.devicetests.TestInstrumentation")]
16+
public class TestInstrumentation : Instrumentation
17+
{
18+
string resultsFileName;
19+
20+
protected TestInstrumentation()
21+
{
22+
}
23+
24+
protected TestInstrumentation(IntPtr handle, JniHandleOwnership transfer)
25+
: base(handle, transfer)
26+
{
27+
}
28+
29+
public override void OnCreate(Bundle arguments)
30+
{
31+
base.OnCreate(arguments);
32+
33+
resultsFileName = arguments.GetString("results-file-name", "TestResults.xml");
34+
35+
Start();
36+
}
37+
38+
public override async void OnStart()
39+
{
40+
base.OnStart();
41+
42+
var bundle = new Bundle();
43+
44+
var entryPoint = new TestsEntryPoint(resultsFileName);
45+
entryPoint.TestsCompleted += (sender, results) =>
46+
{
47+
var message =
48+
$"Tests run: {results.ExecutedTests} " +
49+
$"Passed: {results.PassedTests} " +
50+
$"Inconclusive: {results.InconclusiveTests} " +
51+
$"Failed: {results.FailedTests} " +
52+
$"Ignored: {results.SkippedTests}";
53+
bundle.PutString("test-execution-summary", message);
54+
55+
bundle.PutLong("return-code", results.FailedTests == 0 ? 0 : 1);
56+
};
57+
58+
await entryPoint.RunAsync();
59+
60+
if (File.Exists(entryPoint.TestsResultsFinalPath))
61+
bundle.PutString("test-results-path", entryPoint.TestsResultsFinalPath);
62+
63+
if (bundle.GetLong("return-code", -1) == -1)
64+
bundle.PutLong("return-code", 1);
65+
66+
Finish(Result.Ok, bundle);
67+
}
68+
69+
class TestsEntryPoint : AndroidApplicationEntryPoint
70+
{
71+
readonly string resultsPath;
72+
73+
public TestsEntryPoint(string resultsFileName)
74+
{
75+
#pragma warning disable CS0618 // Type or member is obsolete
76+
var root = Platform.HasApiLevel(30)
77+
? Android.OS.Environment.ExternalStorageDirectory.AbsolutePath
78+
: Application.Context.GetExternalFilesDir(null)?.AbsolutePath ?? FileSystem.AppDataDirectory;
79+
#pragma warning restore CS0618 // Type or member is obsolete
80+
81+
var docsDir = Path.Combine(root, "Documents");
82+
83+
if (!Directory.Exists(docsDir))
84+
Directory.CreateDirectory(docsDir);
85+
86+
resultsPath = Path.Combine(docsDir, resultsFileName);
87+
}
88+
89+
protected override bool LogExcludedTests => true;
90+
91+
public override TextWriter Logger => null;
92+
93+
public override string TestsResultsFinalPath => resultsPath;
94+
95+
protected override int? MaxParallelThreads => System.Environment.ProcessorCount;
96+
97+
protected override IDevice Device { get; } = new TestDevice();
98+
99+
protected override IEnumerable<TestAssemblyInfo> GetTestAssemblies()
100+
{
101+
yield return new TestAssemblyInfo(Assembly.GetExecutingAssembly(), Assembly.GetExecutingAssembly().Location);
102+
yield return new TestAssemblyInfo(typeof(Battery_Tests).Assembly, typeof(Battery_Tests).Assembly.Location);
103+
}
104+
105+
protected override void TerminateWithSuccess()
106+
{
107+
}
108+
109+
protected override TestRunner GetTestRunner(LogWriter logWriter)
110+
{
111+
var testRunner = base.GetTestRunner(logWriter);
112+
var additional = new List<string>
113+
{
114+
$"{Traits.FileProvider}={Traits.FeatureSupport.ToExclude(Platform.HasApiLevel(24))}",
115+
};
116+
testRunner.SkipCategories(Traits.GetSkipTraits(additional));
117+
return testRunner;
118+
}
119+
}
120+
121+
class TestDevice : IDevice
122+
{
123+
public string BundleIdentifier => AppInfo.PackageName;
124+
125+
public string UniqueIdentifier => Guid.NewGuid().ToString("N");
126+
127+
public string Name => DeviceInfo.Name;
128+
129+
public string Model => DeviceInfo.Model;
130+
131+
public string SystemName => DeviceInfo.Platform.ToString();
132+
133+
public string SystemVersion => DeviceInfo.VersionString;
134+
135+
public string Locale => CultureInfo.CurrentCulture.Name;
136+
}
137+
}
138+
}

DeviceTests/DeviceTests.Android/Tests/FileProvider_Tests.cs

+16-10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public void Share_Simple_Text_File_Test()
3939
[InlineData(true, FileProviderLocation.PreferExternal)]
4040
[InlineData(false, FileProviderLocation.Internal)]
4141
[InlineData(false, FileProviderLocation.PreferExternal)]
42+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
4243
public void Get_Shareable_Uri(bool failAccess, FileProviderLocation location)
4344
{
4445
// Always fail to simulate unmounted media
@@ -84,6 +85,7 @@ public void Get_Shareable_Uri(bool failAccess, FileProviderLocation location)
8485
}
8586

8687
[Fact]
88+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
8789
public void No_Media_Fails_Get_External_Cache_Shareable_Uri()
8890
{
8991
// Always fail to simulate unmounted media
@@ -107,6 +109,7 @@ public void No_Media_Fails_Get_External_Cache_Shareable_Uri()
107109
}
108110

109111
[Fact]
112+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
110113
public void Get_External_Cache_Shareable_Uri()
111114
{
112115
// Save a local cache data directory file
@@ -138,6 +141,7 @@ public void Get_External_Cache_Shareable_Uri()
138141
[InlineData(FileProviderLocation.External)]
139142
[InlineData(FileProviderLocation.Internal)]
140143
[InlineData(FileProviderLocation.PreferExternal)]
144+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
141145
public void Get_Existing_Internal_Cache_Shareable_Uri(FileProviderLocation location)
142146
{
143147
// Save a local cache directory file
@@ -160,6 +164,7 @@ public void Get_Existing_Internal_Cache_Shareable_Uri(FileProviderLocation locat
160164
[InlineData(FileProviderLocation.External)]
161165
[InlineData(FileProviderLocation.Internal)]
162166
[InlineData(FileProviderLocation.PreferExternal)]
167+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
163168
public void Get_Existing_External_Cache_Shareable_Uri(FileProviderLocation location)
164169
{
165170
// Save an external cache directory file
@@ -182,14 +187,10 @@ public void Get_Existing_External_Cache_Shareable_Uri(FileProviderLocation locat
182187
[InlineData(FileProviderLocation.External)]
183188
[InlineData(FileProviderLocation.Internal)]
184189
[InlineData(FileProviderLocation.PreferExternal)]
190+
[Trait(Traits.FileProvider, Traits.FeatureSupport.Supported)]
185191
public void Get_Existing_External_Shareable_Uri(FileProviderLocation location)
186192
{
187193
// Save an external directory file
188-
189-
#if !__ANDROID_29__
190-
var externalRoot = AndroidEnvironment.ExternalStorageDirectory.AbsolutePath;
191-
#endif
192-
193194
var root = Platform.AppContext.GetExternalFilesDir(null).AbsolutePath;
194195
var file = CreateFile(root);
195196

@@ -204,12 +205,17 @@ public void Get_Existing_External_Shareable_Uri(FileProviderLocation location)
204205
Assert.Equal("content", shareableUri.Scheme);
205206
Assert.Equal("com.xamarin.essentials.devicetests.fileProvider", shareableUri.Authority);
206207

207-
#if !__ANDROID_29__
208-
// replace the real root with the providers "root"
209-
var segements = Path.Combine(root.Replace(externalRoot, "external_files"), Path.GetFileName(file));
208+
if (Platform.HasApiLevel(29))
209+
{
210+
#pragma warning disable CS0618 // Type or member is obsolete
211+
var externalRoot = AndroidEnvironment.ExternalStorageDirectory.AbsolutePath;
212+
#pragma warning restore CS0618 // Type or member is obsolete
210213

211-
Assert.Equal(segements.Split(Path.DirectorySeparatorChar), shareableUri.PathSegments);
212-
#endif
214+
// replace the real root with the providers "root"
215+
var segements = Path.Combine(root.Replace(externalRoot, "external_files"), Path.GetFileName(file));
216+
217+
Assert.Equal(segements.Split(Path.DirectorySeparatorChar), shareableUri.PathSegments);
218+
}
213219
}
214220

215221
static string CreateFile(string root, string name = "the-file.txt")

DeviceTests/DeviceTests.Shared/AppActions_Tests.cs

+4-8
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@ public void IsSupported()
1313
{
1414
var expectSupported = false;
1515

16-
#if __ANDROID_25__
17-
expectSupported = true;
16+
#if __ANDROID__
17+
expectSupported = Platform.SdkInt >= 25;
18+
#elif __IOS__
19+
expectSupported = Platform.HasOSVersion(9, 0);
1820
#endif
1921

20-
#if __IOS__
21-
if (Platform.HasOSVersion(9, 0))
22-
expectSupported = true;
23-
#endif
2422
Assert.Equal(expectSupported, AppActions.IsSupported);
2523
}
2624

27-
#if __ANDROID_25__ || __IOS__
2825
[Fact]
2926
public async Task GetSetItems()
3027
{
@@ -43,6 +40,5 @@ public async Task GetSetItems()
4340

4441
Assert.Contains(get, a => a.Id == "TEST1");
4542
}
46-
#endif
4743
}
4844
}

DeviceTests/DeviceTests.Shared/Clipboard_Tests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class Clipboard_Tests
99
[Theory]
1010
[InlineData("text")]
1111
[InlineData("some really long test text")]
12+
[Trait(Traits.UI, Traits.FeatureSupport.Supported)]
1213
public Task Set_Clipboard_Values(string text)
1314
{
1415
return Utils.OnMainThread(async () =>
@@ -21,6 +22,7 @@ public Task Set_Clipboard_Values(string text)
2122
[Theory]
2223
[InlineData("text")]
2324
[InlineData("some really long test text")]
25+
[Trait(Traits.UI, Traits.FeatureSupport.Supported)]
2426
public Task Get_Clipboard_Values(string text)
2527
{
2628
return Utils.OnMainThread(async () =>

0 commit comments

Comments
 (0)