// Test sending and receiving a file descriptor. // // This test is pretty complex. It ends up spawning test/fixtures/recvfd.js // as a child to test desired behavior. What happens is // // 1. Create an in-memory pipe via pipe(2). These two file descriptors // are not visible to any other process, and so make a good test-case // for sharing. // 2. Create a a UNIX socket at SOCK_PATH. When a client connects to this // path, they are sent the write end of the pipe from above. // 3. The client is sent n JSON representations of the DATA variable, each // with a different ordinal. We send these delimited by '\n' strings // so that the receiving end can avoid any coalescing that hapepns // due to the stream nature of the socket (e.g. '{}{}' is not a valid // JSON string). // 4. The child process receives file descriptors and JSON blobs and, // whenever it has at least one of each, writes a modified JSON blob // to the FD. The blob is modified to include the child's process ID. // 5. Once the child process has sent n responses, it closes the write end // of the pipe, which signals to the parent that there is no more data // coming. // 6. The parent listens to the read end of the pipe, accumulating JSON // blobs (again, delimited by '\n') and verifying that a) the 'pid' // attribute belongs to the child and b) the 'ord' field has not been // seen in a response yet. This is intended to ensure that all blobs // sent out have been relayed back to us. common = require("../common"); assert = common.assert var buffer = require('buffer'); var child_process = require('child_process'); var fs = require('fs'); var net = require('net'); var netBinding = process.binding('net'); var path = require('path'); var sys = require('sys'); var DATA = { 'ppid' : process.pid, 'ord' : 0 }; var SOCK_PATH = path.join( __dirname, '..', path.basename(__filename, '.js') + '.sock' ); var logChild = function(d) { if (typeof d == 'object') { d = d.toString(); } d.split('\n').forEach(function(l) { if (l.length > 0) { common.debug('CHILD: ' + l); } }); }; // Create a pipe // // We establish a listener on the read end of the pipe so that we can // validate any data sent back by the child. We send the write end of the // pipe to the child and close it off in our process. var pipeFDs = netBinding.pipe(); assert.equal(pipeFDs.length, 2); var seenOrdinals = []; var pipeReadStream = new net.Stream(); pipeReadStream.addListener('data', function(data) { data.toString('utf8').trim().split('\n').forEach(function(d) { var rd = JSON.parse(d); assert.equal(rd.pid, cpp); assert.equal(seenOrdinals.indexOf(rd.ord), -1); seenOrdinals.unshift(rd.ord); }); }); pipeReadStream.open(pipeFDs[0]); pipeReadStream.resume(); // Create a UNIX socket at SOCK_PATH and send DATA and the write end // of the pipe to whoever connects. // // We send two messages here, both with the same pipe FD: one string, and // one buffer. We want to make sure that both datatypes are handled // correctly. var srv = net.createServer(function(s) { var str = JSON.stringify(DATA) + '\n'; DATA.ord = DATA.ord + 1; var buf = new buffer.Buffer(str.length); buf.write(JSON.stringify(DATA) + '\n', 'utf8'); s.write(str, 'utf8', pipeFDs[1]); if (s.write(buf, undefined, pipeFDs[1])) { netBinding.close(pipeFDs[1]); } else { s.addListener('drain', function() { netBinding.close(pipeFDs[1]); }); } }); srv.listen(SOCK_PATH); // Spawn a child running test/fixtures/recvfd.js var cp = child_process.spawn(process.argv[0], [path.join(common.fixturesDir, 'recvfd.js'), SOCK_PATH]); cp.stdout.addListener('data', logChild); cp.stderr.addListener('data', logChild); // When the child exits, clean up and validate its exit status var cpp = cp.pid; cp.addListener('exit', function(code, signal) { srv.close(); // fs.unlinkSync(SOCK_PATH); assert.equal(code, 0); assert.equal(seenOrdinals.length, 2); }); // vim:ts=2 sw=2 et