Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit test to demonstrate how to handle a multi-message conversation with child process #21

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,4 @@ PythonShell.prototype.end = function (callback) {
return this;
};

module.exports = PythonShell;
module.exports = PythonShell;
71 changes: 71 additions & 0 deletions test/python/conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import sys, json, time

ended = False

def is_json(myjson):
try:
json_object = json.loads(myjson)
except ValueError, e:
return False
return True

def makeError(reason):
ended = True
return {
'action': 'error',
'reason': reason
}

def handleKnockKnock(obj):
response = {
'action': 'knockknockjoke'
};

message = obj['message'];
if (message == 'Knock, knock.'):
response['message'] = "Who's there?"
return response;

if (message == 'Orange.'):
response['message'] = "Orange who?"
return response;

if (message == "Orange you glad I didn't say, 'banana'?"):
response['message'] = "Ha ha."
ended = True
return response;

return makeError('Unrecognised knock-knock phase.')

def handleAction(obj):
if 'action' not in obj:
return makeError("Unsupported input; expected 'action' key.")

action = obj['action']
if action == 'knockknockjoke':
return handleKnockKnock(obj)

return makeError("Unrecognised action: {0}".format(action))

def handleLine(line):
if not is_json(line):
return makeError('Malformed input could not be parsed as JSON: {0}'.format(line))

parsed = json.loads(line)

if type (parsed) != type({}):
return makeError('Malformed input: expected JSON object; received JSON primitive instead: {0}'.format(parsed))

return handleAction(parsed)

# simple JSON echo script
while not ended:
line = sys.stdin.readline()
if not line:
break
# for line in sys.stdin:

response = handleLine(line)

print json.dumps(response)
sys.stdout.flush()
159 changes: 122 additions & 37 deletions test/test-python-shell.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var should = require('should');
var PythonShell = require('..');
var util = require('util');
var os = require("os");

describe('PythonShell', function () {

Expand Down Expand Up @@ -63,32 +65,129 @@ describe('PythonShell', function () {
});

describe('.send(message)', function () {
it('should send string messages when mode is "text"', function (done) {
var pyshell = new PythonShell('echo_text.py', {
mode: 'text'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
context('text mode', function() {
it('should send string messages', function (done) {
var pyshell = new PythonShell('echo_text.py', {
mode: 'text'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
});
pyshell.send('hello').send('world').end(function (err) {
if (err) return done(err);
output.should.be.exactly('hello\nworld\n');
done();
});
});
})
context('JSON mode', function() {
it('should send JSON messages', function (done) {
var pyshell = new PythonShell('echo_json.py', {
mode: 'json'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
});
pyshell.send({ a: 'b' }).send(null).send([1, 2, 3]).end(function (err) {
if (err) return done(err);
output.should.be.exactly('{"a": "b"}\nnull\n[1, 2, 3]\n');
done();
});
});
pyshell.send('hello').send('world').end(function (err) {
if (err) return done(err);
output.should.be.exactly('hello\nworld\n');
done();
it('holds a conversation', function (done) {
var pyshell = new PythonShell('conversation.py', {
mode: 'json'
});
var receivedMessages = [];

function makeKnockKnockMessage(message) {
return {
action: 'knockknockjoke',
message: message
};
}

var outgoingMessages = [
"Knock, knock.",
"Orange.",
"Orange you glad I didn't say, 'banana'?"
];

var incomingMessages = [
"Who's there?",
"Orange who?",
"Ha ha."
];

var makeKnockKnockReply = makeKnockKnockMessage;

function handleReply(reply) {
switch(reply.message) {
case incomingMessages[0]:
pyshell.send(makeKnockKnockMessage(outgoingMessages[1]));
break;
case incomingMessages[1]:
pyshell.send(makeKnockKnockMessage(outgoingMessages[2]));
break;
case incomingMessages[2]:
endAndAssert();
break;
}
}

pyshell.on('message', function (message) {
receivedMessages.push(message);

switch(message.action) {
case 'knockknockjoke':
handleReply(message);
break;
default:
done(util.format("Unexpected action: '%s'", data.action))
}
});

pyshell.send(makeKnockKnockMessage(outgoingMessages[0]));

function endAndAssert() {
pyshell.end(function (err) {
if (err) {
return done(err);
}

should(receivedMessages[0])
.eql(makeKnockKnockReply(incomingMessages[0]),
"Correct knock-knock reply received.");

should(receivedMessages[1])
.eql(makeKnockKnockReply(incomingMessages[1]),
"Correct knock-knock reply received.");

should(receivedMessages[2])
.eql(makeKnockKnockReply(incomingMessages[2]),
"Correct knock-knock reply received.");

done();
});
}
});
});
it('should send JSON messages when mode is "json"', function (done) {
var pyshell = new PythonShell('echo_json.py', {
mode: 'json'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
});
pyshell.send({ a: 'b' }).send(null).send([1, 2, 3]).end(function (err) {
if (err) return done(err);
output.should.be.exactly('{"a": "b"}\nnull\n[1, 2, 3]\n');
done();
context('binary mode', function() {
it('should write as-is', function (done) {
var pyshell = new PythonShell('echo_binary.py', {
mode: 'binary'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
});
pyshell.send(new Buffer('i am not a string')).end(function (err) {
if (err) return done(err);
output.should.be.exactly('i am not a string');
done();
});
});
});
it('should use a custom formatter', function (done) {
Expand All @@ -107,20 +206,6 @@ describe('PythonShell', function () {
done();
});
});
it('should write as-is when mode is "binary"', function (done) {
var pyshell = new PythonShell('echo_binary.py', {
mode: 'binary'
});
var output = '';
pyshell.stdout.on('data', function (data) {
output += ''+data;
});
pyshell.send(new Buffer('i am not a string')).end(function (err) {
if (err) return done(err);
output.should.be.exactly('i am not a string');
done();
});
});
});

describe('.receive(data)', function () {
Expand Down