CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/terminal/lib/remote-pty.test.ts
Views: 687
1
/*
2
3
NOTE: these tests are pretty low level. We don't actually use a websocket at all,
4
and explicitly shuffle messages around.
5
6
---
7
*/
8
9
import { Terminal } from "./terminal";
10
import {
11
getOpts,
12
getPrimusMock,
13
isPidRunning,
14
waitForPidToChange,
15
} from "./support";
16
import { getRemotePtyChannelName, getChannelName } from "./util";
17
18
describe("tests remotePty connecting and handling data with **simulated** pty and explicitly pushing messages back and forth (for low level tests)", () => {
19
let terminal;
20
const { path, options } = getOpts();
21
const primus = getPrimusMock();
22
const channel = primus.channel(getChannelName(path));
23
const ptyChannel = primus.channel(getRemotePtyChannelName(path));
24
25
beforeAll(() => {
26
terminal = new Terminal(primus, path, options);
27
});
28
29
afterAll(() => {
30
terminal.close();
31
});
32
33
it("initialize the terminal", async () => {
34
await terminal.init();
35
expect(typeof terminal.getPid()).toBe("number");
36
});
37
38
let spark1, spark2;
39
it("create two clients connection to the terminal", async () => {
40
spark1 = channel.createSpark("192.168.2.1");
41
spark2 = channel.createSpark("192.168.2.2");
42
for (const s of [spark1, spark2]) {
43
// it initially tells us the current computeServerId, right when we connect.
44
const mesg1 = await s.waitForMessage();
45
expect(mesg1).toEqual({ cmd: "computeServerId", id: 0 });
46
const mesg2 = await s.waitForMessage();
47
expect(mesg2).toEqual({ cmd: "no-ignore" });
48
}
49
});
50
51
let remoteSpark;
52
it("connect to remote pty channel and observe that local terminal process terminates", async () => {
53
const pid = terminal.getPid();
54
remoteSpark = ptyChannel.createSpark("192.168.2.2");
55
expect(terminal.getPid()).toBe(undefined);
56
// check that original process is no longer running.
57
expect(await isPidRunning(pid)).toBe(false);
58
});
59
60
it("send data from spark1 and see that it is received by the remoteSpark, then respond and see that the client sparks both receive the response", async () => {
61
// reset client data state
62
spark1.data = spark2.data = "";
63
spark1.emit("data", "17+13");
64
expect(await remoteSpark.waitForData("17+13")).toBe("17+13");
65
remoteSpark.emit("data", "30");
66
expect(await spark1.waitForData("30")).toBe("30");
67
expect(await spark2.waitForData("30")).toBe("30");
68
});
69
70
it("disconect the remoteSpark and see that a local pty is spawned again", async () => {
71
remoteSpark.end();
72
await waitForPidToChange(terminal, 0);
73
const pid = terminal.getPid();
74
expect(await isPidRunning(pid)).toBe(true);
75
});
76
77
it("connect a remote pty again, send a kill command from one of the spark clients, and check that remote pty receives kill command", async () => {
78
remoteSpark = ptyChannel.createSpark("192.168.2.2");
79
expect((await remoteSpark.waitForMessage()).cmd).toBe("init");
80
spark1.emit("data", { cmd: "kill" });
81
expect(await remoteSpark.waitForMessage()).toEqual({ cmd: "kill" });
82
});
83
84
it("sends a change of commands and args from client and sees remote pty receives that", async () => {
85
const command = "/usr/bin/python3";
86
const args = ["-b"];
87
spark1.emit("data", {
88
cmd: "set_command",
89
command,
90
args,
91
});
92
expect(await remoteSpark.waitForMessage()).toEqual({
93
cmd: "set_command",
94
command,
95
args,
96
});
97
});
98
99
it("sends a size message from a client, and observes that remote pty receives a size message", async () => {
100
const rows = 10;
101
const cols = 50;
102
const mesg = { cmd: "size", rows, cols };
103
spark1.emit("data", mesg);
104
expect(await remoteSpark.waitForMessage()).toEqual(mesg);
105
});
106
107
it("sends a cwd message from a client, then responds to that from the remoteSpark, and finally checks that the client gets it", async () => {
108
spark1.emit("data", { cmd: "cwd" });
109
spark1.messages = [];
110
// wait for the message to get sent to our remote spark:
111
expect(await remoteSpark.waitForMessage()).toEqual({ cmd: "cwd" });
112
// send back a cwd
113
remoteSpark.emit("data", { cmd: "cwd", payload: "/home/user" });
114
expect(await spark1.waitForMessage()).toEqual({
115
cmd: "cwd",
116
payload: "/home/user",
117
});
118
});
119
});
120
121
// I disabled all of these for now. They are too difficult to maintain since they are so "fake".
122
123
// describe("test remotePty using actual pty", () => {
124
// let terminal, remote;
125
// const { path, options } = getOpts();
126
// const primus = getPrimusMock();
127
// const channel = primus.channel(getChannelName(path));
128
// const ptyChannel = primus.channel(getRemotePtyChannelName(path));
129
130
// beforeAll(async () => {
131
// await delay(1000);
132
// terminal = new Terminal(primus, path, options);
133
// });
134
135
// afterAll(() => {
136
// terminal.close();
137
// if (remote != null) {
138
// remote.close();
139
// }
140
// });
141
142
// it("initialize the terminal", async () => {
143
// await terminal.init();
144
// expect(typeof terminal.getPid()).toBe("number");
145
// });
146
147
// let spark;
148
// it("create a normal client connected to the terminal", async () => {
149
// spark = channel.createSpark("192.168.2.1");
150
// const mesg = await spark.waitForMessage();
151
// expect(mesg).toEqual({ cmd: "no-ignore" });
152
// });
153
154
// let remoteSpark;
155
// it("create remote terminal, and observe that local terminal process terminates", async () => {
156
// const pid = terminal.getPid();
157
// remoteSpark = ptyChannel.createSpark("192.168.2.2");
158
// remote = new RemoteTerminal(remoteSpark);
159
// expect(terminal.getPid()).toBe(undefined);
160
// // check that original process is no longer running.
161
// expect(await isPidRunning(pid)).toBe(false);
162
// });
163
164
// it("observe that remote terminal process gets created", async () => {
165
// // NOTE: we have to explicitly shuffle the messages along,
166
// // since our spark mock is VERY minimal and is the same object
167
// // for both the client and the server.
168
169
// const mesg = await remoteSpark.waitForMessage();
170
// remote.handleData(mesg);
171
172
// expect(remote.localPty).not.toEqual(undefined);
173
// const pid = remote.localPty.pid;
174
// expect(await isPidRunning(pid)).toBe(true);
175
// });
176
177
// it("use bash to compute something", async () => {
178
// const input = "expr 5077 \\* 389\n";
179
// const output = `${5077 * 389}`;
180
// spark.emit("data", input);
181
182
// // shuttle the data along:
183
// const data = await remoteSpark.waitForData(input);
184
// remote.handleData(data);
185
// const out = await remoteSpark.waitForData(output);
186
// remoteSpark.emit("data", out);
187
188
// const out2 = await spark.waitForData(output);
189
// expect(out2).toContain("5077");
190
// expect(out2).toContain(output);
191
// });
192
193
// it("have client send a size, and see the remote terminal gets that size", async () => {
194
// spark.emit("data", { cmd: "size", rows: 10, cols: 69 });
195
// const mesg = await remoteSpark.waitForMessage();
196
// remote.handleData(mesg);
197
// expect(mesg).toEqual({ cmd: "size", rows: 10, cols: 69 });
198
// // now ask the terminal for its size
199
// spark.emit("data", "stty size\n");
200
201
// const data = await remoteSpark.waitForData("stty size\n");
202
// remote.handleData(data);
203
// const out = await remoteSpark.waitForData("10 69");
204
// remoteSpark.emit("data", out);
205
206
// const out2 = await spark.waitForData("10 69");
207
// expect(out2.trim().slice(-5)).toBe("10 69");
208
// });
209
210
// it("tests the cwd command", async () => {
211
// spark.messages = [];
212
// // first from browser client to project:
213
// spark.emit("data", { cmd: "cwd" });
214
// const mesg = await remoteSpark.waitForMessage();
215
// remote.handleData(mesg);
216
// const mesg2 = await remoteSpark.waitForMessage();
217
// expect(mesg2.payload).toContain("terminal");
218
// remoteSpark.emit("data", mesg2);
219
// const mesg3 = await spark.waitForMessage();
220
// expect(mesg3).toEqual(mesg2);
221
// });
222
223
// // do this last!
224
// it("close the RemoteTerminal, and see that a local pty is spawned again", async () => {
225
// remote.close();
226
// await waitForPidToChange(terminal, 0);
227
// const pid = terminal.getPid();
228
// expect(await isPidRunning(pid)).toBe(true);
229
// });
230
// });
231
232