Skip to content

Commit 7ad5d58

Browse files
committed
jupyter client tests
1 parent 15272e0 commit 7ad5d58

7 files changed

Lines changed: 190 additions & 158 deletions

File tree

‎pythonFiles/PythonTools/ipythonServer.py‎

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections import deque
1717
import os
1818
import warnings
19-
19+
from encodings import utf_8, ascii
2020
try:
2121
import thread
2222
except ImportError:
@@ -50,8 +50,8 @@
5050
from queue import Empty # Python 3
5151

5252
DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON') is not None
53-
TEST = os.environ.get('TEST_DJAYAMANNE_IPYTHON') is not None
54-
TEST = True
53+
TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST') is not None
54+
5555
# The great "support IPython 2, 3, 4" strat begins
5656
if not TEST:
5757
try:
@@ -146,7 +146,7 @@ def connect(self, port):
146146
_debug_write('Connecting to socket port: ' + str(port))
147147
self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
148148
self.conn.connect(('127.0.0.1', port))
149-
_debug_write('Connected to socket port: ' + str(port))
149+
_debug_write('Connected to socket')
150150

151151
# perform the handshake
152152
with self.send_lock:
@@ -171,31 +171,25 @@ def start_processing(self):
171171
# the next command.
172172
self.flush()
173173
self.conn.settimeout(10)
174-
print('check bytes')
175-
_debug_write('Read command bytes')
176174
try:
177175
inp = read_bytes(self.conn, 4)
178-
179-
# self.conn.settimeout(None)
180-
_debug_write('Command bytes received: ')
181-
176+
self.conn.settimeout(None)
182177
cmd = iPythonSocketServer._COMMANDS.get(inp)
183178
if inp and cmd is not None:
184179
id = ""
185180
try:
186181
if iPythonSocketServer._COMMANDS_WITH_IDS.get(inp) == True:
187-
id = read_string(self.conn)
188-
if TEST:
189-
self.replyWithError(inp, id)
190-
else:
191-
cmd(self, id)
182+
while True:
183+
try:
184+
id = read_string(self.conn)
185+
break
186+
except socket.timeout:
187+
pass
188+
cmd(self, id)
192189
else:
193-
if TEST:
194-
self.replyWithError(inp, id)
195-
else:
196-
cmd(self)
190+
cmd(self)
197191
except:
198-
self.replyWithError(inp, id)
192+
self.replyWithError(cmd, id)
199193
else:
200194
if inp:
201195
print ('unknown command', inp)
@@ -224,35 +218,35 @@ def start_processing(self):
224218
def check_for_exit_socket_loop(self):
225219
return self.exit_requested
226220

227-
def replyForTest(self, commandName, id):
228-
message = read_string(self.conn)
229-
with self.send_lock:
230-
_debug_write('Test response for Command: ' + commandName)
231-
write_bytes(self.conn, iPythonSocketServer._TEST)
232-
write_string(self.conn, commandName)
233-
write_string(self.conn, "" if id is None else id)
234-
write_string(self.conn, message)
235-
236221
def replyWithError(self, commandName, id):
237222
with self.send_lock:
238-
_debug_write('Replying with error')
223+
traceMessage = traceback.format_exc()
224+
_debug_write('Replying with error:' + traceMessage)
225+
239226
write_bytes(self.conn, iPythonSocketServer._EROR)
240227
write_string(self.conn, commandName)
241228
write_string(self.conn, "" if id is None else id)
229+
write_string(self.conn, traceMessage)
242230

243231
def _cmd_exit(self):
244232
"""exits the interactive process"""
245233
self.exit_requested = True
246234
self.exit_process()
247235

248-
def _cmd_ping(self):
236+
def _cmd_ping(self, id):
249237
"""ping"""
250238
_debug_write('Ping received')
251-
message = read_string(self.conn)
239+
while True:
240+
try:
241+
message = read_string(self.conn)
242+
break
243+
except socket.timeout:
244+
pass
252245
with self.send_lock:
253246
_debug_write('Pong response being sent out')
254247
write_bytes(self.conn, iPythonSocketServer._PONG)
255-
write_string(self.conn, "pong received with message" + message)
248+
write_string(self.conn, id)
249+
write_string(self.conn, message)
256250

257251
def _cmd_lstk(self, id):
258252
"""List kernel specs"""
@@ -359,6 +353,7 @@ def flush(self):
359353

360354
_COMMANDS_WITH_IDS = {
361355
to_bytes('lstk'): True,
356+
to_bytes('ping'): True,
362357
}
363358

364359

