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/terminal.test.ts
Views: 687
1
import { Terminal } from "./terminal";
2
import {
3
getOpts,
4
getPrimusMock,
5
isPidRunning,
6
getCommandLine,
7
waitForPidToChange,
8
} from "./support";
9
import { getChannelName } from "./util";
10
11
describe("very basic test of creating a terminal and changing shell", () => {
12
let terminal;
13
const { path, options } = getOpts();
14
15
beforeAll(() => {
16
const primus = getPrimusMock();
17
terminal = new Terminal(primus, path, options);
18
});
19
20
afterAll(() => {
21
terminal.close();
22
});
23
24
it("checks conditions of terminal before it is initialized", () => {
25
expect(terminal.getPid()).toBe(undefined);
26
expect(terminal.getPath()).toBe(options.path);
27
expect(terminal.getCommand()).toBe("/bin/bash");
28
});
29
30
it("initializes the terminal and checks conditions", async () => {
31
await terminal.init();
32
expect(typeof terminal.getPid()).toBe("number");
33
});
34
35
it("changes the shell to /bin/sh and sees that the pid changes", async () => {
36
const pid = terminal.getPid();
37
terminal.setCommand("/bin/sh", []);
38
const newPid = await waitForPidToChange(terminal, pid);
39
expect(pid).not.toBe(newPid);
40
// check that original process is no longer running.
41
expect(await isPidRunning(pid)).toBe(false);
42
});
43
});
44
45
describe("create a shell, connect a client, and communicate with it", () => {
46
let terminal;
47
const { path, options } = getOpts();
48
const primus = getPrimusMock();
49
const channel = primus.channel(getChannelName(path));
50
51
beforeAll(() => {
52
terminal = new Terminal(primus, path, options);
53
});
54
55
afterAll(() => {
56
terminal.close();
57
});
58
59
it("initialize the terminal", async () => {
60
await terminal.init();
61
expect(typeof terminal.getPid()).toBe("number");
62
});
63
64
let spark;
65
it("create a client connection to the terminal", () => {
66
spark = channel.createSpark("192.168.2.1");
67
});
68
69
it("waits to receive no-ignore command", async () => {
70
expect(await spark.waitForMessage()).toEqual({
71
cmd: "computeServerId",
72
id: 0,
73
});
74
expect(await spark.waitForMessage()).toEqual({ cmd: "no-ignore" });
75
});
76
77
it("sets the terminal size and confirm it was set", async () => {
78
const rows = 10,
79
cols = 100;
80
expect(terminal.client_sizes[spark.id]).toEqual(undefined);
81
spark.emit("data", { cmd: "size", rows, cols });
82
expect(terminal.client_sizes[spark.id]).toEqual({ rows, cols });
83
// also confirm receipt of size message
84
const mesg = await spark.waitForMessage();
85
expect(mesg).toEqual({ cmd: "size", rows, cols });
86
});
87
88
it("gets the current working directory via a command", async () => {
89
spark.emit("data", { cmd: "cwd" });
90
const mesg = await spark.waitForMessage();
91
expect(mesg.cmd).toBe("cwd");
92
expect(process.cwd().endsWith(mesg.payload)).toBe(true);
93
});
94
95
it("write pwd to terminal and get back the current working directory", async () => {
96
spark.emit("data", "pwd\n");
97
spark.data = "";
98
const resp = await spark.waitForData(process.cwd());
99
expect(resp).toContain(process.cwd());
100
});
101
102
// this test is very flaky and I'm not even sure it makes sense given that the terminal is supposed
103
// to not autorestart unless you explicilty do the right thing. Disabling it.
104
// it("send kill command, send some input (to start new shell), and see that pid changes", async () => {
105
// const pid = terminal.getPid();
106
// spark.emit("data", { cmd: "kill" });
107
// spark.emit("data", "pwd\n");
108
// spark.data = "";
109
// const newPid = await waitForPidToChange(terminal, pid);
110
// expect(pid).not.toBe(newPid);
111
// expect(await isPidRunning(pid)).toBe(false);
112
// });
113
114
it("set shell with set_command see that pid changes", async () => {
115
const pid = terminal.getPid();
116
spark.emit("data", {
117
cmd: "set_command",
118
command: "/usr/bin/sleep",
119
args: ["1000"],
120
});
121
const newPid = await waitForPidToChange(terminal, pid);
122
expect(pid).not.toBe(newPid);
123
expect(await isPidRunning(pid)).toBe(false);
124
expect(await getCommandLine(newPid)).toContain("sleep");
125
});
126
127
it("send some data, then disconnect and reconnect, and verify that history contains that data, and also that terminal continues to work", async () => {
128
spark.emit("data", "echo 'hello cocalc'\n");
129
const resp = await spark.waitForData("hello cocalc");
130
expect(resp).toContain("hello cocalc");
131
spark.end();
132
const id = spark.id;
133
spark = channel.createSpark("192.168.2.1");
134
expect(id).not.toEqual(spark.id);
135
expect(await spark.waitForMessage()).toEqual({
136
cmd: "size",
137
cols: 100,
138
rows: 10,
139
});
140
expect(await spark.waitForMessage()).toEqual({
141
cmd: "computeServerId",
142
id: 0,
143
});
144
expect(await spark.waitForMessage()).toEqual({ cmd: "no-ignore" });
145
expect(spark.data).toContain("hello cocalc");
146
spark.data = "";
147
});
148
});
149
150
describe("collaboration -- two clients connected to the same terminal session", () => {
151
let terminal;
152
const { path, options } = getOpts();
153
const primus = getPrimusMock();
154
const channel = primus.channel(getChannelName(path));
155
156
beforeAll(() => {
157
terminal = new Terminal(primus, path, options);
158
});
159
160
afterAll(() => {
161
terminal.close();
162
});
163
164
let spark1, spark2;
165
it("create two clients connection to the terminal", async () => {
166
await terminal.init();
167
spark1 = channel.createSpark("192.168.2.1");
168
spark2 = channel.createSpark("192.168.2.2");
169
for (const s of [spark1, spark2]) {
170
expect(await s.waitForMessage()).toEqual({
171
cmd: "computeServerId",
172
id: 0,
173
});
174
const mesg = await s.waitForMessage();
175
expect(mesg).toEqual({ cmd: "no-ignore" });
176
}
177
});
178
179
it("have one client send something, and both clients see the input and result", async () => {
180
const input = "expr 5077 \\* 389\n";
181
const output = `${5077 * 389}`;
182
spark1.emit("data", input);
183
const out1 = await spark1.waitForData(output);
184
expect(out1).toContain("5077");
185
expect(out1).toContain(output);
186
const out2 = await spark2.waitForData(output);
187
expect(out2).toContain("5077");
188
expect(out2).toContain(output);
189
// also check that output only appears once:
190
let i = out2.indexOf(output);
191
expect(out2.indexOf(output, i + 1)).toBe(-1);
192
});
193
194
it("set the sizes of the two clients; verify that the min size is returned", async () => {
195
const rows1 = 15,
196
cols1 = 90;
197
const rows2 = 20,
198
cols2 = 70;
199
spark1.emit("data", { cmd: "size", rows: rows1, cols: cols1 });
200
const mesg1 = await spark1.waitForMessage();
201
expect(mesg1).toEqual({ cmd: "size", rows: rows1, cols: cols1 });
202
const mesg1a = await spark2.waitForMessage();
203
expect(mesg1a).toEqual({ cmd: "size", rows: rows1, cols: cols1 });
204
spark2.emit("data", { cmd: "size", rows: rows2, cols: cols2 });
205
const mesg2 = await spark2.waitForMessage();
206
expect(mesg2).toEqual({
207
cmd: "size",
208
rows: Math.min(rows1, rows2),
209
cols: Math.min(cols1, cols2),
210
});
211
});
212
});
213
214