f3e3aab35a
Former-commit-id: 9c2cb47f45fa221e661ab616387c9cda183f283d
493 lines
17 KiB
C#
493 lines
17 KiB
C#
//
|
|
// HttpServerChannelTest.cs
|
|
// - Unit tests for System.Runtime.Remoting.Channels.Http
|
|
//
|
|
// Author: Jeffrey Stedfast <fejj@novell.com>
|
|
//
|
|
// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Reflection;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.Remoting;
|
|
using System.Runtime.Remoting.Channels;
|
|
using System.Runtime.Remoting.Channels.Http;
|
|
using System.Threading;
|
|
using NUnit.Framework;
|
|
|
|
namespace MonoTests.Remoting {
|
|
[TestFixture]
|
|
public class HttpServerChannelTests {
|
|
HttpServerChannel serverChannel;
|
|
int port = 9090;
|
|
|
|
[TestFixtureSetUp]
|
|
public void StartHttpServer ()
|
|
{
|
|
if (serverChannel != null)
|
|
return;
|
|
|
|
serverChannel = new HttpServerChannel ("HttpServerChannelTests", port);
|
|
ChannelServices.RegisterChannel (serverChannel);
|
|
|
|
RemotingConfiguration.RegisterWellKnownServiceType (
|
|
typeof (RemoteObject), "RemoteObject.rem",
|
|
WellKnownObjectMode.Singleton);
|
|
}
|
|
|
|
[TestFixtureTearDown]
|
|
public void StopHttpServer ()
|
|
{
|
|
ChannelServices.UnregisterChannel (serverChannel);
|
|
}
|
|
|
|
struct ParseURLTestCase {
|
|
public readonly string input;
|
|
public readonly string retval;
|
|
public readonly string objectURI;
|
|
|
|
public ParseURLTestCase (string s0, string s1, string s2)
|
|
{
|
|
input = s0;
|
|
retval = s1;
|
|
objectURI = s2;
|
|
}
|
|
};
|
|
|
|
ParseURLTestCase[] ParseURLTests = new ParseURLTestCase[] {
|
|
//new ParseURLTestCase ("http:", "http:", null), // KnownFailure but works on Microsoft's .NET
|
|
new ParseURLTestCase ("http://", "http://", null),
|
|
new ParseURLTestCase ("http:localhost", null, null),
|
|
new ParseURLTestCase ("ftp://localhost", null, null),
|
|
new ParseURLTestCase ("http://localhost", "http://localhost", null),
|
|
new ParseURLTestCase ("hTtP://localhost", "hTtP://localhost", null),
|
|
new ParseURLTestCase ("https://localhost", "https://localhost", null),
|
|
new ParseURLTestCase ("http://localhost:/", "http://localhost:", "/"),
|
|
new ParseURLTestCase ("http://localhost:9090", "http://localhost:9090", null),
|
|
new ParseURLTestCase ("http://localhost:9090/", "http://localhost:9090", "/"),
|
|
new ParseURLTestCase ("http://localhost:9090/RemoteObject.rem", "http://localhost:9090", "/RemoteObject.rem"),
|
|
new ParseURLTestCase ("http://localhost:q24691247abc1297/RemoteObject.rem", "http://localhost:q24691247abc1297", "/RemoteObject.rem"),
|
|
};
|
|
|
|
[Test] // HttpChannel.Parse ()
|
|
[Category ("NotWorking")] // Fails on MS
|
|
public void ParseURL ()
|
|
{
|
|
HttpChannel channel;
|
|
int i;
|
|
|
|
channel = new HttpChannel ();
|
|
|
|
for (i = 0; i < ParseURLTests.Length; i++) {
|
|
string retval, objectURI;
|
|
|
|
retval = channel.Parse (ParseURLTests[i].input, out objectURI);
|
|
|
|
Assert.AreEqual (ParseURLTests[i].retval, retval);
|
|
Assert.AreEqual (ParseURLTests[i].objectURI, objectURI);
|
|
}
|
|
}
|
|
|
|
static void Send (NetworkStream stream, string str)
|
|
{
|
|
byte [] buf = Encoding.ASCII.GetBytes (str);
|
|
|
|
Send (stream, buf);
|
|
}
|
|
|
|
static void Send (NetworkStream stream, byte[] buf)
|
|
{
|
|
//Console.Write ("C: ");
|
|
//DumpByteArray (buf, 3);
|
|
//Console.Write ("\n");
|
|
|
|
stream.Write (buf, 0, buf.Length);
|
|
}
|
|
|
|
static int Receive (NetworkStream stream, int chunks, out byte[] buf)
|
|
{
|
|
byte[] buffer = new byte [4096];
|
|
int n, nread = 0;
|
|
|
|
do {
|
|
if ((n = stream.Read (buffer, nread, buffer.Length - nread)) > 0)
|
|
nread += n;
|
|
|
|
chunks--;
|
|
} while (n > 0 && chunks > 0);
|
|
|
|
//Console.Write ("S: ");
|
|
if (nread > 0) {
|
|
buf = new byte [nread];
|
|
|
|
for (int i = 0; i < nread; i++)
|
|
buf[i] = buffer[i];
|
|
|
|
//DumpByteArray (buf, 3);
|
|
//Console.Write ("\n");
|
|
} else {
|
|
//Console.Write ("(null)\n");
|
|
buf = null;
|
|
}
|
|
|
|
return nread;
|
|
}
|
|
|
|
static string ByteArrayToString (byte[] buf, int indent)
|
|
{
|
|
StringBuilder sb = new StringBuilder ();
|
|
|
|
for (int i = 0; i < buf.Length; i++) {
|
|
if (!Char.IsControl ((char) buf[i])) {
|
|
sb.Append ((char) buf[i]);
|
|
} else if (buf[i] == '\r') {
|
|
sb.Append ("\\r");
|
|
} else if (buf[i] == '\n') {
|
|
sb.Append ("\\n\n");
|
|
for (int j = 0; j < indent; j++)
|
|
sb.Append (' ');
|
|
} else {
|
|
sb.Append (String.Format ("\\x{0:x2}", buf[i]));
|
|
}
|
|
}
|
|
|
|
return sb.ToString ();
|
|
}
|
|
|
|
static void DumpByteArray (byte[] buf, int indent)
|
|
{
|
|
Console.Write (ByteArrayToString (buf, indent));
|
|
}
|
|
|
|
static int GetResponseContentOffset (byte[] response)
|
|
{
|
|
for (int i = 0; i < response.Length - 3; i++) {
|
|
if (response[i + 0] == '\r' && response[i + 1] == '\n' &&
|
|
response[i + 2] == '\r' && response[i + 3] == '\n')
|
|
return i + 3;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static bool ResponseMatches (byte[] expected, byte[] actual)
|
|
{
|
|
int i, j;
|
|
|
|
// First, we compare the first line of the response - they should match
|
|
for (i = 0; i < expected.Length && i < actual.Length; i++) {
|
|
if (actual[i] != expected[i]) {
|
|
// HTTP/1.1 vs HTTP/1.0
|
|
if (i == 7 && expected[0] == 'H' && expected[1] == 'T' && expected[2] == 'T' &&
|
|
expected[3] == 'P' && expected[4] == '/' && expected[5] == '1' && expected[6] == '.' &&
|
|
expected[7] == '1' && actual[7] == '0')
|
|
continue;
|
|
|
|
//Console.WriteLine ("\nFirst line of actual response did not match");
|
|
return false;
|
|
}
|
|
|
|
if (expected[i] == '\n')
|
|
break;
|
|
}
|
|
|
|
if (i >= actual.Length) {
|
|
//Console.WriteLine ("Actual response too short");
|
|
return false;
|
|
}
|
|
|
|
// now compare the content
|
|
i = GetResponseContentOffset (expected);
|
|
j = GetResponseContentOffset (actual);
|
|
|
|
for ( ; i < expected.Length && j < actual.Length; i++, j++) {
|
|
if (actual[j] != expected[i]) {
|
|
//Console.WriteLine ("Content of actual response did not match");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (i < expected.Length) {
|
|
//Console.WriteLine ("Expected more content data...");
|
|
return false;
|
|
}
|
|
|
|
if (j < actual.Length) {
|
|
//Console.WriteLine ("Got too much content data in the server response");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void CreateBinaryMethodInvoke (string assemblyName, string objectName, string methodName, out byte[] content)
|
|
{
|
|
string text = String.Format ("{0}, {1}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", objectName, assemblyName);
|
|
byte[] lead = new byte [] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 };
|
|
byte[] buf;
|
|
int index;
|
|
|
|
content = new byte [lead.Length + 1 + methodName.Length + 1 + 1 + text.Length + 1];
|
|
lead.CopyTo (content, 0);
|
|
index = lead.Length;
|
|
|
|
buf = Encoding.ASCII.GetBytes (methodName);
|
|
content[index++] = (byte) buf.Length;
|
|
buf.CopyTo (content, index);
|
|
index += buf.Length;
|
|
|
|
content[index++] = (byte) 0x12;
|
|
|
|
buf = Encoding.ASCII.GetBytes (text);
|
|
content[index++] = (byte) buf.Length;
|
|
buf.CopyTo (content, index);
|
|
index += buf.Length;
|
|
|
|
content[index] = (byte) 0x0b;
|
|
}
|
|
|
|
[Test]
|
|
[Category ("NotWorking")] // the faked request content string might be wrong?
|
|
public void TestBinaryTransport ()
|
|
{
|
|
string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
|
|
Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
|
|
NetworkStream stream = new NetworkStream (sock);
|
|
byte[] content, buf, outbuf;
|
|
|
|
CreateBinaryMethodInvoke (assemblyName, typeof (RemoteObject).FullName, "ReturnOne", out content);
|
|
|
|
// send our POST request
|
|
Send (stream, String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
|
|
"User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 6.0.6000.0; MS .NET Remoting; MS .NET CLR 2.0.50727.1433 )\r\n" +
|
|
"Content-Type: application/octet-stream\r\n" +
|
|
"Host: 127.0.0.1:{0}\r\n" +
|
|
"Content-Length: {1}\r\n" +
|
|
"Expect: 100-continue\r\n" +
|
|
"Connection: Keep-Alive\r\n" +
|
|
"\r\n", port, content.Length));
|
|
|
|
// create our expected response buffer
|
|
buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
|
|
Receive (stream, 1, out outbuf);
|
|
|
|
Assert.IsNotNull (outbuf, "Server continuation response is null");
|
|
Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
|
|
|
|
// send our content data
|
|
Send (stream, content);
|
|
|
|
// create our expected response buffer
|
|
buf = new byte[] { 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31,
|
|
0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d,
|
|
0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
|
|
0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61,
|
|
0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
|
|
0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74,
|
|
0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x0d,
|
|
0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
|
|
0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 0x45, 0x54,
|
|
0x20, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e,
|
|
0x67, 0x2c, 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e,
|
|
0x45, 0x54, 0x20, 0x43, 0x4c, 0x52, 0x20, 0x32,
|
|
0x2e, 0x30, 0x2e, 0x35, 0x30, 0x37, 0x32, 0x37,
|
|
0x2e, 0x31, 0x34, 0x33, 0x33, 0x0d, 0x0a, 0x43,
|
|
0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c,
|
|
0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32,
|
|
0x38, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11,
|
|
0x08, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00,
|
|
0x0b };
|
|
|
|
Receive (stream, 2, out outbuf);
|
|
|
|
Assert.IsNotNull (outbuf, "Server method-invoke response is null");
|
|
Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
|
|
|
|
stream.Close ();
|
|
}
|
|
|
|
[Test]
|
|
[Category ("NotWorking")] // The test itself passes, but the runtime goes infinite loop at end of nunit-console udner 2.4.8.
|
|
public void TestSoapTransport ()
|
|
{
|
|
string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
|
|
Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
|
|
NetworkStream stream = new NetworkStream (sock);
|
|
string methodName = "ReturnOne";
|
|
string headers, content;
|
|
byte[] buf, outbuf;
|
|
|
|
content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
|
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
|
|
"xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
|
|
"xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
|
|
"xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0/\" " +
|
|
"SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
|
|
"<SOAP-ENV:Body>\r\n" +
|
|
"<i2:{0} id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
|
|
"</i2:{0}>\r\n" +
|
|
"</SOAP-ENV:Body>\r\n" +
|
|
"</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
|
|
|
|
headers = String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
|
|
"User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 6.0.6000.0; MS .NET Remoting; MS .NET CLR 2.0.50727.1433 )\r\n" +
|
|
"Content-Type: text/xml; charset=\"utf-8\"\r\n" +
|
|
"SOAPAction: \"http://schemas.microsoft.com/clr/nsassem/{0}/{1}#{2}\"\r\n" +
|
|
"Host: 127.0.0.1:{3}\r\n" +
|
|
"Content-Length: {4}\r\n" +
|
|
"Expect: 100-continue\r\n" +
|
|
"Connection: Keep-Alive\r\n" +
|
|
"\r\n", typeof (RemoteObject).FullName, assemblyName, methodName, port, content.Length);
|
|
|
|
Send (stream, headers);
|
|
|
|
// create our expected response buffer
|
|
buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
|
|
Receive (stream, 1, out outbuf);
|
|
|
|
Assert.IsNotNull (outbuf, "Server continuation response is null");
|
|
Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
|
|
|
|
// send our content data
|
|
Send (stream, content);
|
|
|
|
// create our expected response buffer
|
|
#if MICROSOFT_DOTNET_SERVER
|
|
content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
|
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
|
|
"xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
|
|
"xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
|
|
"xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0\" " +
|
|
"SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
|
|
"<SOAP-ENV:Body>\r\n" +
|
|
"<i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
|
|
"<return>1</return>\r\n" +
|
|
"</i2:{0}Response>\r\n" +
|
|
"</SOAP-ENV:Body>\r\n" +
|
|
"</SOAP-ENV:Envelope>\r\n", methodName, typeof (RemoteObject).FullName, assemblyName);
|
|
#else
|
|
//slight differences in formatting
|
|
content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
|
|
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
|
|
"xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
|
|
"xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
|
|
"xmlns:clr=\"http://schemas.microsoft.com/clr/\" " +
|
|
"SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" +
|
|
" <SOAP-ENV:Body>\n" +
|
|
" <i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\n" +
|
|
" <return xsi:type=\"xsd:int\">1</return>\n" +
|
|
" </i2:{0}Response>\n" +
|
|
" </SOAP-ENV:Body>\n" +
|
|
"</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
|
|
#endif
|
|
|
|
headers = String.Format ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=\"utf-8\"\r\n" +
|
|
"Server: MS .NET Remoting, MS .NET CLR 2.0.50727.1433\r\n" +
|
|
"Content-Length: {0}\r\n\r\n", content.Length);
|
|
|
|
buf = Encoding.ASCII.GetBytes (headers + content);
|
|
|
|
Receive (stream, 2, out outbuf);
|
|
|
|
Assert.IsNotNull (outbuf, "Server method-invoke response is null");
|
|
Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
|
|
|
|
stream.Close ();
|
|
}
|
|
|
|
object mutex = new object ();
|
|
bool []retvals;
|
|
|
|
void MultiClientStart ()
|
|
{
|
|
int rv = 0;
|
|
|
|
// the prupose of this is just to block until all clients have been created
|
|
lock (mutex) {
|
|
rv++;
|
|
}
|
|
|
|
RemoteObject remObj = new RemoteObject ();
|
|
|
|
rv = remObj.Increment ();
|
|
|
|
// make sure the value returned hasn't been returned to another thread as well
|
|
lock (retvals) {
|
|
Assert.IsTrue (!retvals[rv], "RemoteObject.Increment() has already returned " + rv);
|
|
retvals[rv] = true;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
[Category ("NotWorking")] // disabled as it got not working by NUnit upgrade to 2.4.8
|
|
public void MultiClientConnection ()
|
|
{
|
|
int num_clients = 20;
|
|
|
|
Hashtable options = new Hashtable ();
|
|
options ["timeout"] = 10000; // 10s
|
|
options ["name"] = "MultiClientConnection"; // 10s
|
|
HttpClientChannel clientChannel = new HttpClientChannel (options, null);
|
|
ChannelServices.RegisterChannel (clientChannel);
|
|
try {
|
|
|
|
WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry (
|
|
typeof (RemoteObject), "http://127.0.0.1:9090/RemoteObject.rem");
|
|
RemotingConfiguration.RegisterWellKnownClientType (remoteType);
|
|
|
|
// start a bunch of clients...
|
|
Thread []clients = new Thread [num_clients];
|
|
retvals = new bool [num_clients];
|
|
|
|
lock (mutex) {
|
|
for (int i = 0; i < num_clients; i++) {
|
|
clients[i] = new Thread (MultiClientStart);
|
|
clients[i].Start ();
|
|
retvals[i] = false;
|
|
}
|
|
}
|
|
|
|
// wait for all clients to finish...
|
|
for (int i = 0; i < num_clients; i++)
|
|
clients[i].Join ();
|
|
|
|
for (int i = 0; i < num_clients; i++)
|
|
Assert.IsTrue (retvals[i], "RemoteObject.Incrememnt() didn't return a value of " + i);
|
|
|
|
} finally {
|
|
ChannelServices.UnregisterChannel (clientChannel);
|
|
}
|
|
}
|
|
}
|
|
}
|