@@ -414,6 +409,9 @@ def watch(self):
414409
_debug_write('Socket port received: ' + str(port))
415410
server = iPythonSocketServer()
416411
server.connect(port)
412+
sys.__stdout__.write('Started')
413+
sys.__stdout__.write("\n")
414+
sys.__stdout__.flush()
417415
while True:
418416
try:
419417
self._process_request(self._input.readline())

‎src/client/jupyter/comms/ipythonAdapter.ts‎

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,27 @@ export class iPythonAdapter extends SocketCallbackHandler {
3636
}
3737
}
3838

39+
this.emit('handshake');
3940
return true;
4041
}
4142

4243
private pendingCommands = new Map<string, Deferred<any>>();
4344

44-
public listKernels(): Promise<string[]> {
45-
const def = createDeferred<string[]>()
45+
private createId<T>(): [Deferred<T>, string] {
46+
const def = createDeferred<T>()
4647
const id = this.idDispenser.Allocate().toString();
4748
this.pendingCommands.set(id, def);
49+
return [def, id];
50+
}
51+
private releaseId(id: string) {
52+
this.pendingCommands.delete(id);
53+
this.idDispenser.Free(parseInt(id));
54+
}
55+
56+
public listKernels(): Promise<string[]> {
57+
const [def, id] = this.createId<string[]>();
4858
this.SendRawCommand(Commands.ListKernelsBytes);
4959
this.stream.WriteString(id);
50-
5160
return def.promise;
5261
}
5362

@@ -59,7 +68,7 @@ export class iPythonAdapter extends SocketCallbackHandler {
5968
}
6069

6170
const def = this.pendingCommands.get(id);
62-
this.pendingCommands.delete(id);
71+
this.releaseId(id);
6372

6473
let kernelList: string[];
6574
try {
@@ -73,17 +82,23 @@ export class iPythonAdapter extends SocketCallbackHandler {
7382
def.resolve(kernelList);
7483
}
7584

76-
public ping() {
85+
public ping(message: string) {
86+
const [def, id] = this.createId<string[]>();
7787
this.SendRawCommand(Commands.PingBytes);
78-
this.stream.WriteString('Hello world from Type Script - Функция проверки ИНН и КПП - 长城!')
88+
this.stream.WriteString(id);
89+
this.stream.WriteString(message)
90+
return def.promise;
7991
}
8092

8193
private onPong() {
94+
const id = this.stream.readStringInTransaction();
8295
const message = this.stream.readStringInTransaction();
8396
if (message == undefined) {
8497
return;
8598
}
86-
this.emit("pong", message);
99+
const def = this.pendingCommands.get(id);
100+
this.releaseId(id);
101+
def.resolve(message);
87102
}
88103

89104
private onError() {
Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,71 @@ import { SocketServer } from '../../common/comms/socketServer';
33
import * as child_process from 'child_process';
44
import * as path from 'path';
55
import * as vscode from 'vscode';
6-
import {createDeferred} from '../../common/helpers';
6+
import { createDeferred, Deferred } from '../../common/helpers';
77

8-
export class Main {
8+
export class JupyterClient {
99
constructor(private outputChannel: vscode.OutputChannel) {
1010
}
1111

1212
private process: child_process.ChildProcess;
1313
private socketServer: SocketServer;
1414
private ipythonAdapter: iPythonAdapter;
15-
16-
public start() {
15+
public start(): Promise<any> {
1716
const pyFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'ipythonServer.py');
18-
this.startSocketServer().then(port => {
17+
return this.startSocketServer().then(port => {
1918
const def = createDeferred<any>();
2019
const newEnv = { "DEBUG_DJAYAMANNE_IPYTHON": "1" };
2120
Object.assign(newEnv, process.env);
21+
2222
this.process = child_process.spawn('python', [pyFile, port.toString()], { env: newEnv });
2323
this.process.stdout.setEncoding('utf8');
2424
this.process.stderr.setEncoding('utf8');
2525

26+
let processStarted = false;
27+
let handshakeDone = false;
28+
let isInTestRun = process.env['PYTHON_DONJAYAMANNE_TEST'] === "1";
29+
const testDef = createDeferred<any>();
30+
const promiseToResolve = isInTestRun ? testDef.resolve.bind(testDef) : def.resolve.bind(def);
31+
2632
this.process.stdout.on('data', (data: string) => {
33+
if (data.split(/\r?\n/g).some(line => line === 'Started')) {
34+
processStarted = true;
35+
if (processStarted && handshakeDone) {
36+
promiseToResolve();
37+
}
38+
return;
39+
}
2740
this.outputChannel.append(data);
2841
});
2942
this.process.stderr.on('data', (data: string) => {
3043
this.outputChannel.append(data);
3144
});
3245

33-
setTimeout(() => {
34-
//this.socketServer.
35-
this.ipythonAdapter.ping();
36-
this.ipythonAdapter.listKernels().then(x => {
37-
const y = x;
38-
}, reason => {
39-
const y = reason;
40-
})
41-
}, 1000);
46+
this.ipythonAdapter.on('handshake', () => {
47+
handshakeDone = true;
48+
if (processStarted && handshakeDone) {
49+
promiseToResolve()
50+
}
51+
});
52+
53+
// If we're testing, then test the ping and the pong
54+
if (isInTestRun) {
55+
testDef.promise.then(() => {
56+
// Ok everything has started, now test ping
57+
const msg1 = 'Hello world from Type Script - Функция проверки ИНН и КПП - 长城!1'
58+
const msg2 = 'Hello world from Type Script - Функция проверки ИНН и КПП - 长城!2'
59+
Promise.all<string>([this.ipythonAdapter.ping(msg1), this.ipythonAdapter.ping(msg2)]).then(msgs => {
60+
if (msgs.indexOf(msg1) === -1 || msgs.indexOf(msg2) === -1) {
61+
def.reject('msg1 or msg2 not returned');
62+
}
63+
else {
64+
def.resolve();
65+
}
66+
}).catch(reason => def.reject(reason));
67+
});
68+
}
69+
70+
return def.promise;
4271
});
4372
}
4473
private startSocketServer(): Promise<number> {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as child_process from 'child_process';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
4+
import { Kernel } from './kernel';
5+
import * as vscode from 'vscode';
6+
import { KernelspecMetadata, JupyterMessage } from './contracts';
7+
import { iPythonAdapter } from './comms/ipythonAdapter';
8+
import { SocketServer } from '../common/comms/socketServer';
9+
import { createDeferred } from '../common/helpers';
10+
import * as settings from '../common/configSettings';
11+
12+
const uuid = require('uuid');
13+
const pythonSettings = settings.PythonSettings.getInstance();
14+
15+
export class JupyterClientKernel extends Kernel {
16+
private process: child_process.ChildProcess;
17+
private socketServer: SocketServer;
18+
private ipythonAdapter: iPythonAdapter;
19+
20+
constructor(kernelSpec: KernelspecMetadata, language: string, private connection: any, private connectionFile: string, public kernelProcess?: child_process.ChildProcess) {
21+
super(kernelSpec, language);
22+
}
23+
24+
public start() {
25+
const pyFile = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'PythonTools', 'ipythonServer.py');
26+
this.startSocketServer().then(port => {
27+
const def = createDeferred<any>();
28+
const newEnv = { "DEBUG_DJAYAMANNE_IPYTHON": "1" };
29+
Object.assign(newEnv, process.env);
30+
this.process = child_process.spawn(pythonSettings.pythonPath, [pyFile, port.toString()], { env: newEnv });
31+
this.process.stdout.setEncoding('utf8');
32+
this.process.stderr.setEncoding('utf8');
33+
34+
this.process.stdout.on('data', (data: string) => {
35+
//this.outputChannel.append(data);
36+
});
37+
this.process.stderr.on('data', (data: string) => {
38+
//this.outputChannel.append(data);
39+
});
40+
});
41+
}
42+
private startSocketServer(): Promise<number> {
43+
this.socketServer = new SocketServer();
44+
this.ipythonAdapter = new iPythonAdapter(this.socketServer);
45+
return this.socketServer.Start();
46+
}
47+
48+
public interrupt(): any {
49+
};
50+
51+
public shutdown(restart?: boolean) {
52+
};
53+
54+
public execute(code: string, onResults: Function) {
55+
};
56+
57+
public executeWatch(code: string, onResults: Function) {
58+
};
59+
60+
public complete(code: string, onResults: Function) {
61+
};
62+
63+
public inspect(code: string, cursor_pos, onResults: Function) {
64+
};
65+
}

‎src/client/jupyter/main.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { JupyterSymbolProvider } from './editorIntegration/symbolProvider';
99
import { formatErrorForLogging } from '../common/utils';
1010
import * as telemetryHelper from '../common/telemetry';
1111
import * as telemetryContracts from '../common/telemetryContracts';
12-
import * as main from './comms/main';
12+
import * as main from './comms/jupyterClient';
1313

1414
// Todo: Refactor the error handling and displaying of messages
1515

@@ -79,7 +79,7 @@ export class Jupyter extends vscode.Disposable {
7979
this.status.setActiveKernel(this.kernel ? this.kernel.kernelSpec : null);
8080
}
8181
executeCode(code: string, language: string): Promise<any> {
82-
const m = new main.Main(this.outputChannel);
82+
const m = new main.JupyterClient(this.outputChannel);
8383
m.start();
8484
return Promise.resolve();
8585
// telemetryHelper.sendTelemetryEvent(telemetryContracts.Jupyter.Usage);

0 commit comments

Comments
 (0)