[Checkins] SVN: zc.ngi/trunk/src/zc/ngi/ Renamed some doctest files to .test to emphsize their testiness.
Jim Fulton
jim at zope.com
Tue Jul 6 06:52:38 EDT 2010
Log message for revision 114231:
Renamed some doctest files to .test to emphsize their testiness.
Changed:
D zc.ngi/trunk/src/zc/ngi/README.txt
A zc.ngi/trunk/src/zc/ngi/adapters.test
D zc.ngi/trunk/src/zc/ngi/adapters.txt
A zc.ngi/trunk/src/zc/ngi/async.test
D zc.ngi/trunk/src/zc/ngi/async.txt
A zc.ngi/trunk/src/zc/ngi/blocking.test
D zc.ngi/trunk/src/zc/ngi/blocking.txt
A zc.ngi/trunk/src/zc/ngi/message.test
D zc.ngi/trunk/src/zc/ngi/message.txt
A zc.ngi/trunk/src/zc/ngi/old.test
U zc.ngi/trunk/src/zc/ngi/tests.py
-=-
Deleted: zc.ngi/trunk/src/zc/ngi/README.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/README.txt 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/README.txt 2010-07-06 10:52:37 UTC (rev 114231)
@@ -1,489 +0,0 @@
-Much of the information here is a bit out of date, especially wrt the
-testing APIs. Testing is now much simpler than it used to be.
-See doc/index.txt.
-
-
-=========================
-Network Gateway Interface
-=========================
-
-Network programs are typically difficult to test because they require
-setting up network connections, clients, and servers. In addition,
-application code gets mixed up with networking code.
-
-The Network Gateway Interface (NGI) seeks to improve this situation by
-separating application code from network code. This allows
-application and network code to be tested independently and provides
-greater separation of concerns.
-
-There are several interfaces defined by the NGI:
-
-IImplementation
- APIs for implementing and connecting to TCP servers and for
- implemented and sending messages to UDP servers.
-
-IConnection
- Network connection implementation. This is the core interface that
- applications interact with,
-
-IConnectionHandler
- Application component that handles TCP network input.
-
-IClientConnectHandler
- Application callback that handles successful or failed outgoing
- TCP connections.
-
-IServer
- Application callback to handle incoming connections.
-
-IUDPHandler
- Application callback to handle incoming UDP messages.
-
-The interfaces are split between "implementation" and "application"
-interfaces. An implementation of the NGI provides IImplementation,
-IConnection, IListener, and IUDPListener. An application provides
-IConnectionHandler and one or more of IClientConnectHandler,
-IServer, or IUDPHandler.
-
-For more information, see interfaces.py.
-
-Testing Implementation
-======================
-
-These interface can have a number of implementations. The simplest
-implementation is the testing implementation, which is used to test
-application code.
-
- >>> import zc.ngi.testing
-
-The testing module provides IConnection, IConnector, and IListener
-implementations. We'll use this below to illustrate how application code
-is written.
-
-Implementing Network Clients
-============================
-
-Network clients make connections to and then use these connections to
-communicate with servers. To do so, a client must be provided with an
-IConnector implementation. How this happens is outside the scope of
-the NGI. An IConnector implementation could, for example, be provided
-via the Zope component architecture, or via pkg_resources entry
-points.
-
-Let's create a simple client that calls an echo server and verifies
-that the server properly echoes data sent do it.
-
- >>> class EchoClient:
- ...
- ... def __init__(self, connect):
- ... self.connect = connect
- ...
- ... def check(self, addr, strings):
- ... self.strings = strings
- ... self.connect(addr, self)
- ...
- ... def connected(self, connection):
- ... for s in self.strings:
- ... connection.write(s + '\n')
- ... self.input = ''
- ... connection.setHandler(self)
- ...
- ... def failed_connect(self, reason):
- ... print 'failed connect:', reason
- ...
- ... def handle_input(self, connection, data):
- ... print 'got input:', repr(data)
- ... self.input += data
- ... while '\n' in self.input:
- ... data, self.input = self.input.split('\n', 1)
- ... if self.strings:
- ... expected = self.strings.pop(0)
- ... if data == expected:
- ... print 'matched:', data
- ... else:
- ... print 'unmatched:', data
- ... if not self.strings:
- ... connection.close()
- ... else:
- ... print 'Unexpected input', data
- ...
- ... def handle_close(self, connection, reason):
- ... print 'closed:', reason
- ... if self.strings:
- ... print 'closed prematurely'
- ...
- ... def handle_exception(self, connection, exception):
- ... print 'exception:', exception.__class__.__name__, exception
-
-
-The client implements the IClientConnectHandler and IConnectionHandler
-interfaces. More complex clients might implement these interfaces with
-separate classes.
-
-We'll instantiate our client using the testing connect:
-
- >>> client = EchoClient(zc.ngi.testing.connect)
-
-Now we'll try to check a non-existent server:
-
- >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
- failed connect: no such server
-
-Our client simply prints a message (and gives up) if a connection
-fails. More complex applications might retry, waiting between attempts,
-and so on.
-
-The testing connect always fails unless given a test connection
-ahead of time. We'll create a testing connection and register it so a
-connection can succeed:
-
- >>> connection = zc.ngi.testing.Connection()
- >>> zc.ngi.testing.connectable(('localhost', 42), connection)
-
-We can register multiple connections with the same address:
-
- >>> connection2 = zc.ngi.testing.Connection()
- >>> zc.ngi.testing.connectable(('localhost', 42), connection2)
-
-The connections will be used in order.
-
-Now, our client should be able to connect to the first connection we
-created:
-
- >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
- -> 'hello\n'
- -> 'world\n'
- -> 'how are you?\n'
-
-The test connection echoes data written to it, preceded by "-> ".
-
-Active connections are true:
-
- >>> bool(connection2)
- True
-
-Test connections provide methods generating test input and flow closing
-connections. We can use these to simulate network events. Let's
-generate some input for our client:
-
- >>> connection.test_input('hello')
- got input: 'hello'
-
- >>> connection.test_input('\nbob\n')
- got input: '\nbob\n'
- matched: hello
- unmatched: bob
-
- >>> connection.test_close('done')
- closed: done
- closed prematurely
-
- >>> client.check(('localhost', 42), ['hello'])
- -> 'hello\n'
-
- >>> connection2.test_input('hello\n')
- got input: 'hello\n'
- matched: hello
- -> CLOSE
-
- >>> bool(connection2)
- False
-
-Passing iterables to connections
-================================
-
-The writelines method of IConnection accepts iterables of strings.
-
- >>> def greet():
- ... yield 'hello\n'
- ... yield 'world\n'
-
- >>> zc.ngi.testing.Connection().writelines(greet())
- -> 'hello\n'
- -> 'world\n'
-
-If there is an error in your iterator, or if the iterator returns
-a non-string value, an exception will be reported using
-handle_exception:
-
- >>> def bad():
- ... yield 2
- >>> connection = zc.ngi.testing.Connection()
- >>> connection.setHandler(zc.ngi.testing.PrintingHandler(connection))
- >>> connection.writelines(bad())
- -> EXCEPTION TypeError Got a non-string result from iterable
-
-
-Implementing network servers
-============================
-
-Implementing network servers is very similar to implementing clients,
-except that a listener, rather than a connect is used. Let's
-implement a simple echo server:
-
-
- >>> class EchoServer:
- ...
- ... def __init__(self, connection):
- ... print 'server connected'
- ... self.input = ''
- ... connection.setHandler(self)
- ...
- ... def handle_input(self, connection, data):
- ... print 'server got input:', repr(data)
- ... self.input += data
- ... if '\n' in self.input:
- ... data, self.input = self.input.split('\n', 1)
- ... connection.write(data + '\n')
- ... if data == 'Q':
- ... connection.close()
- ...
- ... def handle_close(self, connection, reason):
- ... print 'server closed:', reason
-
-Our EchoServer *class* provides IServer and implements IInputHandler.
-
-To use a server, we need a listener. We'll use the use the testing
-listener:
-
- >>> listener = zc.ngi.testing.listener(EchoServer)
-
-To simulate a client connection, we create a testing connection and
-call the listener's connect method:
-
- >>> connection = zc.ngi.testing.Connection()
- >>> listener.connect(connection)
- server connected
-
- >>> connection.test_input('hello\n')
- server got input: 'hello\n'
- -> 'hello\n'
-
- >>> connection.test_close('done')
- server closed: done
-
- >>> connection = zc.ngi.testing.Connection()
- >>> listener.connect(connection)
- server connected
-
- >>> connection.test_input('hello\n')
- server got input: 'hello\n'
- -> 'hello\n'
-
- >>> connection.test_input('Q\n')
- server got input: 'Q\n'
- -> 'Q\n'
- -> CLOSE
-
-Note that it is an error to write to a closed connection:
-
- >>> connection.write('Hello')
- Traceback (most recent call last):
- ...
- TypeError: Connection closed
-
-
-Server Control
---------------
-
-The object returned from a listener is an IServerControl. It provides
-access to the active connections:
-
- >>> list(listener.connections())
- []
-
- >>> connection = zc.ngi.testing.Connection()
- >>> listener.connect(connection)
- server connected
-
- >>> list(listener.connections()) == [connection]
- True
-
- >>> connection2 = zc.ngi.testing.Connection()
- >>> listener.connect(connection2)
- server connected
-
- >>> len(list(listener.connections()))
- 2
- >>> connection in list(listener.connections())
- True
- >>> connection2 in list(listener.connections())
- True
-
-Server connections have a control attribute that is the connection's
-server control:
-
- >>> connection.control is listener
- True
-
-Server control objects provide a close method that allows a server to
-be shut down. If the close method is called without arguments, then
-then all server connections are closed immediately and no more
-connections are accepted:
-
- >>> listener.close()
- server closed: stopped
- server closed: stopped
-
- >>> connection = zc.ngi.testing.Connection()
- >>> listener.connect(connection)
- Traceback (most recent call last):
- ...
- TypeError: Listener closed
-
-If a handler function is passed, then connections aren't closed
-immediately:
-
- >>> listener = zc.ngi.testing.listener(EchoServer)
- >>> connection = zc.ngi.testing.Connection()
- >>> listener.connect(connection)
- server connected
- >>> connection2 = zc.ngi.testing.Connection()
- >>> listener.connect(connection2)
- server connected
-
- >>> def handler(control):
- ... if control is listener:
- ... print 'All connections closed'
-
- >>> listener.close(handler)
-
-But no more connections are accepted:
-
- >>> connection3 = zc.ngi.testing.Connection()
- >>> listener.connect(connection3)
- Traceback (most recent call last):
- ...
- TypeError: Listener closed
-
-And the handler will be called when all of the listener's connections
-are closed:
-
- >>> connection.close()
- -> CLOSE
- >>> connection2.close()
- -> CLOSE
- All connections closed
-
-Long output
-===========
-
-Test requests output data written to them. If output exceeds 50
-characters in length, it is wrapped by simply breaking the repr into
-50-characters parts:
-
- >>> connection = zc.ngi.testing.Connection()
- >>> connection.write('hello ' * 50)
- -> 'hello hello hello hello hello hello hello hello h
- .> ello hello hello hello hello hello hello hello hel
- .> lo hello hello hello hello hello hello hello hello
- .> hello hello hello hello hello hello hello hello h
- .> ello hello hello hello hello hello hello hello hel
- .> lo hello hello hello hello hello hello hello hello
- .> '
-
-Text output
-===========
-
-If the output from an application consists of short lines of text, a
-TextConnection can be used. A TextConnection simply outputs its data
-directly.
-
- >>> connection = zc.ngi.testing.TextConnection()
- >>> connection.write('hello\nworld\n')
- hello
- world
-
-END_OF_DATA
-===========
-
-Closing a connection closes it immediately, without sending any
-pending data. An alternate way to close a connection is to write
-zc.ngi.END_OF_DATA. The connection will be automatically closed when
-zc.ngi.END_OF_DATA is encountered in the output stream.
-
- >>> connection.write(zc.ngi.END_OF_DATA)
- -> CLOSE
-
- >>> connection.write('Hello')
- Traceback (most recent call last):
- ...
- TypeError: Connection closed
-
-Connecting servers and clients
-==============================
-
-It is sometimes useful to connect a client handler and a server
-handler. Listeners created with the zc.ngi.testing.listener class have a
-connect method that can be used to create connections to a server.
-
-Let's connect our echo server and client. First, we'll create our
-server using the listener constructor:
-
- >>> listener = zc.ngi.testing.listener(EchoServer)
-
-Then we'll use the connect method on the listener:
-
- >>> client = EchoClient(listener.connect)
- >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
- server connected
- server got input: 'hello\n'
- server got input: 'world\n'
- server got input: 'how are you?\n'
- got input: 'hello\nworld\nhow are you?\n'
- matched: hello
- matched: world
- matched: how are you?
- server closed: closed
-
-.. Peer connectors
-
- Below is an older API for connecting servers and clients in a
- testing environment. The mechanisms defined above are preferred.
-
- The zc.ngi.testing.peer function can be used to create a
- connection to a peer handler. To illustrate, we'll set up an echo
- client that connects to our echo server:
-
- >>> client = EchoClient(zc.ngi.testing.peer(('localhost', 42), EchoServer))
- >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
- server connected
- server got input: 'hello\n'
- server got input: 'world\n'
- server got input: 'how are you?\n'
- got input: 'hello\nworld\nhow are you?\n'
- matched: hello
- matched: world
- matched: how are you?
- server closed: closed
-
-UDP Support
-===========
-
-To send a UDP message, just use an implementation's udp method:
-
- >>> zc.ngi.testing.udp(('', 42), "hello")
-
-If there isn't a server listening, the call will effectively be
-ignored. This is UDP. :)
-
- >>> def my_udp_handler(addr, data):
- ... print 'from %r got %r' % (addr, data)
-
- >>> listener = zc.ngi.testing.udp_listener(('', 42), my_udp_handler)
-
- >>> zc.ngi.testing.udp(('', 42), "hello")
- from '<test>' got 'hello'
-
- >>> listener.close()
- >>> zc.ngi.testing.udp(('', 42), "hello")
-
-A default handler is used if you don't pass a handler:
-
- >>> listener = zc.ngi.testing.udp_listener(('', 43))
- >>> zc.ngi.testing.udp(('', 43), "hello")
- udp from '<test>' to ('', 43):
- 'hello'
-
- >>> listener.close()
- >>> zc.ngi.testing.udp(('', 43), "hello")
Copied: zc.ngi/trunk/src/zc/ngi/adapters.test (from rev 114228, zc.ngi/trunk/src/zc/ngi/adapters.txt)
===================================================================
--- zc.ngi/trunk/src/zc/ngi/adapters.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/adapters.test 2010-07-06 10:52:37 UTC (rev 114231)
@@ -0,0 +1,146 @@
+============
+NGI Adapters
+============
+
+The NGI is a fairly low-level event-based framework. Adapters can be
+used to build higher-level semantics. In this document, we'll describe
+some sample adapters that provide more examples of using the NGI and
+useful building blocks for other applications. The source for these
+adapters can be found in the zc.ngi.adapters module.
+
+Lines
+=====
+
+The first adapter we'll look at collects input into lines. To
+illustrate this, we'll use a handler from zc.ngi.testing that simply
+prints its input:
+
+ >>> import zc.ngi.testing
+ >>> connection = zc.ngi.testing.Connection()
+ >>> handler = zc.ngi.testing.PrintingHandler(connection)
+
+This handler is used by default as the peer
+of testing connections:
+
+ >>> connection.test_input('x' * 80)
+ -> 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ .> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+
+ >>> connection.test_close('test')
+ -> CLOSE test
+
+Now, we'll use the lines adapter to break input into lines, separated
+by newlines. We apply the adapter to a connection:
+
+ >>> import zc.ngi.adapters
+ >>> connection = zc.ngi.testing.Connection()
+ >>> adapter = zc.ngi.adapters.Lines(connection)
+ >>> handler = zc.ngi.testing.PrintingHandler(adapter)
+
+Now, when we provide input, it won't appear until lines are complete:
+
+ >>> connection.test_input('Hello world!')
+ >>> connection.test_input('\n')
+ -> 'Hello world!'
+
+ >>> connection.test_input('Hello\nWorld!\nHow are you')
+ -> 'Hello'
+ -> 'World!'
+
+Only input handling is affected. Other methods of the adapter behave
+as would the underlying connection:
+
+ >>> adapter.write('foo')
+ -> 'foo'
+
+ >>> adapter.writelines("%s\n" % foo for foo in range(3))
+ -> '0\n'
+ -> '1\n'
+ -> '2\n'
+
+.. again with feeling
+
+ >>> adapter.writelines("%s\n" % foo for foo in range(3))
+ -> '0\n'
+ -> '1\n'
+ -> '2\n'
+
+::
+ >>> connection.test_close('test')
+ -> CLOSE test
+
+The original connection is available in the connection attribute:
+
+ >>> adapter.connection is connection
+ True
+
+Sized Messages
+==============
+
+The second adapter we'll look at will handle binary data organized
+into sized messages. Each message has two parts, a length, and a
+payload. Of course, the length gives the length of the payload.
+
+To see this, we'll use the adapter to adapt a testing connection:
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> adapter = zc.ngi.adapters.Sized(connection)
+ >>> handler = zc.ngi.testing.PrintingHandler(adapter)
+
+Now, we'll generate some input. We do so by providing (big-endian) sizes by
+calling struct pack:
+
+ >>> import struct
+ >>> message1 = 'Hello\nWorld!\nHow are you?'
+ >>> message2 = 'This is message 2'
+ >>> connection.test_input(struct.pack(">I", len(message1)))
+ >>> connection.test_input(message1[:10])
+ >>> connection.test_input(message1[10:]+ struct.pack(">I", len(message2)))
+ -> 'Hello\nWorld!\nHow are you?'
+
+ >>> connection.test_input(message2)
+ -> 'This is message 2'
+
+Here we saw that our handler got the two messages individually.
+
+If we write a message, we can see that the message is preceded by the
+message size:
+
+ >>> adapter.write(message1)
+ -> '\x00\x00\x00\x19'
+ -> 'Hello\nWorld!\nHow are you?'
+
+We can give multiple messages using writelines:
+
+ >>> adapter.writelines("%s\n" % foo for foo in range(3))
+ -> '\x00\x00\x00\x02'
+ -> '0\n'
+ -> '\x00\x00\x00\x02'
+ -> '1\n'
+ -> '\x00\x00\x00\x02'
+ -> '2\n'
+
+
+Null messages
+-------------
+
+It can be useful to send Null messages to make sure that a client is
+still connected. The sized adapter supports such messages. Calling
+write with None, sends a null message, which is a message with a
+length of 1 << 32 - 1 and no message data:
+
+ >>> adapter.write(None)
+ -> '\xff\xff\xff\xff'
+
+On input, Null messages are ignored by the sized adapter and are not
+sent to the application:
+
+ >>> connection.test_input('\xff\xff\xff\xff')
+
+Closing
+-------
+
+Closing works too.
+
+ >>> connection.test_close('test')
+ -> CLOSE test
Deleted: zc.ngi/trunk/src/zc/ngi/adapters.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/adapters.txt 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/adapters.txt 2010-07-06 10:52:37 UTC (rev 114231)
@@ -1,146 +0,0 @@
-============
-NGI Adapters
-============
-
-The NGI is a fairly low-level event-based framework. Adapters can be
-used to build higher-level semantics. In this document, we'll describe
-some sample adapters that provide more examples of using the NGI and
-useful building blocks for other applications. The source for these
-adapters can be found in the zc.ngi.adapters module.
-
-Lines
-=====
-
-The first adapter we'll look at collects input into lines. To
-illustrate this, we'll use a handler from zc.ngi.testing that simply
-prints its input:
-
- >>> import zc.ngi.testing
- >>> connection = zc.ngi.testing.Connection()
- >>> handler = zc.ngi.testing.PrintingHandler(connection)
-
-This handler is used by default as the peer
-of testing connections:
-
- >>> connection.test_input('x' * 80)
- -> 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- .> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
-
- >>> connection.test_close('test')
- -> CLOSE test
-
-Now, we'll use the lines adapter to break input into lines, separated
-by newlines. We apply the adapter to a connection:
-
- >>> import zc.ngi.adapters
- >>> connection = zc.ngi.testing.Connection()
- >>> adapter = zc.ngi.adapters.Lines(connection)
- >>> handler = zc.ngi.testing.PrintingHandler(adapter)
-
-Now, when we provide input, it won't appear until lines are complete:
-
- >>> connection.test_input('Hello world!')
- >>> connection.test_input('\n')
- -> 'Hello world!'
-
- >>> connection.test_input('Hello\nWorld!\nHow are you')
- -> 'Hello'
- -> 'World!'
-
-Only input handling is affected. Other methods of the adapter behave
-as would the underlying connection:
-
- >>> adapter.write('foo')
- -> 'foo'
-
- >>> adapter.writelines("%s\n" % foo for foo in range(3))
- -> '0\n'
- -> '1\n'
- -> '2\n'
-
-.. again with feeling
-
- >>> adapter.writelines("%s\n" % foo for foo in range(3))
- -> '0\n'
- -> '1\n'
- -> '2\n'
-
-::
- >>> connection.test_close('test')
- -> CLOSE test
-
-The original connection is available in the connection attribute:
-
- >>> adapter.connection is connection
- True
-
-Sized Messages
-==============
-
-The second adapter we'll look at will handle binary data organized
-into sized messages. Each message has two parts, a length, and a
-payload. Of course, the length gives the length of the payload.
-
-To see this, we'll use the adapter to adapt a testing connection:
-
- >>> connection = zc.ngi.testing.Connection()
- >>> adapter = zc.ngi.adapters.Sized(connection)
- >>> handler = zc.ngi.testing.PrintingHandler(adapter)
-
-Now, we'll generate some input. We do so by providing (big-endian) sizes by
-calling struct pack:
-
- >>> import struct
- >>> message1 = 'Hello\nWorld!\nHow are you?'
- >>> message2 = 'This is message 2'
- >>> connection.test_input(struct.pack(">I", len(message1)))
- >>> connection.test_input(message1[:10])
- >>> connection.test_input(message1[10:]+ struct.pack(">I", len(message2)))
- -> 'Hello\nWorld!\nHow are you?'
-
- >>> connection.test_input(message2)
- -> 'This is message 2'
-
-Here we saw that our handler got the two messages individually.
-
-If we write a message, we can see that the message is preceded by the
-message size:
-
- >>> adapter.write(message1)
- -> '\x00\x00\x00\x19'
- -> 'Hello\nWorld!\nHow are you?'
-
-We can give multiple messages using writelines:
-
- >>> adapter.writelines("%s\n" % foo for foo in range(3))
- -> '\x00\x00\x00\x02'
- -> '0\n'
- -> '\x00\x00\x00\x02'
- -> '1\n'
- -> '\x00\x00\x00\x02'
- -> '2\n'
-
-
-Null messages
--------------
-
-It can be useful to send Null messages to make sure that a client is
-still connected. The sized adapter supports such messages. Calling
-write with None, sends a null message, which is a message with a
-length of 1 << 32 - 1 and no message data:
-
- >>> adapter.write(None)
- -> '\xff\xff\xff\xff'
-
-On input, Null messages are ignored by the sized adapter and are not
-sent to the application:
-
- >>> connection.test_input('\xff\xff\xff\xff')
-
-Closing
--------
-
-Closing works too.
-
- >>> connection.test_close('test')
- -> CLOSE test
Copied: zc.ngi/trunk/src/zc/ngi/async.test (from rev 114228, zc.ngi/trunk/src/zc/ngi/async.txt)
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/async.test 2010-07-06 10:52:37 UTC (rev 114231)
@@ -0,0 +1,250 @@
+=================================
+asyncore-based NGI implementation
+=================================
+
+The async module provides an NGI implementation based on the Python
+standard asyncore framework. It provides 2 objects to be invoked
+directly by applications:
+
+connector
+ an implementation of the NGI IConnector interface
+
+listener
+ an implementation of the NGI IListener interface
+
+The implementation creates a dedicated thread to run an asyncore main
+loop on import.
+
+There's nothing else to say about the implementation from a usage
+point of view. The remainder of this document provides a
+demonstration (test) of using the implementation to create a simple
+word-count server and client.
+
+Demonstration: wordcount
+========================
+
+The wordcount module has a simple word-count server and client
+implementation. We'll run these using the async implementation.
+
+Let's start the wordcount server:
+
+ >>> import zc.ngi.wordcount
+ >>> import zc.ngi.async
+ >>> port = zc.ngi.wordcount.start_server_process(zc.ngi.async.listener)
+
+We passed the listener to be used.
+
+Now, we'll start a number of threads that connect to the server and
+check word counts of some sample documents. If all goes well, we
+shouldn't get any output.
+
+ >>> import threading
+ >>> addr = 'localhost', port
+ >>> threads = [threading.Thread(target=zc.ngi.wordcount.client_thread,
+ ... args=(zc.ngi.async.connect, addr))
+ ... for i in range(200)]
+
+ >>> _ = [thread.start() for thread in threads]
+ >>> _ = [thread.join() for thread in threads]
+
+Iterable input
+==============
+
+We can pass data to the server using an iterator. To illustrate this,
+we'll use the blocking interface:
+
+ >>> import zc.ngi.blocking
+ >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
+ ... timeout=1.0)
+ >>> def hello(name):
+ ... yield "hello\n"
+ ... yield name
+ ... yield "\0"
+
+ >>> output.writelines(hello('world'), timeout=1.0)
+ >>> input.readline(timeout=1.0)
+ '1 2 11\n'
+
+.. Error handling:
+
+ If we pass a non-iterable to writelines, we'll get an immediate
+ error. To demonstrate this we'll violate our output file and
+ access its _connection attribute so that we can bypass the check
+ in the blocking writelines method:
+
+ >>> output._connection.writelines(2) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ TypeError: ...
+
+ >>> output._connection.writelines('foo')
+ Traceback (most recent call last):
+ ...
+ AssertionError: writelines does not accept strings
+
+ If we pass an iterable that returns a non-string, we'll get a type
+ error when we try to read because handle_exception is called in
+ the input handler.
+
+ >>> output.writelines([2], timeout=0.1)
+ Traceback (most recent call last):
+ ...
+ Timeout
+
+ >>> input.readline()
+ Traceback (most recent call last):
+ ...
+ TypeError: ('writelines iterator must return strings', 2)
+
+ If there is an error, then the connection is closed:
+
+ >>> input.read()
+ ''
+
+ >>> output.write('hello')
+ Traceback (most recent call last):
+ ...
+ IOError: I/O operation on closed file
+
+ Handler errors cause connections to be closed. To see this, we'll
+ send the server an error message, which forces an error:
+
+ >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
+ ... timeout=1.0)
+ >>> output.write('E\0')
+ >>> input.read()
+ ''
+
+ Let's create some lame clients:
+
+ >>> import zope.testing.loggingsupport, logging
+ >>> loghandler = zope.testing.loggingsupport.InstalledHandler(
+ ... None, level=logging.ERROR)
+
+ >>> event = threading.Event()
+ >>> class LameClientConnectionHandler:
+ ... def connected(self, connection):
+ ... connection.setHandler(self)
+ ... raise ValueError('Broken connector')
+ ... def handle_close(self, conection, reason):
+ ... self.closed = reason
+ ... event.set()
+
+ >>> handler = LameClientConnectionHandler()
+ >>> zc.ngi.async.connect(addr, handler)
+ >>> _ = event.wait(1)
+
+ >>> print loghandler
+ zc.ngi.async.client ERROR
+ connection handler failed
+
+ >>> handler.closed
+ 'connection handler failed'
+
+
+ >>> loghandler.clear()
+ >>> event.clear()
+
+ >>> class LameClientConnectionHandler:
+ ... def connected(self, connection):
+ ... connection.setHandler(self)
+ ... connection.write('foo\0')
+ ...
+ ... def handle_input(self, data):
+ ... raise ValueError()
+ ...
+ ... def handle_close(self, conection, reason):
+ ... self.closed = reason
+ ... event.set()
+
+ >>> handler = LameClientConnectionHandler()
+ >>> zc.ngi.async.connect(addr, handler)
+ >>> _ = event.wait(1)
+
+ >>> print loghandler
+ zc.ngi.async.client ERROR
+ handle_input failed
+ zc.ngi.async.client ERROR
+ handle_error
+
+ >>> handler.closed
+ TypeError('handle_input() takes exactly 2 arguments (3 given)',)
+
+ >>> loghandler.uninstall()
+
+
+.. stop the server
+
+ >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
+ ... # doctest: +ELLIPSIS
+ handle_input failed
+ Traceback (most recent call last):
+ ...
+ ValueError: E
+
+ The server log was printed. Note that we see the Error that we
+ requested above.
+
+Check logging
+=============
+
+ >>> import sys
+ >>> import time
+
+ >>> if sys.platform == 'win32':
+ ... addr = '127.0.0.1'
+ ... else:
+ ... addr = ''
+ >>> addrText = repr((addr, 9644))
+
+
+ >>> def handler(addr, message):
+ ... print message
+
+ >>> loghandler = zope.testing.loggingsupport.InstalledHandler(
+ ... None, level=logging.INFO)
+
+ >>> listener = zc.ngi.async.listener((addr, 9644), handler)
+ >>> time.sleep(0.1)
+
+ >>> logcontent = str(loghandler)
+ >>> print logcontent # doctest: +ELLIPSIS
+ zc.ngi.async.server INFO
+ listening on ('...', 9644)
+
+ >>> addrText in logcontent
+ True
+
+ >>> listener.close()
+ >>> time.sleep(0.1)
+
+
+Trying to rebind to a port in use:
+
+ >>> loghandler.clear()
+
+ >>> listener = zc.ngi.async.listener(('127.0.0.1', 9645), handler)
+ >>> time.sleep(0.1)
+
+ >>> listener2 = zc.ngi.async.listener(('127.0.0.1', 9645), handler)
+ ... # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ error:...Address already in use...
+
+ >>> time.sleep(0.1)
+
+ >>> logcontent = str(loghandler)
+ >>> print logcontent # doctest: +ELLIPSIS
+ zc.ngi.async.server INFO
+ listening on ('127.0.0.1', 9645)
+ zc.ngi.async.server WARNING
+ unable to listen on ('127.0.0.1', 9645)
+
+ >>> listener.close()
+ >>> zc.ngi.async.cleanup_map()
+ >>> zc.ngi.async.wait(1)
+ >>> loghandler.uninstall()
+
+.. cleanup
+
Deleted: zc.ngi/trunk/src/zc/ngi/async.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.txt 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/async.txt 2010-07-06 10:52:37 UTC (rev 114231)
@@ -1,250 +0,0 @@
-=================================
-asyncore-based NGI implementation
-=================================
-
-The async module provides an NGI implementation based on the Python
-standard asyncore framework. It provides 2 objects to be invoked
-directly by applications:
-
-connector
- an implementation of the NGI IConnector interface
-
-listener
- an implementation of the NGI IListener interface
-
-The implementation creates a dedicated thread to run an asyncore main
-loop on import.
-
-There's nothing else to say about the implementation from a usage
-point of view. The remainder of this document provides a
-demonstration (test) of using the implementation to create a simple
-word-count server and client.
-
-Demonstration: wordcount
-========================
-
-The wordcount module has a simple word-count server and client
-implementation. We'll run these using the async implementation.
-
-Let's start the wordcount server:
-
- >>> import zc.ngi.wordcount
- >>> import zc.ngi.async
- >>> port = zc.ngi.wordcount.start_server_process(zc.ngi.async.listener)
-
-We passed the listener to be used.
-
-Now, we'll start a number of threads that connect to the server and
-check word counts of some sample documents. If all goes well, we
-shouldn't get any output.
-
- >>> import threading
- >>> addr = 'localhost', port
- >>> threads = [threading.Thread(target=zc.ngi.wordcount.client_thread,
- ... args=(zc.ngi.async.connect, addr))
- ... for i in range(200)]
-
- >>> _ = [thread.start() for thread in threads]
- >>> _ = [thread.join() for thread in threads]
-
-Iterable input
-==============
-
-We can pass data to the server using an iterator. To illustrate this,
-we'll use the blocking interface:
-
- >>> import zc.ngi.blocking
- >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
- ... timeout=1.0)
- >>> def hello(name):
- ... yield "hello\n"
- ... yield name
- ... yield "\0"
-
- >>> output.writelines(hello('world'), timeout=1.0)
- >>> input.readline(timeout=1.0)
- '1 2 11\n'
-
-.. Error handling:
-
- If we pass a non-iterable to writelines, we'll get an immediate
- error. To demonstrate this we'll violate our output file and
- access its _connection attribute so that we can bypass the check
- in the blocking writelines method:
-
- >>> output._connection.writelines(2) # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- TypeError: ...
-
- >>> output._connection.writelines('foo')
- Traceback (most recent call last):
- ...
- AssertionError: writelines does not accept strings
-
- If we pass an iterable that returns a non-string, we'll get a type
- error when we try to read because handle_exception is called in
- the input handler.
-
- >>> output.writelines([2], timeout=0.1)
- Traceback (most recent call last):
- ...
- Timeout
-
- >>> input.readline()
- Traceback (most recent call last):
- ...
- TypeError: ('writelines iterator must return strings', 2)
-
- If there is an error, then the connection is closed:
-
- >>> input.read()
- ''
-
- >>> output.write('hello')
- Traceback (most recent call last):
- ...
- IOError: I/O operation on closed file
-
- Handler errors cause connections to be closed. To see this, we'll
- send the server an error message, which forces an error:
-
- >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
- ... timeout=1.0)
- >>> output.write('E\0')
- >>> input.read()
- ''
-
- Let's create some lame clients:
-
- >>> import zope.testing.loggingsupport, logging
- >>> loghandler = zope.testing.loggingsupport.InstalledHandler(
- ... None, level=logging.ERROR)
-
- >>> event = threading.Event()
- >>> class LameClientConnectionHandler:
- ... def connected(self, connection):
- ... connection.setHandler(self)
- ... raise ValueError('Broken connector')
- ... def handle_close(self, conection, reason):
- ... self.closed = reason
- ... event.set()
-
- >>> handler = LameClientConnectionHandler()
- >>> zc.ngi.async.connect(addr, handler)
- >>> _ = event.wait(1)
-
- >>> print loghandler
- zc.ngi.async.client ERROR
- connection handler failed
-
- >>> handler.closed
- 'connection handler failed'
-
-
- >>> loghandler.clear()
- >>> event.clear()
-
- >>> class LameClientConnectionHandler:
- ... def connected(self, connection):
- ... connection.setHandler(self)
- ... connection.write('foo\0')
- ...
- ... def handle_input(self, data):
- ... raise ValueError()
- ...
- ... def handle_close(self, conection, reason):
- ... self.closed = reason
- ... event.set()
-
- >>> handler = LameClientConnectionHandler()
- >>> zc.ngi.async.connect(addr, handler)
- >>> _ = event.wait(1)
-
- >>> print loghandler
- zc.ngi.async.client ERROR
- handle_input failed
- zc.ngi.async.client ERROR
- handle_error
-
- >>> handler.closed
- TypeError('handle_input() takes exactly 2 arguments (3 given)',)
-
- >>> loghandler.uninstall()
-
-
-.. stop the server
-
- >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
- ... # doctest: +ELLIPSIS
- handle_input failed
- Traceback (most recent call last):
- ...
- ValueError: E
-
- The server log was printed. Note that we see the Error that we
- requested above.
-
-Check logging
-=============
-
- >>> import sys
- >>> import time
-
- >>> if sys.platform == 'win32':
- ... addr = '127.0.0.1'
- ... else:
- ... addr = ''
- >>> addrText = repr((addr, 9644))
-
-
- >>> def handler(addr, message):
- ... print message
-
- >>> loghandler = zope.testing.loggingsupport.InstalledHandler(
- ... None, level=logging.INFO)
-
- >>> listener = zc.ngi.async.listener((addr, 9644), handler)
- >>> time.sleep(0.1)
-
- >>> logcontent = str(loghandler)
- >>> print logcontent # doctest: +ELLIPSIS
- zc.ngi.async.server INFO
- listening on ('...', 9644)
-
- >>> addrText in logcontent
- True
-
- >>> listener.close()
- >>> time.sleep(0.1)
-
-
-Trying to rebind to a port in use:
-
- >>> loghandler.clear()
-
- >>> listener = zc.ngi.async.listener(('127.0.0.1', 9645), handler)
- >>> time.sleep(0.1)
-
- >>> listener2 = zc.ngi.async.listener(('127.0.0.1', 9645), handler)
- ... # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- error:...Address already in use...
-
- >>> time.sleep(0.1)
-
- >>> logcontent = str(loghandler)
- >>> print logcontent # doctest: +ELLIPSIS
- zc.ngi.async.server INFO
- listening on ('127.0.0.1', 9645)
- zc.ngi.async.server WARNING
- unable to listen on ('127.0.0.1', 9645)
-
- >>> listener.close()
- >>> zc.ngi.async.cleanup_map()
- >>> zc.ngi.async.wait(1)
- >>> loghandler.uninstall()
-
-.. cleanup
-
Copied: zc.ngi/trunk/src/zc/ngi/blocking.test (from rev 114228, zc.ngi/trunk/src/zc/ngi/blocking.txt)
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/blocking.test 2010-07-06 10:52:37 UTC (rev 114231)
@@ -0,0 +1,194 @@
+The blocking module is deprecated.
+
+=======================
+Blocking network access
+=======================
+
+The NGI normally uses an event-based networking model in which
+application code reacts to incoming data. That model works well for
+some applications, especially server applications, but can be a bit of
+a bother for simpler applications, especially client applications.
+
+The zc.ngi.blocking module provides a simple blocking network model.
+The open function can be used to create a pair of file-like objects
+that can be used for writing output and reading input. To illustrate
+this, we'll use the wordcount server. We'll use the peer function to
+create a testing connector that connects to the server directory
+without using a network:
+
+ >>> import zc.ngi.wordcount
+ >>> import zc.ngi.testing
+ >>> connector = zc.ngi.testing.peer(('localhost', 42),
+ ... zc.ngi.wordcount.Server)
+
+The open function is called with an address and a connect callable:
+
+ >>> import zc.ngi.blocking
+ >>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
+
+The output file lets us send output to the server:
+
+ >>> output.write("Hello\n")
+ >>> output.write("world\n")
+ >>> output.write("\0")
+
+The wordcount server accepts a sequence of text from the
+client. Delimited by null characters. For each input text, it
+generates a line of summary statistics:
+
+ >>> input.readline()
+ '2 2 12\n'
+
+We can use the writelines method to send data using an iterator:
+
+ >>> def hello(name):
+ ... yield "hello\n"
+ ... yield name
+ ... yield "\0"
+
+ >>> output.writelines(hello("everyone"))
+ >>> output.writelines(hello("bob"))
+
+To close the connection to the server, we'll send a close command,
+which is a documenty consisting of the letter "C":
+
+ >>> output.write("C\0")
+
+This causes the server to close the connection after it has sent its
+data.
+
+We can use the read function to read either a fixed number of bytes
+from the server:
+
+ >>> input.read(5)
+ '1 2 1'
+
+Or to read the remaining data:
+
+ >>> input.read()
+ '4\n1 2 9\n'
+
+If read is called without a size, it won't return until the server has
+closed the connection.
+
+In this example, we've been careful to only read as much data as the
+server produces. For example, we called read without passing a length
+only after sending a quit command to the server. When using the
+blocking library, care is needed to avoid a deadlock, in which both
+sides of a connection are waiting for input.
+
+The blocking open and input methods accept an optional timeout
+argument. The timeout argument accepts a floating-point time-out
+value, in seconds. If a connection or input operation times out, a
+Timeout exception is raised:
+
+ >>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
+ >>> import time
+ >>> then = time.time()
+ >>> input.read(5, timeout=0.5)
+ Traceback (most recent call last):
+ ...
+ Timeout
+
+ >>> 0.5 <= (time.time() - then) < 1
+ True
+
+The readline and readlines functions accept a timeout as well:
+
+ >>> then = time.time()
+ >>> input.readline(timeout=0.5)
+ Traceback (most recent call last):
+ ...
+ Timeout
+
+ >>> 0.5 <= (time.time() - then) < 1
+ True
+
+ >>> then = time.time()
+ >>> input.readlines(timeout=0.5)
+ Traceback (most recent call last):
+ ...
+ Timeout
+
+ >>> 0.5 <= (time.time() - then) < 1
+ True
+
+Timeouts can also be specified when connecting. To illustrate this,
+we'll pass a do-nothing connector:
+
+ >>> then = time.time()
+ >>> zc.ngi.blocking.open(None, (lambda *args: None), timeout=0.5)
+ Traceback (most recent call last):
+ ...
+ ConnectionTimeout
+
+ >>> 0.5 <= (time.time() - then) < 1
+ True
+
+Low-level connection management
+===============================
+
+When we used open above, we passed an address and a connect callable, and the
+open function created a connection and created file-like objects for
+output and input. The connect function can be used to create a
+connection without a file-like object:
+
+ >>> connection = zc.ngi.blocking.connect(('localhost', 42), connector)
+
+The if the open function is called without a connect callable, the the first
+object must be a connection object and output and input objects for
+that connection will be returned:
+
+ >>> output, input = zc.ngi.blocking.open(connection)
+ >>> output.write("Hello\n")
+ >>> output.write("world\n")
+ >>> output.write("\0")
+ >>> input.readline()
+ '2 2 12\n'
+
+Like the open function, the connect function accepts a timeout:
+
+ >>> then = time.time()
+ >>> zc.ngi.blocking.connect(None, (lambda *args: None), timeout=0.5)
+ Traceback (most recent call last):
+ ...
+ ConnectionTimeout
+
+ >>> 0.5 <= (time.time() - then) < 1
+ True
+
+
+Blocking Client Requests
+========================
+
+ >>> import zc.ngi.generator
+ >>> @zc.ngi.generator.handler
+ ... def server(c):
+ ... while 1:
+ ... c.write((yield).upper())
+
+ >>> import zc.ngi.adapters
+ >>> @zc.ngi.adapters.Lines.handler
+ ... def client(c):
+ ... c.write('ho world\n')
+ ... print (yield)
+
+ >>> import zc.ngi.async
+ >>> address = 'ngiexample.zope.com', 9000
+ >>> zc.ngi.blocking.request(zc.ngi.async.connect, address, client)
+ ... # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ ConnectionFailed: ...
+
+The connection above failed because there wasn't a listener.
+Let's try after starting a listener:
+
+ >>> listener = zc.ngi.async.listener(None, server)
+ >>> address = listener.address
+
+ >>> zc.ngi.blocking.request(zc.ngi.async.connect, address, client)
+ HO WORLD
+
+ >>> listener.close()
+ >>> zc.ngi.async.wait(1)
Deleted: zc.ngi/trunk/src/zc/ngi/blocking.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.txt 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/blocking.txt 2010-07-06 10:52:37 UTC (rev 114231)
@@ -1,194 +0,0 @@
-The blocking module is deprecated.
-
-=======================
-Blocking network access
-=======================
-
-The NGI normally uses an event-based networking model in which
-application code reacts to incoming data. That model works well for
-some applications, especially server applications, but can be a bit of
-a bother for simpler applications, especially client applications.
-
-The zc.ngi.blocking module provides a simple blocking network model.
-The open function can be used to create a pair of file-like objects
-that can be used for writing output and reading input. To illustrate
-this, we'll use the wordcount server. We'll use the peer function to
-create a testing connector that connects to the server directory
-without using a network:
-
- >>> import zc.ngi.wordcount
- >>> import zc.ngi.testing
- >>> connector = zc.ngi.testing.peer(('localhost', 42),
- ... zc.ngi.wordcount.Server)
-
-The open function is called with an address and a connect callable:
-
- >>> import zc.ngi.blocking
- >>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
-
-The output file lets us send output to the server:
-
- >>> output.write("Hello\n")
- >>> output.write("world\n")
- >>> output.write("\0")
-
-The wordcount server accepts a sequence of text from the
-client. Delimited by null characters. For each input text, it
-generates a line of summary statistics:
-
- >>> input.readline()
- '2 2 12\n'
-
-We can use the writelines method to send data using an iterator:
-
- >>> def hello(name):
- ... yield "hello\n"
- ... yield name
- ... yield "\0"
-
- >>> output.writelines(hello("everyone"))
- >>> output.writelines(hello("bob"))
-
-To close the connection to the server, we'll send a close command,
-which is a documenty consisting of the letter "C":
-
- >>> output.write("C\0")
-
-This causes the server to close the connection after it has sent its
-data.
-
-We can use the read function to read either a fixed number of bytes
-from the server:
-
- >>> input.read(5)
- '1 2 1'
-
-Or to read the remaining data:
-
- >>> input.read()
- '4\n1 2 9\n'
-
-If read is called without a size, it won't return until the server has
-closed the connection.
-
-In this example, we've been careful to only read as much data as the
-server produces. For example, we called read without passing a length
-only after sending a quit command to the server. When using the
-blocking library, care is needed to avoid a deadlock, in which both
-sides of a connection are waiting for input.
-
-The blocking open and input methods accept an optional timeout
-argument. The timeout argument accepts a floating-point time-out
-value, in seconds. If a connection or input operation times out, a
-Timeout exception is raised:
-
- >>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
- >>> import time
- >>> then = time.time()
- >>> input.read(5, timeout=0.5)
- Traceback (most recent call last):
- ...
- Timeout
-
- >>> 0.5 <= (time.time() - then) < 1
- True
-
-The readline and readlines functions accept a timeout as well:
-
- >>> then = time.time()
- >>> input.readline(timeout=0.5)
- Traceback (most recent call last):
- ...
- Timeout
-
- >>> 0.5 <= (time.time() - then) < 1
- True
-
- >>> then = time.time()
- >>> input.readlines(timeout=0.5)
- Traceback (most recent call last):
- ...
- Timeout
-
- >>> 0.5 <= (time.time() - then) < 1
- True
-
-Timeouts can also be specified when connecting. To illustrate this,
-we'll pass a do-nothing connector:
-
- >>> then = time.time()
- >>> zc.ngi.blocking.open(None, (lambda *args: None), timeout=0.5)
- Traceback (most recent call last):
- ...
- ConnectionTimeout
-
- >>> 0.5 <= (time.time() - then) < 1
- True
-
-Low-level connection management
-===============================
-
-When we used open above, we passed an address and a connect callable, and the
-open function created a connection and created file-like objects for
-output and input. The connect function can be used to create a
-connection without a file-like object:
-
- >>> connection = zc.ngi.blocking.connect(('localhost', 42), connector)
-
-The if the open function is called without a connect callable, the the first
-object must be a connection object and output and input objects for
-that connection will be returned:
-
- >>> output, input = zc.ngi.blocking.open(connection)
- >>> output.write("Hello\n")
- >>> output.write("world\n")
- >>> output.write("\0")
- >>> input.readline()
- '2 2 12\n'
-
-Like the open function, the connect function accepts a timeout:
-
- >>> then = time.time()
- >>> zc.ngi.blocking.connect(None, (lambda *args: None), timeout=0.5)
- Traceback (most recent call last):
- ...
- ConnectionTimeout
-
- >>> 0.5 <= (time.time() - then) < 1
- True
-
-
-Blocking Client Requests
-========================
-
- >>> import zc.ngi.generator
- >>> @zc.ngi.generator.handler
- ... def server(c):
- ... while 1:
- ... c.write((yield).upper())
-
- >>> import zc.ngi.adapters
- >>> @zc.ngi.adapters.Lines.handler
- ... def client(c):
- ... c.write('ho world\n')
- ... print (yield)
-
- >>> import zc.ngi.async
- >>> address = 'ngiexample.zope.com', 9000
- >>> zc.ngi.blocking.request(zc.ngi.async.connect, address, client)
- ... # doctest: +ELLIPSIS
- Traceback (most recent call last):
- ...
- ConnectionFailed: ...
-
-The connection above failed because there wasn't a listener.
-Let's try after starting a listener:
-
- >>> listener = zc.ngi.async.listener(None, server)
- >>> address = listener.address
-
- >>> zc.ngi.blocking.request(zc.ngi.async.connect, address, client)
- HO WORLD
-
- >>> listener.close()
- >>> zc.ngi.async.wait(1)
Copied: zc.ngi/trunk/src/zc/ngi/message.test (from rev 114228, zc.ngi/trunk/src/zc/ngi/message.txt)
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/message.test 2010-07-06 10:52:37 UTC (rev 114231)
@@ -0,0 +1,41 @@
+==============
+Message Client
+==============
+
+The message client is a simple NGI client that sends a single message
+and waits for a response. To illustrate, we'll use a simple echo
+server:
+
+ >>> class EchoServer:
+ ...
+ ... def __init__(self, connection):
+ ... self.input = ''
+ ... connection.setHandler(self)
+ ...
+ ... def handle_input(self, connection, data):
+ ... self.input += data
+ ... if '\n' in self.input:
+ ... data, self.input = self.input.split('\n', 1)
+ ... connection.write(data.upper() + '\n')
+ ...
+ ... def handle_close(self, connection, reason):
+ ... pass
+
+ >>> import zc.ngi.testing
+ >>> connect = zc.ngi.testing.peer('foo', EchoServer)
+
+and we'll use the message client to send it a message and get a
+response.
+
+ >>> import zc.ngi.message
+ >>> import re
+ >>> expected = re.compile('\n').search
+ >>> zc.ngi.message.message(connect, 'foo', 'hello world!\n', expected)
+ 'HELLO WORLD!\n'
+
+If we give an invalid address, we'll get an exception:
+
+ >>> zc.ngi.message.message(connect, 'bar', 'hello world!\n', expected)
+ Traceback (most recent call last):
+ ...
+ CouldNotConnect: connection refused
Deleted: zc.ngi/trunk/src/zc/ngi/message.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.txt 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/message.txt 2010-07-06 10:52:37 UTC (rev 114231)
@@ -1,41 +0,0 @@
-==============
-Message Client
-==============
-
-The message client is a simple NGI client that sends a single message
-and waits for a response. To illustrate, we'll use a simple echo
-server:
-
- >>> class EchoServer:
- ...
- ... def __init__(self, connection):
- ... self.input = ''
- ... connection.setHandler(self)
- ...
- ... def handle_input(self, connection, data):
- ... self.input += data
- ... if '\n' in self.input:
- ... data, self.input = self.input.split('\n', 1)
- ... connection.write(data.upper() + '\n')
- ...
- ... def handle_close(self, connection, reason):
- ... pass
-
- >>> import zc.ngi.testing
- >>> connect = zc.ngi.testing.peer('foo', EchoServer)
-
-and we'll use the message client to send it a message and get a
-response.
-
- >>> import zc.ngi.message
- >>> import re
- >>> expected = re.compile('\n').search
- >>> zc.ngi.message.message(connect, 'foo', 'hello world!\n', expected)
- 'HELLO WORLD!\n'
-
-If we give an invalid address, we'll get an exception:
-
- >>> zc.ngi.message.message(connect, 'bar', 'hello world!\n', expected)
- Traceback (most recent call last):
- ...
- CouldNotConnect: connection refused
Copied: zc.ngi/trunk/src/zc/ngi/old.test (from rev 114228, zc.ngi/trunk/src/zc/ngi/README.txt)
===================================================================
--- zc.ngi/trunk/src/zc/ngi/old.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/old.test 2010-07-06 10:52:37 UTC (rev 114231)
@@ -0,0 +1,489 @@
+Much of the information here is a bit out of date, especially wrt the
+testing APIs. Testing is now much simpler than it used to be.
+See doc/index.txt.
+
+
+=========================
+Network Gateway Interface
+=========================
+
+Network programs are typically difficult to test because they require
+setting up network connections, clients, and servers. In addition,
+application code gets mixed up with networking code.
+
+The Network Gateway Interface (NGI) seeks to improve this situation by
+separating application code from network code. This allows
+application and network code to be tested independently and provides
+greater separation of concerns.
+
+There are several interfaces defined by the NGI:
+
+IImplementation
+ APIs for implementing and connecting to TCP servers and for
+ implemented and sending messages to UDP servers.
+
+IConnection
+ Network connection implementation. This is the core interface that
+ applications interact with,
+
+IConnectionHandler
+ Application component that handles TCP network input.
+
+IClientConnectHandler
+ Application callback that handles successful or failed outgoing
+ TCP connections.
+
+IServer
+ Application callback to handle incoming connections.
+
+IUDPHandler
+ Application callback to handle incoming UDP messages.
+
+The interfaces are split between "implementation" and "application"
+interfaces. An implementation of the NGI provides IImplementation,
+IConnection, IListener, and IUDPListener. An application provides
+IConnectionHandler and one or more of IClientConnectHandler,
+IServer, or IUDPHandler.
+
+For more information, see interfaces.py.
+
+Testing Implementation
+======================
+
+These interface can have a number of implementations. The simplest
+implementation is the testing implementation, which is used to test
+application code.
+
+ >>> import zc.ngi.testing
+
+The testing module provides IConnection, IConnector, and IListener
+implementations. We'll use this below to illustrate how application code
+is written.
+
+Implementing Network Clients
+============================
+
+Network clients make connections to and then use these connections to
+communicate with servers. To do so, a client must be provided with an
+IConnector implementation. How this happens is outside the scope of
+the NGI. An IConnector implementation could, for example, be provided
+via the Zope component architecture, or via pkg_resources entry
+points.
+
+Let's create a simple client that calls an echo server and verifies
+that the server properly echoes data sent do it.
+
+ >>> class EchoClient:
+ ...
+ ... def __init__(self, connect):
+ ... self.connect = connect
+ ...
+ ... def check(self, addr, strings):
+ ... self.strings = strings
+ ... self.connect(addr, self)
+ ...
+ ... def connected(self, connection):
+ ... for s in self.strings:
+ ... connection.write(s + '\n')
+ ... self.input = ''
+ ... connection.setHandler(self)
+ ...
+ ... def failed_connect(self, reason):
+ ... print 'failed connect:', reason
+ ...
+ ... def handle_input(self, connection, data):
+ ... print 'got input:', repr(data)
+ ... self.input += data
+ ... while '\n' in self.input:
+ ... data, self.input = self.input.split('\n', 1)
+ ... if self.strings:
+ ... expected = self.strings.pop(0)
+ ... if data == expected:
+ ... print 'matched:', data
+ ... else:
+ ... print 'unmatched:', data
+ ... if not self.strings:
+ ... connection.close()
+ ... else:
+ ... print 'Unexpected input', data
+ ...
+ ... def handle_close(self, connection, reason):
+ ... print 'closed:', reason
+ ... if self.strings:
+ ... print 'closed prematurely'
+ ...
+ ... def handle_exception(self, connection, exception):
+ ... print 'exception:', exception.__class__.__name__, exception
+
+
+The client implements the IClientConnectHandler and IConnectionHandler
+interfaces. More complex clients might implement these interfaces with
+separate classes.
+
+We'll instantiate our client using the testing connect:
+
+ >>> client = EchoClient(zc.ngi.testing.connect)
+
+Now we'll try to check a non-existent server:
+
+ >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
+ failed connect: no such server
+
+Our client simply prints a message (and gives up) if a connection
+fails. More complex applications might retry, waiting between attempts,
+and so on.
+
+The testing connect always fails unless given a test connection
+ahead of time. We'll create a testing connection and register it so a
+connection can succeed:
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> zc.ngi.testing.connectable(('localhost', 42), connection)
+
+We can register multiple connections with the same address:
+
+ >>> connection2 = zc.ngi.testing.Connection()
+ >>> zc.ngi.testing.connectable(('localhost', 42), connection2)
+
+The connections will be used in order.
+
+Now, our client should be able to connect to the first connection we
+created:
+
+ >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
+ -> 'hello\n'
+ -> 'world\n'
+ -> 'how are you?\n'
+
+The test connection echoes data written to it, preceded by "-> ".
+
+Active connections are true:
+
+ >>> bool(connection2)
+ True
+
+Test connections provide methods generating test input and flow closing
+connections. We can use these to simulate network events. Let's
+generate some input for our client:
+
+ >>> connection.test_input('hello')
+ got input: 'hello'
+
+ >>> connection.test_input('\nbob\n')
+ got input: '\nbob\n'
+ matched: hello
+ unmatched: bob
+
+ >>> connection.test_close('done')
+ closed: done
+ closed prematurely
+
+ >>> client.check(('localhost', 42), ['hello'])
+ -> 'hello\n'
+
+ >>> connection2.test_input('hello\n')
+ got input: 'hello\n'
+ matched: hello
+ -> CLOSE
+
+ >>> bool(connection2)
+ False
+
+Passing iterables to connections
+================================
+
+The writelines method of IConnection accepts iterables of strings.
+
+ >>> def greet():
+ ... yield 'hello\n'
+ ... yield 'world\n'
+
+ >>> zc.ngi.testing.Connection().writelines(greet())
+ -> 'hello\n'
+ -> 'world\n'
+
+If there is an error in your iterator, or if the iterator returns
+a non-string value, an exception will be reported using
+handle_exception:
+
+ >>> def bad():
+ ... yield 2
+ >>> connection = zc.ngi.testing.Connection()
+ >>> connection.setHandler(zc.ngi.testing.PrintingHandler(connection))
+ >>> connection.writelines(bad())
+ -> EXCEPTION TypeError Got a non-string result from iterable
+
+
+Implementing network servers
+============================
+
+Implementing network servers is very similar to implementing clients,
+except that a listener, rather than a connect is used. Let's
+implement a simple echo server:
+
+
+ >>> class EchoServer:
+ ...
+ ... def __init__(self, connection):
+ ... print 'server connected'
+ ... self.input = ''
+ ... connection.setHandler(self)
+ ...
+ ... def handle_input(self, connection, data):
+ ... print 'server got input:', repr(data)
+ ... self.input += data
+ ... if '\n' in self.input:
+ ... data, self.input = self.input.split('\n', 1)
+ ... connection.write(data + '\n')
+ ... if data == 'Q':
+ ... connection.close()
+ ...
+ ... def handle_close(self, connection, reason):
+ ... print 'server closed:', reason
+
+Our EchoServer *class* provides IServer and implements IInputHandler.
+
+To use a server, we need a listener. We'll use the use the testing
+listener:
+
+ >>> listener = zc.ngi.testing.listener(EchoServer)
+
+To simulate a client connection, we create a testing connection and
+call the listener's connect method:
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> listener.connect(connection)
+ server connected
+
+ >>> connection.test_input('hello\n')
+ server got input: 'hello\n'
+ -> 'hello\n'
+
+ >>> connection.test_close('done')
+ server closed: done
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> listener.connect(connection)
+ server connected
+
+ >>> connection.test_input('hello\n')
+ server got input: 'hello\n'
+ -> 'hello\n'
+
+ >>> connection.test_input('Q\n')
+ server got input: 'Q\n'
+ -> 'Q\n'
+ -> CLOSE
+
+Note that it is an error to write to a closed connection:
+
+ >>> connection.write('Hello')
+ Traceback (most recent call last):
+ ...
+ TypeError: Connection closed
+
+
+Server Control
+--------------
+
+The object returned from a listener is an IServerControl. It provides
+access to the active connections:
+
+ >>> list(listener.connections())
+ []
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> listener.connect(connection)
+ server connected
+
+ >>> list(listener.connections()) == [connection]
+ True
+
+ >>> connection2 = zc.ngi.testing.Connection()
+ >>> listener.connect(connection2)
+ server connected
+
+ >>> len(list(listener.connections()))
+ 2
+ >>> connection in list(listener.connections())
+ True
+ >>> connection2 in list(listener.connections())
+ True
+
+Server connections have a control attribute that is the connection's
+server control:
+
+ >>> connection.control is listener
+ True
+
+Server control objects provide a close method that allows a server to
+be shut down. If the close method is called without arguments, then
+then all server connections are closed immediately and no more
+connections are accepted:
+
+ >>> listener.close()
+ server closed: stopped
+ server closed: stopped
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> listener.connect(connection)
+ Traceback (most recent call last):
+ ...
+ TypeError: Listener closed
+
+If a handler function is passed, then connections aren't closed
+immediately:
+
+ >>> listener = zc.ngi.testing.listener(EchoServer)
+ >>> connection = zc.ngi.testing.Connection()
+ >>> listener.connect(connection)
+ server connected
+ >>> connection2 = zc.ngi.testing.Connection()
+ >>> listener.connect(connection2)
+ server connected
+
+ >>> def handler(control):
+ ... if control is listener:
+ ... print 'All connections closed'
+
+ >>> listener.close(handler)
+
+But no more connections are accepted:
+
+ >>> connection3 = zc.ngi.testing.Connection()
+ >>> listener.connect(connection3)
+ Traceback (most recent call last):
+ ...
+ TypeError: Listener closed
+
+And the handler will be called when all of the listener's connections
+are closed:
+
+ >>> connection.close()
+ -> CLOSE
+ >>> connection2.close()
+ -> CLOSE
+ All connections closed
+
+Long output
+===========
+
+Test requests output data written to them. If output exceeds 50
+characters in length, it is wrapped by simply breaking the repr into
+50-characters parts:
+
+ >>> connection = zc.ngi.testing.Connection()
+ >>> connection.write('hello ' * 50)
+ -> 'hello hello hello hello hello hello hello hello h
+ .> ello hello hello hello hello hello hello hello hel
+ .> lo hello hello hello hello hello hello hello hello
+ .> hello hello hello hello hello hello hello hello h
+ .> ello hello hello hello hello hello hello hello hel
+ .> lo hello hello hello hello hello hello hello hello
+ .> '
+
+Text output
+===========
+
+If the output from an application consists of short lines of text, a
+TextConnection can be used. A TextConnection simply outputs its data
+directly.
+
+ >>> connection = zc.ngi.testing.TextConnection()
+ >>> connection.write('hello\nworld\n')
+ hello
+ world
+
+END_OF_DATA
+===========
+
+Closing a connection closes it immediately, without sending any
+pending data. An alternate way to close a connection is to write
+zc.ngi.END_OF_DATA. The connection will be automatically closed when
+zc.ngi.END_OF_DATA is encountered in the output stream.
+
+ >>> connection.write(zc.ngi.END_OF_DATA)
+ -> CLOSE
+
+ >>> connection.write('Hello')
+ Traceback (most recent call last):
+ ...
+ TypeError: Connection closed
+
+Connecting servers and clients
+==============================
+
+It is sometimes useful to connect a client handler and a server
+handler. Listeners created with the zc.ngi.testing.listener class have a
+connect method that can be used to create connections to a server.
+
+Let's connect our echo server and client. First, we'll create our
+server using the listener constructor:
+
+ >>> listener = zc.ngi.testing.listener(EchoServer)
+
+Then we'll use the connect method on the listener:
+
+ >>> client = EchoClient(listener.connect)
+ >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
+ server connected
+ server got input: 'hello\n'
+ server got input: 'world\n'
+ server got input: 'how are you?\n'
+ got input: 'hello\nworld\nhow are you?\n'
+ matched: hello
+ matched: world
+ matched: how are you?
+ server closed: closed
+
+.. Peer connectors
+
+ Below is an older API for connecting servers and clients in a
+ testing environment. The mechanisms defined above are preferred.
+
+ The zc.ngi.testing.peer function can be used to create a
+ connection to a peer handler. To illustrate, we'll set up an echo
+ client that connects to our echo server:
+
+ >>> client = EchoClient(zc.ngi.testing.peer(('localhost', 42), EchoServer))
+ >>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
+ server connected
+ server got input: 'hello\n'
+ server got input: 'world\n'
+ server got input: 'how are you?\n'
+ got input: 'hello\nworld\nhow are you?\n'
+ matched: hello
+ matched: world
+ matched: how are you?
+ server closed: closed
+
+UDP Support
+===========
+
+To send a UDP message, just use an implementation's udp method:
+
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+
+If there isn't a server listening, the call will effectively be
+ignored. This is UDP. :)
+
+ >>> def my_udp_handler(addr, data):
+ ... print 'from %r got %r' % (addr, data)
+
+ >>> listener = zc.ngi.testing.udp_listener(('', 42), my_udp_handler)
+
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+ from '<test>' got 'hello'
+
+ >>> listener.close()
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+
+A default handler is used if you don't pass a handler:
+
+ >>> listener = zc.ngi.testing.udp_listener(('', 43))
+ >>> zc.ngi.testing.udp(('', 43), "hello")
+ udp from '<test>' to ('', 43):
+ 'hello'
+
+ >>> listener.close()
+ >>> zc.ngi.testing.udp(('', 43), "hello")
Modified: zc.ngi/trunk/src/zc/ngi/tests.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/tests.py 2010-07-06 10:46:19 UTC (rev 114230)
+++ zc.ngi/trunk/src/zc/ngi/tests.py 2010-07-06 10:52:37 UTC (rev 114231)
@@ -418,15 +418,15 @@
'doc/index.txt',
),
doctest.DocFileSuite(
- 'README.txt',
+ 'old.test',
'testing.test',
- 'message.txt',
- 'adapters.txt',
- 'blocking.txt',
+ 'message.test',
+ 'adapters.test',
+ 'blocking.test',
'async-udp.test',
),
doctest.DocFileSuite(
- 'async.txt',
+ 'async.test',
setUp=async_evil_setup, tearDown=cleanup_async,
),
doctest.DocTestSuite(setUp=cleanup_async, tearDown=cleanup_async),
More information about the checkins
mailing list