http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs new file mode 100644 index 0000000..9e332fe --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Process +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using Apache.Ignite.Core.Impl; + + /// <summary> + /// Defines forked Ignite node. + /// </summary> + public class IgniteProcess + { + /** Executable file name. */ + private static readonly string ExeName = "Ignite.exe"; + + /** Executable process name. */ + private static readonly string ExeProcName = ExeName.Substring(0, ExeName.IndexOf('.')); + + /** Executable configuration file name. */ + private static readonly string ExeCfgName = ExeName + ".config"; + + /** Executable backup configuration file name. */ + private static readonly string ExeCfgBakName = ExeCfgName + ".bak"; + + /** Directory where binaries are stored. */ + private static readonly string ExeDir; + + /** Full path to executable. */ + private static readonly string ExePath; + + /** Full path to executable configuration file. */ + private static readonly string ExeCfgPath; + + /** Full path to executable configuration file backup. */ + private static readonly string ExeCfgBakPath; + + /** Default process output reader. */ + private static readonly IIgniteProcessOutputReader DfltOutReader = new IgniteProcessConsoleOutputReader(); + + /** Process. */ + private readonly Process _proc; + + /// <summary> + /// Static initializer. + /// </summary> + static IgniteProcess() + { + // 1. Locate executable file and related stuff. + DirectoryInfo dir = new FileInfo(new Uri(typeof(IgniteProcess).Assembly.CodeBase).LocalPath).Directory; + + // ReSharper disable once PossibleNullReferenceException + ExeDir = dir.FullName; + + FileInfo[] exe = dir.GetFiles(ExeName); + + + // TODO: IGNITE-1367 + /* + if (exe.Length == 0) + throw new Exception(ExeName + " is not found in test output directory: " + dir.FullName); + + ExePath = exe[0].FullName; + + FileInfo[] exeCfg = dir.GetFiles(ExeCfgName); + + if (exeCfg.Length == 0) + throw new Exception(ExeCfgName + " is not found in test output directory: " + dir.FullName); + + ExeCfgPath = exeCfg[0].FullName; + + ExeCfgBakPath = Path.Combine(ExeDir, ExeCfgBakName); + + File.Delete(ExeCfgBakPath);*/ + } + + /// <summary> + /// Save current configuration to backup. + /// </summary> + public static void SaveConfigurationBackup() + { + File.Copy(ExeCfgPath, ExeCfgBakPath, true); + } + + /// <summary> + /// Restore configuration from backup. + /// </summary> + public static void RestoreConfigurationBackup() + { + File.Copy(ExeCfgBakPath, ExeCfgPath, true); + } + + /// <summary> + /// Replace application configuration with another one. + /// </summary> + /// <param name="relPath">Path to config relative to executable directory.</param> + public static void ReplaceConfiguration(string relPath) + { + File.Copy(Path.Combine(ExeDir, relPath), ExeCfgPath, true); + } + + /// <summary> + /// Kill all GridGain processes. + /// </summary> + public static void KillAll() + { + foreach (Process proc in Process.GetProcesses()) + { + if (proc.ProcessName.Equals(ExeProcName)) + { + proc.Kill(); + + proc.WaitForExit(); + } + } + } + + /// <summary> + /// Construector. + /// </summary> + /// <param name="args">Arguments</param> + public IgniteProcess(params string[] args) : this(DfltOutReader, args) { } + + /// <summary> + /// Construector. + /// </summary> + /// <param name="outReader">Output reader.</param> + /// <param name="args">Arguments.</param> + public IgniteProcess(IIgniteProcessOutputReader outReader, params string[] args) + { + // Add test dll path + args = args.Concat(new[] {"-assembly=" + GetType().Assembly.Location}).ToArray(); + + _proc = Start(ExePath, IgniteManager.GetIgniteHome(null), outReader, args); + } + + /// <summary> + /// Starts a grid process. + /// </summary> + /// <param name="exePath">Exe path.</param> + /// <param name="ggHome">GridGain home.</param> + /// <param name="outReader">Output reader.</param> + /// <param name="args">Arguments.</param> + /// <returns>Started process.</returns> + public static Process Start(string exePath, string ggHome, IIgniteProcessOutputReader outReader = null, + params string[] args) + { + Debug.Assert(!string.IsNullOrEmpty(exePath)); + Debug.Assert(!string.IsNullOrEmpty(ggHome)); + + // 1. Define process start configuration. + var sb = new StringBuilder(); + + foreach (string arg in args) + sb.Append('\"').Append(arg).Append("\" "); + + var procStart = new ProcessStartInfo + { + FileName = exePath, + Arguments = sb.ToString() + }; + + if (!string.IsNullOrEmpty(ggHome)) + procStart.EnvironmentVariables[IgniteManager.EnvIgniteHome] = ggHome; + + procStart.EnvironmentVariables["GRIDGAIN_NATIVE_TEST_CLASSPATH"] = "true"; + + procStart.CreateNoWindow = true; + procStart.UseShellExecute = false; + + procStart.RedirectStandardOutput = true; + procStart.RedirectStandardError = true; + + var workDir = Path.GetDirectoryName(exePath); + + if (workDir != null) + procStart.WorkingDirectory = workDir; + + Console.WriteLine("About to run Ignite.exe process [exePath=" + exePath + ", arguments=" + sb + ']'); + + // 2. Start. + var proc = Process.Start(procStart); + + Debug.Assert(proc != null); + + // 3. Attach output readers to avoid hangs. + outReader = outReader ?? DfltOutReader; + + Attach(proc, proc.StandardOutput, outReader, false); + Attach(proc, proc.StandardError, outReader, true); + + return proc; + } + + /// <summary> + /// Whether the process is still alive. + /// </summary> + public bool Alive + { + get + { + return !_proc.HasExited; + } + } + + /// <summary> + /// Kill process. + /// </summary> + public void Kill() + { + _proc.Kill(); + } + + /// <summary> + /// Join process. + /// </summary> + /// <returns>Exit code.</returns> + public int Join() + { + _proc.WaitForExit(); + + return _proc.ExitCode; + } + + /// <summary> + /// Join process with timeout. + /// </summary> + /// <param name="timeout">Timeout in milliseconds.</param> + /// <returns><c>True</c> if process exit occurred before timeout.</returns> + public bool Join(int timeout) + { + return _proc.WaitForExit(timeout); + } + + /// <summary> + /// Join process with timeout. + /// </summary> + /// <param name="timeout">Timeout in milliseconds.</param> + /// <param name="exitCode">Exit code.</param> + /// <returns><c>True</c> if process exit occurred before timeout.</returns> + public bool Join(int timeout, out int exitCode) + { + if (_proc.WaitForExit(timeout)) + { + exitCode = _proc.ExitCode; + + return true; + } + exitCode = 0; + + return false; + } + + /// <summary> + /// Attach output reader to the process. + /// </summary> + /// <param name="proc">Process.</param> + /// <param name="reader">Process stream reader.</param> + /// <param name="outReader">Output reader.</param> + /// <param name="err">Whether this is error stream.</param> + private static void Attach(Process proc, StreamReader reader, IIgniteProcessOutputReader outReader, bool err) + { + Thread thread = new Thread(() => + { + while (!proc.HasExited) + outReader.OnOutput(proc, reader.ReadLine(), err); + }) {IsBackground = true}; + + + thread.Start(); + } + } +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs new file mode 100644 index 0000000..00cc040 --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Process +{ + using System; + using System.Diagnostics; + + /// <summary> + /// Output reader pushing data to the console. + /// </summary> + public class IgniteProcessConsoleOutputReader : IIgniteProcessOutputReader + { + /** Out message format. */ + private static readonly string OutFormat = ">>> {0} OUT: {1}"; + + /** Error message format. */ + private static readonly string ErrFormat = ">>> {0} ERR: {1}"; + + /** <inheritDoc /> */ + public void OnOutput(Process proc, string data, bool err) + { + Console.WriteLine(err ? ErrFormat : OutFormat, proc.Id, data); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs new file mode 100644 index 0000000..f80c4eb --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Query +{ + /// <summary> + /// Test person. + /// </summary> + internal class ImplicitPortablePerson + { + /// <summary> + /// Initializes a new instance of the <see cref="ImplicitPortablePerson"/> class. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="age">The age.</param> + public ImplicitPortablePerson(string name, int age) + { + Name = name; + Age = age; + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the age. + /// </summary> + public int Age { get; set; } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs new file mode 100644 index 0000000..16bd07d --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Query +{ + /// <summary> + /// Test person. + /// </summary> + internal class NoDefPortablePerson + { + /// <summary> + /// Gets or sets the name. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the age. + /// </summary> + public int Age { get; set; } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs new file mode 100644 index 0000000..1e11001 --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Query +{ + using Apache.Ignite.Core.Portable; + + /// <summary> + /// Test person. + /// </summary> + internal class PortablePerson : IPortableMarshalAware + { + /// <summary> + /// Initializes a new instance of the <see cref="PortablePerson"/> class. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="age">The age.</param> + public PortablePerson(string name, int age) + { + Name = name; + Age = age; + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the address. + /// </summary> + public string Address { get; set; } + + /// <summary> + /// Gets or sets the age. + /// </summary> + public int Age { get; set; } + + /** <ineritdoc /> */ + public void WritePortable(IPortableWriter writer) + { + writer.WriteString("name", Name); + writer.WriteString("address", Address); + writer.WriteInt("age", Age); + } + + /** <ineritdoc /> */ + public void ReadPortable(IPortableReader reader) + { + Name = reader.ReadString("name"); + Address = reader.ReadString("address"); + Age = reader.ReadInt("age"); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs new file mode 100644 index 0000000..8ed2899 --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Reflection.Emit; + using System.Runtime.Serialization; + using System.Xml; + using Apache.Ignite.Core.Cluster; + using Apache.Ignite.Core.Compute; + using Apache.Ignite.Core.Impl; + using NUnit.Framework; + + /// <summary> + /// Tests for native serialization. + /// </summary> + public class SerializationTest + { + /** Grid name. */ + private const string GridName = "SerializationTest"; + + /// <summary> + /// Set up routine. + /// </summary> + [TestFixtureSetUp] + public void SetUp() + { + var cfg = new IgniteConfigurationEx + { + GridName = GridName, + JvmClasspath = TestUtils.CreateTestClasspath(), + JvmOptions = TestUtils.TestJavaOptions(), + SpringConfigUrl = "config\\native-client-test-cache.xml" + }; + + Ignition.Start(cfg); + } + + /// <summary> + /// Tear down routine. + /// </summary> + [TestFixtureTearDown] + public void TearDown() + { + Ignition.StopAll(true); + } + + /// <summary> + /// Test complex file serialization. + /// </summary> + [Test] + public void TestSerializableXmlDoc() + { + var grid = Ignition.GetIgnite(GridName); + var cache = grid.Cache<int, SerializableXmlDoc>("replicated"); + + var doc = new SerializableXmlDoc(); + + doc.LoadXml("<document><test1>val</test1><test2 attr=\"x\" /></document>"); + + for (var i = 0; i < 50; i++) + { + // Test cache + cache.Put(i, doc); + + var resultDoc = cache.Get(i); + + Assert.AreEqual(doc.OuterXml, resultDoc.OuterXml); + + // Test task with document arg + CheckTask(grid, doc); + } + } + + /// <summary> + /// Checks task execution. + /// </summary> + /// <param name="grid">Grid.</param> + /// <param name="arg">Task arg.</param> + private static void CheckTask(IIgnite grid, object arg) + { + var jobResult = grid.Compute().Execute(new CombineStringsTask(), arg); + + var nodeCount = grid.Cluster.Nodes().Count; + + var expectedRes = + CombineStringsTask.CombineStrings(Enumerable.Range(0, nodeCount).Select(x => arg.ToString())); + + Assert.AreEqual(expectedRes, jobResult.InnerXml); + } + + /// <summary> + /// Tests custom serialization binder. + /// </summary> + [Test] + public void TestSerializationBinder() + { + const int count = 50; + + var cache = Ignition.GetIgnite(GridName).Cache<int, object>("local"); + + // Put multiple objects from muliple same-named assemblies to cache + for (var i = 0; i < count; i++) + { + dynamic val = Activator.CreateInstance(GenerateDynamicType()); + + val.Id = i; + val.Name = "Name_" + i; + + cache.Put(i, val); + } + + // Verify correct deserialization + for (var i = 0; i < count; i++) + { + dynamic val = cache.Get(i); + + Assert.AreEqual(val.Id, i); + Assert.AreEqual(val.Name, "Name_" + i); + } + } + + /// <summary> + /// Generates a Type in runtime, puts it into a dynamic assembly. + /// </summary> + /// <returns></returns> + public static Type GenerateDynamicType() + { + var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( + new AssemblyName("GridSerializationTestDynamicAssembly"), AssemblyBuilderAccess.Run); + + var moduleBuilder = asmBuilder.DefineDynamicModule("GridSerializationTestDynamicModule"); + + var typeBuilder = moduleBuilder.DefineType("GridSerializationTestDynamicType", + TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Serializable); + + typeBuilder.DefineField("Id", typeof (int), FieldAttributes.Public); + + typeBuilder.DefineField("Name", typeof (string), FieldAttributes.Public); + + return typeBuilder.CreateType(); + } + } + + [Serializable] + [DataContract] + public sealed class SerializableXmlDoc : XmlDocument, ISerializable + { + /// <summary> + /// Default ctor. + /// </summary> + public SerializableXmlDoc() + { + // No-op + } + + /// <summary> + /// Serialization ctor. + /// </summary> + private SerializableXmlDoc(SerializationInfo info, StreamingContext context) + { + LoadXml(info.GetString("xmlDocument")); + } + + /** <inheritdoc /> */ + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("xmlDocument", OuterXml, typeof(string)); + } + } + + [Serializable] + public class CombineStringsTask : IComputeTask<object, string, SerializableXmlDoc> + { + public IDictionary<IComputeJob<string>, IClusterNode> Map(IList<IClusterNode> subgrid, object arg) + { + return subgrid.ToDictionary(x => (IComputeJob<string>) new ToStringJob {Arg = arg}, x => x); + } + + public ComputeJobResultPolicy Result(IComputeJobResult<string> res, IList<IComputeJobResult<string>> rcvd) + { + return ComputeJobResultPolicy.Wait; + } + + public SerializableXmlDoc Reduce(IList<IComputeJobResult<string>> results) + { + var result = new SerializableXmlDoc(); + + result.LoadXml(CombineStrings(results.Select(x => x.Data()))); + + return result; + } + + public static string CombineStrings(IEnumerable<string> strings) + { + var text = string.Concat(strings.Select(x => string.Format("<val>{0}</val>", x))); + + return string.Format("<document>{0}</document>", text); + } + } + + [Serializable] + public class ToStringJob : IComputeJob<string> + { + /// <summary> + /// Job argument. + /// </summary> + public object Arg { get; set; } + + /** <inheritdoc /> */ + public string Execute() + { + return Arg.ToString(); + } + + /** <inheritdoc /> */ + public void Cancel() + { + // No-op. + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs new file mode 100644 index 0000000..44e1d71 --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs @@ -0,0 +1,741 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Services +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Reflection; + using Apache.Ignite.Core.Impl.Memory; + using Apache.Ignite.Core.Impl.Portable; + using Apache.Ignite.Core.Impl.Services; + using Apache.Ignite.Core.Portable; + using Apache.Ignite.Core.Services; + using NUnit.Framework; + + /// <summary> + /// Tests <see cref="ServiceProxySerializer"/> functionality. + /// </summary> + public class ServiceProxyTest + { + /** */ + private TestIgniteService _svc; + + /** */ + private readonly PortableMarshaller _marsh = new PortableMarshaller(new PortableConfiguration + { + TypeConfigurations = new[] + { + new PortableTypeConfiguration(typeof (TestPortableClass)), + new PortableTypeConfiguration(typeof (CustomExceptionPortable)) + } + }); + + /** */ + protected readonly IPortables Portables; + + /** */ + private readonly PlatformMemoryManager _memory = new PlatformMemoryManager(1024); + + /** */ + protected bool KeepPortable; + + /** */ + protected bool SrvKeepPortable; + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProxyTest"/> class. + /// </summary> + public ServiceProxyTest() + { + Portables = new PortablesImpl(_marsh); + } + + /// <summary> + /// Tests object class methods proxying. + /// </summary> + [Test] + public void TestObjectClassMethods() + { + var prx = GetProxy(); + + prx.IntProp = 12345; + + Assert.AreEqual("12345", prx.ToString()); + Assert.AreEqual("12345", _svc.ToString()); + Assert.AreEqual(12345, prx.GetHashCode()); + Assert.AreEqual(12345, _svc.GetHashCode()); + } + + /// <summary> + /// Tests properties proxying. + /// </summary> + [Test] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public void TestProperties() + { + var prx = GetProxy(); + + prx.IntProp = 10; + Assert.AreEqual(10, prx.IntProp); + Assert.AreEqual(10, _svc.IntProp); + + _svc.IntProp = 15; + Assert.AreEqual(15, prx.IntProp); + Assert.AreEqual(15, _svc.IntProp); + + prx.ObjProp = "prop1"; + Assert.AreEqual("prop1", prx.ObjProp); + Assert.AreEqual("prop1", _svc.ObjProp); + + prx.ObjProp = null; + Assert.IsNull(prx.ObjProp); + Assert.IsNull(_svc.ObjProp); + + prx.ObjProp = new TestClass {Prop = "prop2"}; + Assert.AreEqual("prop2", ((TestClass)prx.ObjProp).Prop); + Assert.AreEqual("prop2", ((TestClass)_svc.ObjProp).Prop); + } + + /// <summary> + /// Tests void methods proxying. + /// </summary> + [Test] + public void TestVoidMethods() + { + var prx = GetProxy(); + + prx.VoidMethod(); + Assert.AreEqual("VoidMethod", prx.InvokeResult); + Assert.AreEqual("VoidMethod", _svc.InvokeResult); + + prx.VoidMethod(10); + Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult); + + prx.VoidMethod(10, "string"); + Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult); + + prx.VoidMethod(10, "string", "arg"); + Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult); + + prx.VoidMethod(10, "string", "arg", "arg1", 2, 3, "arg4"); + Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult); + } + + /// <summary> + /// Tests object methods proxying. + /// </summary> + [Test] + public void TestObjectMethods() + { + var prx = GetProxy(); + + Assert.AreEqual("ObjectMethod", prx.ObjectMethod()); + Assert.AreEqual("ObjectMethod987", prx.ObjectMethod(987)); + Assert.AreEqual("ObjectMethod987str123", prx.ObjectMethod(987, "str123")); + Assert.AreEqual("ObjectMethod987str123TestClass", prx.ObjectMethod(987, "str123", new TestClass())); + Assert.AreEqual("ObjectMethod987str123TestClass34arg5arg6", + prx.ObjectMethod(987, "str123", new TestClass(), 3, 4, "arg5", "arg6")); + } + + /// <summary> + /// Tests methods that exist in proxy interface, but do not exist in the actual service. + /// </summary> + [Test] + public void TestMissingMethods() + { + var prx = GetProxy(); + + var ex = Assert.Throws<InvalidOperationException>(() => prx.MissingMethod()); + + Assert.AreEqual("Failed to invoke proxy: there is no method 'MissingMethod'" + + " in type 'Apache.Ignite.Core.Tests.Services.ServiceProxyTest+TestIgniteService'", ex.Message); + } + + /// <summary> + /// Tests ambiguous methods handling (multiple methods with the same signature). + /// </summary> + [Test] + public void TestAmbiguousMethods() + { + var prx = GetProxy(); + + var ex = Assert.Throws<InvalidOperationException>(() => prx.AmbiguousMethod(1)); + + Assert.AreEqual("Failed to invoke proxy: there are 2 methods 'AmbiguousMethod' in type " + + "'Apache.Ignite.Core.Tests.Services.ServiceProxyTest+TestIgniteService' with (Int32) arguments, " + + "can't resolve ambiguity.", ex.Message); + } + + [Test] + public void TestException() + { + var prx = GetProxy(); + + var err = Assert.Throws<ServiceInvocationException>(prx.ExceptionMethod); + Assert.AreEqual("Expected exception", err.InnerException.Message); + + var ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionMethod()); + Assert.IsTrue(ex.ToString().Contains("+CustomException")); + } + + [Test] + public void TestPortableMarshallingException() + { + var prx = GetProxy(); + + var ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(false, false)); + + if (KeepPortable) + { + Assert.AreEqual("Proxy method invocation failed with a portable error. " + + "Examine PortableCause for details.", ex.Message); + + Assert.IsNotNull(ex.PortableCause); + Assert.IsNull(ex.InnerException); + } + else + { + Assert.AreEqual("Proxy method invocation failed with an exception. " + + "Examine InnerException for details.", ex.Message); + + Assert.IsNull(ex.PortableCause); + Assert.IsNotNull(ex.InnerException); + } + + ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(true, false)); + Assert.IsTrue(ex.ToString().Contains( + "Call completed with error, but error serialization failed [errType=CustomExceptionPortable, " + + "serializationErrMsg=Expected exception in CustomExceptionPortable.WritePortable]")); + + ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(true, true)); + Assert.IsTrue(ex.ToString().Contains( + "Call completed with error, but error serialization failed [errType=CustomExceptionPortable, " + + "serializationErrMsg=Expected exception in CustomExceptionPortable.WritePortable]")); + } + + /// <summary> + /// Creates the proxy. + /// </summary> + protected ITestIgniteServiceProxyInterface GetProxy() + { + return GetProxy<ITestIgniteServiceProxyInterface>(); + } + + /// <summary> + /// Creates the proxy. + /// </summary> + protected T GetProxy<T>() + { + _svc = new TestIgniteService(Portables); + + var prx = new ServiceProxy<T>(InvokeProxyMethod).GetTransparentProxy(); + + Assert.IsFalse(ReferenceEquals(_svc, prx)); + + return prx; + } + + /// <summary> + /// Invokes the proxy. + /// </summary> + /// <param name="method">Method.</param> + /// <param name="args">Arguments.</param> + /// <returns> + /// Invocation result. + /// </returns> + private object InvokeProxyMethod(MethodBase method, object[] args) + { + using (var inStream = new PlatformMemoryStream(_memory.Allocate())) + using (var outStream = new PlatformMemoryStream(_memory.Allocate())) + { + // 1) Write to a stream + inStream.WriteBool(SrvKeepPortable); // WriteProxyMethod does not do this, but Java does + + ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method, args); + + inStream.SynchronizeOutput(); + + inStream.Seek(0, SeekOrigin.Begin); + + // 2) call InvokeServiceMethod + string mthdName; + object[] mthdArgs; + + ServiceProxySerializer.ReadProxyMethod(inStream, _marsh, out mthdName, out mthdArgs); + + var result = ServiceProxyInvoker.InvokeServiceMethod(_svc, mthdName, mthdArgs); + + ServiceProxySerializer.WriteInvocationResult(outStream, _marsh, result.Key, result.Value); + + _marsh.StartMarshal(outStream).WriteString("unused"); // fake Java exception details + + outStream.SynchronizeOutput(); + + outStream.Seek(0, SeekOrigin.Begin); + + return ServiceProxySerializer.ReadInvocationResult(outStream, _marsh, KeepPortable); + } + } + + /// <summary> + /// Test service interface. + /// </summary> + protected interface ITestIgniteServiceProperties + { + /** */ + int IntProp { get; set; } + + /** */ + object ObjProp { get; set; } + + /** */ + string InvokeResult { get; } + } + + /// <summary> + /// Test service interface to check ambiguity handling. + /// </summary> + protected interface ITestIgniteServiceAmbiguity + { + /** */ + int AmbiguousMethod(int arg); + } + + /// <summary> + /// Test service interface. + /// </summary> + protected interface ITestIgniteService : ITestIgniteServiceProperties + { + /** */ + void VoidMethod(); + + /** */ + void VoidMethod(int arg); + + /** */ + void VoidMethod(int arg, string arg1, object arg2 = null); + + /** */ + void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args); + + /** */ + object ObjectMethod(); + + /** */ + object ObjectMethod(int arg); + + /** */ + object ObjectMethod(int arg, string arg1, object arg2 = null); + + /** */ + object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args); + + /** */ + void ExceptionMethod(); + + /** */ + void CustomExceptionMethod(); + + /** */ + void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead); + + /** */ + TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2); + + /** */ + IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2); + + /** */ + IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2); + + /** */ + int AmbiguousMethod(int arg); + } + + /// <summary> + /// Test service interface. Does not derive from actual interface, but has all the same method signatures. + /// </summary> + protected interface ITestIgniteServiceProxyInterface + { + /** */ + int IntProp { get; set; } + + /** */ + object ObjProp { get; set; } + + /** */ + string InvokeResult { get; } + + /** */ + void VoidMethod(); + + /** */ + void VoidMethod(int arg); + + /** */ + void VoidMethod(int arg, string arg1, object arg2 = null); + + /** */ + void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args); + + /** */ + object ObjectMethod(); + + /** */ + object ObjectMethod(int arg); + + /** */ + object ObjectMethod(int arg, string arg1, object arg2 = null); + + /** */ + object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args); + + /** */ + void ExceptionMethod(); + + /** */ + void CustomExceptionMethod(); + + /** */ + void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead); + + /** */ + TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2); + + /** */ + IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2); + + /** */ + IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2); + + /** */ + void MissingMethod(); + + /** */ + int AmbiguousMethod(int arg); + } + + /// <summary> + /// Test service. + /// </summary> + [Serializable] + private class TestIgniteService : ITestIgniteService, ITestIgniteServiceAmbiguity + { + /** */ + private readonly IPortables _portables; + + /// <summary> + /// Initializes a new instance of the <see cref="TestIgniteService"/> class. + /// </summary> + /// <param name="portables">The portables.</param> + public TestIgniteService(IPortables portables) + { + _portables = portables; + } + + /** <inheritdoc /> */ + public int IntProp { get; set; } + + /** <inheritdoc /> */ + public object ObjProp { get; set; } + + /** <inheritdoc /> */ + public string InvokeResult { get; private set; } + + /** <inheritdoc /> */ + public void VoidMethod() + { + InvokeResult = "VoidMethod"; + } + + /** <inheritdoc /> */ + public void VoidMethod(int arg) + { + InvokeResult = "VoidMethod" + arg; + } + + /** <inheritdoc /> */ + public void VoidMethod(int arg, string arg1, object arg2 = null) + { + InvokeResult = "VoidMethod" + arg + arg1 + arg2; + } + + /** <inheritdoc /> */ + public void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args) + { + InvokeResult = "VoidMethod" + arg + arg1 + arg2 + string.Concat(args.Select(x => x.ToString())); + } + + /** <inheritdoc /> */ + public object ObjectMethod() + { + return "ObjectMethod"; + } + + /** <inheritdoc /> */ + public object ObjectMethod(int arg) + { + return "ObjectMethod" + arg; + } + + /** <inheritdoc /> */ + public object ObjectMethod(int arg, string arg1, object arg2 = null) + { + return "ObjectMethod" + arg + arg1 + arg2; + } + + /** <inheritdoc /> */ + public object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args) + { + return "ObjectMethod" + arg + arg1 + arg2 + string.Concat(args.Select(x => x.ToString())); + } + + /** <inheritdoc /> */ + public void ExceptionMethod() + { + throw new ArithmeticException("Expected exception"); + } + + /** <inheritdoc /> */ + public void CustomExceptionMethod() + { + throw new CustomException(); + } + + /** <inheritdoc /> */ + public void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead) + { + throw new CustomExceptionPortable {ThrowOnRead = throwOnRead, ThrowOnWrite = throwOnWrite}; + } + + /** <inheritdoc /> */ + public TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2) + { + return arg2.Deserialize<TestPortableClass>(); + } + + /** <inheritdoc /> */ + public IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2) + { + return _portables.ToPortable<IPortableObject>(arg2); + } + + /** <inheritdoc /> */ + public IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2) + { + return _portables.ToPortable<IPortableObject>(arg2.Deserialize<TestPortableClass>()); + } + + /** <inheritdoc /> */ + public override string ToString() + { + return IntProp.ToString(); + } + + /** <inheritdoc /> */ + public override int GetHashCode() + { + return IntProp.GetHashCode(); + } + + /** <inheritdoc /> */ + int ITestIgniteService.AmbiguousMethod(int arg) + { + return arg; + } + + /** <inheritdoc /> */ + int ITestIgniteServiceAmbiguity.AmbiguousMethod(int arg) + { + return -arg; + } + } + + /// <summary> + /// Test serializable class. + /// </summary> + [Serializable] + private class TestClass + { + /** */ + public string Prop { get; set; } + + /** <inheritdoc /> */ + public override string ToString() + { + return "TestClass" + Prop; + } + } + + /// <summary> + /// Custom non-serializable exception. + /// </summary> + private class CustomException : Exception + { + + } + + /// <summary> + /// Custom non-serializable exception. + /// </summary> + private class CustomExceptionPortable : Exception, IPortableMarshalAware + { + /** */ + public bool ThrowOnWrite { get; set; } + + /** */ + public bool ThrowOnRead { get; set; } + + /** <inheritdoc /> */ + public void WritePortable(IPortableWriter writer) + { + writer.WriteBoolean("ThrowOnRead", ThrowOnRead); + + if (ThrowOnWrite) + throw new Exception("Expected exception in CustomExceptionPortable.WritePortable"); + } + + /** <inheritdoc /> */ + public void ReadPortable(IPortableReader reader) + { + ThrowOnRead = reader.ReadBoolean("ThrowOnRead"); + + if (ThrowOnRead) + throw new Exception("Expected exception in CustomExceptionPortable.ReadPortable"); + } + } + + /// <summary> + /// Portable object for method argument/result. + /// </summary> + protected class TestPortableClass : IPortableMarshalAware + { + /** */ + public string Prop { get; set; } + + /** */ + public bool ThrowOnWrite { get; set; } + + /** */ + public bool ThrowOnRead { get; set; } + + /** <inheritdoc /> */ + public void WritePortable(IPortableWriter writer) + { + writer.WriteString("Prop", Prop); + writer.WriteBoolean("ThrowOnRead", ThrowOnRead); + + if (ThrowOnWrite) + throw new Exception("Expected exception in TestPortableClass.WritePortable"); + } + + /** <inheritdoc /> */ + public void ReadPortable(IPortableReader reader) + { + Prop = reader.ReadString("Prop"); + ThrowOnRead = reader.ReadBoolean("ThrowOnRead"); + + if (ThrowOnRead) + throw new Exception("Expected exception in TestPortableClass.ReadPortable"); + } + } + } + + /// <summary> + /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on client. + /// </summary> + public class ServiceProxyTestKeepPortableClient : ServiceProxyTest + { + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableClient"/> class. + /// </summary> + public ServiceProxyTestKeepPortableClient() + { + KeepPortable = true; + } + + [Test] + public void TestPortableMethods() + { + var prx = GetProxy(); + + var obj = new TestPortableClass { Prop = "PropValue" }; + + var result = prx.PortableResultMethod(1, obj); + + Assert.AreEqual(obj.Prop, result.Deserialize<TestPortableClass>().Prop); + } + } + + /// <summary> + /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on server. + /// </summary> + public class ServiceProxyTestKeepPortableServer : ServiceProxyTest + { + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableServer"/> class. + /// </summary> + public ServiceProxyTestKeepPortableServer() + { + SrvKeepPortable = true; + } + + [Test] + public void TestPortableMethods() + { + var prx = GetProxy(); + + var obj = new TestPortableClass { Prop = "PropValue" }; + var portObj = Portables.ToPortable<IPortableObject>(obj); + + var result = prx.PortableArgMethod(1, portObj); + + Assert.AreEqual(obj.Prop, result.Prop); + } + } + + /// <summary> + /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on client and on server. + /// </summary> + public class ServiceProxyTestKeepPortableClientServer : ServiceProxyTest + { + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableClientServer"/> class. + /// </summary> + public ServiceProxyTestKeepPortableClientServer() + { + KeepPortable = true; + SrvKeepPortable = true; + } + + [Test] + public void TestPortableMethods() + { + var prx = GetProxy(); + + var obj = new TestPortableClass { Prop = "PropValue" }; + var portObj = Portables.ToPortable<IPortableObject>(obj); + + var result = prx.PortableArgAndResultMethod(1, portObj); + + Assert.AreEqual(obj.Prop, result.Deserialize<TestPortableClass>().Prop); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs new file mode 100644 index 0000000..ba45dbd --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Services +{ + using System.Collections.Generic; + using System.Diagnostics; + using Apache.Ignite.Core.Cluster; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Services; + + /// <summary> + /// Services async wrapper to simplify testing. + /// </summary> + public class ServicesAsyncWrapper : IServices + { + /** Wrapped async services. */ + private readonly IServices _services; + + /// <summary> + /// Initializes a new instance of the <see cref="ServicesAsyncWrapper"/> class. + /// </summary> + /// <param name="services">Services to wrap.</param> + public ServicesAsyncWrapper(IServices services) + { + _services = services.WithAsync(); + } + + /** <inheritDoc /> */ + public IServices WithAsync() + { + return this; + } + + /** <inheritDoc /> */ + public bool IsAsync + { + get { return true; } + } + + /** <inheritDoc /> */ + public IFuture GetFuture() + { + Debug.Fail("ServicesAsyncWrapper.Future() should not be called. It always returns null."); + return null; + } + + /** <inheritDoc /> */ + public IFuture<TResult> GetFuture<TResult>() + { + Debug.Fail("ServicesAsyncWrapper.Future() should not be called. It always returns null."); + return null; + } + + /** <inheritDoc /> */ + public IClusterGroup ClusterGroup + { + get { return _services.ClusterGroup; } + } + + /** <inheritDoc /> */ + public void DeployClusterSingleton(string name, IService service) + { + _services.DeployClusterSingleton(name, service); + WaitResult(); + } + + /** <inheritDoc /> */ + public void DeployNodeSingleton(string name, IService service) + { + _services.DeployNodeSingleton(name, service); + WaitResult(); + } + + /** <inheritDoc /> */ + public void DeployKeyAffinitySingleton<TK>(string name, IService service, string cacheName, TK affinityKey) + { + _services.DeployKeyAffinitySingleton(name, service, cacheName, affinityKey); + WaitResult(); + } + + /** <inheritDoc /> */ + public void DeployMultiple(string name, IService service, int totalCount, int maxPerNodeCount) + { + _services.DeployMultiple(name, service, totalCount, maxPerNodeCount); + WaitResult(); + } + + /** <inheritDoc /> */ + public void Deploy(ServiceConfiguration configuration) + { + _services.Deploy(configuration); + WaitResult(); + } + + /** <inheritDoc /> */ + public void Cancel(string name) + { + _services.Cancel(name); + WaitResult(); + } + + /** <inheritDoc /> */ + public void CancelAll() + { + _services.CancelAll(); + WaitResult(); + } + + /** <inheritDoc /> */ + public ICollection<IServiceDescriptor> GetServiceDescriptors() + { + return _services.GetServiceDescriptors(); + } + + /** <inheritDoc /> */ + public T GetService<T>(string name) + { + return _services.GetService<T>(name); + } + + /** <inheritDoc /> */ + public ICollection<T> GetServices<T>(string name) + { + return _services.GetServices<T>(name); + } + + /** <inheritDoc /> */ + public T GetServiceProxy<T>(string name) where T : class + { + return _services.GetServiceProxy<T>(name); + } + + /** <inheritDoc /> */ + public T GetServiceProxy<T>(string name, bool sticky) where T : class + { + return _services.GetServiceProxy<T>(name, sticky); + } + + /** <inheritDoc /> */ + public IServices WithKeepPortable() + { + return new ServicesAsyncWrapper(_services.WithKeepPortable()); + } + + /** <inheritDoc /> */ + public IServices WithServerKeepPortable() + { + return new ServicesAsyncWrapper(_services.WithServerKeepPortable()); + } + + /// <summary> + /// Waits for the async result. + /// </summary> + private void WaitResult() + { + _services.GetFuture().Get(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs new file mode 100644 index 0000000..7f5aa44 --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -0,0 +1,823 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using Apache.Ignite.Core.Cluster; + using Apache.Ignite.Core.Common; + using Apache.Ignite.Core.Portable; + using Apache.Ignite.Core.Resource; + using Apache.Ignite.Core.Services; + using NUnit.Framework; + + /// <summary> + /// Services tests. + /// </summary> + public class ServicesTest + { + /** */ + private const string SvcName = "Service1"; + + /** */ + private const string CacheName = "cache1"; + + /** */ + private const int AffKey = 25; + + /** */ + protected IIgnite Grid1; + + /** */ + protected IIgnite Grid2; + + /** */ + protected IIgnite Grid3; + + /** */ + protected IIgnite[] Grids; + + [TestFixtureTearDown] + public void FixtureTearDown() + { + StopGrids(); + } + + /// <summary> + /// Executes before each test. + /// </summary> + [SetUp] + public void SetUp() + { + StartGrids(); + EventsTestHelper.ListenResult = true; + } + + /// <summary> + /// Executes after each test. + /// </summary> + [TearDown] + public void TearDown() + { + try + { + Services.Cancel(SvcName); + + TestUtils.AssertHandleRegistryIsEmpty(1000, Grid1, Grid2, Grid3); + } + catch (Exception) + { + // Restart grids to cleanup + StopGrids(); + + throw; + } + finally + { + EventsTestHelper.AssertFailures(); + + if (TestContext.CurrentContext.Test.Name.StartsWith("TestEventTypes")) + StopGrids(); // clean events for other tests + } + } + + /// <summary> + /// Tests deployment. + /// </summary> + [Test] + public void TestDeploy([Values(true, false)] bool portable) + { + var cfg = new ServiceConfiguration + { + Name = SvcName, + MaxPerNodeCount = 3, + TotalCount = 3, + NodeFilter = new NodeFilter {NodeId = Grid1.Cluster.LocalNode.Id}, + Service = portable ? new TestIgniteServicePortable() : new TestIgniteServiceSerializable() + }; + + Services.Deploy(cfg); + + CheckServiceStarted(Grid1, 3); + } + + /// <summary> + /// Tests cluster singleton deployment. + /// </summary> + [Test] + public void TestDeployClusterSingleton() + { + var svc = new TestIgniteServiceSerializable(); + + Services.DeployClusterSingleton(SvcName, svc); + + var svc0 = Services.GetServiceProxy<ITestIgniteService>(SvcName); + + // Check that only one node has the service. + foreach (var grid in Grids) + { + if (grid.Cluster.LocalNode.Id == svc0.NodeId) + CheckServiceStarted(grid); + else + Assert.IsNull(grid.Services().GetService<TestIgniteServiceSerializable>(SvcName)); + } + } + + /// <summary> + /// Tests node singleton deployment. + /// </summary> + [Test] + public void TestDeployNodeSingleton() + { + var svc = new TestIgniteServiceSerializable(); + + Services.DeployNodeSingleton(SvcName, svc); + + Assert.AreEqual(1, Grid1.Services().GetServices<ITestIgniteService>(SvcName).Count); + Assert.AreEqual(1, Grid2.Services().GetServices<ITestIgniteService>(SvcName).Count); + Assert.AreEqual(1, Grid3.Services().GetServices<ITestIgniteService>(SvcName).Count); + } + + /// <summary> + /// Tests key affinity singleton deployment. + /// </summary> + [Test] + public void TestDeployKeyAffinitySingleton() + { + var svc = new TestIgniteServicePortable(); + + Services.DeployKeyAffinitySingleton(SvcName, svc, CacheName, AffKey); + + var affNode = Grid1.Affinity(CacheName).MapKeyToNode(AffKey); + + var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName); + + Assert.AreEqual(affNode.Id, prx.NodeId); + } + + /// <summary> + /// Tests key affinity singleton deployment. + /// </summary> + [Test] + public void TestDeployKeyAffinitySingletonPortable() + { + var services = Services.WithKeepPortable(); + + var svc = new TestIgniteServicePortable(); + + var affKey = new PortableObject {Val = AffKey}; + + services.DeployKeyAffinitySingleton(SvcName, svc, CacheName, affKey); + + var prx = services.GetServiceProxy<ITestIgniteService>(SvcName); + + Assert.IsTrue(prx.Initialized); + } + + /// <summary> + /// Tests multiple deployment. + /// </summary> + [Test] + public void TestDeployMultiple() + { + var svc = new TestIgniteServiceSerializable(); + + Services.DeployMultiple(SvcName, svc, Grids.Length * 5, 5); + + foreach (var grid in Grids) + CheckServiceStarted(grid, 5); + } + + /// <summary> + /// Tests cancellation. + /// </summary> + [Test] + public void TestCancel() + { + for (var i = 0; i < 10; i++) + { + Services.DeployNodeSingleton(SvcName + i, new TestIgniteServicePortable()); + Assert.IsNotNull(Services.GetService<ITestIgniteService>(SvcName + i)); + } + + Services.Cancel(SvcName + 0); + Services.Cancel(SvcName + 1); + + Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + 0)); + Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + 1)); + + for (var i = 2; i < 10; i++) + Assert.IsNotNull(Services.GetService<ITestIgniteService>(SvcName + i)); + + Services.CancelAll(); + + for (var i = 0; i < 10; i++) + Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + i)); + } + + /// <summary> + /// Tests service proxy. + /// </summary> + [Test] + public void TestGetServiceProxy([Values(true, false)] bool portable) + { + // Test proxy without a service + var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName); + + Assert.IsTrue(prx != null); + + var ex = Assert.Throws<ServiceInvocationException>(() => Assert.IsTrue(prx.Initialized)).InnerException; + Assert.AreEqual("Failed to find deployed service: " + SvcName, ex.Message); + + // Deploy to grid2 & grid3 + var svc = portable + ? new TestIgniteServicePortable {TestProperty = 17} + : new TestIgniteServiceSerializable {TestProperty = 17}; + + Grid1.Cluster.ForNodeIds(Grid2.Cluster.LocalNode.Id, Grid3.Cluster.LocalNode.Id).Services() + .DeployNodeSingleton(SvcName, + svc); + + // Make sure there is no local instance on grid1 + Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName)); + + // Get proxy + prx = Services.GetServiceProxy<ITestIgniteService>(SvcName); + + // Check proxy properties + Assert.IsNotNull(prx); + Assert.AreEqual(prx.GetType(), svc.GetType()); + Assert.AreEqual(prx.ToString(), svc.ToString()); + Assert.AreEqual(17, prx.TestProperty); + Assert.IsTrue(prx.Initialized); + Assert.IsTrue(prx.Executed); + Assert.IsFalse(prx.Cancelled); + Assert.AreEqual(SvcName, prx.LastCallContextName); + + // Check err method + Assert.Throws<ServiceInvocationException>(() => prx.ErrMethod(123)); + + // Check local scenario (proxy should not be created for local instance) + Assert.IsTrue(ReferenceEquals(Grid2.Services().GetService<ITestIgniteService>(SvcName), + Grid2.Services().GetServiceProxy<ITestIgniteService>(SvcName))); + + // Check sticky = false: call multiple times, check that different nodes get invoked + var invokedIds = Enumerable.Range(1, 100).Select(x => prx.NodeId).Distinct().ToList(); + Assert.AreEqual(2, invokedIds.Count); + + // Check sticky = true: all calls should be to the same node + prx = Services.GetServiceProxy<ITestIgniteService>(SvcName, true); + invokedIds = Enumerable.Range(1, 100).Select(x => prx.NodeId).Distinct().ToList(); + Assert.AreEqual(1, invokedIds.Count); + + // Proxy does not work for cancelled service. + Services.CancelAll(); + + Assert.Throws<ServiceInvocationException>(() => { Assert.IsTrue(prx.Cancelled); }); + } + + /// <summary> + /// Tests the duck typing: proxy interface can be different from actual service interface, + /// only called method signature should be compatible. + /// </summary> + [Test] + public void TestDuckTyping([Values(true, false)] bool local) + { + var svc = new TestIgniteServicePortable {TestProperty = 33}; + + // Deploy locally or to the remote node + var nodeId = (local ? Grid1 : Grid2).Cluster.LocalNode.Id; + + var cluster = Grid1.Cluster.ForNodeIds(nodeId); + + cluster.Services().DeployNodeSingleton(SvcName, svc); + + // Get proxy + var prx = Services.GetServiceProxy<ITestIgniteServiceProxyInterface>(SvcName); + + // NodeId signature is the same as in service + Assert.AreEqual(nodeId, prx.NodeId); + + // Method signature is different from service signature (object -> object), but is compatible. + Assert.AreEqual(15, prx.Method(15)); + + // TestProperty is object in proxy and int in service, getter works.. + Assert.AreEqual(33, prx.TestProperty); + + // .. but setter does not + var ex = Assert.Throws<ServiceInvocationException>(() => { prx.TestProperty = new object(); }); + Assert.AreEqual("Object of type 'System.Object' cannot be converted to type 'System.Int32'.", + ex.InnerException.Message); + } + + /// <summary> + /// Tests service descriptors. + /// </summary> + [Test] + public void TestServiceDescriptors() + { + Services.DeployKeyAffinitySingleton(SvcName, new TestIgniteServiceSerializable(), CacheName, 1); + + var descriptors = Services.GetServiceDescriptors(); + + Assert.AreEqual(1, descriptors.Count); + + var desc = descriptors.Single(); + + Assert.AreEqual(SvcName, desc.Name); + Assert.AreEqual(CacheName, desc.CacheName); + Assert.AreEqual(1, desc.AffinityKey); + Assert.AreEqual(1, desc.MaxPerNodeCount); + Assert.AreEqual(1, desc.TotalCount); + Assert.AreEqual(typeof(TestIgniteServiceSerializable), desc.Type); + Assert.AreEqual(Grid1.Cluster.LocalNode.Id, desc.OriginNodeId); + + var top = desc.TopologySnapshot; + var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName); + + Assert.AreEqual(1, top.Count); + Assert.AreEqual(prx.NodeId, top.Keys.Single()); + Assert.AreEqual(1, top.Values.Single()); + } + + /// <summary> + /// Tests the client portable flag. + /// </summary> + [Test] + public void TestWithKeepPortableClient() + { + var svc = new TestIgniteServicePortable(); + + // Deploy to grid2 + Grid1.Cluster.ForNodeIds(Grid2.Cluster.LocalNode.Id).Services().WithKeepPortable() + .DeployNodeSingleton(SvcName, svc); + + // Get proxy + var prx = Services.WithKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName); + + var obj = new PortableObject {Val = 11}; + + var res = (IPortableObject) prx.Method(obj); + Assert.AreEqual(11, res.Deserialize<PortableObject>().Val); + + res = (IPortableObject) prx.Method(Grid1.Portables().ToPortable<IPortableObject>(obj)); + Assert.AreEqual(11, res.Deserialize<PortableObject>().Val); + } + + /// <summary> + /// Tests the server portable flag. + /// </summary> + [Test] + public void TestWithKeepPortableServer() + { + var svc = new TestIgniteServicePortable(); + + // Deploy to grid2 + Grid1.Cluster.ForNodeIds(Grid2.Cluster.LocalNode.Id).Services().WithServerKeepPortable() + .DeployNodeSingleton(SvcName, svc); + + // Get proxy + var prx = Services.WithServerKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName); + + var obj = new PortableObject { Val = 11 }; + + var res = (PortableObject) prx.Method(obj); + Assert.AreEqual(11, res.Val); + + res = (PortableObject)prx.Method(Grid1.Portables().ToPortable<IPortableObject>(obj)); + Assert.AreEqual(11, res.Val); + } + + /// <summary> + /// Tests server and client portable flag. + /// </summary> + [Test] + public void TestWithKeepPortableBoth() + { + var svc = new TestIgniteServicePortable(); + + // Deploy to grid2 + Grid1.Cluster.ForNodeIds(Grid2.Cluster.LocalNode.Id).Services().WithKeepPortable().WithServerKeepPortable() + .DeployNodeSingleton(SvcName, svc); + + // Get proxy + var prx = Services.WithKeepPortable().WithServerKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName); + + var obj = new PortableObject { Val = 11 }; + + var res = (IPortableObject)prx.Method(obj); + Assert.AreEqual(11, res.Deserialize<PortableObject>().Val); + + res = (IPortableObject)prx.Method(Grid1.Portables().ToPortable<IPortableObject>(obj)); + Assert.AreEqual(11, res.Deserialize<PortableObject>().Val); + } + + /// <summary> + /// Tests exception in Initialize. + /// </summary> + [Test] + public void TestInitException() + { + var svc = new TestIgniteServiceSerializable { ThrowInit = true }; + + var ex = Assert.Throws<IgniteException>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1)); + Assert.AreEqual("Expected exception", ex.Message); + + var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName); + + Assert.IsNull(svc0); + } + + /// <summary> + /// Tests exception in Execute. + /// </summary> + [Test] + public void TestExecuteException() + { + var svc = new TestIgniteServiceSerializable { ThrowExecute = true }; + + Services.DeployMultiple(SvcName, svc, Grids.Length, 1); + + var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName); + + // Execution failed, but service exists. + Assert.IsNotNull(svc0); + Assert.IsFalse(svc0.Executed); + } + + /// <summary> + /// Tests exception in Cancel. + /// </summary> + [Test] + public void TestCancelException() + { + var svc = new TestIgniteServiceSerializable { ThrowCancel = true }; + + Services.DeployMultiple(SvcName, svc, Grids.Length, 1); + + CheckServiceStarted(Grid1); + + Services.CancelAll(); + + // Cancellation failed, but service is removed. + foreach (var grid in Grids) + Assert.IsNull(grid.Services().GetService<ITestIgniteService>(SvcName)); + } + + [Test] + public void TestMarshalExceptionOnRead() + { + var svc = new TestIgniteServicePortableErr(); + + var ex = Assert.Throws<IgniteException>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1)); + Assert.AreEqual("Expected exception", ex.Message); + + var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName); + + Assert.IsNull(svc0); + } + + [Test] + public void TestMarshalExceptionOnWrite() + { + var svc = new TestIgniteServicePortableErr {ThrowOnWrite = true}; + + var ex = Assert.Throws<Exception>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1)); + Assert.AreEqual("Expected exception", ex.Message); + + var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName); + + Assert.IsNull(svc0); + } + + /// <summary> + /// Starts the grids. + /// </summary> + private void StartGrids() + { + if (Grid1 != null) + return; + + Grid1 = Ignition.Start(Configuration("config\\compute\\compute-grid1.xml")); + Grid2 = Ignition.Start(Configuration("config\\compute\\compute-grid2.xml")); + Grid3 = Ignition.Start(Configuration("config\\compute\\compute-grid3.xml")); + + Grids = new[] { Grid1, Grid2, Grid3 }; + } + + /// <summary> + /// Stops the grids. + /// </summary> + private void StopGrids() + { + Grid1 = Grid2 = Grid3 = null; + Grids = null; + + Ignition.StopAll(true); + } + + /// <summary> + /// Checks that service has started on specified grid. + /// </summary> + private static void CheckServiceStarted(IIgnite grid, int count = 1) + { + var services = grid.Services().GetServices<TestIgniteServiceSerializable>(SvcName); + + Assert.AreEqual(count, services.Count); + + var svc = services.First(); + + Assert.IsNotNull(svc); + + Assert.IsTrue(svc.Initialized); + + Thread.Sleep(100); // Service runs in a separate thread, wait for it to execute. + + Assert.IsTrue(svc.Executed); + Assert.IsFalse(svc.Cancelled); + + Assert.AreEqual(grid.Cluster.LocalNode.Id, svc.NodeId); + } + + /// <summary> + /// Gets the Ignite configuration. + /// </summary> + private static IgniteConfiguration Configuration(string springConfigUrl) + { + return new IgniteConfiguration + { + SpringConfigUrl = springConfigUrl, + JvmClasspath = TestUtils.CreateTestClasspath(), + JvmOptions = TestUtils.TestJavaOptions(), + PortableConfiguration = new PortableConfiguration + { + TypeConfigurations = new List<PortableTypeConfiguration> + { + new PortableTypeConfiguration(typeof(TestIgniteServicePortable)), + new PortableTypeConfiguration(typeof(TestIgniteServicePortableErr)), + new PortableTypeConfiguration(typeof(PortableObject)) + } + } + }; + } + + /// <summary> + /// Gets the services. + /// </summary> + protected virtual IServices Services + { + get { return Grid1.Services(); } + } + + /// <summary> + /// Test service interface for proxying. + /// </summary> + private interface ITestIgniteService + { + int TestProperty { get; set; } + + /** */ + bool Initialized { get; } + + /** */ + bool Cancelled { get; } + + /** */ + bool Executed { get; } + + /** */ + Guid NodeId { get; } + + /** */ + string LastCallContextName { get; } + + /** */ + object Method(object arg); + + /** */ + object ErrMethod(object arg); + } + + /// <summary> + /// Test service interface for proxy usage. + /// Has some of the original interface members with different signatures. + /// </summary> + private interface ITestIgniteServiceProxyInterface + { + /** */ + Guid NodeId { get; } + + /** */ + object TestProperty { get; set; } + + /** */ + int Method(int arg); + } + + #pragma warning disable 649 + + /// <summary> + /// Test serializable service. + /// </summary> + [Serializable] + private class TestIgniteServiceSerializable : IService, ITestIgniteService + { + /** */ + [InstanceResource] + private IIgnite _grid; + + /** <inheritdoc /> */ + public int TestProperty { get; set; } + + /** <inheritdoc /> */ + public bool Initialized { get; private set; } + + /** <inheritdoc /> */ + public bool Cancelled { get; private set; } + + /** <inheritdoc /> */ + public bool Executed { get; private set; } + + /** <inheritdoc /> */ + public Guid NodeId + { + get { return _grid.Cluster.LocalNode.Id; } + } + + /** <inheritdoc /> */ + public string LastCallContextName { get; private set; } + + /** */ + public bool ThrowInit { get; set; } + + /** */ + public bool ThrowExecute { get; set; } + + /** */ + public bool ThrowCancel { get; set; } + + /** */ + public object Method(object arg) + { + return arg; + } + + /** */ + public object ErrMethod(object arg) + { + throw new ArgumentNullException("arg", "ExpectedException"); + } + + /** <inheritdoc /> */ + public void Init(IServiceContext context) + { + if (ThrowInit) + throw new Exception("Expected exception"); + + CheckContext(context); + + Assert.IsFalse(context.IsCancelled); + Initialized = true; + } + + /** <inheritdoc /> */ + public void Execute(IServiceContext context) + { + if (ThrowExecute) + throw new Exception("Expected exception"); + + CheckContext(context); + + Assert.IsFalse(context.IsCancelled); + Assert.IsTrue(Initialized); + Assert.IsFalse(Cancelled); + + Executed = true; + } + + /** <inheritdoc /> */ + public void Cancel(IServiceContext context) + { + if (ThrowCancel) + throw new Exception("Expected exception"); + + CheckContext(context); + + Assert.IsTrue(context.IsCancelled); + + Cancelled = true; + } + + /// <summary> + /// Checks the service context. + /// </summary> + private void CheckContext(IServiceContext context) + { + LastCallContextName = context.Name; + + if (context.AffinityKey != null && !(context.AffinityKey is int)) + { + var portableObject = context.AffinityKey as IPortableObject; + + var key = portableObject != null + ? portableObject.Deserialize<PortableObject>() + : (PortableObject) context.AffinityKey; + + Assert.AreEqual(AffKey, key.Val); + } + + Assert.IsNotNull(_grid); + + Assert.IsTrue(context.Name.StartsWith(SvcName)); + Assert.AreNotEqual(Guid.Empty, context.ExecutionId); + } + } + + /// <summary> + /// Test portable service. + /// </summary> + private class TestIgniteServicePortable : TestIgniteServiceSerializable, IPortableMarshalAware + { + /** <inheritdoc /> */ + public void WritePortable(IPortableWriter writer) + { + writer.WriteInt("TestProp", TestProperty); + } + + /** <inheritdoc /> */ + public void ReadPortable(IPortableReader reader) + { + TestProperty = reader.ReadInt("TestProp"); + } + } + + /// <summary> + /// Test portable service with exceptions in marshalling. + /// </summary> + private class TestIgniteServicePortableErr : TestIgniteServiceSerializable, IPortableMarshalAware + { + /** */ + public bool ThrowOnWrite { get; set; } + + /** <inheritdoc /> */ + public void WritePortable(IPortableWriter writer) + { + writer.WriteInt("TestProp", TestProperty); + + if (ThrowOnWrite) + throw new Exception("Expected exception"); + } + + /** <inheritdoc /> */ + public void ReadPortable(IPortableReader reader) + { + TestProperty = reader.ReadInt("TestProp"); + + throw new Exception("Expected exception"); + } + } + + /// <summary> + /// Test node filter. + /// </summary> + [Serializable] + private class NodeFilter : IClusterNodeFilter + { + /// <summary> + /// Gets or sets the node identifier. + /// </summary> + public Guid NodeId { get; set; } + + /** <inheritdoc /> */ + public bool Invoke(IClusterNode node) + { + return node.Id == NodeId; + } + } + + /// <summary> + /// Portable object. + /// </summary> + private class PortableObject + { + public int Val { get; set; } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/5cec202c/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTestAsync.cs ---------------------------------------------------------------------- diff --git a/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTestAsync.cs b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTestAsync.cs new file mode 100644 index 0000000..68ae93e --- /dev/null +++ b/modules/platform/src/test/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTestAsync.cs @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Services +{ + using Apache.Ignite.Core.Services; + + /// <summary> + /// Services async tests. + /// </summary> + public class ServicesTestAsync : ServicesTest + { + /** <inheritdoc /> */ + protected override IServices Services + { + get { return new ServicesAsyncWrapper(Grid1.Services()); } + } + } +} \ No newline at end of file
