This blog post is about making life easier when testing Oracle Coherence .Net client applications by using the Oracle Tools library on Git Hub. In my last blog about Oracle Tools I talked about how you can use Oracle Tools to easily test Oracle Coherence applications in Java and now it is the turn of the Oracle Coherence .Net developer to have life made easier. Previously I covered how you can use Oracle Tools to run an Oracle Coherence cluster in-process in the same JVM as the test code is running. Using Oracle Tools it is easy to configure and control the cluster members from within the test code and be safe in the knowledge that the cluster will die when the tests finish. Well, all this is now available in the .Net world too in my Oracle Tools .Net GitHub repository. If you are like other .Net developers I know, you resort to forking cluster processes when writing any sort of integration or acceptance test, this will be a thing of the past with Oracle Tools for Coherence .Net so test suites and CI builds can be more reliable andtest easier to run and configure from unit test code.
I have been wanting to put a .Net wrapper around this code for a while, way back when it was still part of the Oracle Coherence Incubator. The refactoring into Oracle Tools has actually made this job a bit easier as the code is much more modular and self contained and easier to build.
How It Works
Before we dive into some examples I will give a brief desription of how this works. I have not re-written all of Oracle Tools in .Net, that would have taken too long, and would not really be possible. Not everyone realizes this, but the Windows versions of Java come with a jvm.dll file that allows you to run a JVM inside a normal Windows process. You can communicate with the jvm.dll using the Java Native Interface. Once you have a JNI session instantiated and the JVM running it is possible to instantiate Java classes from your Windows code (in this case .Net) and communicate with those classes. The only down side is that JNI is a pig to work with so anything that makes JNI easier is very welcome.
There are a few commercial products that will do the job of making JNI easier, but Oracle Tools is open source so I did not want to go down that route. There are also a few open source JNI wrappers but they all seemd to have missing functionality until I found jni4net. The jni4net library allows you to generate .Net wrapper classes around your Java classes (and vice-versa). The classes and methods look identical in both languages, which is really cool, what’s more it works and is easy to set-up and integrate into a build.
So, the simple thing would be to say that I just ran jni4net over the Oracle Tools library and hey-presto we were away – but life is never that simple. There are a few little gotchas I needed to overcome. The Oracle Tools library is Java and some of it does not map well directly to the .Net world – I’m not the worlds best .Net developer by a long way, but even I could see where things didn’t look right to someone with a .Net brain. Finally jni4net does not wrap generics so even though generics are present on the Java classes they are lost in the generated code, so I needed to fix that too – by hand coding the stuff rather than fixing jni4net. All in all there was still a fair bit of .Net coding to be done – which believe me, for a hard core Java developer like me was a bit of a learning curve – all I can say is thank heavens for JetBrains Resharper. Being a massive IntelliJ fan getting Resharper saved my life as it makes Visual Studio look a lot like IntelliJ.
Oracle Tools .Net Examples
I will leave how to build Oracle Tools .Net until later in the post, first we will jump straight into some examples. If you have read the previous post they will look very familiar – which is the whole point. I will cover the examples in the same order as the last post, just to be consistent. The code for these examples are available int the code in the GitHub repository. All of these examples are written as NUnit test classes.
Initialising Oracle Tools .Net
As I have mentioned already, the way all of this code works is that it wraps classes running in an embedded JVM inside the .Net process. This JVM needs to be initialised, which jni4net provides code for, before it can be used. All of this initialisation code has been wrappered away inside a class called OracleTools in the Oracle.Tools namespace.
To make life a bit easier the Oracle Tools.Net dll comes with all of the relevant Java jar files embedded inside it so you do not need to worry about dependencies. When Oracle Tools is initialised and the JVM started all of these jars can be extracted onto the classpath of the embedded JVM. These dependencies are not the same as your applications dependencies, they are just for running Oracle Tools so you do not need to worry about trying to add your own jar files to this classpath.
The OracleTools
class is a singleton, you cannot construct it, the only instance is available using the OracleTools.Wrapper
member which you can then call methods on. The majority of the methods are a fluent style interface, that is they return the instance of OracleTools
so you can chain calls together.
All of the examples we see below use the same, or similar, initialisation code
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); }
The code above first checks to see whether Oracle Tools has already been initialise – you cannot start the JVM twice.
If OracleTools
has not been initialised we call the AddEmbeddedJarsToClassPath
then the Initialise
method.
The AddEmbeddedJarsToClassPath
tells OracleTools
that we want it to add the jar files embedded in the OracleTools.dll to the JVM classpath. To do this they will be extracted to the directory named in the parameter to the AddEmbeddedJarsToClassPath
method. In this case we extract them to a folder called “lib”, which is relative to the working directory, so we will create a folder called “lib” below the working directory and extract the jar files into it.
The Initialise
method then starts the JVM with the class path we told it to use.
Class Path Options
Note The classpath of the embedded JVM is not the same as the classpath of the application you are going to test, they are completly independent. The embedded JVM only needs the jar file dependencies for oracle tools. In most cases you would not need to add any more jar files than those embedded in the DLL, but if you do you can call other methods on OracleTools.Wrapper
OracleTools.Wrapper.AddClassPath(path)
This method adds the specified entry to the classpath. Just like Java, this can be a directory containing class files and resources or it can be the path to a jar file.
OracleTools.Wrapper.AddAllJarsToClassPath(folder)
This method adds all of the jar files in the specified folder to the classpath. It is the same as using wild cards in a normal Java classpath.
Java Home – Which Version of Java is Used?
Obviously you already have one or more versions of Java JDK installed on your PC. By default Oracle Tools will look in the Windows Registry and find the default version of Java – which is usually the latest version you installed. If you have multiple versions and you want Oracle tools to use a specific one you can use OracleTools.Wrapper.SetJavaHome(home)
where the parameter is the path to the location the JDK is installed.
JVM Memory
It is likely that you will want to set things like the various memory options for the embedded JVM, especially if you are going to run multiple container processes inside it, for example running multiple cluster members as part of a test. Oracle Tools will use the default JVM settings unless told otherwise, these settings vary with O/S type, 64 or 32 bit and Java version. You can change these with the AddJvmOption
method. You call this method for each JVM option you want to set, the options are normal JVM options, for example
- OracleTools.Wrapper
- .AddJvmOption("-Xms1024m")
- .AddJvmOption("-Xms1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
OracleTools.Wrapper .AddJvmOption("-Xms1024m") .AddJvmOption("-Xms1024m") .AddJvmOption("-XX:MaxPermSize=300m")
The above options, for the standard Oracle JVM, set the minimum (-Xms) and maximum (-Xmx) heap memory to 1024m (1GByte) and set the maximum Perm Space to 300m (300 MBytes). Apologies if this is a bit basic if you are a Java developer but I am assuming that some people reading this are mostly .Net developers who may not be too familiar with JVM settings.
Other JVM Settings
There may be occasions when for some reason you need to pass other arguments to the embedded JVM. You can use the same AddJvmOption
to pass any valid option to the JVM, just put one option in each call to AddJvmOption
. Remember though that this embedded JVM is not going to run your application, it is only going to run the Java Oracle Tools which in turn will run your application code. The settings and classpath you configure do not need to be the same as your application.
Simple External Process Example
The first example is running an external non-Java forked process, which in this case will be the equivalent of cmd.exe -C dir
which as you all know is just going to run the dir
command in the current directory.
- using NUnit.Framework;
- using Oracle.Tools.Runtime;
- using Oracle.Tools.Runtime.Console;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunSomethingNonJavaTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-Xmx1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunSomethingNonJava()
- {
- SimpleApplicationSchema schema = new SimpleApplicationSchema("cmd.exe")
- .AddArgument("/C")
- .AddArgument("dir");
- IApplicationBuilder<SimpleApplication,SimpleApplicationSchema> builder = new SimpleApplicationBuilder();
- var application = builder.Realize(schema, "CMD", new SystemApplicationConsole());
- application.WaitFor();
- var exitCode = application.Destroy();
- Assert.AreEqual(0, exitCode);
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime; using Oracle.Tools.Runtime.Console; namespace Oracle.Tools.Examples { [TestFixture] public class RunSomethingNonJavaTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-Xmx1024m") .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunSomethingNonJava() { SimpleApplicationSchema schema = new SimpleApplicationSchema("cmd.exe") .AddArgument("/C") .AddArgument("dir"); IApplicationBuilder<SimpleApplication,SimpleApplicationSchema> builder = new SimpleApplicationBuilder(); var application = builder.Realize(schema, "CMD", new SystemApplicationConsole()); application.WaitFor(); var exitCode = application.Destroy(); Assert.AreEqual(0, exitCode); } } }
The first thing you can see is the ClassInitialize
method at the top of the class. This method is annotated with [TestFixtureSetup]
which means that NUnit should run this method once before any tests in this class are run. This method runs the oracle Tools initialisation code I covered in the previous section.
Note: The initialisation code above sets the maximum heap to 1GB and the Perm Gen size to 300MB. I am running this code in a 64bit Windows 7 session on VMWare Fusion with lots of memory on a 16GB Mac Book Pro. If you are trying to run it on certain lower spec Windows configurations – like the 32bit XP at my current client – then you might not be able to create a JVM with that much heap so you may need to drop it down to say -Xmx900m.
The [Test]
annotated method ShouldRunSomethingNonJava
is the code that will run our cmd.exe /C dir
process.
- First we create the instance of
SimpleApplicationSchema
which is the schema class for non-Java applications. Its constructor argument is the executable to run, in this casecmd.exe
- Then we call the
AddArgument
method a couple of times on the schema to set the command line arguments, in this case/C
anddir
- Next we create a builder that will realize our process; here we create a
SimpleApplicationBuilder
which is the builder for creating simple forked processes
There are various methods on SimpleApplicationSchema
to set other properties of the application, for example you could add the following test method to the code above just to prove that you can configure environemtn variables for the process.
- [Test]
- public void ShouldRunSomethingNonJavaWithEnvironment()
- {
- SimpleApplicationSchema schema = new SimpleApplicationSchema("cmd.exe")
- .SetEnvironmentVariable("DIR_NAME", "C:\\")
- .AddArgument("/C")
- .AddArgument("dir")
- .AddArgument("%DIR_NAME%");
- IApplicationBuilder<SimpleApplication, SimpleApplicationSchema> builder = new SimpleApplicationBuilder();
- var application = builder.Realize(schema, "CMD", new SystemApplicationConsole());
- application.WaitFor();
- var exitCode = application.Destroy();
- Assert.AreEqual(0, exitCode);
- }
[Test] public void ShouldRunSomethingNonJavaWithEnvironment() { SimpleApplicationSchema schema = new SimpleApplicationSchema("cmd.exe") .SetEnvironmentVariable("DIR_NAME", "C:\\") .AddArgument("/C") .AddArgument("dir") .AddArgument("%DIR_NAME%"); IApplicationBuilder<SimpleApplication, SimpleApplicationSchema> builder = new SimpleApplicationBuilder(); var application = builder.Realize(schema, "CMD", new SystemApplicationConsole()); application.WaitFor(); var exitCode = application.Destroy(); Assert.AreEqual(0, exitCode); }
In the above example we use a system property to specify the name of the directory we want a listing of, which in the above test is C:\
You can use SimpleApplicationSchema
and SimpleApplicationBuilder
to configure and run any process at all, although there are already ways to do this in .Net. The power of Oracle Tools comes with running Java processes, in particular processes running inside the embedded JVM.
If you run the above test you will see in the console output the captured stdout of the dir command and the directory listing.
External Non-Coherence Java Process
This example code is going to run a simple externally forked Java process.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunExternalJavaProcessTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-Xmx1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunExternalJavaApplication()
- {
- const string classpath = LibFolder + "/oracle-tools-net-java-tests.jar";
- const string classToRun = "com.oracle.tools.test.MyTestClass";
- var schema = new SimpleJavaApplicationSchema(classToRun, classpath)
- .AddArgument("Arg0")
- .AddArgument("Arg1");
- var builder = new ExternalJavaApplicationBuilder<SimpleJavaApplication, SimpleJavaApplicationSchema>();
- var application = builder.Realize(schema, "JavaTest", new SystemApplicationConsole());
- var exitCode = application.WaitFor();
- application.Destroy();
- Assert.AreEqual(0, exitCode);
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; namespace Oracle.Tools.Examples { [TestFixture] public class RunExternalJavaProcessTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-Xmx1024m") .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunExternalJavaApplication() { const string classpath = LibFolder + "/oracle-tools-net-java-tests.jar"; const string classToRun = "com.oracle.tools.test.MyTestClass"; var schema = new SimpleJavaApplicationSchema(classToRun, classpath) .AddArgument("Arg0") .AddArgument("Arg1"); var builder = new ExternalJavaApplicationBuilder<SimpleJavaApplication, SimpleJavaApplicationSchema>(); var application = builder.Realize(schema, "JavaTest", new SystemApplicationConsole()); var exitCode = application.WaitFor(); application.Destroy(); Assert.AreEqual(0, exitCode); } } }
You will see there is the same initialisation code, the main bit of the example is in the ShouldRunExternalJavaApplication
method. In this example we are going to run a simple Java test class that I have included. All this class does is dumps to stdout the System properties and program arguments. Its main purpose is to prove that what is configured in the schema is what is passed to the process.
- The first thing we need to do is set up the class path of our application being tested. In this case we only need the jar file containing the test class.
- We then create a SimpleJavaApplicationSchema instance specifying the name of our class to run
com.oracle.tools.test.MyTestClass
and the classpath. - Using the schema we specify that we want to pass two arguments to the process, “Arg0” and “Arg1”, we should see these dumped out in the process output. There are a number of other methods on SimpleJavaApplicationSchema that allow you to configure various Java specific properties of the application.
- We next create our builder that will realize the running process. In this case we use an
ExternalJavaApplicationBuilder
as we want the process to be forked - Now we have a builder we can call
Realize
to get the application instance. We pass the schema to the builder’sRealize
method along with the application name (in this case we call it “JavaTest” and we use aSystemApplicationConsole
to capture the application output. - We then call
WaitFor
on the application to wait for it to terminate and once it has finished we assert that the exit code was zero.
This was a very simple example but the SimpleJavaApplicationSchema
allows you to configure any Java application.
Virtualized Non-Coherence Java Process Example
We can run the exact same Java class again but this time as a virtual process contained withing the same .Net process that is running the .Net code.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunVirtualizedJavaProcessTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-Xmx1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunVirtualizedJavaApplication()
- {
- const string classpath = LibFolder + "/oracle-tools-net-java-tests.jar";
- const string classToRun = "com.oracle.tools.test.MyTestClass";
- var schema = new SimpleJavaApplicationSchema(classToRun, classpath)
- .AddArgument("Arg0")
- .AddArgument("Arg1");
- var builder = new VirtualizedJavaApplicationBuilder<SimpleJavaApplication, SimpleJavaApplicationSchema>();
- var application = builder.Realize(schema, "JavaTest", new SystemApplicationConsole());
- var exitCode = application.WaitFor();
- Assert.AreEqual(0, exitCode);
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; namespace Oracle.Tools.Examples { [TestFixture] public class RunVirtualizedJavaProcessTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-Xmx1024m") .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunVirtualizedJavaApplication() { const string classpath = LibFolder + "/oracle-tools-net-java-tests.jar"; const string classToRun = "com.oracle.tools.test.MyTestClass"; var schema = new SimpleJavaApplicationSchema(classToRun, classpath) .AddArgument("Arg0") .AddArgument("Arg1"); var builder = new VirtualizedJavaApplicationBuilder<SimpleJavaApplication, SimpleJavaApplicationSchema>(); var application = builder.Realize(schema, "JavaTest", new SystemApplicationConsole()); var exitCode = application.WaitFor(); Assert.AreEqual(0, exitCode); } } }
You can see that this code is exactly the same as the previous example except for the builder used to realize the application. This time on line 34 we use a VirtualizedJavaApplicationBuilder
so that the application runs within the sandboxed JVM inside the .Net process.
External Coherence Process Example
We can use SimpleJavaApplicationSchema to configure any Java application, including Oracle Coherence, but Oracle Tools makes life easier for us by providing a schema specifically for building Coherence processes. This schema is ClusterMemberSchema
and includes a number of methods that allow us to configure the application without having to remember all of the correct Java system property names.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Coherence;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- using Assert = Oracle.Tools.Testing.Assert;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunExternalCoherenceNodeTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunCoherenceNode()
- {
- const string classpath = LibFolder + "/coherence.jar";
- var schema = new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath)
- .SetClusterName("TestCluster")
- .SetSingleServerMode()
- .SetStorageEnabled(true)
- .SetJmxManagementMode(JmxManagementMode.All);
- var builder = new ExternalJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>();
- var clusterMember = builder.Realize(schema, "Storage", new SystemApplicationConsole());
- Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(1));
- clusterMember.Destroy();
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Coherence; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; using Assert = Oracle.Tools.Testing.Assert; namespace Oracle.Tools.Examples { [TestFixture] public class RunExternalCoherenceNodeTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunCoherenceNode() { const string classpath = LibFolder + "/coherence.jar"; var schema = new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath) .SetClusterName("TestCluster") .SetSingleServerMode() .SetStorageEnabled(true) .SetJmxManagementMode(JmxManagementMode.All); var builder = new ExternalJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>(); var clusterMember = builder.Realize(schema, "Storage", new SystemApplicationConsole()); Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(1)); clusterMember.Destroy(); } } }
The example above is very similar to the previous examples with the same basic steps, initialise, set up classpath, create schema, create builder, realize application, stop application.
- The initialisation code is again the same as previous – in reality this code would go into a common base class for all of your tests.
- For this test we only require the Coherence jar on the class path
- We now use a
ClusterMemberSchema
to configure our Coherence node. TheClusterMemberSchema
contains various methods to set the different parameters of the node. In this case we are keeping it all very basic but you should be able to see by looking at the available methods onClusterMemberSchema
what other functionality is available - We use
ExternalJavaApplicationBuilder
to realise the running node in the same way we did previously and capture the output with aSystemApplicationConsole
- The next part is different – we want to wait until the node has formed a cluster. In this case it will cluster with itself so there will only be a single cluster member. We do the check using a deferred assertion to wait until the result of calling
clusterMember.ClusterSize
is equal to 1 - Finally after the cluster is running we destroy the process and wait for it to stop
. There is also documentation in the Java version’s Java Docs.
Virtualized Coherence Process Example
As with running a virtual basic Java application in the example above we can run a virtual Coherence node in the same process as the .Net code by just changing the builder.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Coherence;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- using Oracle.Tools.Runtime.Network;
- using com.oracle.tools.runtime.java.virtualization;
- using Assert = Oracle.Tools.Testing.Assert;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunVirtualizedCoherenceNodeTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-Xmx1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunCoherenceNode()
- {
- const string classpath = LibFolder + "/coherence.jar";
- AvailablePortEnumerator ports = new AvailablePortEnumerator(40000);
- var schema = new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath)
- .SetClusterName("TestCluster")
- .SetSingleServerMode()
- .SetStorageEnabled(true)
- .SetJmxManagementMode(JmxManagementMode.All)
- .SetJmxPort(ports)
- .SetRoleName("RunVirtualizedCoherenceNodeTest");
- var builder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>();
- var clusterMember = builder.Realize(schema, "Storage", new SystemApplicationConsole());
- Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(1));
- clusterMember.Destroy();
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Coherence; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; using Oracle.Tools.Runtime.Network; using com.oracle.tools.runtime.java.virtualization; using Assert = Oracle.Tools.Testing.Assert; namespace Oracle.Tools.Examples { [TestFixture] public class RunVirtualizedCoherenceNodeTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-Xmx1024m") .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunCoherenceNode() { const string classpath = LibFolder + "/coherence.jar"; AvailablePortEnumerator ports = new AvailablePortEnumerator(40000); var schema = new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath) .SetClusterName("TestCluster") .SetSingleServerMode() .SetStorageEnabled(true) .SetJmxManagementMode(JmxManagementMode.All) .SetJmxPort(ports) .SetRoleName("RunVirtualizedCoherenceNodeTest"); var builder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>(); var clusterMember = builder.Realize(schema, "Storage", new SystemApplicationConsole()); Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(1)); clusterMember.Destroy(); } } }
You can see the only change is the use of a VirtualizedJavaApplicationBuilder
on lines 43 and 45 to realise the cluster member.
Coherence Cluster Example
As with the Java version of Oracle Tools, the .Net version allows you to manage a whole cluster of processes, as that is how we normally work with Coherence applications and how you will most likely want to test your Coherence .Net application. As we have already seen the only difference between external Java applications and applications contained within the .Net process is which builder you use I am just going to use the internal version in this example to run a whole Oracle Coherence cluster inside the .Net process.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Coherence;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- using Oracle.Tools.Runtime.Network;
- using com.oracle.tools.runtime.java.virtualization;
- using Assert = Oracle.Tools.Testing.Assert;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class RunVirtualizedCoherenceClusterTest
- {
- public const string LibFolder = "lib";
- [TestFixtureSetUp]
- public void ClassInitialize()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- }
- [Test]
- public void ShouldRunVirtualizedCoherenceCluster()
- {
- var memberBuilder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>();
- var clusterBuilder = new ClusterBuilder();
- clusterBuilder.AddBuilder(memberBuilder, CreateStorageNodeSchema(), "Data", 2);
- clusterBuilder.AddBuilder(memberBuilder, CreateExtendProxySchema(), "Proxy", 1);
- var cluster = clusterBuilder.Realize(new SystemApplicationConsole());
- var memberCount = cluster.Count;
- var clusterMember = cluster["Data-0"];
- Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(memberCount));
- var proxyMember = cluster["Proxy-0"];
- Assert.Eventually(() => proxyMember.IsServiceRunning("TcpProxyService"), Is.EqualTo(true));
- cluster.Destroy();
- }
- private ClusterMemberSchema CreateStorageNodeSchema()
- {
- return CreateCommonSchema()
- .SetStorageEnabled(true)
- .SetRoleName("VirtualizedCoherenceClusterData");
- }
- private ClusterMemberSchema CreateExtendProxySchema()
- {
- return CreateCommonSchema()
- .SetStorageEnabled(false)
- .SetSystemProperty("tangosol.coherence.extend.enabled", "true")
- .SetSystemProperty("tangosol.coherence.extend.port", new AvailablePortEnumerator(40000))
- .SetRoleName("VirtualizedCoherenceClusterProxy");
- }
- private ClusterMemberSchema CreateCommonSchema()
- {
- const string classpath = LibFolder + "/coherence.jar";
- return new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath)
- .SetClusterName("TestCluster")
- .SetSingleServerMode()
- .SetJmxManagementMode(JmxManagementMode.LocalOnly)
- .SetJmxPort(new AvailablePortEnumerator(40000));
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Coherence; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; using Oracle.Tools.Runtime.Network; using com.oracle.tools.runtime.java.virtualization; using Assert = Oracle.Tools.Testing.Assert; namespace Oracle.Tools.Examples { [TestFixture] public class RunVirtualizedCoherenceClusterTest { public const string LibFolder = "lib"; [TestFixtureSetUp] public void ClassInitialize() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } } [Test] public void ShouldRunVirtualizedCoherenceCluster() { var memberBuilder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>(); var clusterBuilder = new ClusterBuilder(); clusterBuilder.AddBuilder(memberBuilder, CreateStorageNodeSchema(), "Data", 2); clusterBuilder.AddBuilder(memberBuilder, CreateExtendProxySchema(), "Proxy", 1); var cluster = clusterBuilder.Realize(new SystemApplicationConsole()); var memberCount = cluster.Count; var clusterMember = cluster["Data-0"]; Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(memberCount)); var proxyMember = cluster["Proxy-0"]; Assert.Eventually(() => proxyMember.IsServiceRunning("TcpProxyService"), Is.EqualTo(true)); cluster.Destroy(); } private ClusterMemberSchema CreateStorageNodeSchema() { return CreateCommonSchema() .SetStorageEnabled(true) .SetRoleName("VirtualizedCoherenceClusterData"); } private ClusterMemberSchema CreateExtendProxySchema() { return CreateCommonSchema() .SetStorageEnabled(false) .SetSystemProperty("tangosol.coherence.extend.enabled", "true") .SetSystemProperty("tangosol.coherence.extend.port", new AvailablePortEnumerator(40000)) .SetRoleName("VirtualizedCoherenceClusterProxy"); } private ClusterMemberSchema CreateCommonSchema() { const string classpath = LibFolder + "/coherence.jar"; return new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath) .SetClusterName("TestCluster") .SetSingleServerMode() .SetJmxManagementMode(JmxManagementMode.LocalOnly) .SetJmxPort(new AvailablePortEnumerator(40000)); } } }
This example is a little more involved but the basics are the same as the others; the main work gets done in the ShouldRunVirtualizedCoherenceCluster
test method.
- First we create a builder that will be used to build the individual members of our cluster; we are using the
VirtualizedJavaApplicationBuilder
to run the cluster internally. - Next we create our
ClusterBuilder
which will build all of the cluster members in one go. TheClusterBuilder
is really just a utility that builds aCluster
which is a just a fancy container around a number of processes - Once we have the
ClusterBuilder
we tell it what to build by adding schema to it. First we add a storage node schema which we create in theCreateStorageNodeSchema()
method. We tell the cluster builder to use theVirtualizedJavaApplicationBuilder
to build the storage nodes, we give the nodes the name prefix “Data” and we tell theClusterBuilder
we want two instances of the storage node creating - Next we add the extend proxy to the
ClusterBuilder
. We tell theClusterBuilder
to use the sameVirtualizedJavaApplicationBuilder
, the schema created by theCreateExtendProxySchema()
method, give the node the name prefix “Proxy” and tell theClusterBuilder
we want one instance creating. - We then call
Realize
on theClusterBuilder
to create our cluster; in this case two storage nodes and a storage disabled proxy node.
Once the ClusterBuilder
returns the Cluster
we know the processes have started but the cluster is not actually ready to do anything useful as it takes Coherence some time to form the cluster and start all the services in each node; we need to wait for everything to be ready.
We can get individual applications from the cluster container by name. The Cluster
is an implementation of a .Net Dictionary
so you can access members by their name. The name of the member will be the name prefix we used when we added the schema to the ClusterBuilder
followed by an instance number. So in the above example we have two members with the prefix “Data” and one with the prefix “Proxy”. The instance numbers start at zero, so our member names will be Data-0
Data-1
and Proxy-0
- We get the first storage node from the cluster and then do a deferred assertion on it to wait until the cluster has the correct size – in this case three. Once this happens we know all of the nodes have joined the cluster
- As this is supposed to be a Coherence .Net client test we would be connecting over Coherence*Extend so we need to make sure the proxy node has started its Proxy Service. We do this with another deferred assertion that waits for the service with a specified name to start. As we are using the default Coherence cache configuration file from the Coherence jar this proxy service is called
TcpProxyService
End to End NUnit Coherence .Net Example
So now you have seen how to start a cluster the final example is a full blown end-to-end test that starts the cluster and does some cache operations, so it is more typical of a test you would have in a .Net client application.
- using NUnit.Framework;
- using Oracle.Tools.Runtime.Coherence;
- using Oracle.Tools.Runtime.Console;
- using Oracle.Tools.Runtime.Java;
- using Oracle.Tools.Runtime.Network;
- using Tangosol.Net;
- using Tangosol.Util.Aggregator;
- using Tangosol.Util.Filter;
- using Assert = Oracle.Tools.Testing.Assert;
- namespace Oracle.Tools.Examples
- {
- [TestFixture]
- public class EndToEndTest
- {
- public const string LibFolder = "lib";
- private Cluster _cluster;
- [TestFixtureSetUp]
- public void StartCluster()
- {
- if (!OracleTools.Wrapper.Initialised)
- {
- OracleTools.Wrapper
- .AddJvmOption("-Xmx1024m")
- .AddJvmOption("-XX:MaxPermSize=300m")
- .AddEmbeddedJarsToClassPath(LibFolder)
- .Initialise();
- }
- var memberBuilder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>();
- var clusterBuilder = new ClusterBuilder();
- clusterBuilder.AddBuilder(memberBuilder, CreateStorageNodeSchema(), "Data", 2);
- clusterBuilder.AddBuilder(memberBuilder, CreateExtendProxySchema(), "Proxy", 1);
- _cluster = clusterBuilder.Realize(new SystemApplicationConsole());
- }
- [SetUp]
- public void AssertThatClusterIsReady()
- {
- var memberCount = _cluster.Count;
- var clusterMember = _cluster["Data-0"];
- Assert.That(clusterMember, Is.Not.Null);
- Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(memberCount));
- var proxyMember = _cluster["Proxy-0"];
- Assert.That(proxyMember, Is.Not.Null);
- Assert.Eventually(() => proxyMember.IsServiceRunning("TcpProxyService"), Is.EqualTo(true));
- }
- [Test]
- public void ShouldInteractWithCluster()
- {
- var cache = CacheFactory.GetCache("dist-test");
- Assert.That(cache.Count, Is.EqualTo(0));
- cache["Key-1"] = "Value-1";
- Assert.That(cache.Count, Is.EqualTo(1));
- Assert.That((string)cache["Key-1"], Is.EqualTo("Value-1"));
- cache["Key-2"] = "Value-2";
- cache["Key-3"] = "Value-3";
- var count = (int)cache.Aggregate(AlwaysFilter.Instance, new Count());
- Assert.That(count, Is.EqualTo(3));
- }
- [TestFixtureTearDown]
- public void StopCluster()
- {
- if (_cluster != null)
- {
- _cluster.Destroy();
- }
- }
- private static ClusterMemberSchema CreateStorageNodeSchema()
- {
- return CreateCommonSchema()
- .SetStorageEnabled(true);
- }
- private static ClusterMemberSchema CreateExtendProxySchema()
- {
- return CreateCommonSchema()
- .SetStorageEnabled(false)
- .SetSystemProperty("tangosol.coherence.extend.enabled", "true")
- .SetSystemProperty("tangosol.coherence.extend.port", "9099");
- }
- private static ClusterMemberSchema CreateCommonSchema()
- {
- const string classpath = LibFolder + "/coherence.jar";
- return new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath)
- .SetClusterName("TestCluster")
- .SetSingleServerMode()
- .SetJmxManagementMode(JmxManagementMode.LocalOnly)
- .SetJmxPort(new AvailablePortEnumerator(40000))
- .SetPofEnabled(true);
- }
- }
- }
using NUnit.Framework; using Oracle.Tools.Runtime.Coherence; using Oracle.Tools.Runtime.Console; using Oracle.Tools.Runtime.Java; using Oracle.Tools.Runtime.Network; using Tangosol.Net; using Tangosol.Util.Aggregator; using Tangosol.Util.Filter; using Assert = Oracle.Tools.Testing.Assert; namespace Oracle.Tools.Examples { [TestFixture] public class EndToEndTest { public const string LibFolder = "lib"; private Cluster _cluster; [TestFixtureSetUp] public void StartCluster() { if (!OracleTools.Wrapper.Initialised) { OracleTools.Wrapper .AddJvmOption("-Xmx1024m") .AddJvmOption("-XX:MaxPermSize=300m") .AddEmbeddedJarsToClassPath(LibFolder) .Initialise(); } var memberBuilder = new VirtualizedJavaApplicationBuilder<ClusterMember, ClusterMemberSchema>(); var clusterBuilder = new ClusterBuilder(); clusterBuilder.AddBuilder(memberBuilder, CreateStorageNodeSchema(), "Data", 2); clusterBuilder.AddBuilder(memberBuilder, CreateExtendProxySchema(), "Proxy", 1); _cluster = clusterBuilder.Realize(new SystemApplicationConsole()); } [SetUp] public void AssertThatClusterIsReady() { var memberCount = _cluster.Count; var clusterMember = _cluster["Data-0"]; Assert.That(clusterMember, Is.Not.Null); Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(memberCount)); var proxyMember = _cluster["Proxy-0"]; Assert.That(proxyMember, Is.Not.Null); Assert.Eventually(() => proxyMember.IsServiceRunning("TcpProxyService"), Is.EqualTo(true)); } [Test] public void ShouldInteractWithCluster() { var cache = CacheFactory.GetCache("dist-test"); Assert.That(cache.Count, Is.EqualTo(0)); cache["Key-1"] = "Value-1"; Assert.That(cache.Count, Is.EqualTo(1)); Assert.That((string)cache["Key-1"], Is.EqualTo("Value-1")); cache["Key-2"] = "Value-2"; cache["Key-3"] = "Value-3"; var count = (int)cache.Aggregate(AlwaysFilter.Instance, new Count()); Assert.That(count, Is.EqualTo(3)); } [TestFixtureTearDown] public void StopCluster() { if (_cluster != null) { _cluster.Destroy(); } } private static ClusterMemberSchema CreateStorageNodeSchema() { return CreateCommonSchema() .SetStorageEnabled(true); } private static ClusterMemberSchema CreateExtendProxySchema() { return CreateCommonSchema() .SetStorageEnabled(false) .SetSystemProperty("tangosol.coherence.extend.enabled", "true") .SetSystemProperty("tangosol.coherence.extend.port", "9099"); } private static ClusterMemberSchema CreateCommonSchema() { const string classpath = LibFolder + "/coherence.jar"; return new ClusterMemberSchema(ClusterMemberSchema.DefaultCacheServerClassname, classpath) .SetClusterName("TestCluster") .SetSingleServerMode() .SetJmxManagementMode(JmxManagementMode.LocalOnly) .SetJmxPort(new AvailablePortEnumerator(40000)) .SetPofEnabled(true); } } }
This example is a little more like a real test class would be, although in a whole suite of test classes we would probably extract the initialisation and cluster start into a method in a test base class that runs once when the suite starts and we would destory the cluster when the suite ends. This is much better than starting the cluster for every test as your test suite will run much faster if you only build your cluster once for the suite.
We have a StartCluster()
method annotated with [TestFixtureSetUp]
that NUnit will run once before the anything else in the class is run. In this method we start our cluster in the same way we did in the previous example.
As mentioned previously we cannot proceed with the tests unless we know the cluster is ready. I have put this code into the AssertThatClusterIsReady()
method annotated with [Setup]
which NUnit will run before every test method so we know before each method that the cluster is still running. This code is pretty much the same as the code we used in the previous example to verify the cluster size and make sure the proxy service is running.
There is only a single [Test]
method ShouldInteractWithCluster()
in this example. It basically gets a cache, puts data into it and does some assertions. Very simple, but you get the idea and should be able to see how you can pretty much put any client side code in here to test your application.
We have a single method annotated with [TestFixtureTearDown]
called DestroyCluster()
that NUnit will call after all the tests in the class have run and as its name suggests it just calls Destroy
on the cluster to stop all of the nodes.
The other methods in the class are the schema creation methods which should be pretty self explanitory.
Easy Coherence .Net Testing
So that is all of the examples. They have been pretty basic in terms of the applications they have run but you should get the general idea of how Oracle Tools .Net works and how to use it. There are a lot of methods on the various schema classes I have not covered but what they do should be pretty obvious from their names and you can look at the documentation in the Java Docs for the Oracle Tool Java version.
You can see from the examples, especially the last one, that it is very easy to start an Oracle Coherence cluster inside an embedded JVM inside the same .Net process that your test code is running in. The cluster is easy to configure from your unit test code and eveything is all in one place, which is better that trying to spawn a forked cluster. Under the covers it uses the Oracle Tools Java code which is a project run and maintained by the guys at Oracle so should be pretty stable and funtional.
A good place to start is by looking at the OracleTools.Example Visual Studio project in the Oracle Tools .Net source on GitHub as this will show you the dependencies your test project will require. It also has all of the above examples.
Deferred Assertions
In a few of the examples I have used deferred assertions to verify a condition is true within a certain time, which is very useful for something mostly asynchronous like Coherence. The Java version of Oracle Tools has this functionality and it is also in Oracle Tools .Net but it works in a different way. The Java version relies on the Java dynaimic proxy functionality to work and although there is a way in .Net to have proxies it is more limited than the Java equivalent and would not have worked. In the end I wrote something similar using .Net delegates.
In the code you can do something like this
- using Assert = Oracle.Tools.Testing.Assert;
- ...
- Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(3));
using Assert = Oracle.Tools.Testing.Assert; ... Assert.Eventually(() => clusterMember.ClusterSize, Is.EqualTo(3));
This code will run the lamda () => clusterMember.ClusterSize
and assert the return value is equal to 3. If the assertion fails it will be retried until the timeout has passed, then the assertion will fail. The default timeout is 30 seconds but if you want a different timeout value you can do this
- Assert.Within(TimeSpan.FromSeconds(10)).That(() => clusterMember.ClusterSize, Is.EqualTo(3));
Assert.Within(TimeSpan.FromSeconds(10)).That(() => clusterMember.ClusterSize, Is.EqualTo(3));
The Assert.Within
method takes a normal C# TimeSpan
object (in the above example 10 seconds) and the That
method then specifies the assertion that should be true within that time.
Building Oracle Tools .Net
Currently Oracle Tools .Net is not available as a pre-built package. If you want to use it you are free to clone my GitHub repository and build it yourself. The build process is pretty straightforward. So, step 1, clone the repo, or download the zip of the repo from https://github.com/thegridman/oracle-tools-net.
Maven
The build process uses Maven, which is a popular Java build tool and is used by the Java versions of Oracle Tools and the Coherence Incubator. If you are a hard core .Net debveloper who does not know Maven I assume you have at least one friendly Java developer on your team if your are building Oracle Coherence applications so this should be easy to get set up. You can get Maven here http://maven.apache.org If you already know Maven then how to build the project should be plain enough.
Once you have Maven installed you need to make sure you have a version of the Oracle Coherence jar file in your Maven repository. I am not providing one due to licensing, but I assume you have one already. As I write this Oracle Tools .Net is dependant on Coherence 3.7.1.7 although you can change the dependency by changing the property in the top level Maven pom.xml file for the Project.
Build Settings
The build will compile all of the C# code and run the NUnit tests but to do this it needs to know the location of various things. These are configured in the pom.xml file in the root folder of the project. If you look in the properties section you will see these lines
<dotnet.path>C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319</dotnet.path> <dotnet.devenv.path>C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE</dotnet.devenv.path> <nunit.path>C:\Program Files (x86)\NUnit 2.6.2</nunit.path>
The above properties are form my system which is Windows 7 64 bit with .Net v4 and Nunit. You may need to change them to match your own system. I know there are ways to automate this using Maven profiles but I have not found the time to do it yet.
Run the Build
Now everything is ready, open a command prompt in the top level folder of the project and run
mvn clean install
This will run the full build of the project, along with all of the tests. At the end, there will be various build artefacts in the different target
sub-directories. The Oracle Tools library will be in a file oracle-tools-net\target\oracle-tools-net-csharp-0.0.1-SNAPSHOT.zip
. All of the artefacts will also be in your local Maven repository.
Visual Studio Projects
There are a few Visual Studio solution files for the project if you want to do anything with the C# code. These are in the oracle-tools-net-csharp folder and oracle-tools-net-examples folder. I have Visual Studio 2012, so there are solution files for that version. There are some solution files for Visual Studio 2010 too but I cannot guarantee they are up to date. I am sure if you are a competent .Net developer you will be able to work out how to create a solution around the various C# project files.
The main code base is in the oracle-tools-net-csharp\OracleTools.sln file. If you open this solution you can see all of the code that makes up the .Net version of Oracle tools along with the unit tests. There are two project files in this solutiion, oracle-tools-net-csharp\OracleTools\OracleTools.csproj and oracle-tools-net-csharp\OracleTools.Test\OracleTools.Test.csproj
The usage examples code is in the oracle-tools-net-examples\OracleTools.Examples.sln file. If you open this you can see the examples from this blog post written as NUnit tests. There is only a single project file in this solution, oracle-tools-net-examples\OracleTools.Examples\OracleTools.Examples.csproj
Hopefully that is enough information to allow you to take the code and get it working in your own applications. If it does not quite fit what you need it is straight forward enough and your are obviously free to fork my repository and do whatever you want with the code.
Thanks for your blog. Very interesting articles. I’m interested in your opinions of .Net support with IBM’s Extreme Scale. If you don’t have any opinion, could you point me an “unbiased” site that does describe the differences between Extreme Scale and Coherence?
Thanks for your blog. The theme is brilliant!
-ken