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/support.ts
Views: 687
1
/*
2
Some mocking and other funtionality that is very useful for unit testing.
3
4
The code below does not use Primus *at all* to implement anything -- it's just
5
a lightweight mocking of the same thing in process for unit testing purposes.
6
*/
7
8
import type { PrimusWithChannels } from "./types";
9
import { EventEmitter } from "events";
10
import { callback, delay } from "awaiting";
11
import type { Spark } from "primus";
12
import { uuid } from "@cocalc/util/misc";
13
import { exec } from "child_process";
14
import { once } from "@cocalc/util/async-utils";
15
16
import debug from "debug";
17
const logger = debug("cocalc:test:terminal");
18
19
const exec1 = (cmd: string, cb) => {
20
exec(cmd, (_err, stdout, stderr) => {
21
cb(undefined, { stdout, stderr });
22
});
23
};
24
25
export const isPidRunning = async (pid: number) => {
26
const { stdout } = await callback(exec1, `ps -p ${pid} -o pid=`);
27
return stdout.trim() != "";
28
};
29
30
export const getCommandLine = async (pid) => {
31
const { stdout } = await callback(exec1, `ps -p ${pid} -o comm=`);
32
return stdout;
33
};
34
35
export const waitForPidToChange = async (terminal, pid) => {
36
let i = 1;
37
while (true) {
38
const newPid = terminal.getPid();
39
if (newPid != null && newPid != pid) {
40
return newPid;
41
}
42
await delay(5 * i);
43
i += 1;
44
}
45
};
46
47
class PrimusSparkMock extends EventEmitter {
48
id: string = uuid();
49
address: { ip: string };
50
data: string = "";
51
messages: object[] = [];
52
53
constructor(ip: string) {
54
super();
55
this.address = { ip };
56
}
57
58
write = (data) => {
59
if (this.messages == null) return;
60
logger("spark write", data);
61
if (typeof data == "object") {
62
this.messages.push(data);
63
} else {
64
this.data += data;
65
}
66
this.emit("write");
67
};
68
69
end = () => {
70
this.emit("end");
71
this.removeAllListeners();
72
const t = this as any;
73
delete t.id;
74
delete t.address;
75
delete t.data;
76
delete t.messages;
77
};
78
79
waitForMessage = async () => {
80
while (true) {
81
if (this.messages.length > 0) {
82
return this.messages.shift();
83
}
84
await once(this, "write");
85
}
86
};
87
88
waitForData = async (x: number | string) => {
89
let data = "";
90
const isDone = () => {
91
if (typeof x == "number") {
92
return data.length >= x;
93
} else {
94
return data.includes(x);
95
}
96
};
97
while (!isDone()) {
98
if (this.data.length > 0) {
99
data += this.data;
100
// console.log("so far", { data });
101
this.data = "";
102
}
103
if (!isDone()) {
104
await once(this, "write");
105
}
106
}
107
return data;
108
};
109
}
110
111
class PrimusChannelMock extends EventEmitter {
112
name: string;
113
sparks: { [id: string]: Spark } = {};
114
115
constructor(name) {
116
super();
117
this.name = name;
118
}
119
120
write = (data) => {
121
if (this.sparks == null) return;
122
for (const spark of Object.values(this.sparks)) {
123
spark.write(data);
124
}
125
};
126
127
createSpark = (address) => {
128
const spark = new PrimusSparkMock(address) as unknown as Spark;
129
this.sparks[spark.id] = spark;
130
this.emit("connection", spark);
131
this.on("end", () => {
132
delete this.sparks[spark.id];
133
});
134
return spark;
135
};
136
137
destroy = () => {
138
this.removeAllListeners();
139
if (this.sparks != null) {
140
for (const spark of Object.values(this.sparks)) {
141
spark.end();
142
}
143
}
144
const t = this as any;
145
delete t.name;
146
delete t.sparks;
147
};
148
}
149
150
class PrimusMock {
151
channels: { [name: string]: PrimusChannelMock } = {};
152
153
channel = (name) => {
154
if (this.channels[name] == null) {
155
this.channels[name] = new PrimusChannelMock(name);
156
}
157
return this.channels[name];
158
};
159
}
160
161
export function getPrimusMock(): PrimusWithChannels {
162
const primus = new PrimusMock();
163
return primus as unknown as PrimusWithChannels;
164
}
165
166
export function getOpts() {
167
const name = uuid();
168
const path = `.${name}.term-0.term`;
169
const options = {
170
path: `${name}.term`,
171
};
172
return { path, options };
173
}
174
175