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 00000000000..fe81ff9e279 Binary files /dev/null and b/src/Mono.Android/Test/Resources/drawable/AndroidPressed.png differ diff --git a/src/Mono.Android/Test/Resources/drawable/Icon.png b/src/Mono.Android/Test/Resources/drawable/Icon.png new file mode 100644 index 00000000000..a07c69fa5a0 Binary files /dev/null and b/src/Mono.Android/Test/Resources/drawable/Icon.png differ diff --git a/src/Mono.Android/Test/Resources/drawable/android_button.xml b/src/Mono.Android/Test/Resources/drawable/android_button.xml new file mode 100644 index 00000000000..85d525f4787 --- /dev/null +++ b/src/Mono.Android/Test/Resources/drawable/android_button.xml @@ -0,0 +1,8 @@ + + + + + + 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 00000000000..f84d0fe4a5e Binary files /dev/null and b/src/Mono.Android/Test/Resources/drawable/android_focused.png differ diff --git a/src/Mono.Android/Test/Resources/drawable/android_normal.png b/src/Mono.Android/Test/Resources/drawable/android_normal.png new file mode 100644 index 00000000000..94a70842530 Binary files /dev/null and b/src/Mono.Android/Test/Resources/drawable/android_normal.png differ diff --git a/src/Mono.Android/Test/Resources/layout/Main.axml b/src/Mono.Android/Test/Resources/layout/Main.axml new file mode 100644 index 00000000000..25d909baff7 --- /dev/null +++ b/src/Mono.Android/Test/Resources/layout/Main.axml @@ -0,0 +1,17 @@ + + + + + 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 + +