From 1b3a76c6874853049e89bbc113b22bc632ed5ca4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 16 May 2016 14:55:13 -0400 Subject: [PATCH] [Mono.Android-Test] Import Mono.Android tests from monodroid/9c5b3712 (#32) Import monodroid/tests/runtime from monodroid/9c5b3712. Add a toplevel `make run-apk-tests` target to "full stack" tests, in which a .apk is created, installed, and executed on an attached Android device. `make run-apk-tests` requires that `adb` be in $PATH, and uses GNU make(1) features, and... Additionally, tools/scripts/xabuild *must* be used to execute the `SignAndroidPackage` target, to ensure that the local/"in tree" assemblies are used. There is no "within xbuild" facility to alter where target framework assemblies are resolved from, i.e no MSBuild properties currently control the resolution order, only environment variables, and MSBuild can't *set* environment variables... The $(ADB_TARGET) variable can be used to control on which target Android device the tests will be installed and executed on: # Install & run tests on *only* connected USB device $ make run-apk-tests ADB_TARGET=-d # Install & run tests on *only* connected emulator $ make run-apk-tests ADB_TARGET=-e # Install & run tests on specified device, listed via `adb devices`. $ make run-apk-tests ADB_TARGET="-s 036533824381cfcb" Sorry potential/future Windows developers. *Running* tests will require manual testing or running on OS X or Linux for now... Note: These tests DO NOT PASS. In fact, they *crash*: $ make run-apk-tests ... Target RunTests: Executing: "$HOME/android-toolchain/sdk/platform-tools/adb" shell am instrument -w Mono.Android_Tests/xamarin.android.runtimetests.TestInstrumentation INSTRUMENTATION_RESULT: shortMsg=Process crashed. INSTRUMENTATION_CODE: 0 $ adb logcat ... E mono : Unhandled Exception: E mono : System.ObjectDisposedException: Cannot access a disposed object. E mono : Object name: 'System.Net.Sockets.Socket'. E mono : at System.Net.Sockets.Socket.ThrowIfDisposedAndClosed () <0xa93923f0 + 0x00054> in :0 E mono : at System.Net.Sockets.Socket.AcceptAsync (System.Net.Sockets.SocketAsyncEventArgs e) <0x9b8f9680 + 0x0001b> in :0 E mono : at System.Net.EndPointListener.Accept (System.Net.Sockets.Socket socket, System.Net.Sockets.SocketAsyncEventArgs e) <0x9b8f95d0 + 0x0003f> in :0 E mono : at System.Net.EndPointListener.ProcessAccept (System.Net.Sockets.SocketAsyncEventArgs args) <0x9b8e0340 + 0x0007f> in :0 E mono : at System.Net.EndPointListener.OnAccept (System.Object sender, System.Net.Sockets.SocketAsyncEventArgs e) <0x9b8e0310 + 0x00017> in :0 E mono : at System.Net.Sockets.SocketAsyncEventArgs.OnCompleted (System.Net.Sockets.SocketAsyncEventArgs e) <0x9b8e02c8 + 0x0003b> in :0 E mono : at System.Net.Sockets.SocketAsyncEventArgs.Complete () <0x9b8e02a0 + 0x0001f> in :0 E mono : at System.Net.Sockets.Socket.m__0 (System.IAsyncResult ares) <0x9b8dfd40 + 0x002af> in :0 E mono : at System.Net.Sockets.SocketAsyncResult+c__AnonStorey0.<>m__0 (System.Object _) <0xa892f720 + 0x0002b> in :0 E mono : at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () <0xa938f6b8 + 0x0002f> in :0 E mono : at System.Threading.ThreadPoolWorkQueue.Dispatch () <0xa938e358 + 0x001bb> in :0 E mono : at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () <0xa938e1a8 + 0x00007> in :0 Looks like a Socket and/or ThreadPool bug in mono. --- Makefile | 22 +- Xamarin.Android.sln | 15 + src/Mono.Android/Test/.gitignore | 1 + .../Test/Android.App/ApplicationTest.cs | 89 ++ .../Test/Android.Content/IntentTest.cs | 21 + .../Test/Android.OS/HandlerTest.cs | 32 + .../Test/Android.Runtime/CharSequenceTest.cs | 21 + .../Android.Runtime/JavaCollectionTest.cs | 37 + .../Android.Runtime/JnienvArrayMarshaling.cs | 352 ++++++ .../XmlReaderPullParserTest.cs | 22 + .../XmlReaderResourceParserTest.cs | 22 + .../Test/Android.Widget/AdapterTests.cs | 134 +++ src/Mono.Android/Test/Assets/AboutAssets.txt | 19 + .../Test/Java.Interop/JavaConvertTest.cs | 143 +++ .../Java.Interop/JavaObjectExtensionsTests.cs | 79 ++ .../Test/Java.Interop/JnienvTest.cs | 491 ++++++++ .../Test/Java.Lang/ObjectArrayMarshaling.cs | 44 + src/Mono.Android/Test/Java.Lang/ObjectTest.cs | 160 +++ .../Test/Mono.Android-Tests.csproj | 113 ++ .../Test/Mono.Android-Tests.targets | 33 + .../Test/Properties/AndroidManifest.xml | 12 + .../Test/Properties/AssemblyInfo.cs | 28 + .../Test/Resources/AboutResources.txt | 44 + .../Test/Resources/Resource.designer.cs | 203 ++++ .../Resources/color/WhiterShadeOfPale.xml | 5 + .../Resources/drawable/AndroidPressed.png | Bin 0 -> 2337 bytes .../Test/Resources/drawable/Icon.png | Bin 0 -> 2574 bytes .../Resources/drawable/android_button.xml | 8 + .../Resources/drawable/android_focused.png | Bin 0 -> 2785 bytes .../Resources/drawable/android_normal.png | Bin 0 -> 2056 bytes .../Test/Resources/layout/Main.axml | 17 + .../Test/Resources/layout/NameFixups.axml | 49 + .../Test/Resources/values/Strings.xml | 5 + .../Test/Resources/values/colors.xml | 4 + .../Test/Resources/values/theme.axml | 4 + .../Resources/xml/XmlReaderResourceParser.xml | 1 + .../System.IO.Compression/GZipStreamTest.cs | 32 + .../Test/System.IO/DriveInfoTest.cs | 28 + .../Test/System.Net/NetworkInterfaces.cs | 192 +++ src/Mono.Android/Test/System.Net/ProxyTest.cs | 32 + src/Mono.Android/Test/System.Net/SslTest.cs | 79 ++ .../Test/System.Threading/InterlockedTest.cs | 22 + src/Mono.Android/Test/System/AppDomainTest.cs | 44 + src/Mono.Android/Test/System/ExceptionTest.cs | 34 + src/Mono.Android/Test/System/TimeZoneTest.cs | 144 +++ .../AndroidClientHandlerTests.cs | 180 +++ .../HttpClientIntegrationTests.cs | 1046 +++++++++++++++++ .../MainActivity.cs | 24 + .../Xamarin.Android.RuntimeTests/MyIntent.cs | 14 + .../NonJavaObject.cs | 13 + .../TestInstrumentation.cs | 26 + src/Mono.Android/Test/jni/Android.mk | 10 + src/Mono.Android/Test/jni/Application.mk | 2 + src/Mono.Android/Test/jni/reuse-threads.c | 202 ++++ .../Utilities/Files.cs | 1 + .../Xamarin.Android.Build.Tasks.csproj | 2 + 56 files changed, 4355 insertions(+), 2 deletions(-) create mode 100644 src/Mono.Android/Test/.gitignore create mode 100644 src/Mono.Android/Test/Android.App/ApplicationTest.cs create mode 100644 src/Mono.Android/Test/Android.Content/IntentTest.cs create mode 100644 src/Mono.Android/Test/Android.OS/HandlerTest.cs create mode 100644 src/Mono.Android/Test/Android.Runtime/CharSequenceTest.cs create mode 100644 src/Mono.Android/Test/Android.Runtime/JavaCollectionTest.cs create mode 100644 src/Mono.Android/Test/Android.Runtime/JnienvArrayMarshaling.cs create mode 100644 src/Mono.Android/Test/Android.Runtime/XmlReaderPullParserTest.cs create mode 100644 src/Mono.Android/Test/Android.Runtime/XmlReaderResourceParserTest.cs create mode 100644 src/Mono.Android/Test/Android.Widget/AdapterTests.cs create mode 100644 src/Mono.Android/Test/Assets/AboutAssets.txt create mode 100644 src/Mono.Android/Test/Java.Interop/JavaConvertTest.cs create mode 100644 src/Mono.Android/Test/Java.Interop/JavaObjectExtensionsTests.cs create mode 100644 src/Mono.Android/Test/Java.Interop/JnienvTest.cs create mode 100644 src/Mono.Android/Test/Java.Lang/ObjectArrayMarshaling.cs create mode 100644 src/Mono.Android/Test/Java.Lang/ObjectTest.cs create mode 100644 src/Mono.Android/Test/Mono.Android-Tests.csproj create mode 100644 src/Mono.Android/Test/Mono.Android-Tests.targets create mode 100644 src/Mono.Android/Test/Properties/AndroidManifest.xml create mode 100644 src/Mono.Android/Test/Properties/AssemblyInfo.cs create mode 100644 src/Mono.Android/Test/Resources/AboutResources.txt create mode 100644 src/Mono.Android/Test/Resources/Resource.designer.cs create mode 100644 src/Mono.Android/Test/Resources/color/WhiterShadeOfPale.xml create mode 100644 src/Mono.Android/Test/Resources/drawable/AndroidPressed.png create mode 100644 src/Mono.Android/Test/Resources/drawable/Icon.png create mode 100644 src/Mono.Android/Test/Resources/drawable/android_button.xml create mode 100644 src/Mono.Android/Test/Resources/drawable/android_focused.png create mode 100644 src/Mono.Android/Test/Resources/drawable/android_normal.png create mode 100644 src/Mono.Android/Test/Resources/layout/Main.axml create mode 100644 src/Mono.Android/Test/Resources/layout/NameFixups.axml create mode 100644 src/Mono.Android/Test/Resources/values/Strings.xml create mode 100644 src/Mono.Android/Test/Resources/values/colors.xml create mode 100644 src/Mono.Android/Test/Resources/values/theme.axml create mode 100644 src/Mono.Android/Test/Resources/xml/XmlReaderResourceParser.xml create mode 100644 src/Mono.Android/Test/System.IO.Compression/GZipStreamTest.cs create mode 100644 src/Mono.Android/Test/System.IO/DriveInfoTest.cs create mode 100644 src/Mono.Android/Test/System.Net/NetworkInterfaces.cs create mode 100644 src/Mono.Android/Test/System.Net/ProxyTest.cs create mode 100644 src/Mono.Android/Test/System.Net/SslTest.cs create mode 100644 src/Mono.Android/Test/System.Threading/InterlockedTest.cs create mode 100644 src/Mono.Android/Test/System/AppDomainTest.cs create mode 100644 src/Mono.Android/Test/System/ExceptionTest.cs create mode 100644 src/Mono.Android/Test/System/TimeZoneTest.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.Net/AndroidClientHandlerTests.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.Net/HttpClientIntegrationTests.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MainActivity.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MyIntent.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NonJavaObject.cs create mode 100644 src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs create mode 100644 src/Mono.Android/Test/jni/Android.mk create mode 100644 src/Mono.Android/Test/jni/Application.mk create mode 100644 src/Mono.Android/Test/jni/reuse-threads.c diff --git a/Makefile b/Makefile index e5a73a63f13..1c51739ce7d 100644 --- a/Makefile +++ b/Makefile @@ -26,12 +26,11 @@ prepare: (cd external/Java.Interop && nuget restore) -run-all-tests: run-nunit-tests +run-all-tests: run-nunit-tests run-apk-tests clean: $(MSBUILD) /t:Clean - # $(call RUN_NUNIT_TEST,filename,log-lref?) define RUN_NUNIT_TEST MONO_TRACE_LISTENER=Console.Out \ @@ -43,3 +42,22 @@ endef run-nunit-tests: $(NUNIT_TESTS) $(foreach t,$(NUNIT_TESTS), $(call RUN_NUNIT_TEST,$(t),1)) + +# Test .apk projects must satisfy the following requirements: +# 1. They must have a UnDeploy target +# 2. They must have a Deploy target +# 3. They must have a RunTests target +TEST_APK_PROJECTS = \ + src/Mono.Android/Test/Mono.Android-Tests.csproj + +# Syntax: $(call RUN_TEST_APK,path/to/project.csproj) +define RUN_TEST_APK + # Must use xabuild to ensure correct assemblies are resolved + tools/scripts/xabuild /t:SignAndroidPackage $(1) && \ + $(MSBUILD) /t:UnDeploy $(1) && \ + $(MSBUILD) /t:Deploy $(1) && \ + $(MSBUILD) /t:RunTests $(1) $(if $(ADB_TARGET),"/p:AdbTarget=$(ADB_TARGET)",) +endef + +run-apk-tests: + $(foreach p, $(TEST_APK_PROJECTS), $(call RUN_TEST_APK, $(p))) diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 8827d8d95f2..e9840de9e12 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.ProjectTools", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Build.Tests", "src\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\Xamarin.Android.Build.Tests.csproj", "{53E4ABF0-1085-45F9-B964-DCAE4B819998}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Android-Tests", "src\Mono.Android\Test\Mono.Android-Tests.csproj", "{40EAD437-216B-4DF4-8258-3F47E1672C3A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|AnyCPU = Debug|AnyCPU @@ -238,6 +240,18 @@ Global {53E4ABF0-1085-45F9-B964-DCAE4B819998}.XAIntegrationDebug|AnyCPU.Build.0 = Debug|Any CPU {53E4ABF0-1085-45F9-B964-DCAE4B819998}.XAIntegrationRelease|AnyCPU.ActiveCfg = Debug|Any CPU {53E4ABF0-1085-45F9-B964-DCAE4B819998}.XAIntegrationRelease|AnyCPU.Build.0 = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.Release|AnyCPU.Build.0 = Release|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationDebug|Any CPU.ActiveCfg = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationDebug|Any CPU.Build.0 = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationRelease|Any CPU.ActiveCfg = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationRelease|Any CPU.Build.0 = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationDebug|AnyCPU.ActiveCfg = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationDebug|AnyCPU.Build.0 = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationRelease|AnyCPU.ActiveCfg = Debug|Any CPU + {40EAD437-216B-4DF4-8258-3F47E1672C3A}.XAIntegrationRelease|AnyCPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} @@ -266,6 +280,7 @@ Global {4D603AA3-3BFD-43C8-8050-0CD6C2601126} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {2DD1EE75-6D8D-4653-A800-0A24367F7F38} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} {53E4ABF0-1085-45F9-B964-DCAE4B819998} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} + {40EAD437-216B-4DF4-8258-3F47E1672C3A} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 diff --git a/src/Mono.Android/Test/.gitignore b/src/Mono.Android/Test/.gitignore new file mode 100644 index 00000000000..a93e887bab1 --- /dev/null +++ b/src/Mono.Android/Test/.gitignore @@ -0,0 +1 @@ +libs diff --git a/src/Mono.Android/Test/Android.App/ApplicationTest.cs b/src/Mono.Android/Test/Android.App/ApplicationTest.cs new file mode 100644 index 00000000000..1e7a4f5c4f4 --- /dev/null +++ b/src/Mono.Android/Test/Android.App/ApplicationTest.cs @@ -0,0 +1,89 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; +using Android.Runtime; + +using NUnit.Framework; + +namespace Android.AppTests +{ + [TestFixture] + public class ApplicationTest + { + [Test] + public void ApplicationContextIsApp () + { + Assert.IsTrue (Application.Context is App); + Assert.IsTrue (App.Created); + } + + [Test] + public void SynchronizationContext_Is_ThreadingSynchronizationContextCurrent () + { + bool same = false; + Application.SynchronizationContext.Send (_ => { + var c = System.Threading.SynchronizationContext.Current; + same = object.ReferenceEquals (c, Application.SynchronizationContext); + }, null); + Assert.IsTrue (same); + } + + [Test] + public void SynchronizationContext_Post_DoesNotBlock () + { + // To ensure we're on the main thread: + bool sendFinishedBeforePost = false; + Application.SynchronizationContext.Send (_ => { + bool postWasExecuted = false; + Application.SynchronizationContext.Post (_2 => { + postWasExecuted = true; + }, null); + if (!postWasExecuted) + sendFinishedBeforePost = true; + }, null); + Assert.IsTrue (sendFinishedBeforePost); + } + + [Test] + public void EnsureAndroidManifestIsUpdated () + { + var klass = Java.Lang.Class.FromType (typeof (RenamedActivity)); + var context = Application.Context; + using (var n = new ComponentName (context, klass)) { + var activityInfo = context.PackageManager.GetActivityInfo (n, 0); + var configChanges = activityInfo.ConfigChanges; + Assert.AreEqual (ConfigChanges.KeyboardHidden, configChanges); + } + } + } + + public class App : Application { + + public static bool Created; + + public App (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + Created = true; + } + + public override void OnCreate () + { + base.OnCreate (); + } + } + + [Activity] + public class RenamedActivity : Activity { + + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + Finish (); + } + } +} diff --git a/src/Mono.Android/Test/Android.Content/IntentTest.cs b/src/Mono.Android/Test/Android.Content/IntentTest.cs new file mode 100644 index 00000000000..62ed32d632a --- /dev/null +++ b/src/Mono.Android/Test/Android.Content/IntentTest.cs @@ -0,0 +1,21 @@ +using System; + +using Android.Content; + +using NUnit.Framework; + +namespace Android.ContentTests +{ + [TestFixture] + public class IntentTest + { + [Test] + public void PutCharSequenceArrayListExtra_NullValue () + { + using (var intent = new Intent ()) { + intent.PutCharSequenceArrayListExtra ("null", null); + Assert.AreEqual (null, intent.GetCharSequenceArrayListExtra ("null")); + } + } + } +} diff --git a/src/Mono.Android/Test/Android.OS/HandlerTest.cs b/src/Mono.Android/Test/Android.OS/HandlerTest.cs new file mode 100644 index 00000000000..25c1841f054 --- /dev/null +++ b/src/Mono.Android/Test/Android.OS/HandlerTest.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using Android.OS; + +using NUnit.Framework; + +namespace Xamarin.Android.RuntimeTests { + + [TestFixture] + public class HandlerTest { + + [Test] + public void RemoveDisposedInstance () + { + using (var t = new HandlerThread ("RemoveDisposedInstance")) { + t.Start (); + using (var h = new Handler (t.Looper)) { + var e = new ManualResetEvent (false); + Java.Lang.Runnable r = null; + r = new Java.Lang.Runnable (() => { + e.Set (); + r.Dispose (); + }); + h.Post (r.Run); + e.WaitOne (); + } + + t.QuitSafely (); + } + } + } +} diff --git a/src/Mono.Android/Test/Android.Runtime/CharSequenceTest.cs b/src/Mono.Android/Test/Android.Runtime/CharSequenceTest.cs new file mode 100644 index 00000000000..f1d7ac627df --- /dev/null +++ b/src/Mono.Android/Test/Android.Runtime/CharSequenceTest.cs @@ -0,0 +1,21 @@ +using System; + +using Android.Runtime; + +using NUnit.Framework; + +namespace Android.RuntimeTests { + + [TestFixture] + public class CharSequenceTest { + + [Test] + public void ToLocalJniHandle () + { + using (var s = new Java.Lang.String ("s")) { + var p = CharSequence.ToLocalJniHandle (s); + JNIEnv.DeleteLocalRef (p); + } + } + } +} diff --git a/src/Mono.Android/Test/Android.Runtime/JavaCollectionTest.cs b/src/Mono.Android/Test/Android.Runtime/JavaCollectionTest.cs new file mode 100644 index 00000000000..40623849331 --- /dev/null +++ b/src/Mono.Android/Test/Android.Runtime/JavaCollectionTest.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Android.Runtime; + +using NUnit.Framework; + +namespace Android.RuntimeTests +{ + [TestFixture] + public class JavaCollectionTest + { + [Test] + public void CopyTo () + { + using (var al = new Java.Util.ArrayList ()) { + al.Add (1); + al.Add (2); + al.Add (3); + + using (var c = new JavaCollection (al.Handle, JniHandleOwnership.DoNotTransfer)) { + var to = new int[3]; + c.CopyTo (to, 0); + Assert.IsTrue (new[]{1,2,3}.SequenceEqual (to)); + } + + using (var c = new JavaCollection (al.Handle, JniHandleOwnership.DoNotTransfer)) { + var to = new int[3]; + c.CopyTo (to, 0); + Assert.IsTrue (new[]{1,2,3}.SequenceEqual (to)); + } + } + } + } +} + diff --git a/src/Mono.Android/Test/Android.Runtime/JnienvArrayMarshaling.cs b/src/Mono.Android/Test/Android.Runtime/JnienvArrayMarshaling.cs new file mode 100644 index 00000000000..3a54dea0e10 --- /dev/null +++ b/src/Mono.Android/Test/Android.Runtime/JnienvArrayMarshaling.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; + +using Android.App; +using Android.Content; +using Android.Graphics; +using Android.Runtime; +using Android.Views; + +using NUnit.Framework; + +using Java.LangTests; + +namespace Android.RuntimeTests { + + [TestFixture] + public class JnienvArrayMarshaling { + + [Test] + public void MarshalInt32ArrayArray () + { + var states = new []{ + new[]{1, 2, 3}, + new[]{4, 5, 6}, + }; + var colors = new[]{7, 8}; + var list = new global::Android.Content.Res.ColorStateList (states, colors); + Assert.AreEqual (7, list.GetColorForState (states [0], Color.Transparent)); + Assert.AreEqual (8, list.GetColorForState (states [1], Color.Transparent)); + } + + [Test] + public void CopyArray_JavaToSystemByteArray () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var copy = new byte [3]; + JNIEnv.CopyArray (byteArray.Handle, copy, typeof (byte)); + AssertArrays ("CopyArray(Handle, byte[])", copy, (byte) 1, (byte) 2, (byte) 3); + } + } + + [Test] + public void CopyArray_Byte_JavaToGenericArrayT () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var copy = new byte [3]; + JNIEnv.CopyArray (byteArray.Handle, copy); + AssertArrays ("CopyArray(Handle, byte[])", copy, (byte) 1, (byte) 2, (byte) 3); + } + } + + [Test] + public void CopyArray_JavaToSystemArray () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var copy = new byte [3]; + JNIEnv.CopyArray (byteArray.Handle, (Array) copy); + AssertArrays ("CopyArray(Handle, Array)", copy, (byte) 1, (byte) 2, (byte) 3); + } + } + + [Test] + public void CopyArray_SystemByteArrayToJava () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var orig = new byte[]{ 4, 5, 6 }; + JNIEnv.CopyArray (orig, byteArray.Handle); + var copy = JNIEnv.GetArray (byteArray.Handle); + AssertArrays ("CopyArray(byte[], Handle)", copy, orig); + } + } + + [Test] + public void CopyArray_GenericByteArrayToJava () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var orig = new byte[]{ 4, 5, 6 }; + JNIEnv.CopyArray (orig, byteArray.Handle); + var copy = JNIEnv.GetArray (byteArray.Handle); + AssertArrays ("CopyArray(byte[], Handle)", copy, orig); + } + } + + [Test] + public void CopyArray_JavaLangStringArrayArrayToSystemStringArrayArray () + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{new[]{"a", "b"}, new[]{"c", "d"}}), JniHandleOwnership.TransferLocalRef)) { + var values = new[]{new string [2], new string [2]}; + JNIEnv.CopyArray (stringArray.Handle, values); + AssertArrays ("GetArray", values, new string[]{"a", "b"}, new string[]{"c", "d"}); + } + } + + [Test] + public void CopyArray_JavaLangObjectArrayToJavaLangStringArray () + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{"a", "b"}), JniHandleOwnership.TransferLocalRef)) { + Java.Lang.Object[] values = (Java.Lang.Object[]) JNIEnv.GetArray (stringArray.Handle, JniHandleOwnership.DoNotTransfer, typeof(Java.Lang.Object)); + values [0] = new Java.Lang.String ("c"); + JNIEnv.CopyArray (values, stringArray.Handle); + Assert.AreEqual ("c", JNIEnv.GetArrayItem (stringArray.Handle, 0)); + Assert.AreEqual ("c", JNIEnv.GetArrayItem (stringArray.Handle, 0)); + } + } + + [Test] + public void ByteArrayArray_IsConvertibleTo_JavaLangObjectArray () + { + /* + * Yay, Java array covariance allows this: + * byte[][] a = new byte[][]{new byte[]{1,2}}; + * Object[] o = a; + * byte[] c = (byte[]) o [0]; + */ + IntPtr x = JNIEnv.NewArray(new byte[][]{new byte[]{11, 12}, new byte[]{21, 22}}); + Assert.AreEqual ("[[B", JNIEnv.GetClassNameFromInstance (x)); + var items = JNIEnv.GetArray(x); + JNIEnv.DeleteLocalRef (x); + + Assert.AreEqual (2, items.Length); + Assert.AreEqual (typeof (Java.Lang.Object), items [0].GetType ()); + + var bytes = new byte[2]; + JNIEnv.CopyArray (items [0].Handle, bytes); + AssertArrays ("CopyArray", bytes, (byte) 11, (byte) 12); + } + + [Test] + public void NewArray_JavaLangString() + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[] { new Java.Lang.String ("a"), new Java.Lang.String ("b") }), JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual ("[Ljava/lang/String;", JNIEnv.GetClassNameFromInstance (stringArray.Handle)); + } + } + + [Test] + public void CopyObjectArray () + { + IntPtr p = JNIEnv.NewObjectArray (new byte[]{1, 2, 3}); + byte[] dest = new byte [3]; + JNIEnv.CopyObjectArray (p, dest); + AssertArrays ("CopyObjectArray: java->C#", dest, (byte)1, (byte)2, (byte)3); + dest = new byte[] { 42 }; + JNIEnv.CopyObjectArray (dest, p); + byte written; + using (var b = JNIEnv.GetArrayItem(p, 0)) + written = (byte) b.ByteValue (); + Assert.AreEqual (42, written); + JNIEnv.DeleteLocalRef (p); + } + + [Test] + public void GetArray_Byte () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + var copy = JNIEnv.GetArray (byteArray.Handle); + AssertArrays ("GetArray", copy, (byte) 1, (byte) 2, (byte) 3); + } + } + + [Test] + public void GetArray_ByteArrayArray () + { + byte[][] data = new byte[][]{ + new byte[]{11, 12, 13}, + new byte[]{21, 22, 23}, + new byte[]{31, 32, 33}, + }; + using (var byteArrayArray = new Java.Lang.Object (JNIEnv.NewArray (data), JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual ("[[B", JNIEnv.GetClassNameFromInstance (byteArrayArray.Handle)); + byte[][] data2 = JNIEnv.GetArray (byteArrayArray.Handle); + Assert.AreEqual (data, data2); + byte[][] data3 = (byte[][]) JNIEnv.GetArray (byteArrayArray.Handle, JniHandleOwnership.DoNotTransfer, typeof (byte[])); + Assert.AreEqual (data, data3); + JNIEnv.CopyArray (data3, byteArrayArray.Handle); + } + } + + [Test] + public void GetArray_JavaLangByteArrayToSystemByteArray () + { + var byteObjectArray = new Java.Lang.Byte[]{ + new Java.Lang.Byte (1), + new Java.Lang.Byte (2), + new Java.Lang.Byte (3), + }; + byte[] byteArray = JNIEnv.GetArray(byteObjectArray); + AssertArrays ("GetArray: Java.Lang.Byte[]->byte[]", byteArray, (byte) 1, (byte) 2, (byte) 3); + } + + [Test] + public void GetArray_JavaLangStringArrayArrayToSystemStringArrayArray () + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{new[]{"a", "b"}, new[]{"c", "d"}}), JniHandleOwnership.TransferLocalRef)) { + string[][] values = JNIEnv.GetArray(stringArray.Handle); + AssertArrays ("GetArray", values, new string[]{"a", "b"}, new string[]{"c", "d"}); + } + } + + [Test] + public void GetArray_KeycodeEnum () + { + using (var enumArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{Keycode.A}), JniHandleOwnership.TransferLocalRef)) { + var copy = JNIEnv.GetArray(enumArray.Handle); + AssertArrays ("GetArray", copy, Keycode.A); + } + } + + [Test] + public void GetArray_JavaLangStringArrayToJavaLangObjectArray () + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{"a", "b"}), JniHandleOwnership.TransferLocalRef)) { + Java.Lang.Object[] values = (Java.Lang.Object[]) JNIEnv.GetArray (stringArray.Handle, JniHandleOwnership.DoNotTransfer, typeof(Java.Lang.Object)); + Assert.AreEqual (2, values.Length); + Assert.AreEqual (typeof(Java.Lang.String), values [0].GetType ()); + Assert.AreEqual ("a", values [0].ToString ()); + Assert.AreEqual (typeof(Java.Lang.String), values [1].GetType ()); + Assert.AreEqual ("b", values [1].ToString ()); + } + } + + [Test] + public void GetArrayItem () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual (2, JNIEnv.GetArrayItem (byteArray.Handle, 1)); + JNIEnv.SetArrayItem (byteArray.Handle, 1, (byte) 42); + Assert.AreEqual (42, JNIEnv.GetArrayItem (byteArray.Handle, 1)); + } + } + + [Test] + public void GetArrayItem_Int32ArrayArray () + { + IntPtr array = JNIEnv.NewObjectArray (1, Java.Lang.Class.Object); + Assert.AreEqual ("[Ljava/lang/Object;", JNIEnv.GetClassNameFromInstance (array)); + int[] seq = new int[]{1, 2, 3}; + JNIEnv.SetArrayItem (array, 0, seq); + int[] oArray = JNIEnv.GetArrayItem (array, 0); + AssertArrays ("GetArrayItem_Int32ArrayArray", seq, oArray); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void SetArrayItem () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + JNIEnv.SetArrayItem (byteArray.Handle, 1, (byte) 42); + + var copy = new byte [3]; + JNIEnv.CopyArray (byteArray.Handle, copy); + AssertArrays ("CopyArray", copy, (byte) 1, (byte) 42, (byte) 3); + } + } + + [Test] + public void SetArrayItem_JavaLangString () + { + using (var stringArray = new Java.Lang.Object (JNIEnv.NewArray (new[]{"a", "b"}), JniHandleOwnership.TransferLocalRef)) { + using (var v = new Java.Lang.String ("d")) + JNIEnv.SetArrayItem (stringArray.Handle, 1, v); + Assert.AreEqual ("d", JNIEnv.GetArrayItem (stringArray.Handle, 1)); + } + } + + [Test] + public void GetObjectArray () + { + using (var byteArray = new Java.Lang.Object (JNIEnv.NewArray (new byte[]{1,2,3}), JniHandleOwnership.TransferLocalRef)) { + object[] data = JNIEnv.GetObjectArray (byteArray.Handle, new[]{typeof (byte), typeof (byte), typeof (byte)}); + AssertArrays ("GetObjectArray", data, (object) 1, (object) 2, (object) 3); + } + using (var objectArray = + new Java.Lang.Object ( + JNIEnv.NewArray ( + new Java.Lang.Object[]{Application.Context, 42L, "string"}, + typeof (Java.Lang.Object)), + JniHandleOwnership.TransferLocalRef)) { + object[] values = JNIEnv.GetObjectArray (objectArray.Handle, new[]{typeof(Context), typeof (int)}); + Assert.AreEqual (3, values.Length); + Assert.IsTrue (object.ReferenceEquals (values [0], Application.Context)); + Assert.IsTrue (values [1] is int); + Assert.AreEqual (42, (int)values [1]); + Assert.AreEqual ("string", values [2].ToString ()); + } + } + + [Test] + public void NewArray_Int32ArrayArray () + { + IntPtr x = JNIEnv.NewArray(new int[][]{new[]{11, 12}, new []{21, 22}}); + string t = JNIEnv.GetClassNameFromInstance (x); + JNIEnv.DeleteLocalRef (x); + Assert.AreEqual ("[[I", t); + } + + // http://bugzilla.xamarin.com/show_bug.cgi?id=12479 + [Test] + public void NewArray_Int32ArrayArray_ShouldNotLeak () + { + int[][] array = new int[][]{ + new int[]{1,2,3,4}, + new int[]{5,6,7,8}, + }; + + // 600 chosen as LREF table is 512 entries, so if this leaks it should overflow + for (int i = 0; i < 600; ++i) { + IntPtr l = JNIEnv.NewArray (array); + JNIEnv.DeleteLocalRef (l); + } + } + + [Test] + public void NewArray_UseJcwTypeWhenRenamed () + { + IntPtr lref = JNIEnv.NewArray(new CreateInstance_OverrideAbsListView_Adapter[0]); + Assert.AreEqual ( + "[Lcom/xamarin/android/runtimetests/CreateInstance_OverrideAbsListView_Adapter;", + JNIEnv.GetClassNameFromInstance (lref)); + JNIEnv.DeleteLocalRef (lref); + } + + [Test] + public void NewObjectArray_SystemByteArrayToJavaLangByteArray () + { + IntPtr p = JNIEnv.NewObjectArray (new byte[]{1, 2, 3}); + string t = JNIEnv.GetClassNameFromInstance (p); + JNIEnv.DeleteLocalRef (p); + Assert.AreEqual ("[Ljava/lang/Byte;", t); + } + + // http://bugzilla.xamarin.com/show_bug.cgi?id=360 + [Test] + public void BoundArrayPropertiesHaveSetters () + { + using (var opt = new BitmapFactory.Options ()) { + opt.InTempStorage = new byte [] {1, 3, 5}; + var inTempStorage = opt.InTempStorage; + Assert.AreEqual (3, inTempStorage.Count); + AssertArrays ("BoundArrayPropertiesHaveSetters", inTempStorage, (byte) 1, (byte) 3, (byte) 5); + Assert.DoesNotThrow (() => ((IDisposable)inTempStorage).Dispose ()); + } + } + + static void AssertArrays (string message, IList actual, params T[] expected) + { + Assert.AreEqual (expected.Length, actual.Count, message); + for (int i = 0; i < expected.Length; ++i) + Assert.AreEqual (expected [i], actual [i], message); + } + } +} diff --git a/src/Mono.Android/Test/Android.Runtime/XmlReaderPullParserTest.cs b/src/Mono.Android/Test/Android.Runtime/XmlReaderPullParserTest.cs new file mode 100644 index 00000000000..0ed7315f60e --- /dev/null +++ b/src/Mono.Android/Test/Android.Runtime/XmlReaderPullParserTest.cs @@ -0,0 +1,22 @@ +using System; + +using Android.App; +using Android.Runtime; + +using NUnit.Framework; + +using MyResource = global::Xamarin.Android.RuntimeTests.Resource; + +namespace Android.RuntimeTests { + + [TestFixture] + public class XmlReaderPullParserTest { + + [Test] + public void ToLocalJniHandle () + { + var p = Application.Context.Resources.GetXml (MyResource.Xml.XmlReaderResourceParser); + JNIEnv.DeleteLocalRef (XmlReaderPullParser.ToLocalJniHandle (p)); + } + } +} diff --git a/src/Mono.Android/Test/Android.Runtime/XmlReaderResourceParserTest.cs b/src/Mono.Android/Test/Android.Runtime/XmlReaderResourceParserTest.cs new file mode 100644 index 00000000000..2fc6e740c67 --- /dev/null +++ b/src/Mono.Android/Test/Android.Runtime/XmlReaderResourceParserTest.cs @@ -0,0 +1,22 @@ +using System; + +using Android.App; +using Android.Runtime; + +using NUnit.Framework; + +using MyResource = global::Xamarin.Android.RuntimeTests.Resource; + +namespace Android.RuntimeTests { + + [TestFixture] + public class XmlReaderResourceParserTest { + + [Test] + public void ToLocalJniHandle () + { + var p = Application.Context.Resources.GetXml (MyResource.Xml.XmlReaderResourceParser); + JNIEnv.DeleteLocalRef (XmlReaderResourceParser.ToLocalJniHandle (p)); + } + } +} diff --git a/src/Mono.Android/Test/Android.Widget/AdapterTests.cs b/src/Mono.Android/Test/Android.Widget/AdapterTests.cs new file mode 100644 index 00000000000..d4b74983f3d --- /dev/null +++ b/src/Mono.Android/Test/Android.Widget/AdapterTests.cs @@ -0,0 +1,134 @@ +using System; + +using NUnit.Framework; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Widget; +using Android.Runtime; + +namespace Android.WidgetTests { + + [TestFixture] + public class AdapterTests { + + [Test] + public void InvokeOverriddenAbsListView_AdapterProperty () + { + IntPtr grefAbsListView_class = JNIEnv.FindClass ("android/widget/AbsListView"); + // AbsListView doesn't override getAdapter(), and thus it inherits the + // AdapterView method; no need to check its behavior. + IntPtr AbsListView_setAdapter = IntPtr.Zero; + if ((int) Build.VERSION.SdkInt >= 11) { + AbsListView_setAdapter = JNIEnv.GetMethodID (grefAbsListView_class, "setAdapter", "(Landroid/widget/ListAdapter;)V"); + } + + IntPtr grefAdapterView_class = JNIEnv.FindClass ("android/widget/AdapterView"); + IntPtr AdapterView_getAdapter = JNIEnv.GetMethodID (grefAdapterView_class, "getAdapter", "()Landroid/widget/Adapter;"); + IntPtr AdapterView_setAdapter = JNIEnv.GetMethodID (grefAdapterView_class, "setAdapter", "(Landroid/widget/Adapter;)V"); + + JNIEnv.DeleteGlobalRef (grefAbsListView_class); + JNIEnv.DeleteGlobalRef (grefAdapterView_class); + + using (var adapter = new CanOverrideAbsListView_Adapter (Application.Context)) { + var a = Java.Lang.Object.GetObject( + JNIEnv.CallObjectMethod (adapter.Handle, AdapterView_getAdapter), JniHandleOwnership.TransferLocalRef); + Assert.AreSame (adapter.AdapterValue, a); + + if (AbsListView_setAdapter != IntPtr.Zero) { + adapter.AdapterSetterInvoked = false; + JNIEnv.CallVoidMethod (adapter.Handle, AbsListView_setAdapter, new JValue (IntPtr.Zero)); + Assert.IsTrue (adapter.AdapterSetterInvoked); + } + + adapter.AdapterSetterInvoked = false; + JNIEnv.CallVoidMethod (adapter.Handle, AdapterView_setAdapter, new JValue (IntPtr.Zero)); + Assert.IsTrue (adapter.AdapterSetterInvoked); + } + } + + [Test] + public void GridView_Adapter () + { + var view = new GridView (Application.Context); + var adapter = view.Adapter; + view.Adapter = adapter; + } + } + + public class CanOverrideAbsListView_Adapter : AbsListView { + + public CanOverrideAbsListView_Adapter (Context context) + : base (context) + { + AdapterValue = new ArrayAdapter (context, 0); + } + + protected override void Dispose (bool disposing) + { + if (!disposing) + return; + AdapterValue.Dispose (); + AdapterValue = null; + } + + public ArrayAdapter AdapterValue; + + public bool AdapterSetterInvoked; + + public override IListAdapter Adapter { + get {return AdapterValue;} + set { + AdapterSetterInvoked = true; + } + } + + public override void SetSelection (int position) + { + throw new NotImplementedException(); + } + + + /* + * On Pre-Honeycomb targets, the + * (IntPtr, JniHandleOwnership) ctor is reqiured because AbsListView + * constructor virtually invokes getAdapter() and the normal + * JNIEnv.AllocObject()-fu doesn't work. + * + * Executing: "/opt/android/sdk/platform-tools/adb" shell am instrument -w Xamarin.Android.RuntimeTests/xamarin.android.runtimetests.TestInstrumentation + * INSTRUMENTATION_RESULT: failure: Xamarin.Android.RuntimeTests.AdapterTests.InvokeOverriddenAbsListView_AdapterProperty=System.NotSupportedException : Unable to activate instance of type Xamarin.Android.RuntimeTests.CanOverrideAbsListView_Adapter from native handle 41e44de8 + * ----> System.MissingMethodException : No constructor found for Xamarin.Android.RuntimeTests.CanOverrideAbsListView_Adapter::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) + * ----> Java.Interop.JavaLocationException : Exception of type 'Java.Interop.JavaLocationException' was thrown. + * at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in :0 + * at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in :0 + * at Java.Lang.Object._GetObject[AbsListView] (IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Lang.Object.GetObject[AbsListView] (IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Lang.Object.GetObject[AbsListView] (IntPtr jnienv, IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Android.Widget.AbsListView.n_GetAdapter (IntPtr jnienv, IntPtr native__this) [0x00000] in :0 + * at (wrapper dynamic-method) object:2173e40b-99e1-484f-8c82-1f45de2f5a3a (intptr,intptr) + * --MissingMethodException + * at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in :0 + * --JavaLocationException + * Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. + * + * --- End of managed exception stack trace --- + * java.lang.Error: Java callstack: + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.n_getAdapter(Native Method) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.getAdapter(CanOverrideAbsListView_Adapter.java:46) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.getAdapter(CanOverrideAbsListView_Adapter.java:4) + * at android.widget.AdapterView.setFocusableInTouchMode(AdapterView.java:699) + * at android.widget.AbsListView.initAbsListView(AbsListView.java:812) + * at android.widget.AbsListView.(AbsListView.java:753) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.(CanOverrideAbsListView_Adapter.java:22) + * at xamarin.android.nunitlite.TestSuiteInstrumentation.n_onStart(Native Method) + * at xamarin.android.nunitlite.TestSuiteInstrumentation.onStart(TestSuiteInstrumentation.java:52) + * at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701) + */ + public CanOverrideAbsListView_Adapter (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + } +} diff --git a/src/Mono.Android/Test/Assets/AboutAssets.txt b/src/Mono.Android/Test/Assets/AboutAssets.txt new file mode 100644 index 00000000000..ee398862952 --- /dev/null +++ b/src/Mono.Android/Test/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with you package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file diff --git a/src/Mono.Android/Test/Java.Interop/JavaConvertTest.cs b/src/Mono.Android/Test/Java.Interop/JavaConvertTest.cs new file mode 100644 index 00000000000..92552cf6033 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop/JavaConvertTest.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Android.App; +using Android.Content; +using Android.Runtime; + +using NUnit.Framework; + +using Xamarin.Android.RuntimeTests; + +namespace Java.InteropTests +{ + [TestFixture] + public class JavaConvertTest + { + [Test] + public void Conversions () + { + var entries = new []{ + new {Key = "b", Value = (object) (byte) 0x01}, + new {Key = "c", Value = (object) 'c'}, + new {Key = "d", Value = (object) 1.0 }, + new {Key = "f", Value = (object) 2.0f }, + new {Key = "i", Value = (object) 3}, + new {Key = "j", Value = (object) 4L}, + new {Key = "s", Value = (object) (short) 5}, + new {Key = "z", Value = (object) false }, + new {Key = "_", Value = (object) "string" }, + new {Key = "nil", Value = (object) null }, + new {Key = "jlb", Value = (object) new Java.Lang.Byte (10)}, + new {Key = "jlc", Value = (object) new Java.Lang.Character ('d')}, + new {Key = "jld", Value = (object) new Java.Lang.Double (12.01)}, + new {Key = "jlf", Value = (object) new Java.Lang.Float (13.02f)}, + new {Key = "jli", Value = (object) new Java.Lang.Integer (14)}, + new {Key = "jlj", Value = (object) new Java.Lang.Long (15L)}, + new {Key = "jls", Value = (object) new Java.Lang.Short (16)}, + new {Key = "jlz", Value = (object) new Java.Lang.Boolean (true)}, + new {Key = "jl_", Value = (object) new Java.Lang.String ("JavaString")}, + new {Key = "njo", Value = (object) new NonJavaObject ()}, + new {Key = "jo", Value = (object) new MyIntent ()}, + }; + Action compare = (e, a, m) => { + if (a != null) + Assert.AreEqual (e.GetType (), a.GetType (), m); + Assert.IsTrue (object.Equals (e, a), m); + }; + + using (var d = new JavaDictionary()) { + foreach (var e in entries) + d.Add (e.Key, e.Value); + foreach (var e in entries) { + object v; + Assert.IsTrue (d.TryGetValue (e.Key, out v), "JavaDictionary.TryGetValue: " + e.Key); + compare (e.Value, v, "JavaDictionary: " + e.Key); + } + } + + using (var d = new JavaDictionary ()) { + foreach (var e in entries) + d.Add (e.Key, e.Value); + foreach (var e in entries) { + object v = d [e.Key]; + if (v == null && e.Value != null) + Assert.Fail ("JavaDictionary.this[] returned unexpected value."); + compare (e.Value, v, "JavaDictionary: " + e.Key); + } + } + + using (var l = new JavaList (entries.Select (e => e.Value))) { + for (int i = 0; i < entries.Length; ++i) { + compare (entries [i].Value, l [i], "JavaList: " + entries [i].Key); + } + } + + using (var l = new JavaList (entries.Select (e => e.Value))) { + for (int i = 0; i < entries.Length; ++i) { + compare (entries [i].Value, l [i], "JavaList: " + entries [i].Key); + } + } + + do { + var c = JavaCollection.FromJniHandle ( + JavaCollection.ToLocalJniHandle (entries.Select (e => e.Value).ToArray ()), + JniHandleOwnership.TransferLocalRef); + int i = 0; + foreach (object v in c) { + compare (entries [i].Value, v, "JavaCollection through lref: " + entries [i].Key); + i++; + } + ((IDisposable) c).Dispose (); + } while (false); + + do { + var c = JavaCollection.FromJniHandle ( + JavaCollection.ToLocalJniHandle (entries.Select (e => e.Value).ToArray ()), + JniHandleOwnership.TransferLocalRef); + int i = 0; + foreach (object v in c) { + compare (entries [i].Value, v, "JavaCollection through lref: " + entries [i].Key); + i++; + } + ((IDisposable) c).Dispose (); + } while (false); + } + + [Test] + public void NullStringMarshalsAsIntPtrZero () + { + var list = new JavaList (); + list.Add (null); + Assert.AreEqual (null, list [0]); + } + + [Test] + public void MarshalInt23Array () + { + using (var values = new JavaList( + CreateList (new[]{1,2,3}, new[]{4,5,6}, new[]{7,8,9}).Handle, + JniHandleOwnership.DoNotTransfer)) { + Assert.AreEqual (3, values.Count); + + Assert.IsTrue (values [0].SequenceEqual (new[]{1, 2, 3})); + Assert.IsTrue (values [1].SequenceEqual (new[]{4, 5, 6})); + Assert.IsTrue (values [2].SequenceEqual (new[]{7, 8, 9})); + } + } + + static Java.Util.ArrayList CreateList (params int[][] items) + { + var list = new Java.Util.ArrayList (); + foreach (int[] values in items) { + using (var v = new Java.Lang.Object ( + JNIEnv.NewArray (values), + JniHandleOwnership.TransferLocalRef)) + list.Add (v); + } + return list; + } + } +} + diff --git a/src/Mono.Android/Test/Java.Interop/JavaObjectExtensionsTests.cs b/src/Mono.Android/Test/Java.Interop/JavaObjectExtensionsTests.cs new file mode 100644 index 00000000000..d936393c032 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop/JavaObjectExtensionsTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Android.App; +using Android.Content; +using Android.Runtime; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests { + + [TestFixture] + public class JavaObjectExtensionsTests { + + [Test] + public void JavaCast_BaseToGenericWrapper () + { + using (var list = new JavaList (new[]{ 1, 2, 3 })) + using (var generic = JavaObjectExtensions.JavaCast> (list)) { + // Yay, no exceptions! + Assert.AreEqual (1, generic [0]); + } + } + + [Test] + public void JavaCast_InterfaceCast () + { + IntPtr g; + using (var n = new Java.Lang.Integer (42)) { + g = JNIEnv.NewGlobalRef (n.Handle); + } + // We want a Java.Lang.Object so that we create an IComparableInvoker + // instead of just getting back the original instance. + using (var o = Java.Lang.Object.GetObject (g, JniHandleOwnership.TransferGlobalRef)) { + var c = JavaObjectExtensions.JavaCast (o); + c.Dispose (); + } + } + + [Test] + public void JavaCast_InvalidTypeCastThrows () + { + using (var s = new Java.Lang.String ("value")) { + Assert.Throws (() => JavaObjectExtensions.JavaCast (s)); + } + } + + [Test] + public void JavaCast_CheckForManagedSubclasses () + { + using (var o = CreateObject ()) { + Assert.Throws (() => JavaObjectExtensions.JavaCast (o)); + } + } + + static Java.Lang.Object CreateObject () + { + var ctor = JNIEnv.GetMethodID (Java.Lang.Class.Object, "", "()V"); + var value = JNIEnv.NewObject (Java.Lang.Class.Object, ctor); + return new Java.Lang.Object (value, JniHandleOwnership.TransferLocalRef); + } + } + + class MyObject : Java.Lang.Object, Java.Lang.ICloneable { + + public MyObject () + { + } + + public MyObject (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + } +} + diff --git a/src/Mono.Android/Test/Java.Interop/JnienvTest.cs b/src/Mono.Android/Test/Java.Interop/JnienvTest.cs new file mode 100644 index 00000000000..ffef95baf03 --- /dev/null +++ b/src/Mono.Android/Test/Java.Interop/JnienvTest.cs @@ -0,0 +1,491 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; + +using Android.App; +using Android.Graphics; +using Android.Runtime; +using Android.Content; + +using Java.Interop; + +using NUnit.Framework; +using Android.OS; + +namespace Java.InteropTests +{ + [TestFixture] + public class JnienvTest + { + [Test] + public void TestMyPaintColor () + { + using (var p = new MyPaint ()) { + var g = JNIEnv.GetMethodID(p.Class.Handle, "getColor", "()I"); + int c = JNIEnv.CallIntMethod(p.Handle, g); + Assert.AreEqual (0x11223344, c); + var s = JNIEnv.GetMethodID(p.Class.Handle, "setColor", "(I)V"); + JNIEnv.CallVoidMethod (p.Handle, s, new JValue (0x22331144)); + Assert.AreEqual (0x22331144, p.SetColor.ToArgb ()); + } + } + + delegate void CB (IntPtr jnienv, IntPtr java_instance); + + [DllImport ("reuse-threads")] + static extern int rt_invoke_callback_on_new_thread (CB cb); + + [Test] + public void ThreadReuse () + { + Java.Lang.JavaSystem.LoadLibrary ("reuse-threads"); + CB cb = (env, instance) => { + Console.WriteLine ("CrossThreadObjectInteractions: JNIEnv.Handle={0} env={1}, instance={2}", + JNIEnv.Handle.ToString ("x"), env.ToString ("x"), instance.ToString ("x")); + if (env != JNIEnv.Handle) + Console.WriteLine ("GOOD: they should differ (on the second call)...."); + if (instance == IntPtr.Zero) + return; + using (var o = Java.Lang.Object.GetObject(env, instance, JniHandleOwnership.DoNotTransfer)) { + Console.WriteLine ("CrossThreadObjectInteractions: o.Handle={0}", o.Handle.ToString ("x")); + } + }; + rt_invoke_callback_on_new_thread (cb); + GC.KeepAlive (cb); + } + + [Test] + public void DeleteLrefOnWrongThread () + { + Console.WriteLine ("Delete JNI local refs on wrong thread..."); + IntPtr lref = IntPtr.Zero; + var t = new Thread (() => { + lref = JNIEnv.NewArray(new[]{1,2,3}); + }); + Console.WriteLine ("Do we die?"); + JNIEnv.DeleteLocalRef (lref); + Console.WriteLine ("still alive!"); + } + + static readonly bool HaveJavaInterop = AppDomain.CurrentDomain.GetAssemblies ().Any (a => a.FullName.StartsWith ("Java.Interop,")); + + [Test] + public void InvokingNullInstanceDoesNotCrashDalvik () + { + using (var o = new Java.Lang.Object (IntPtr.Zero, JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual (IntPtr.Zero, o.Handle); + if (HaveJavaInterop) { + Assert.Throws(() => o.ToString ()); + } else { + Assert.Throws(() => o.ToString ()); + } + } + } + + [Test] + public void NewGenericTypeThrows () + { + try { + var lrefInstance = JNIEnv.StartCreateInstance (typeof (GenericHolder<>), "()V"); + JNIEnv.FinishCreateInstance (lrefInstance, "()V"); + } catch (NotSupportedException) { + } + } + + [Test] + public void NewObjectArrayWithNullArray () + { + Assert.AreEqual (IntPtr.Zero, JNIEnv.NewObjectArray (null), "#1"); + } + + [Test] + public void NewObjectArrayWithObjectArray () + { + var array = JNIEnv.NewObjectArray (new Java.Lang.String [0]); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual (0, JNIEnv.GetArrayLength (array), "#2"); + Assert.AreEqual ("[Ljava/lang/String;", JNIEnv.GetClassNameFromInstance (array), "#3"); + JNIEnv.DeleteLocalRef (array); + + array = JNIEnv.NewObjectArray (new Java.Lang.String [1] { new Java.Lang.String ("str")}); + Assert.AreNotEqual (IntPtr.Zero, array, "#4"); + Assert.AreEqual (1, JNIEnv.GetArrayLength (array), "#5"); + Assert.AreEqual ("[Ljava/lang/String;", JNIEnv.GetClassNameFromInstance (array), "#6"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void NewObjectArrayWithNullElement () + { + var array = JNIEnv.NewObjectArray (new Java.Lang.String [1]); + Assert.AreNotEqual (IntPtr.Zero, array, "#2"); + Assert.AreEqual (1, JNIEnv.GetArrayLength (array), "#3"); + Assert.AreEqual ("[Ljava/lang/String;", JNIEnv.GetClassNameFromInstance (array), "#4"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void NewObjectArrayWithIntArray () + { + var array = JNIEnv.NewObjectArray (new int [1]); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual (1, JNIEnv.GetArrayLength (array), "#2"); + Assert.AreEqual ("[Ljava/lang/Integer;", JNIEnv.GetClassNameFromInstance (array), "#3"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void NewObjectArrayWithIntArrayAndEmptyArray () + { + //empty array gives the right type + var array = JNIEnv.NewObjectArray (new int [0]); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual (0, JNIEnv.GetArrayLength (array), "#2"); + Assert.AreEqual ("[Ljava/lang/Integer;", JNIEnv.GetClassNameFromInstance (array), "#3"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void NewObjectArrayWithNonJavaType () + { + //empty array gives the right type + var array = JNIEnv.NewObjectArray (new Type [1] { typeof (Type) }); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual ("[Ljava/lang/Object;", JNIEnv.GetClassNameFromInstance (array), "#2"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + public void NewObjectArrayWithNonJavaTypeAndEmptyArray () + { + //empty array gives the right type + var array = JNIEnv.NewObjectArray (new Type [0]); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual ("[Ljava/lang/Object;", JNIEnv.GetClassNameFromInstance (array), "#2"); + JNIEnv.DeleteLocalRef (array); + } + + [Test] + [Ignore ("This crashes on the emulator")] + public void NewObjectArrayWithBadValues () + { + try { + JNIEnv.NewObjectArray (-1, JNIEnv.FindClass (typeof (Java.Lang.Object))); + Assert.Fail ("Must throw"); + } catch (Java.Lang.OutOfMemoryError e) { + //XXX shouldn't this exception be an ArgumentException? + } + + try { + JNIEnv.NewObjectArray (1, IntPtr.Zero); + Assert.Fail ("Must throw"); + } catch (Java.Lang.NullPointerException e) { + //XXX shouldn't this exception be an ArgumentException? + } + } + + [Test] + public void NewObjectArray_UsesOnlyTypeParameter () + { + using (var s = new Java.Lang.String ("foo")) + using (var i = new Java.Lang.Integer (42)) { + var array = JNIEnv.NewObjectArray (s, i); + Assert.AreNotEqual (IntPtr.Zero, array, "#1"); + Assert.AreEqual ("[Ljava/lang/Object;", JNIEnv.GetClassNameFromInstance (array), "#2"); + Assert.AreEqual (2, JNIEnv.GetArrayLength (array)); + JNIEnv.DeleteLocalRef (array); + } + } + + [Test] + public void SetField_PermitNullValues () + { + using (var resource = new Intent.ShortcutIconResource ()) { + var f = JNIEnv.GetFieldID (resource.Class.Handle, "packageName", "Ljava/lang/String;"); + Console.WriteLine ("# f=0x{0}", f.ToString ("x")); + resource.PackageName = null; + Assert.AreEqual (null, resource.PackageName); + } + } + + [Test] + public void CreateTypeWithExportedMethods () + { + using (var e = new ContainsExportedMethods ()) { + e.Exported (); + Assert.AreEqual (1, e.Count); + IntPtr m = JNIEnv.GetMethodID (e.Class.Handle, "Exported", "()V"); + JNIEnv.CallVoidMethod (e.Handle, m); + Assert.AreEqual (2, e.Count); + } + } + + [Test] + public void ActivatedDirectObjectSubclassesShouldBeRegistered () + { + if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1) + Assert.Ignore ("Skipping test due to Bug #34141"); + + using (var ContainsExportedMethods_class = Java.Lang.Class.FromType (typeof (ContainsExportedMethods))) { + var ContainsExportedMethods_init = JNIEnv.GetMethodID (ContainsExportedMethods_class.Handle, "", "()V"); + + var o = JNIEnv.StartCreateInstance (ContainsExportedMethods_class.Handle, ContainsExportedMethods_init); + JNIEnv.FinishCreateInstance (o, ContainsExportedMethods_class.Handle, ContainsExportedMethods_init); + + /* + * Before the fix to to Bxc#32311, this will trigger an ART abort. + * + * StartCreateInstance()+FinishCreateInstance() will cause a ContainsExportedMethods instance + * to be created, but it (1) isn't "registered" (meaning Java.Lang.Object.PeekObject(IntPtr) + * will return null) and (2) doesn't even contain a JNI Global Reference, but instead a + * JNI *Local* Ref! + * + * This causes ART to abort when attempting to use an invalid JNI local reference from the finalizer. + */ + GC.Collect (); + GC.WaitForPendingFinalizers (); + + var v = Java.Lang.Object.GetObject(o, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (v); + Assert.IsTrue (v.Constructed); + v.Dispose (); + } + } + + [Test] + public void ActivatedDirectThrowableSubclassesShouldBeRegistered () + { + if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1) + Assert.Ignore ("Skipping test due to Bug #34141"); + + using (var ThrowableActivatedFromJava_class = Java.Lang.Class.FromType (typeof (ThrowableActivatedFromJava))) { + var ThrowableActivatedFromJava_init = JNIEnv.GetMethodID (ThrowableActivatedFromJava_class.Handle, "", "()V"); + + var o = JNIEnv.StartCreateInstance (ThrowableActivatedFromJava_class.Handle, ThrowableActivatedFromJava_init); + JNIEnv.FinishCreateInstance (o, ThrowableActivatedFromJava_class.Handle, ThrowableActivatedFromJava_init); + + GC.Collect (); + GC.WaitForPendingFinalizers (); + + var v = Java.Lang.Object.GetObject(o, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (v); + Assert.IsTrue (v.Constructed); + v.Dispose (); + } + } + + [Test] + public void ConversionsAndThreadsAndInstanceMappingsOhMy () + { + IntPtr lrefJliArray = JNIEnv.NewObjectArray (new[]{1}); + IntPtr grefJliArray = JNIEnv.NewGlobalRef (lrefJliArray); + JNIEnv.DeleteLocalRef (lrefJliArray); + + Java.Lang.Object[] jarray = (Java.Lang.Object[]) + JNIEnv.GetArray (grefJliArray, JniHandleOwnership.DoNotTransfer, typeof(Java.Lang.Object)); + + Exception ignore_t1 = null; + Exception ignore_t2 = null; + + var t1 = new Thread (() => { + int[] output_array1 = new int[1]; + for (int i = 0; i < 2000; ++i) { + Console.WriteLine ("# t1 iter: {0}", i); + try { + JNIEnv.CopyObjectArray (grefJliArray, output_array1); + } catch (Exception e) { + ignore_t1 = e; + break; + } + } + }); + var t2 = new Thread (() => { + for (int i = 0; i < 2000; ++i) { + Console.WriteLine ("# t2 iter: {0}", i); + try { + JNIEnv.GetArray(jarray); + } catch (Exception e) { + ignore_t2 = e; + break; + } + } + }); + + t1.Start (); + t2.Start (); + t1.Join (); + t2.Join (); + + for (int i = 0; i < jarray.Length; ++i) { + jarray [i].Dispose (); + jarray [i] = null; + } + + JNIEnv.DeleteGlobalRef (grefJliArray); + + Assert.IsNull (ignore_t1, string.Format ("No exception should be thrown [t1]! Got: {0}", ignore_t1)); + Assert.IsNull (ignore_t2, string.Format ("No exception should be thrown [t2]! Got: {0}", ignore_t2)); + } + + [Test] + public void MoarThreadingTests () + { + IntPtr lrefJliArray = JNIEnv.NewObjectArray (new[]{1}); + IntPtr grefJliArray = JNIEnv.NewGlobalRef (lrefJliArray); + JNIEnv.DeleteLocalRef (lrefJliArray); + + Exception ignore_t1 = null; + Exception ignore_t2 = null; + + var t1 = new Thread (() => { + int[] output_array1 = new int[1]; + for (int i = 0; i < 2000; ++i) { + Console.WriteLine ("# t1 iter: {0}", i); + try { + JNIEnv.CopyObjectArray (grefJliArray, output_array1); + } catch (Exception e) { + ignore_t1 = e; + break; + } + } + }); + var t2 = new Thread (() => { + for (int i = 0; i < 2000; ++i) { + Console.WriteLine ("# t2 iter: {0}", i); + try { + JNIEnv.GetObjectArray (grefJliArray, new[]{typeof (int)}); + } catch (Exception e) { + ignore_t2 = e; + break; + } + } + }); + + t1.Start (); + t2.Start (); + t1.Join (); + t2.Join (); + + JNIEnv.DeleteGlobalRef (grefJliArray); + + Assert.IsNull (ignore_t1, string.Format ("No exception should be thrown [t1]! Got: {0}", ignore_t1)); + Assert.IsNull (ignore_t2, string.Format ("No exception should be thrown [t2]! Got: {0}", ignore_t2)); + } + + [DllImport ("__Internal")] + static extern IntPtr monodroid_typemap_java_to_managed (string java); + + [Test] + public void JavaToManagedTypeMapping () + { + var m = monodroid_typemap_java_to_managed ("android/content/res/Resources"); + Assert.AreNotEqual (IntPtr.Zero, m); + m = monodroid_typemap_java_to_managed ("this/type/does/not/exist"); + Assert.AreEqual (IntPtr.Zero, m); + } + + [DllImport ("__Internal")] + static extern IntPtr monodroid_typemap_managed_to_java (string java); + + [Test] + public void ManagedToJavaTypeMapping () + { + var m = monodroid_typemap_managed_to_java (typeof (Activity).AssemblyQualifiedName); + Assert.AreNotEqual (IntPtr.Zero, m); + m = monodroid_typemap_managed_to_java (typeof (JnienvTest).AssemblyQualifiedName); + Assert.AreEqual (IntPtr.Zero, m); + } + + [Test] + public void DoNotLeakWeakReferences () + { + GC.Collect (); + GC.WaitForPendingFinalizers (); + + var surfaced = Runtime.GetSurfacedObjects (); + int startCount = surfaced.Count; + + Assert.IsTrue (surfaced.All (s => s.Target != null), "#1"); + + WeakReference r = null; + var t = new Thread (() => { + var c = new MyCb (); + Assert.AreEqual (startCount + 1, Runtime.GetSurfacedObjects ().Count, "#2"); + r = new WeakReference (c); + }); + t.Start (); + t.Join (); + + GC.Collect (); + GC.WaitForPendingFinalizers (); + GC.Collect (); + GC.WaitForPendingFinalizers (); + + surfaced = Runtime.GetSurfacedObjects (); + Assert.AreEqual (startCount, surfaced.Count, "#3"); + Assert.IsTrue (surfaced.All (s => s.Target != null), "#4"); + } + } + + class MyCb : Java.Lang.Object, Java.Lang.IRunnable { + public void Run () + { + Console.WriteLine ("MyCb.Run! JNIEnv.Handle={0}", JNIEnv.Handle.ToString ("x")); + } + } + + class ContainsExportedMethods : Java.Lang.Object { + + public bool Constructed; + + public int Count; + + public ContainsExportedMethods () + { + Console.WriteLine ("# ContainsExportedMethods: constructed! Handle=0x{0}", Handle.ToString ("x")); + Constructed = true; + } + + [Export] + public void Exported () + { + Count++; + } + } + + class ThrowableActivatedFromJava : Java.Lang.Throwable { + + public bool Constructed; + + public ThrowableActivatedFromJava () + { + Constructed = true; + } + } + + class GenericHolder : Java.Lang.Object { + + public T Value {get; set;} + + } + + #region BXC_374 + class MyPaint : Paint { + + public Color SetColor; + + public override Color Color { + get { + Console.WriteLine ("get_Color"); + return new Color (a:0x11, r:0x22, g:0x33, b:0x44); + } + set { + Console.WriteLine ("set_Color({0})", value.ToArgb ()); + SetColor = value; + base.Color = value; + } + } + } + #endregion +} diff --git a/src/Mono.Android/Test/Java.Lang/ObjectArrayMarshaling.cs b/src/Mono.Android/Test/Java.Lang/ObjectArrayMarshaling.cs new file mode 100644 index 00000000000..ee3b3181d3b --- /dev/null +++ b/src/Mono.Android/Test/Java.Lang/ObjectArrayMarshaling.cs @@ -0,0 +1,44 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Runtime; +using Android.Views; + +using NUnit.Framework; + +namespace Java.LangTests { + + [TestFixture] + public class ObjectArrayMarshaling { + + [Test] + public void CastJavaLangObjectArrayToByteArrayThrows () + { + using (var objectArray = new Java.Lang.Object (JNIEnv.NewArray ( + new Java.Lang.Object[]{new byte[]{0x1, 0x2, 0x3}}), JniHandleOwnership.TransferLocalRef)) { + Assert.Throws (typeof (InvalidCastException), () => { +#pragma warning disable 219 + var ignore = (byte[]) objectArray; +#pragma warning restore 219 + }); + } + } + + [Test] + public void CastJavaLangObjectToJavaLangObjectArray () + { + using (var objectArray = new Java.Lang.Object (JNIEnv.NewArray ( + new Java.Lang.Object[]{new byte[]{0x1, 0x2, 0x3}}), JniHandleOwnership.TransferLocalRef)) { + var values = (Java.Lang.Object[]) objectArray; + Assert.IsNotNull (values); + Assert.AreEqual (1, values.Length); + using (var dataObject = values [0]) { + byte[] data = (byte[]) dataObject; + Assert.AreEqual (new byte[]{1, 2, 3}, data); + } + } + } + } +} + diff --git a/src/Mono.Android/Test/Java.Lang/ObjectTest.cs b/src/Mono.Android/Test/Java.Lang/ObjectTest.cs new file mode 100644 index 00000000000..9eb8421d2f1 --- /dev/null +++ b/src/Mono.Android/Test/Java.Lang/ObjectTest.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; + +using Android.App; +using Android.Content; +using Android.Runtime; +using Android.Widget; + +using NUnit.Framework; + +namespace Java.LangTests +{ + [TestFixture] + public class ObjectTest + { + [Test] + public void GetObject_ReturnsMostDerivedType () + { + IntPtr lref = JNIEnv.NewString ("Hello, world!"); + using (Java.Lang.Object s = Java.Lang.Object.GetObject(lref, JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual (typeof (Java.Lang.String), s.GetType ()); + } + + lref = JNIEnv.CreateInstance ("android/gesture/Gesture", "()V"); + using (Java.Lang.Object g = Java.Lang.Object.GetObject(lref, JniHandleOwnership.TransferLocalRef)) { + Assert.AreEqual (typeof (global::Android.Gestures.Gesture), g.GetType ()); + } + } + + [Test] + public void JavaConvert_FromJavaObject_ShouldNotBreakExistingReferences () + { + Func toInt = GetIJavaObjectToInt32 (); + + using (var instance = new Java.Lang.Integer (42)) { + Assert.AreSame (instance, Java.Lang.Object.GetObject(instance.Handle, JniHandleOwnership.DoNotTransfer)); + Assert.IsTrue (Java.Interop.Runtime.GetSurfacedObjects () + .Any (o => object.ReferenceEquals (o.Target , instance))); + int e = toInt (instance); + Assert.AreEqual (42, e); + Assert.AreSame (instance, Java.Lang.Object.GetObject(instance.Handle, JniHandleOwnership.DoNotTransfer)); + } + } + + static Func GetIJavaObjectToInt32 () + { + var JavaConvert = typeof (Java.Lang.Object).Assembly.GetType ("Java.Interop.JavaConvert"); + var FromJavaObject_T = JavaConvert.GetMethods (BindingFlags.Public | BindingFlags.Static) + .First (m => m.Name == "FromJavaObject" && m.IsGenericMethod); + return (Func) Delegate.CreateDelegate ( + typeof(Func), + FromJavaObject_T.MakeGenericMethod (typeof (int))); + } + + [Test] + public void JnienvCreateInstance_RegistersMultipleInstances () + { + using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) { + + var intermediate = CreateInstance_OverrideAbsListView_Adapter.Intermediate; + var registered = Java.Lang.Object.GetObject(adapter.Handle, JniHandleOwnership.DoNotTransfer); + + Assert.AreNotSame (adapter, intermediate); + Assert.AreSame (adapter, registered); + } + } + } + + /* + * Using JNIEnv.NewObject()/JNIEnv.CreateInstance() is "bad, mkay?", because + * using them _may_ result in a Java-side activation & registration of a + * "temporary" instance; dragons be here. + * + * Alas, this is the pre-4.10 behavior! + */ + [Register (CreateInstance_OverrideAbsListView_Adapter.JcwType)] + public class CreateInstance_OverrideAbsListView_Adapter : AbsListView { + + /* (IntPtr, JniHandleOwnership) ctor is reqiured because AbsListView + * constructor virtually invokes getAdapter(): + * + * Executing: "/opt/android/sdk/platform-tools/adb" shell am instrument -w Xamarin.Android.RuntimeTests/xamarin.android.runtimetests.TestInstrumentation + * INSTRUMENTATION_RESULT: failure: Xamarin.Android.RuntimeTests.AdapterTests.InvokeOverriddenAbsListView_AdapterProperty=System.NotSupportedException : Unable to activate instance of type Xamarin.Android.RuntimeTests.CanOverrideAbsListView_Adapter from native handle 41e44de8 + * ----> System.MissingMethodException : No constructor found for Xamarin.Android.RuntimeTests.CanOverrideAbsListView_Adapter::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) + * ----> Java.Interop.JavaLocationException : Exception of type 'Java.Interop.JavaLocationException' was thrown. + * at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in :0 + * at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in :0 + * at Java.Lang.Object._GetObject[AbsListView] (IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Lang.Object.GetObject[AbsListView] (IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Lang.Object.GetObject[AbsListView] (IntPtr jnienv, IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Android.Widget.AbsListView.n_GetAdapter (IntPtr jnienv, IntPtr native__this) [0x00000] in :0 + * at (wrapper dynamic-method) object:2173e40b-99e1-484f-8c82-1f45de2f5a3a (intptr,intptr) + * --MissingMethodException + * at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in :0 + * at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in :0 + * --JavaLocationException + * Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. + * + * --- End of managed exception stack trace --- + * java.lang.Error: Java callstack: + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.n_getAdapter(Native Method) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.getAdapter(CanOverrideAbsListView_Adapter.java:46) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.getAdapter(CanOverrideAbsListView_Adapter.java:4) + * at android.widget.AdapterView.setFocusableInTouchMode(AdapterView.java:699) + * at android.widget.AbsListView.initAbsListView(AbsListView.java:812) + * at android.widget.AbsListView.(AbsListView.java:753) + * at xamarin.android.runtimetests.CanOverrideAbsListView_Adapter.(CanOverrideAbsListView_Adapter.java:22) + * at xamarin.android.nunitlite.TestSuiteInstrumentation.n_onStart(Native Method) + * at xamarin.android.nunitlite.TestSuiteInstrumentation.onStart(TestSuiteInstrumentation.java:52) + * at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701) + */ + public CreateInstance_OverrideAbsListView_Adapter (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + Intermediate = this; + } + + internal const string JcwType = "com/xamarin/android/runtimetests/CreateInstance_OverrideAbsListView_Adapter"; + + public static CreateInstance_OverrideAbsListView_Adapter Intermediate; + + public CreateInstance_OverrideAbsListView_Adapter (Context context) + : base ( + JNIEnv.CreateInstance ( + JcwType, + "(Landroid/content/Context;)V", + new JValue (context)), + JniHandleOwnership.TransferLocalRef) + { + AdapterValue = new ArrayAdapter (context, 0); + } + + protected override void Dispose (bool disposing) + { + if (!disposing) + return; + AdapterValue.Dispose (); + AdapterValue = null; + } + + public ArrayAdapter AdapterValue; + + public bool AdapterSetterInvoked; + + public override IListAdapter Adapter { + get {return AdapterValue;} + set { + AdapterSetterInvoked = true; + } + } + + public override void SetSelection (int position) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Mono.Android/Test/Mono.Android-Tests.csproj b/src/Mono.Android/Test/Mono.Android-Tests.csproj new file mode 100644 index 00000000000..03290e0d4cb --- /dev/null +++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {40EAD437-216B-4DF4-8258-3F47E1672C3A} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Xamarin.Android.RuntimeTests + True + Resources\Resource.designer.cs + Resource + Resources + Assets + Mono.Android-Tests + Properties\AndroidManifest.xml + v4.2 + + + true + false + ..\..\..\bin\TestDebug + DEBUG; + prompt + 4 + None + false + true + + + true + ..\..\..\bin\TestRelease + prompt + 4 + false + false + true + armeabi-v7a;x86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android-toolchain + {8FF78EB6-6FC8-46A7-8A15-EBBA9045C5FA} + False + False + + + Xamarin.Android.Build.Tasks + {3F1F2F50-AF1A-4A5A-BEDB-193372F068D7} + False + False + + + diff --git a/src/Mono.Android/Test/Mono.Android-Tests.targets b/src/Mono.Android/Test/Mono.Android-Tests.targets new file mode 100644 index 00000000000..5e64d80ee34 --- /dev/null +++ b/src/Mono.Android/Test/Mono.Android-Tests.targets @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mono.Android/Test/Properties/AndroidManifest.xml b/src/Mono.Android/Test/Properties/AndroidManifest.xml new file mode 100644 index 00000000000..431bf85f62f --- /dev/null +++ b/src/Mono.Android/Test/Properties/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/Mono.Android/Test/Properties/AssemblyInfo.cs b/src/Mono.Android/Test/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..dbb0cde4889 --- /dev/null +++ b/src/Mono.Android/Test/Properties/AssemblyInfo.cs @@ -0,0 +1,28 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.App; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("runtime")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Xamarin Inc.")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Xamarin Inc.")] +[assembly: AssemblyTrademark("Xamarin")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/src/Mono.Android/Test/Resources/AboutResources.txt b/src/Mono.Android/Test/Resources/AboutResources.txt new file mode 100644 index 00000000000..10f52d46021 --- /dev/null +++ b/src/Mono.Android/Test/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/src/Mono.Android/Test/Resources/Resource.designer.cs b/src/Mono.Android/Test/Resources/Resource.designer.cs new file mode 100644 index 00000000000..c2c639d63fa --- /dev/null +++ b/src/Mono.Android/Test/Resources/Resource.designer.cs @@ -0,0 +1,203 @@ +#pragma warning disable 1591 +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Mono Runtime Version: 4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +[assembly: Android.Runtime.ResourceDesignerAttribute("Xamarin.Android.RuntimeTests.Resource", IsApplication=true)] + +namespace Xamarin.Android.RuntimeTests +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + public partial class Resource + { + + static Resource() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + public static void UpdateIdValues() + { + global::Xamarin.Android.NUnitLite.Resource.Id.OptionHostName = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionHostName; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionPort = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionPort; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionRemoteServer = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionRemoteServer; + global::Xamarin.Android.NUnitLite.Resource.Id.OptionsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.OptionsButton; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultFullName = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultFullName; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultMessage; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultResultState = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultResultState; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultRunSingleMethodTest = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultRunSingleMethodTest; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultStackTrace = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultStackTrace; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsFailed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsFailed; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsId = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsId; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsIgnored = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsIgnored; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsInconclusive = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsInconclusive; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsMessage = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsMessage; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsPassed = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsPassed; + global::Xamarin.Android.NUnitLite.Resource.Id.ResultsResult = global::Xamarin.Android.RuntimeTests.Resource.Id.ResultsResult; + global::Xamarin.Android.NUnitLite.Resource.Id.RunTestsButton = global::Xamarin.Android.RuntimeTests.Resource.Id.RunTestsButton; + global::Xamarin.Android.NUnitLite.Resource.Id.TestSuiteListView = global::Xamarin.Android.RuntimeTests.Resource.Id.TestSuiteListView; + global::Xamarin.Android.NUnitLite.Resource.Layout.options = global::Xamarin.Android.RuntimeTests.Resource.Layout.options; + global::Xamarin.Android.NUnitLite.Resource.Layout.results = global::Xamarin.Android.RuntimeTests.Resource.Layout.results; + global::Xamarin.Android.NUnitLite.Resource.Layout.test_result = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_result; + global::Xamarin.Android.NUnitLite.Resource.Layout.test_suite = global::Xamarin.Android.RuntimeTests.Resource.Layout.test_suite; + } + + public partial class Attribute + { + + static Attribute() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Attribute() + { + } + } + + public partial class Drawable + { + + // aapt resource value: 0x7f020000 + public const int android_button = 2130837504; + + // aapt resource value: 0x7f020001 + public const int android_focused = 2130837505; + + // aapt resource value: 0x7f020002 + public const int android_normal = 2130837506; + + // aapt resource value: 0x7f020003 + public const int AndroidPressed = 2130837507; + + // aapt resource value: 0x7f020004 + public const int Icon = 2130837508; + + static Drawable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Drawable() + { + } + } + + public partial class Id + { + + // aapt resource value: 0x7f050001 + public const int OptionHostName = 2131034113; + + // aapt resource value: 0x7f050002 + public const int OptionPort = 2131034114; + + // aapt resource value: 0x7f050000 + public const int OptionRemoteServer = 2131034112; + + // aapt resource value: 0x7f050010 + public const int OptionsButton = 2131034128; + + // aapt resource value: 0x7f05000b + public const int ResultFullName = 2131034123; + + // aapt resource value: 0x7f05000d + public const int ResultMessage = 2131034125; + + // aapt resource value: 0x7f05000c + public const int ResultResultState = 2131034124; + + // aapt resource value: 0x7f05000a + public const int ResultRunSingleMethodTest = 2131034122; + + // aapt resource value: 0x7f05000e + public const int ResultStackTrace = 2131034126; + + // aapt resource value: 0x7f050006 + public const int ResultsFailed = 2131034118; + + // aapt resource value: 0x7f050003 + public const int ResultsId = 2131034115; + + // aapt resource value: 0x7f050007 + public const int ResultsIgnored = 2131034119; + + // aapt resource value: 0x7f050008 + public const int ResultsInconclusive = 2131034120; + + // aapt resource value: 0x7f050009 + public const int ResultsMessage = 2131034121; + + // aapt resource value: 0x7f050005 + public const int ResultsPassed = 2131034117; + + // aapt resource value: 0x7f050004 + public const int ResultsResult = 2131034116; + + // aapt resource value: 0x7f05000f + public const int RunTestsButton = 2131034127; + + // aapt resource value: 0x7f050011 + public const int TestSuiteListView = 2131034129; + + static Id() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Id() + { + } + } + + public partial class Layout + { + + // aapt resource value: 0x7f030000 + public const int options = 2130903040; + + // aapt resource value: 0x7f030001 + public const int results = 2130903041; + + // aapt resource value: 0x7f030002 + public const int test_result = 2130903042; + + // aapt resource value: 0x7f030003 + public const int test_suite = 2130903043; + + static Layout() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Layout() + { + } + } + + public partial class Xml + { + + // aapt resource value: 0x7f040000 + public const int XmlReaderResourceParser = 2130968576; + + static Xml() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Xml() + { + } + } + } +} +#pragma warning restore 1591 diff --git a/src/Mono.Android/Test/Resources/color/WhiterShadeOfPale.xml b/src/Mono.Android/Test/Resources/color/WhiterShadeOfPale.xml new file mode 100644 index 00000000000..a5bf8da09f2 --- /dev/null +++ b/src/Mono.Android/Test/Resources/color/WhiterShadeOfPale.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Mono.Android/Test/Resources/drawable/AndroidPressed.png b/src/Mono.Android/Test/Resources/drawable/AndroidPressed.png new file mode 100644 index 0000000000000000000000000000000000000000..fe81ff9e279c00aedb46b20018de030caea50cab GIT binary patch literal 2337 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 + + + + + diff --git a/src/Mono.Android/Test/Resources/drawable/android_focused.png b/src/Mono.Android/Test/Resources/drawable/android_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..f84d0fe4a5e403782c0d4db032a8b2f3053c80b8 GIT binary patch literal 2785 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 m>h&96Gax~ICRL&Fq2e#q;;tApN9c + + + + diff --git a/src/Mono.Android/Test/Resources/layout/NameFixups.axml b/src/Mono.Android/Test/Resources/layout/NameFixups.axml new file mode 100644 index 00000000000..ba6a000ff55 --- /dev/null +++ b/src/Mono.Android/Test/Resources/layout/NameFixups.axml @@ -0,0 +1,49 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Mono.Android/Test/Resources/values/Strings.xml b/src/Mono.Android/Test/Resources/values/Strings.xml new file mode 100644 index 00000000000..2b362e2a776 --- /dev/null +++ b/src/Mono.Android/Test/Resources/values/Strings.xml @@ -0,0 +1,5 @@ + + + Hello World, Click Me! + Xamarin.Android.ResourceCaseSensitivity + diff --git a/src/Mono.Android/Test/Resources/values/colors.xml b/src/Mono.Android/Test/Resources/values/colors.xml new file mode 100644 index 00000000000..b2ff9fa5b95 --- /dev/null +++ b/src/Mono.Android/Test/Resources/values/colors.xml @@ -0,0 +1,4 @@ + + + #aaaaaa + \ No newline at end of file diff --git a/src/Mono.Android/Test/Resources/values/theme.axml b/src/Mono.Android/Test/Resources/values/theme.axml new file mode 100644 index 00000000000..d52b7e49920 --- /dev/null +++ b/src/Mono.Android/Test/Resources/values/theme.axml @@ -0,0 +1,4 @@ + + + #aaaaaa + \ No newline at end of file diff --git a/src/Mono.Android/Test/Resources/xml/XmlReaderResourceParser.xml b/src/Mono.Android/Test/Resources/xml/XmlReaderResourceParser.xml new file mode 100644 index 00000000000..1dc0f3e7485 --- /dev/null +++ b/src/Mono.Android/Test/Resources/xml/XmlReaderResourceParser.xml @@ -0,0 +1 @@ + diff --git a/src/Mono.Android/Test/System.IO.Compression/GZipStreamTest.cs b/src/Mono.Android/Test/System.IO.Compression/GZipStreamTest.cs new file mode 100644 index 00000000000..4ee78665ef1 --- /dev/null +++ b/src/Mono.Android/Test/System.IO.Compression/GZipStreamTest.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.IO.Compression; + +using NUnit.Framework; + +namespace System.IO.CompressionTests +{ + [TestFixture] + public class GzipStreamTest + { + [Test] + public void Compression () + { + const string expected = "Hello, compressed world!"; + var o = new MemoryStream (); + + using (var gzip = new StreamWriter (new GZipStream (o, CompressionMode.Compress))) + gzip.WriteLine (expected); + + o = new MemoryStream (o.ToArray ()); + o.Position = 0; + + string result; + using (var gzip = new StreamReader (new GZipStream (o, CompressionMode.Decompress))) + result = gzip.ReadLine (); + + Assert.AreEqual (expected, result); + } + } +} + diff --git a/src/Mono.Android/Test/System.IO/DriveInfoTest.cs b/src/Mono.Android/Test/System.IO/DriveInfoTest.cs new file mode 100644 index 00000000000..cab4cb2ac5e --- /dev/null +++ b/src/Mono.Android/Test/System.IO/DriveInfoTest.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; + +using NUnit.Framework; + +namespace System.IOTests { + + [TestFixture] + public class DriveInfoTest { + + [Test] + public void TotalFreeSpace_IsNotInt64_MaxValue () + { + foreach (DriveInfo drive in DriveInfo.GetDrives()) { + if (drive.IsReady) { + try { + Console.WriteLine ("# DriveInfo: Name={0}; TotalFreeSpace={1}", drive.Name, drive.TotalFreeSpace); + Assert.AreNotEqual (drive.TotalFreeSpace, long.MaxValue); + } catch (UnauthorizedAccessException e) { + Console.Error.WriteLine ("DriveInfo.TotalFreeSpace IGNORING path '{0}': {1}", drive.Name, e); + } catch (IOException e) { + Console.Error.WriteLine ("DriveInfo.TotalFreeSpace IGNORING path '{0}': {1}", drive.Name, e); + } + } + } + } + } +} diff --git a/src/Mono.Android/Test/System.Net/NetworkInterfaces.cs b/src/Mono.Android/Test/System.Net/NetworkInterfaces.cs new file mode 100644 index 00000000000..1372ddb0789 --- /dev/null +++ b/src/Mono.Android/Test/System.Net/NetworkInterfaces.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using Java.Net; + +using NUnit.Framework; + +using MNetworkInterface = System.Net.NetworkInformation.NetworkInterface; +using JNetworkInterface = Java.Net.NetworkInterface; + +namespace System.NetTests +{ + [TestFixture] + public class NetworkInterfacesTest + { + sealed class InterfaceInfo + { + public string Name { get; set; } + public bool IsLoopback { get; set; } + public bool IsUp { get; set; } + public byte[] HardwareAddress { get; set; } + public List Addresses { get; set; } + + public override string ToString () + { + return string.Format ("[InterfaceInfo: Name={0}, IsLoopback={1}, IsUp={2}, HardwareAddress={3}, Addresses={4}]", Name, IsLoopback, IsUp, HardwareAddress, Addresses); + } + + public override bool Equals (object obj) + { + if (obj == null) + return false; + if (obj == this) + return true; + + var other = obj as InterfaceInfo; + if (other == null) + return false; + + return Name == other.Name && + IsLoopback == other.IsLoopback && + IsUp == other.IsUp && + HardwareAddressesAreEqual (HardwareAddress, other.HardwareAddress) && + AddressesAreEqual (Addresses, other.Addresses); + } + + bool AddressesAreEqual (List one, List two) + { + if (one == two) + return true; + if (one == null || two == null) + return false; + if (one.Count != two.Count) + return false; + + foreach (IPAddress addr in one) { + if (!two.Contains (addr)) + return false; + } + + return true; + } + + bool HardwareAddressesAreEqual (byte[] one, byte[] two) + { + if (one == two) + return true; + if (one == null || two == null) + return false; + if (one.Length != two.Length) + return false; + + for (int i = 0; i < one.Length; i++) { + if (one [i] != two [i]) + return false; + } + + return true; + } + } + + //[Test] + public void DotNetInterfacesShouldEqualJavaInterfaces () + { + List dotnetInterfaces = GetInfos (MNetworkInterface.GetAllNetworkInterfaces ()); + List javaInterfaces = GetInfos (JNetworkInterface.NetworkInterfaces); + + Assert.IsNotNull (dotnetInterfaces, "#1.1"); + Assert.IsTrue (dotnetInterfaces.Count > 0, "#1.2"); + + Assert.IsNotNull (javaInterfaces, "#2.1"); + Assert.IsTrue (javaInterfaces.Count > 0, "#2.2"); + + Assert.AreEqual (dotnetInterfaces.Count, javaInterfaces.Count, "#3.1"); + + int counter = 4; + foreach (InterfaceInfo inf in dotnetInterfaces) { + counter++; + Assert.IsNotNull (inf, String.Format ("#{0}.1", counter)); + Assert.IsFalse (String.IsNullOrEmpty (inf.Name), String.Format ("#{0}.2", counter)); + Assert.IsTrue (javaInterfaces.Contains (inf), "#{0}.3 ({1} not found in Java interfaces)", counter, inf.Name); + Console.WriteLine ("Interface {0}: passed", inf.Name); + } + } + + List CollectAddresses (MNetworkInterface inf) + { + var ret = new List (); + + foreach (UnicastIPAddressInformation addr in inf.GetIPProperties ().UnicastAddresses) + ret.Add (addr.Address); + + return ret; + } + + List CollectAddresses (JNetworkInterface inf) + { + var ret = new List (); + + Java.Util.IEnumeration addresses = inf.InetAddresses; + while (addresses.HasMoreElements) { + var addr = addresses.NextElement () as InetAddress; + if (addr == null) + continue; + ret.Add (new IPAddress (addr.GetAddress ())); + } + + return ret; + } + + bool IsInterfaceUp (MNetworkInterface inf) + { + switch (inf.OperationalStatus) { + case OperationalStatus.Dormant: + case OperationalStatus.Up: + return true; + + default: + // Android considers 'lo' to be always up + return inf.NetworkInterfaceType == NetworkInterfaceType.Loopback; + } + } + + byte[] GetHardwareAddress (MNetworkInterface inf) + { + byte[] bytes = inf.GetPhysicalAddress ().GetAddressBytes (); + // Map to android's idea of device address + if (bytes.Length == 0 || inf.NetworkInterfaceType == NetworkInterfaceType.Unknown || inf.NetworkInterfaceType == NetworkInterfaceType.Tunnel) + return null; + return bytes; + } + + List GetInfos (MNetworkInterface[] interfaces) + { + var ret = new List (); + + foreach (MNetworkInterface inf in interfaces) { + ret.Add (new InterfaceInfo { + Name = inf.Name, + IsLoopback = inf.NetworkInterfaceType == NetworkInterfaceType.Loopback, + IsUp = IsInterfaceUp (inf), + HardwareAddress = GetHardwareAddress (inf), + Addresses = CollectAddresses (inf) + }); + } + + return ret; + } + + List GetInfos (Java.Util.IEnumeration interfaces) + { + var ret = new List (); + + while (interfaces.HasMoreElements) { + var inf = interfaces.NextElement () as JNetworkInterface; + if (inf == null) + continue; + + ret.Add (new InterfaceInfo { + Name = inf.Name, + IsLoopback = inf.IsLoopback, + IsUp = inf.IsUp, + HardwareAddress = inf.GetHardwareAddress (), + Addresses = CollectAddresses (inf) + }); + } + + return ret; + } + } +} diff --git a/src/Mono.Android/Test/System.Net/ProxyTest.cs b/src/Mono.Android/Test/System.Net/ProxyTest.cs new file mode 100644 index 00000000000..0887dfce7a0 --- /dev/null +++ b/src/Mono.Android/Test/System.Net/ProxyTest.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Net; + +using NUnit.Framework; + +namespace System.NetTests { + + [TestFixture] + public class ProxyTest { + + // https://bugzilla.xamarin.com/show_bug.cgi?id=14968 + [Test] + public void QuoteInvalidQuoteUrlsShouldWork () + { + string url = "http://example.com/?query&foo|bar"; + var request = (HttpWebRequest) WebRequest.Create (url); + request.Method = "GET"; + var response = (HttpWebResponse) request.GetResponse (); + int len = 0; + using (var _r = new StreamReader (response.GetResponseStream ())) { + char[] buf = new char [4096]; + int n; + while ((n = _r.Read (buf, 0, buf.Length)) > 0) { + /* ignore; we just want to make sure we can read */ + len += n; + } + } + Assert.IsTrue (len > 0); + } + } +} diff --git a/src/Mono.Android/Test/System.Net/SslTest.cs b/src/Mono.Android/Test/System.Net/SslTest.cs new file mode 100644 index 00000000000..b6d1fb92667 --- /dev/null +++ b/src/Mono.Android/Test/System.Net/SslTest.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace System.NetTests { + + [TestFixture] + public class SslTest { + + // https://xamarin.desk.com/agent/case/35534 + [Test] + public void SslWithinTasksShouldWork () + { + var cb = ServicePointManager.ServerCertificateValidationCallback; + ServicePointManager.ServerCertificateValidationCallback = (s, cert, chain, policy) => { + Console.WriteLine ("# ServerCertificateValidationCallback"); + return true; + }; + + TaskStatus status = 0; + Exception exception = null; + + var thread = new Thread (() => { + string url = "https://pipeline.internalx.com/"; + + var downloadTask = new WebClient ().DownloadDataTaskAsync (url); + var completeTask = downloadTask.ContinueWith (t => { + Console.WriteLine ("# DownloadDataTaskAsync complete; status={0}; exception={1}", t.Status, t.Exception); + status = t.Status; + exception = t.Exception; + }); + completeTask.Wait (); + }); + thread.Start (); + thread.Join (); + + ServicePointManager.ServerCertificateValidationCallback = cb; + Assert.AreEqual (TaskStatus.RanToCompletion, status); + } + + [Test] + public void HttpsShouldWork () + { + // string url = "https://bugzilla.novell.com/show_bug.cgi?id=634817"; + string url = "https://encrypted.google.com/"; + // string url = "http://slashdot.org"; + var request = (HttpWebRequest) WebRequest.Create(url); + request.Method = "GET"; + var response = (HttpWebResponse) request.GetResponse (); + int len = 0; + using (var _r = new StreamReader (response.GetResponseStream ())) { + char[] buf = new char [4096]; + int n; + while ((n = _r.Read (buf, 0, buf.Length)) > 0) { + /* ignore; we just want to make sure we can read */ + len += n; + } + } + Assert.IsTrue (len > 0); + } + + [Test (Description="Bug https://bugzilla.xamarin.com/show_bug.cgi?id=18962")] + public void VerifyTrustedCertificates () + { + Assert.DoesNotThrow (() => { + var tcpClient = new TcpClient ("google.com", 443); + using (var ssl = new SslStream (tcpClient.GetStream (), false)) { + ssl.AuthenticateAsClient ("google.com"); + } + }, "Certificate validation"); + } + } +} diff --git a/src/Mono.Android/Test/System.Threading/InterlockedTest.cs b/src/Mono.Android/Test/System.Threading/InterlockedTest.cs new file mode 100644 index 00000000000..bb2a2fd7efa --- /dev/null +++ b/src/Mono.Android/Test/System.Threading/InterlockedTest.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; + +using NUnit.Framework; + +namespace System.ThreadingTests { + + [TestFixture] + public class InterlockedTest { + + [Test] + public void CAS64 () + { + long i = 0, j = 42; + + j = Interlocked.CompareExchange (ref i, 42, 0); + + Assert.AreEqual (i, 42); + Assert.AreEqual (j, 0); + } + } +} diff --git a/src/Mono.Android/Test/System/AppDomainTest.cs b/src/Mono.Android/Test/System/AppDomainTest.cs new file mode 100644 index 00000000000..e33127dca12 --- /dev/null +++ b/src/Mono.Android/Test/System/AppDomainTest.cs @@ -0,0 +1,44 @@ +using System; +using System.Globalization; + +using Android.App; +using Android.Content; +using Android.Runtime; + +using NUnit.Framework; + +namespace SystemTests { + + [TestFixture] + public class AppDomainTest { + + [Test] + public void DateTime_Now_Works () + { + new Boom().Bang(); + + + var otherDomain = AppDomain.CreateDomain ("other domain"); + + var otherType = typeof (Boom); + var obj = (Boom) otherDomain.CreateInstanceAndUnwrap ( + otherType.Assembly.FullName, + otherType.FullName); + obj.Bang (); + } + } + + class Boom : MarshalByRefObject + { + public void Bang() + { + var x = DateTime.Now; + Console.WriteLine ("Within AppDomain {0}, DateTime.Now={1}.", AppDomain.CurrentDomain.FriendlyName, x); + } + + public override object InitializeLifetimeService () + { + return null; + } + } +} diff --git a/src/Mono.Android/Test/System/ExceptionTest.cs b/src/Mono.Android/Test/System/ExceptionTest.cs new file mode 100644 index 00000000000..cc8aaab7a50 --- /dev/null +++ b/src/Mono.Android/Test/System/ExceptionTest.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; + +using Android.App; +using Android.Content; +using Android.Runtime; + +using NUnit.Framework; + +namespace Xamarin.Android.RuntimeTests { + + [TestFixture] + public class ExceptionTest { + + static Java.Lang.Throwable CreateJavaProxyThrowable (Exception e) + { + var JavaProxyThrowable_type = typeof (Java.Lang.Object) + .Assembly + .GetType ("Android.Runtime.JavaProxyThrowable"); + return (Java.Lang.Throwable) Activator.CreateInstance (JavaProxyThrowable_type, e); + } + + [Test] + public void InnerExceptionIsSet () + { + var ex = new InvalidOperationException ("boo!"); + using (var source = new Java.Lang.Throwable ("detailMessage", CreateJavaProxyThrowable (ex))) + using (var alias = new Java.Lang.Throwable (source.Handle, JniHandleOwnership.DoNotTransfer)) { + Assert.AreEqual ("detailMessage", alias.Message); + Assert.AreSame (ex, alias.InnerException); + } + } + } +} diff --git a/src/Mono.Android/Test/System/TimeZoneTest.cs b/src/Mono.Android/Test/System/TimeZoneTest.cs new file mode 100644 index 00000000000..eac9dc93b63 --- /dev/null +++ b/src/Mono.Android/Test/System/TimeZoneTest.cs @@ -0,0 +1,144 @@ +using System; +using System.Globalization; + +using Android.App; +using Android.Content; + +using NUnit.Framework; + +namespace Xamarin.Android.RuntimeTests +{ + [TestFixture] + public class TimeZoneTest + { + [Test] + public void TestDaylightSavingsTime () + { + using (var jtz = Java.Util.TimeZone.Default) { + CompareTimeZoneData (TimeZone.CurrentTimeZone, TimeZoneInfo.Local, jtz); + } + } + + static void CompareTimeZoneData (TimeZone tz, TimeZoneInfo tzi, Java.Util.TimeZone jtz) + { + Console.WriteLine ("## Comparing TimeZone Data:"); + Console.WriteLine ("# TimeZone: StandardName={0}; DaylightName={1}", + tz.StandardName, tz.DaylightName); + Console.WriteLine ("# TimeZoneInfo: StandardName={0}; DaylightName={1}; DisplayName={2}; Id={3}", + tzi.StandardName, tzi.DaylightName, tzi.DisplayName, tzi.Id); + Console.WriteLine ("# Java TimeZone: DisplayName={0}; ID={1}", + jtz.DisplayName, jtz.ID); + bool found_errors = false; + for (int year = 2012; year < 2015; ++year) { + if (tz != null) { + var dst = tz.GetDaylightChanges (year); + Console.WriteLine ("Year: {0}; DST: {1} -> {2}", year, dst.Start.ToString ("g"), dst.End.ToString ("g")); + } + for (int month = 1; month <= 12; month++) { + int lastDayInMonth = DateTime.DaysInMonth (year, month); + for (int day = 1; day <= lastDayInMonth; day++) { + var localtime = new DateTime (year, month, day, 10, 00, 00, DateTimeKind.Local); + var utctime = localtime.ToUniversalTime (); + + var tz_offset = tz.GetUtcOffset (localtime); + var tzi_offset = tzi.GetUtcOffset (localtime); + + var tz_dst = tz.IsDaylightSavingTime (localtime); + var tzi_dst = tzi.IsDaylightSavingTime (localtime); + + using (var jd = ToDate (localtime)) { + var jtz_dst = jtz.InDaylightTime (jd); + var ms = (int)(localtime - new DateTime (year, month, day, 0, 0, 0, DateTimeKind.Local)).TotalMilliseconds; + var jtz_offseti = jtz.GetOffset (1, year, month - 1, day, ((int) localtime.DayOfWeek)+1, ms); + var jtz_offset = TimeSpan.FromMilliseconds (jtz_offseti); + + if (tz_offset != tzi_offset || tz_offset != jtz_offset || tzi_offset != jtz_offset || + tz_dst != tzi_dst || tz_dst != jtz_dst || tzi_dst != jtz_dst) { + found_errors = true; + Console.WriteLine ("MISMATCH! @ {0} [{1}]", localtime, jd.ToLocaleString ()); + Console.WriteLine ("\t TimeZone Offset: {0}", tz_offset); + Console.WriteLine ("\t TimeZoneInfo Offset: {0}", tzi_offset); + Console.WriteLine ("\tJava TimeZone Offset: {0}", jtz_offset); + Console.WriteLine ("\t TimeZone DST: {0}", tz_dst); + Console.WriteLine ("\t TimeZoneInfo DST: {0}", tzi_dst); + Console.WriteLine ("\t Java TimeZone DST: {0}", jtz_dst); + } + } + } + } + } + if (found_errors) { + var rules = tzi.GetAdjustmentRules (); + for (int i = 0; i < rules.Length; ++i) { + var rule = rules [i]; + Console.WriteLine ("# AdjustmentRules[{0}]: DaylightDelta={1}; DateStart={2}; DateEnd={3}; DaylightTransitionStart={4}/{5} @ {6}; DaylightTransitionEnd={7}/{8} @ {9}", + i, + rule.DaylightDelta, + rule.DateStart.ToString ("yyyy-MM-dd"), rule.DateEnd.ToString ("yyyy-MM-dd"), + rule.DaylightTransitionStart.Month, rule.DaylightTransitionStart.Day, + rule.DaylightTransitionStart.TimeOfDay.ToString ("hh:mm:ss"), + rule.DaylightTransitionEnd.Month, rule.DaylightTransitionEnd.Day, + rule.DaylightTransitionEnd.TimeOfDay.ToString ("hh:mm:ss")); + } + } + Assert.IsFalse (found_errors, "TimeZoneData MISMATCH! See logcat output for details."); + } + + static Java.Util.Date ToDate (DateTime time) + { + return new Java.Util.Date (time.Year - 1900, time.Month - 1, time.Day, time.Hour, time.Minute, time.Second); + } + + [Test] + public void Transitions () + { + var hasDst = TimeZoneInfo.Local.SupportsDaylightSavingTime; + var tz = TimeZone.CurrentTimeZone; + var tzi = TimeZoneInfo.Local; + var changes = tz.GetDaylightChanges (2014); + using (var jtz = Java.Util.TimeZone.Default) { + var start = changes.Start; + if (tzi.IsInvalidTime (changes.Start)) { + // The time is invalid because *it does not exist*. + // For example, if DST starts at 2AM, the time transition is *actually* ..., 01:58AM, 01:59AM, 03:00AM, 03:01AM, ... + // There is no 2AM. + start = start + changes.Delta; + } + using (var d = ToDate (start)) { + Assert.AreEqual (jtz.InDaylightTime (d), tz.IsDaylightSavingTime (start), + string.Format ("Within DST Start time mismatch: Java({0}) != .NET({1})!", d.ToLocaleString (), start)); + } + if (tz.IsDaylightSavingTime (start)) { + var mPreStart = changes.Start - changes.Delta; + using (var preStart = ToDate (mPreStart)) { + Assert.AreEqual (jtz.InDaylightTime (preStart), tz.IsDaylightSavingTime (mPreStart), + string.Format ("DST-1h in-DST mismatch: Java({0}) != .NET({1})", preStart.ToLocaleString (), mPreStart)); + } + var mPostStart = changes.Start + changes.Delta; + using (var postStart = ToDate (mPostStart)) { + Assert.AreEqual (jtz.InDaylightTime (postStart), tz.IsDaylightSavingTime (mPostStart), + string.Format ("DST+1h in-DST mismatch: Java({0}) != .NET({1})", postStart.ToLocaleString (), mPostStart)); + } + } + using (var d = ToDate (changes.End)) { + Assert.AreEqual (jtz.InDaylightTime (d), tz.IsDaylightSavingTime (changes.End)); + if (tz.IsDaylightSavingTime (changes.Start)) { + // At end-of-DST, we "gain" an hour, so the previous hour is *repeated* + // e.g. 12 AM, 1 AM, 1 AM, 2 AM, ... + // Check *2* hours prior to avoid the repeated hour. + var mPreEnd = changes.End.AddHours (-2); + using (var preEnd = ToDate (mPreEnd)) { + Assert.AreEqual (jtz.InDaylightTime (preEnd), tz.IsDaylightSavingTime (mPreEnd), + string.Format ("ST-1h in-DST mismatch: Java({0}) != .NET({1})", preEnd.ToLocaleString (), mPreEnd)); + } + var mPostEnd = changes.End.AddHours (1); + using (var postEnd = ToDate (mPostEnd)) { + Assert.AreEqual (jtz.InDaylightTime (postEnd), tz.IsDaylightSavingTime (mPostEnd), + string.Format ("ST+1h out-DST mismatch: Java({0}) != .NET({1})", postEnd.ToLocaleString (), mPostEnd)); + } + } + } + } + } + } +} diff --git a/src/Mono.Android/Test/Xamarin.Android.Net/AndroidClientHandlerTests.cs b/src/Mono.Android/Test/Xamarin.Android.Net/AndroidClientHandlerTests.cs new file mode 100644 index 00000000000..51e491c73ab --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.Net/AndroidClientHandlerTests.cs @@ -0,0 +1,180 @@ +// +// HttpClientHandlerTest.cs +// +// Authors: +// Marek Safar +// +// Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) +// +// 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; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using System.Net.Http; +using System.Net; + +using Android.OS; + +namespace Xamarin.Android.NetTests { + + public abstract class HttpClientHandlerTestBase + { + protected abstract HttpClientHandler CreateHandler (); + + class Proxy : IWebProxy + { + public ICredentials Credentials { + get { + throw new NotImplementedException (); + } + set { + throw new NotImplementedException (); + } + } + + public Uri GetProxy (Uri destination) + { + throw new NotImplementedException (); + } + + public bool IsBypassed (Uri host) + { + throw new NotImplementedException (); + } + } + + [Test] + public void Properties_Defaults () + { + var h = CreateHandler (); + Assert.IsTrue (h.AllowAutoRedirect, "#1"); + Assert.AreEqual (DecompressionMethods.None, h.AutomaticDecompression, "#2"); + Assert.AreEqual (0, h.CookieContainer.Count, "#3"); + Assert.AreEqual (4096, h.CookieContainer.MaxCookieSize, "#3b"); + Assert.AreEqual (null, h.Credentials, "#4"); + Assert.AreEqual (50, h.MaxAutomaticRedirections, "#5"); + Assert.AreEqual (int.MaxValue, h.MaxRequestContentBufferSize, "#6"); + Assert.IsFalse (h.PreAuthenticate, "#7"); + Assert.IsNull (h.Proxy, "#8"); + Assert.IsTrue (h.SupportsAutomaticDecompression, "#9"); + Assert.IsTrue (h.SupportsProxy, "#10"); + Assert.IsTrue (h.SupportsRedirectConfiguration, "#11"); + Assert.IsTrue (h.UseCookies, "#12"); + Assert.IsFalse (h.UseDefaultCredentials, "#13"); + Assert.IsTrue (h.UseProxy, "#14"); + Assert.AreEqual (ClientCertificateOption.Manual, h.ClientCertificateOptions, "#15"); + } + + [Test] + public void Properties_Invalid () + { + var h = CreateHandler (); + try { + h.MaxAutomaticRedirections = 0; + Assert.Fail ("#1"); + } catch (ArgumentOutOfRangeException) { + } + + try { + h.MaxRequestContentBufferSize = -1; + Assert.Fail ("#2"); + } catch (ArgumentOutOfRangeException) { + } + + h.UseProxy = false; + try { + h.Proxy = new Proxy (); + Assert.Fail ("#3"); + } catch (InvalidOperationException) { + } + } + + [Test] + public void Properties_AfterClientCreation () + { + var h = CreateHandler (); + h.AllowAutoRedirect = true; + + // We may modify properties after creating the HttpClient. + using (var c = new HttpClient (h, true)) { + h.AllowAutoRedirect = false; + } + } + + [Test] + public void Disposed () + { + var h = CreateHandler (); + h.Dispose (); + var c = new HttpClient (h); + try { + c.GetAsync ("http://google.com").Wait (); + Assert.Fail ("#1"); + } catch (AggregateException e) { + Assert.IsTrue (e.InnerException is ObjectDisposedException, "#2"); + } + } + } + + [TestFixture] + public class AndroidClientHandlerTests : HttpClientHandlerTestBase + { + const string Tls_1_2_Url = "https://tlstest.xamdev.com/"; + + protected override HttpClientHandler CreateHandler () + { + return new Xamarin.Android.Net.AndroidClientHandler (); + } + + [Test] + public void Tls_1_2_Url_Works () + { + if (((int) Build.VERSION.SdkInt) < 21) { + Assert.Ignore ("Host platform doesn't support TLS 1.2."); + return; + } + using (var c = new HttpClient (CreateHandler ())) { + var tr = c.GetAsync (Tls_1_2_Url); + tr.Wait (); + tr.Result.EnsureSuccessStatusCode (); + } + } + + [Test] + public void Sanity_Tls_1_2_Url_WithMonoClientHandlerFails () + { + using (var c = new HttpClient (new HttpClientHandler ())) { + try { + var tr = c.GetAsync (Tls_1_2_Url); + tr.Wait (); + tr.Result.EnsureSuccessStatusCode (); + Assert.Fail ("SHOULD NOT BE REACHED: Mono's HttpClientHandler doesn't support TLS 1.2."); + } + catch (AggregateException e) { + Assert.IsTrue (e.InnerExceptions.Any (ie => ie is WebException)); + } + } + } + } +} diff --git a/src/Mono.Android/Test/Xamarin.Android.Net/HttpClientIntegrationTests.cs b/src/Mono.Android/Test/Xamarin.Android.Net/HttpClientIntegrationTests.cs new file mode 100644 index 00000000000..d5eeaf0183c --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.Net/HttpClientIntegrationTests.cs @@ -0,0 +1,1046 @@ +// +// HttpClientIntegrationTests.cs +// +// Authors: +// Marek Safar +// +// Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) +// +// 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; +using System.Collections.Generic; +using NUnit.Framework; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Linq; +using System.IO; + +namespace Xamarin.Android.NetTests { + + public abstract class HttpClientIntegrationTestBase + { + protected abstract HttpClientHandler CreateHandler (); + + class CustomStream : Stream + { + public override void Flush () + { + throw new NotImplementedException (); + } + + int pos; + + public override int Read (byte[] buffer, int offset, int count) + { + ++pos; + if (pos > 4) + return 0; + + return 11; + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotImplementedException (); + } + + public override void SetLength (long value) + { + throw new NotImplementedException (); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotImplementedException (); + } + + public override bool CanRead { + get { + return true; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + throw new NotImplementedException (); + } + } + + public override long Length { + get { + throw new NotImplementedException (); + } + } + + public override long Position { + get { + throw new NotImplementedException (); + } + set { + throw new NotImplementedException (); + } + } + } + + const int WaitTimeout = 5000; + + string port, TestHost, LocalServer; + + [SetUp] + public void SetupFixture () + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + port = "810"; + } else { + port = "8810"; + } + + TestHost = "localhost:" + port; + LocalServer = string.Format ("http://{0}/", TestHost); + } + + [Test] + public void Ctor_Default () + { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + Assert.IsNull (client.BaseAddress, "#1"); + Assert.IsNotNull (client.DefaultRequestHeaders, "#2"); // TODO: full check + Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#3"); + Assert.AreEqual (TimeSpan.FromSeconds (100), client.Timeout, "#4"); + } + } + + + [Test] + public void CancelRequestViaProxy () + { + using (var handler = CreateHandler ()) { + handler.Proxy = new WebProxy ("192.168.10.25:8888/"); // proxy that doesn't exist + handler.UseProxy = true; + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + + var httpClient = new HttpClient (handler) { + BaseAddress = new Uri ("https://google.com"), + Timeout = TimeSpan.FromMilliseconds (1) + }; + + try { + var restRequest = new HttpRequestMessage { + Method = HttpMethod.Post, + RequestUri = new Uri ("foo", UriKind.Relative), + Content = new StringContent ("", null, "application/json") + }; + + httpClient.PostAsync (restRequest.RequestUri, restRequest.Content).Wait (WaitTimeout); + Assert.Fail ("#1"); + } catch (AggregateException e) { + Console.WriteLine ("CancelRequestViaProxy exception: {0}", e); + Assert.IsTrue (e.InnerException is TaskCanceledException, "#2"); + } + } + } + + [Test] + public void Properties () + { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + client.BaseAddress = null; + client.MaxResponseContentBufferSize = int.MaxValue; + client.Timeout = Timeout.InfiniteTimeSpan; + + Assert.IsNull (client.BaseAddress, "#1"); + Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#2"); + Assert.AreEqual (Timeout.InfiniteTimeSpan, client.Timeout, "#3"); + } + } + + [Test] + public void Properties_Invalid () + { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + try { + client.MaxResponseContentBufferSize = 0; + Assert.Fail ("#1"); + } catch (ArgumentOutOfRangeException) { + } + + try { + client.Timeout = TimeSpan.MinValue; + Assert.Fail ("#2"); + } catch (ArgumentOutOfRangeException) { + } + } + } +#if TODO + [Test] + public void Send_Complete_Default () + { + bool? failed = null; + var listener = CreateListener (l => { + try { + var request = l.Request; + + Assert.IsNull (request.AcceptTypes, "#1"); + Assert.AreEqual (0, request.ContentLength64, "#2"); + Assert.IsNull (request.ContentType, "#3"); + Assert.AreEqual (0, request.Cookies.Count, "#4"); + Assert.IsFalse (request.HasEntityBody, "#5"); + Assert.AreEqual (TestHost, request.Headers["Host"], "#6b"); + Assert.AreEqual ("GET", request.HttpMethod, "#7"); + Assert.IsFalse (request.IsAuthenticated, "#8"); +#if false + Assert.IsTrue (request.IsLocal, "#9"); +#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) + Assert.IsFalse (request.IsSecureConnection, "#10"); + Assert.IsFalse (request.IsWebSocketRequest, "#11"); + Assert.IsTrue (request.KeepAlive, "#12"); + Assert.AreEqual (HttpVersion.Version11, request.ProtocolVersion, "#13"); + Assert.IsNull (request.ServiceName, "#14"); + Assert.IsNull (request.UrlReferrer, "#15"); + Assert.IsNotNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header + Assert.IsNull (request.UserLanguages, "#17"); + failed = false; + } catch (Exception e) { + Console.WriteLine ("# jonp: Send_Complete_Default"); + Console.WriteLine (e); + failed = true; + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_Version_1_0 () + { + bool? failed = null; + + var listener = CreateListener (l => { + try { + var request = l.Request; + + Assert.IsNull (request.AcceptTypes, "#1"); + Assert.AreEqual (0, request.ContentLength64, "#2"); + Assert.IsNull (request.ContentType, "#3"); + Assert.AreEqual (0, request.Cookies.Count, "#4"); + Assert.IsFalse (request.HasEntityBody, "#5"); + Assert.AreEqual (1, request.Headers.Count, "#6"); + Assert.AreEqual (TestHost, request.Headers["Host"], "#6a"); + Assert.AreEqual ("GET", request.HttpMethod, "#7"); + Assert.IsFalse (request.IsAuthenticated, "#8"); +#if false + Assert.IsTrue (request.IsLocal, "#9"); +#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) + Assert.IsFalse (request.IsSecureConnection, "#10"); + Assert.IsFalse (request.IsWebSocketRequest, "#11"); + Assert.IsFalse (request.KeepAlive, "#12"); +#if false // Java HTTP client doesn't support 1.0, always uses 1.1 + Assert.AreEqual (HttpVersion.Version10, request.ProtocolVersion, "#13"); +#endif + Assert.IsNull (request.ServiceName, "#14"); + Assert.IsNull (request.UrlReferrer, "#15"); + Assert.IsNotNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header + Assert.IsNull (request.UserLanguages, "#17"); + failed = false; + } catch (Exception e) { + Console.WriteLine ("# jonp: Send_Complete_Version_1_0"); + Console.WriteLine (e); + failed = true; + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + //request.Version = HttpVersion.Version10; + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Close (); + } + } + } + + // This is failing because the `try/catch` block (lines 308-336) aren't executed, + // and thus `failed` (line 304) is `null` on line 361, resulting in a NRE. + [Test] + public void Send_Complete_ClientHandlerSettings () + { + bool? failed = null; + + var listener = CreateListener (l => { + var request = l.Request; + + try { + Assert.IsNull (request.AcceptTypes, "#1"); + Assert.AreEqual (0, request.ContentLength64, "#2"); + Assert.IsNull (request.ContentType, "#3"); + Assert.AreEqual (1, request.Cookies.Count, "#4"); + Assert.AreEqual (new Cookie ("mycookie", "vv"), request.Cookies[0], "#4a"); + Assert.IsFalse (request.HasEntityBody, "#5"); + Assert.AreEqual (4, request.Headers.Count, "#6"); + Assert.AreEqual (TestHost, request.Headers["Host"], "#6a"); + Assert.AreEqual ("gzip", request.Headers["Accept-Encoding"], "#6b"); + Assert.AreEqual ("mycookie=vv", request.Headers["Cookie"], "#6c"); + Assert.AreEqual ("GET", request.HttpMethod, "#7"); + Assert.IsFalse (request.IsAuthenticated, "#8"); +#if false + Assert.IsTrue (request.IsLocal, "#9"); +#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) + Assert.IsFalse (request.IsSecureConnection, "#10"); + Assert.IsFalse (request.IsWebSocketRequest, "#11"); + Assert.IsTrue (request.KeepAlive, "#12"); +#if false // Java HTTP client doesn't support 1.0, always uses 1.1 + Assert.AreEqual (HttpVersion.Version10, request.ProtocolVersion, "#13"); +#endif + Assert.IsNull (request.ServiceName, "#14"); + Assert.IsNull (request.UrlReferrer, "#15"); +#if false + Assert.IsNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header +#endif + Assert.IsNull (request.UserLanguages, "#17"); + failed = false; + } catch (Exception x) { + Console.WriteLine ("# jonp: Send_Complete_ClientHandlerSettings: ERROR"); + Console.WriteLine (x.ToString ()); + failed = true; + } + }); + + using (listener) { + try { + using (var chandler = CreateHandler ()) { + chandler.AllowAutoRedirect = true; + chandler.AutomaticDecompression = DecompressionMethods.GZip; + chandler.MaxAutomaticRedirections = 33; + chandler.MaxRequestContentBufferSize = 5555; + chandler.PreAuthenticate = true; + chandler.CookieContainer.Add (new Uri (LocalServer), new Cookie ("mycookie", "vv")); + chandler.UseCookies = true; + chandler.UseDefaultCredentials = true; + chandler.Proxy = new WebProxy ("ee"); + chandler.UseProxy = true; + + var client = new HttpClient (chandler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + request.Version = HttpVersion.Version10; + request.Headers.Add ("Keep-Alive", "false"); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + Console.WriteLine ("# jonp: Send_Complete_ClientHandlerSettings: failed? {0}", failed.HasValue); + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_CustomHeaders () + { + bool? failed = null; + + var listener = CreateListener (l => { + var request = l.Request; + try { + Assert.AreEqual ("vv", request.Headers["aa"], "#1"); + + var response = l.Response; + response.Headers.Add ("rsp", "rrr"); + response.Headers.Add ("upgrade", "vvvvaa"); + response.Headers.Add ("Date", "aa"); + response.Headers.Add ("cache-control", "audio"); + + response.StatusDescription = "test description"; + response.ProtocolVersion = HttpVersion.Version10; + response.SendChunked = true; + response.RedirectLocation = "w3.org"; + + failed = false; + } catch { + failed = true; + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + Assert.IsTrue (request.Headers.TryAddWithoutValidation ("aa", "vv"), "#0"); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + + IEnumerable values; + Assert.IsTrue (response.Headers.TryGetValues ("rsp", out values), "#102"); + Assert.AreEqual ("rrr", values.First (), "#102a"); + + Assert.IsTrue (response.Headers.TryGetValues ("Transfer-Encoding", out values), "#103"); + Assert.AreEqual ("chunked", values.First (), "#103a"); + Assert.AreEqual (true, response.Headers.TransferEncodingChunked, "#103b"); + + Assert.IsTrue (response.Headers.TryGetValues ("Date", out values), "#104"); + Assert.AreEqual (1, values.Count (), "#104b"); + // .NET overwrites Date, Mono does not + // Assert.IsNotNull (response.Headers.Date, "#104c"); + + Assert.AreEqual (new ProductHeaderValue ("vvvvaa"), response.Headers.Upgrade.First (), "#105"); + + Assert.AreEqual ("audio", response.Headers.CacheControl.Extensions.First ().Name, "#106"); + + Assert.AreEqual ("w3.org", response.Headers.Location.OriginalString, "#107"); + + Assert.AreEqual ("test description", response.ReasonPhrase, "#110"); + Assert.AreEqual (HttpVersion.Version11, response.Version, "#111"); + + Assert.AreEqual (false, failed, "#112"); + } + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_CustomHeaders_SpecialSeparators () + { + bool? failed = null; + + var listener = CreateListener (l => { + var request = l.Request; + + try { + Assert.AreEqual ("MLK,Android,Phone,1.1.9", request.UserAgent, "#1"); + failed = false; + } catch (Exception ex) { + failed = true; + Console.WriteLine (ex); + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + + client.DefaultRequestHeaders.Add ("User-Agent", "MLK Android Phone 1.1.9"); + + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_CustomHeaders_Host () + { + bool? failed = null; + var listener = CreateListener (l => { + var request = l.Request; + + try { + Assert.AreEqual ("customhost", request.Headers["Host"], "#1"); + failed = false; + } catch (Exception ex) { + failed = true; + Console.WriteLine (ex); + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + + client.DefaultRequestHeaders.Add ("Host", "customhost"); + + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + [Test] + public void Send_Transfer_Encoding_Chunked () + { + bool? failed = null; + + var listener = CreateListener (l => { + var request = l.Request; + + try { + Assert.AreEqual (5, request.Headers.Count, "#1"); + failed = false; + } catch (Exception ex) { + failed = true; + Console.WriteLine (ex); + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + client.DefaultRequestHeaders.TransferEncodingChunked = true; + + client.GetAsync (LocalServer).Wait (); + + Assert.AreEqual (false, failed, "#102"); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } +#endif // TODO + [Test] + public void Send_Transfer_Encoding_Custom () + { + bool? failed = null; + + var listener = CreateListener (l => { + failed = true; + }); + + using (listener) { + try { + var client = new HttpClient (); + client.DefaultRequestHeaders.TransferEncoding.Add (new TransferCodingHeaderValue ("chunked2")); + + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + + try { + client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Wait (); + Assert.Fail ("#1"); + } catch (AggregateException e) { + Assert.AreEqual (typeof (ProtocolViolationException), e.InnerException.GetType (), "#2"); + } + Assert.IsNull (failed, "#102"); + } finally { + listener.Abort (); + listener.Close (); + } + } + } +#if TODO + [Test] + public void Send_Complete_Content () + { + var listener = CreateListener (l => { + var request = l.Request; + l.Response.OutputStream.WriteByte (55); + l.Response.OutputStream.WriteByte (75); + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + Assert.IsTrue (request.Headers.TryAddWithoutValidation ("aa", "vv"), "#0"); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("7K", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + + IEnumerable values; + Assert.IsTrue (response.Headers.TryGetValues ("Transfer-Encoding", out values), "#102"); + Assert.AreEqual ("chunked", values.First (), "#102a"); + Assert.AreEqual (true, response.Headers.TransferEncodingChunked, "#102b"); + } + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_Content_MaxResponseContentBufferSize () + { + var listener = CreateListener (l => { + var request = l.Request; + var b = new byte[4000]; + l.Response.OutputStream.Write (b, 0, b.Length); + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + client.MaxResponseContentBufferSize = 1000; + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual (4000, response.Content.ReadAsStringAsync ().Result.Length, "#100"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); + } + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Complete_Content_MaxResponseContentBufferSize_Error () + { + var listener = CreateListener (l => { + var request = l.Request; + var b = new byte[4000]; + l.Response.OutputStream.Write (b, 0, b.Length); + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + client.MaxResponseContentBufferSize = 1000; + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + + try { + client.SendAsync (request, HttpCompletionOption.ResponseContentRead).Wait (WaitTimeout); + Assert.Fail ("#2"); + } catch (AggregateException e) { + Assert.IsTrue (e.InnerException is HttpRequestException, "#3"); + } + } + } finally { + listener.Close (); + } + } + } +#endif + public void Send_Complete_NoContent (HttpMethod method) + { + bool? failed = null; + var listener = CreateListener (l => { + try { + var request = l.Request; + + Assert.AreEqual (6, request.Headers.Count, $"#1-{method}"); + Assert.AreEqual ("0", request.Headers ["Content-Length"], $"#1b-{method}"); + Assert.AreEqual (method.Method, request.HttpMethod, $"#2-{method}"); + Console.WriteLine ($"Asserts are fine - {method}"); + failed = false; + } catch (Exception ex) { + failed = true; + Console.WriteLine (ex); + } + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (method, LocalServer); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, $"#100-{method}"); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, $"#101-{method}"); + Assert.AreEqual (false, failed, $"#102-{method}"); + } + } finally { + listener.Close (); + } + } + } +#if TODO + [Test] + public void Send_Complete_NoContent_POST () + { + Send_Complete_NoContent (HttpMethod.Post); + } + + [Test] + public void Send_Complete_NoContent_PUT () + { + Send_Complete_NoContent (HttpMethod.Put); + } + + + [Test] + public void Send_Complete_NoContent_DELETE () + { + Send_Complete_NoContent (HttpMethod.Delete); + } + + [Test] + public void Send_Complete_Error () + { + var listener = CreateListener (l => { + var response = l.Response; + response.StatusCode = 500; + }); + + using (listener) { + try { + using (var handler = CreateHandler ()) { + var client = new HttpClient (handler); + var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); + var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; + + Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); + Assert.AreEqual (HttpStatusCode.InternalServerError, response.StatusCode, "#101"); + } + } finally { + listener.Close (); + } + } + } +#endif + [Test] + public void Send_Content_Get () + { + var listener = CreateListener (l => { + var request = l.Request; + l.Response.OutputStream.WriteByte (72); + }); + + using (listener) { + try { + var client = new HttpClient (); + var r = new HttpRequestMessage (HttpMethod.Get, LocalServer); + var response = client.SendAsync (r).Result; + + Assert.AreEqual ("H", response.Content.ReadAsStringAsync ().Result); + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Content_BomEncoding () + { + var listener = CreateListener (l => { + var request = l.Request; + + var str = l.Response.OutputStream; + str.WriteByte (0xEF); + str.WriteByte (0xBB); + str.WriteByte (0xBF); + str.WriteByte (71); + }); + + using (listener) { + try { + var client = new HttpClient (); + var r = new HttpRequestMessage (HttpMethod.Get, LocalServer); + var response = client.SendAsync (r).Result; + + Assert.AreEqual ("G", response.Content.ReadAsStringAsync ().Result); + } finally { + listener.Close (); + } + } + } + + [Test] + public void Send_Content_Put () + { + bool passed = false; + var listener = CreateListener (l => { + var request = l.Request; + passed = 7 == request.ContentLength64; + passed &= request.ContentType == "text/plain; charset=utf-8"; + passed &= request.InputStream.ReadByte () == 'm'; + }); + + using (listener) { + try { + var client = new HttpClient (); + var r = new HttpRequestMessage (HttpMethod.Put, LocalServer); + r.Content = new StringContent ("my text"); + var response = client.SendAsync (r).Result; + + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#1"); + Assert.IsTrue (passed, "#2"); + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + [Test] + public void Send_Content_Put_CustomStream () + { + bool passed = false; + var listener = CreateListener (l => { + var request = l.Request; + passed = 44 == request.ContentLength64; + passed &= request.ContentType == null; + }); + + using (listener) { + try { + var client = new HttpClient (); + var r = new HttpRequestMessage (HttpMethod.Put, LocalServer); + r.Content = new StreamContent (new CustomStream ()); + var response = client.SendAsync (r).Result; + + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#1"); + Assert.IsTrue (passed, "#2"); + } finally { + listener.Abort (); + + listener.Close (); + } + } + } + + [Test] + public void Send_Invalid () + { + var client = new HttpClient (CreateHandler ()); + try { + client.SendAsync (null).Wait (WaitTimeout); + Assert.Fail ("#1"); + } catch (ArgumentNullException) { + } + + try { + var request = new HttpRequestMessage (); + client.SendAsync (request).Wait (WaitTimeout); + Assert.Fail ("#2"); + } catch (InvalidOperationException) { + } + } + + [Test] + [Category ("MobileNotWorking")] // Missing encoding + public void GetString_Many () + { + var client = new HttpClient (CreateHandler ()); + var t1 = client.GetStringAsync ("http://example.org"); + var t2 = client.GetStringAsync ("http://example.org"); + Assert.IsTrue (Task.WaitAll (new [] { t1, t2 }, WaitTimeout)); + } + +#if TODO + // Currently fails because GetByteArrayAsync().Wait(timeout) doesn't throw + [Test] + public void GetByteArray_ServerError () + { + var listener = CreateListener (l => { + var response = l.Response; + response.StatusCode = 500; + l.Response.OutputStream.WriteByte (72); + }); + + using (listener) { + try { + var client = new HttpClient (CreateHandler ()); + try { + client.GetByteArrayAsync (LocalServer).Wait (WaitTimeout); + Assert.Fail ("#1"); + } catch (AggregateException e) { + Console.WriteLine ("# jonp: GetByteArray_ServerError"); + Console.WriteLine (e); + Assert.IsTrue (e.InnerException is HttpRequestException, "#2"); + } + } finally { + listener.Close (); + } + } + } +#endif // TODO + + [Test] + public void DisallowAutoRedirect () + { + var listener = CreateListener (l => { + var request = l.Request; + var response = l.Response; + + response.StatusCode = (int)HttpStatusCode.Moved; + response.RedirectLocation = "http://xamarin.com/"; + }); + + using (listener) { + try { + var chandler = CreateHandler (); + chandler.AllowAutoRedirect = false; + var client = new HttpClient (chandler); + + try { + client.GetStringAsync (LocalServer).Wait (WaitTimeout); + Assert.Fail ("#1"); + } catch (AggregateException e) { + Assert.IsTrue (e.InnerException is HttpRequestException, "#2"); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } +#if TODO + [Test] + public void RequestUriAfterRedirect () + { + var listener = CreateListener (l => { + var request = l.Request; + var response = l.Response; + + response.StatusCode = (int)HttpStatusCode.Moved; + response.RedirectLocation = "http://xamarin.com/"; + }); + + using (listener) { + try { + var chandler = CreateHandler (); + chandler.AllowAutoRedirect = true; + var client = new HttpClient (chandler); + + var r = client.GetAsync (LocalServer); + Assert.IsTrue (r.Wait (WaitTimeout), "#1"); + var resp = r.Result; + Assert.AreEqual ("http://xamarin.com/", resp.RequestMessage.RequestUri.AbsoluteUri, "#2"); + } finally { + listener.Abort (); + listener.Close (); + } + } + } +#endif +#if false + // It doesn't appear to be possible to satisfy this test, because e.g. + // HttpClientHandler.set_AllowAutoRedirect only throws when + // HttpClientHandler.sentRequest is true, and sentRequest is only set + // if HttpClientHandler.SendAsync() is invoked, and *we can't call it*. + // Perhaps a mono implementation bug? + [Test] + /* + * Properties may only be modified before sending the first request. + */ + public void ModifyHandlerAfterFirstRequest () + { + var chandler = CreateHandler (); + chandler.AllowAutoRedirect = true; + var client = new HttpClient (chandler, true); + + var listener = CreateListener (l => { + var response = l.Response; + response.StatusCode = 200; + response.OutputStream.WriteByte (55); + }); + + try { + client.GetStringAsync (LocalServer).Wait (WaitTimeout); + try { + chandler.AllowAutoRedirect = false; + Assert.Fail ("#1"); + } catch (InvalidOperationException) { + ; + } + } finally { + listener.Abort (); + listener.Close (); + } + } +#endif + + HttpListener CreateListener (Action contextAssert) + { + var l = new HttpListener (); + l.Prefixes.Add (string.Format ("http://+:{0}/", port)); + l.Start (); + l.BeginGetContext (ar => { + var ctx = l.EndGetContext (ar); + try { + if (contextAssert != null) + contextAssert (ctx); + } finally { + ctx.Response.Close (); + } + }, null); + + return l; + } + } + + [TestFixture] + public class AndroidClientHandlerIntegrationTests : HttpClientIntegrationTestBase + { + protected override HttpClientHandler CreateHandler () + { + return new Xamarin.Android.Net.AndroidClientHandler (); + } + } +} diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MainActivity.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MainActivity.cs new file mode 100644 index 00000000000..a69679300d4 --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MainActivity.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using Android.App; +using Android.OS; +using Xamarin.Android.NUnitLite; + +namespace Xamarin.Android.RuntimeTests +{ + [Activity (Label = "runtime", MainLauncher = true, + Name="xamarin.android.runtimetests.MainActivity")] + public class MainActivity : TestSuiteActivity + { + protected override void OnCreate (Bundle bundle) + { + // tests can be inside the main assembly + AddTest (Assembly.GetExecutingAssembly ()); + // or in any reference assemblies + // AddTest (typeof (Your.Library.TestClass).Assembly); + + // Once you called base.OnCreate(), you cannot add more assemblies. + base.OnCreate (bundle); + } + } +} + diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MyIntent.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MyIntent.cs new file mode 100644 index 00000000000..b4edf87d2e6 --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/MyIntent.cs @@ -0,0 +1,14 @@ +using Android.Content; + +namespace Xamarin.Android.RuntimeTests { + + class MyIntent : Intent { + + public override System.Collections.Generic.IList GetStringArrayListExtra (string name) + { + return name == "values" + ? new[]{"a", "b", "c"} + : null; + } + } +} diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NonJavaObject.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NonJavaObject.cs new file mode 100644 index 00000000000..793ed63ef07 --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/NonJavaObject.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Android.RuntimeTests { + + class NonJavaObject { + + public object Value; + + public override string ToString () + { + return "This object is Sooooo not on java."; + } + } +} + diff --git a/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs new file mode 100644 index 00000000000..2de66f3b995 --- /dev/null +++ b/src/Mono.Android/Test/Xamarin.Android.RuntimeTests/TestInstrumentation.cs @@ -0,0 +1,26 @@ +using System; +using System.Reflection; + +using Android.App; +using Android.Content; +using Android.Runtime; + +using Xamarin.Android.NUnitLite; + +namespace Xamarin.Android.RuntimeTests { + + [Instrumentation (Name="xamarin.android.runtimetests.TestInstrumentation")] + public class TestInstrumentation : TestSuiteInstrumentation { + + public TestInstrumentation (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + + protected override void AddTests () + { + AddTest (Assembly.GetExecutingAssembly ()); + } + } +} + diff --git a/src/Mono.Android/Test/jni/Android.mk b/src/Mono.Android/Test/jni/Android.mk new file mode 100644 index 00000000000..4080fa3aa20 --- /dev/null +++ b/src/Mono.Android/Test/jni/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_LDLIBS := -llog +LOCAL_MODULE := reuse-threads +LOCAL_SRC_FILES := reuse-threads.c + +include $(BUILD_SHARED_LIBRARY) + diff --git a/src/Mono.Android/Test/jni/Application.mk b/src/Mono.Android/Test/jni/Application.mk new file mode 100644 index 00000000000..49ec634fc40 --- /dev/null +++ b/src/Mono.Android/Test/jni/Application.mk @@ -0,0 +1,2 @@ +# Build both ARMv5TE and ARMv7-A machine code. +APP_ABI := arm64-v8a armeabi armeabi-v7a x86 x86_64 diff --git a/src/Mono.Android/Test/jni/reuse-threads.c b/src/Mono.Android/Test/jni/reuse-threads.c new file mode 100644 index 00000000000..bdc1f415694 --- /dev/null +++ b/src/Mono.Android/Test/jni/reuse-threads.c @@ -0,0 +1,202 @@ +/* + * https://bugzilla.xamarin.com/show_bug.cgi?id=13521 + * SIGSEGV while executing native code + * + * This appears to be similar to a (now private) forum thread: + * http://forums.xamarin.com/discussion/3473/sigsegv-on-globaljava-lang-object-getobject + * + * What happens is that when using "certain" Android libraries (in this case, + * the Sygic GPS navigation library: http://www.sygic.com/en ), the process + * will abort with a SIGSEGV. + * + * What happens is: + * 1. A thread 'T' is created. Java, pthread, doens't matter, as on Android + * java.lang.Thread is backed by pthreads anyway. + * 2. Execution of T enters XA, JNIEnv.Handle is set via + * JNIInvokeInterface::AttachCurrentThread(), then the JNIEnv* is cached + * in TLS. + * 3. T is disassociated with Dalvik "as if" via + * JNIInvokeInterface::DetachCurrentThread() and the JNIEnv* parameter T + * is invalidated. + * 4. T calls JNIInvokeInterface::AttachCurrentThread(), and a new & different + * JNIEnv* value is created. + * 5. T re-enters XA, and XA tries to use the (now invalid) JNIEnv.Handle value. + * 6. *BOOM* [0] + * + * The actual Sygic library DOES NOT call DetachCurrentThread(). However, I + * can get a similar crash by using it. + * + * Implementation: + * + * Steps 1 & 2 are easy: rt_invoke_callback_on_new_thread() uses + * pthread_create() to launch _call_cb_from_new_thread() on a new Thread. + * _call_cb_from_new_thread()'s thread is thread 'T'. + * + * Step 3 is harder, as the "trivial" idea of having the + * worker thread do AttachCurrentThread(), DetachCurrentThread(), + * AttachCurrentThread(), results in the second AttachCurrentThread() + * returning the same JNIEnv* value as the first call. + * + * What is presumably happening is that the JNIEnv* pointer is held by a + * Dalvik Thread*, which in turn is referenced by a java.lang.Thread instance. + * We need to clear out all of those in order for AttachCurrentThread() to + * return a new value, and we can do that by provoking a GC. + * + * Thus, Step 3 occurs in two parts: + * + * 3(a): Thread 'T' calls DetachCurrentThread(), then waits on a semaphore + * while the main thread calls java.lang.Runtime.gc(). + * 3(b): The main thread calls Runtime.gc(), then posts to a semaphore so that + * Thread 'T' continues execution. + * + * Once (3) is working, the rest fails as expected. + * + * [0] `adb logcat` output of the crash: + * E/mono-rt ( 6999): Stacktrace: + * E/mono-rt ( 6999): + * E/mono-rt ( 6999): at <0xffffffff> + * E/mono-rt ( 6999): at (wrapper managed-to-native) Android.Runtime.JNIEnv._monodroid_get_identity_hash_code (intptr,intptr) + * E/mono-rt ( 6999): at Android.Runtime.JNIEnv.m__C2 (intptr) + * E/mono-rt ( 6999): at Java.Lang.Object.GetObject (intptr,Android.Runtime.JniHandleOwnership,System.Type) + * E/mono-rt ( 6999): at Java.Lang.Object._GetObject (intptr,Android.Runtime.JniHandleOwnership) + * E/mono-rt ( 6999): at Java.Lang.Object.GetObject (intptr,Android.Runtime.JniHandleOwnership) + * E/mono-rt ( 6999): at Xamarin.Android.RuntimeTests.JnienvTest.m__8 (intptr,intptr) [0x0003b] in /Users/jon/Dropbox/Developer/xamarin/monodroid/tests/runtime/Java.Interop/JnienvTest.cs:40 + * E/mono-rt ( 6999): at (wrapper native-to-managed) Xamarin.Android.RuntimeTests.JnienvTest.m__8 (intptr,intptr) + * E/mono-rt ( 6999): + * E/mono-rt ( 6999): ================================================================= + * E/mono-rt ( 6999): Got a SIGSEGV while executing native code. This usually indicates + * E/mono-rt ( 6999): a fatal error in the mono runtime or one of the native libraries + * E/mono-rt ( 6999): used by your application. + * E/mono-rt ( 6999): ================================================================= + * E/mono-rt ( 6999): + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*CB)(JNIEnv *env, jobject self); + +static JavaVM *gvm; +static sem_t start_gc_on_main; +static sem_t finished_gc_on_main; + +JNIEXPORT jint JNICALL +JNI_OnLoad (JavaVM *vm, void *reserved) +{ + int r; + if ((r = sem_init (&start_gc_on_main, 0, 0)) < 0 || + (r = sem_init (&finished_gc_on_main, 0, 0)) < 0) { + __android_log_print (ANDROID_LOG_FATAL, "XA/RuntimeTest", "Could not allocate semaphore: %i %s", errno, strerror (errno)); + exit (-1); + } + gvm = vm; + return JNI_VERSION_1_6; +} + +static JNIEnv * +_get_env (const char *where) +{ + JNIEnv *env; + int r = (*gvm)->AttachCurrentThread (gvm, &env, NULL); + if (r != JNI_OK) { + __android_log_print (ANDROID_LOG_FATAL, "XA/RuntimeTest", "AttachCurrentThread() failed at %s: %i", where, r); + exit (-1); + } + return env; +} + +static jobject +_create_java_instance (JNIEnv *env) +{ + jclass Object_class = (*env)->FindClass (env, "java/lang/Object"); + jmethodID Object_ctor = (*env)->GetMethodID (env, Object_class, "", "()V"); + + jobject instance = (*env)->NewObject (env, Object_class, Object_ctor); + + (*env)->DeleteLocalRef (env, Object_class); + + return instance; +} + +static void* +_call_cb_from_new_thread (void *cb) +{ + JNIEnv *env, *old_env; + int r; + CB _cb = cb; + + old_env = env = _get_env ("_call_cb_from_new_thread"); + + /* 2: Execution of T enters managed code... */ + _cb (env, NULL); + + /* 3(a): Detach current thread from JVM... */ + r = (*gvm)->DetachCurrentThread (gvm); + assert (r == 0 && !"DetachCurrentThread() failed!"); + env = NULL; + + r = sem_post (&start_gc_on_main); + assert (r == 0 && !"sem_post(start_gc_on_main)"); + r = sem_wait (&finished_gc_on_main); + assert (r == 0 && !"sem_wait(finished_gc_on_main)"); + + /* 4: T calls AttachCurrentThread(), JNIEnv* differs */ + env = _get_env ("_call_cb_from_new_thread: take 2!"); + if (old_env == env) { + __android_log_print (ANDROID_LOG_INFO, "XA/RuntimeTest", "FAILURE: JNIEnv* wasn't changed!"); + } + + /* 5: Execution of T enters managed code... */ + jobject instance = _create_java_instance (env); + _cb (env, instance); + + return NULL; +} + +static void +_gc (JNIEnv* env) +{ + jclass Runtime_class = (*env)->FindClass (env, "java/lang/Runtime"); + jmethodID Runtime_getRuntime = (*env)->GetStaticMethodID (env, Runtime_class, "getRuntime", "()Ljava/lang/Runtime;"); + jmethodID Runtime_gc = (*env)->GetMethodID (env, Runtime_class, "gc", "()V"); + jobject runtime = (*env)->CallStaticObjectMethod (env, Runtime_class, Runtime_getRuntime); + (*env)->CallVoidMethod (env, runtime, Runtime_gc); + (*env)->DeleteLocalRef (env, Runtime_class); + (*env)->DeleteLocalRef (env, runtime); +} + +JNIEXPORT int JNICALL +rt_invoke_callback_on_new_thread (CB cb) +{ + pthread_t t; + int r; + void *tr; + JNIEnv *env = _get_env ("rt_invoke_callback_on_new_thread"); + + /* 1: Craete a thread... */ + r = pthread_create (&t, NULL, _call_cb_from_new_thread, cb); + if (r) { + __android_log_print (ANDROID_LOG_INFO, "XA/RuntimeTest", "InvokeFromNewThread: pthread_create() failed! %i: %s", r, strerror (r)); + return -1; + } + + /* 3(b): Ensure Dalvik gets a chance to cleanup the old JNIEnv* */ + sem_wait (&start_gc_on_main); + _gc (env); + _gc (env); /* for good measure... */ + + /* Allow (4) to execute... */ + sem_post (&finished_gc_on_main); + + pthread_join (t, &tr); + + return 0; +} + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs b/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs index 8f9ed7459ab..6c4ed2e5455 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs @@ -206,6 +206,7 @@ public static void ExtractAll(ZipFile zip, string destination, if (string.Equals(entry.FileName, "__MACOSX", StringComparison.OrdinalIgnoreCase) || string.Equals(entry.FileName, ".DS_Store", StringComparison.OrdinalIgnoreCase)) continue; + Console.Error.WriteLine ("# jonp: Files.ExtractAll: extracting entry: {0} to destination='{1}' extractExitingFileAction={2}", entry.FileName, destination, extractExitingFileAction); entry.Extract (destination, extractExitingFileAction); } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index e5dd40fb953..ece87faa8ab 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -65,6 +65,8 @@ ..\..\packages\FSharp.Compiler.CodeDom.1.0.0.1\lib\net40\FSharp.Compiler.CodeDom.dll + +