@torrent-tv/proxy 2.80.19 → 2.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/research/double-spawn-2026-09-10.md +171 -0
- package/services/disk/DiskSpace.js +146 -0
- package/services/disk/wire.js +60 -0
- package/services/encode/EncodeRun.js +25 -3
- package/services/encode/SegmentStore.js +137 -9
- package/services/hls-session-manager.js +26 -45
- package/services/orchestrators/EncodeOrchestrator.js +4 -1
- package/services/piece-store/allowance.js +107 -0
- package/services/piece-store/piece-disk-store.js +365 -0
- package/services/piece-store/shared-piece-store.js +1549 -1535
- package/services/torrent-worker/client.js +32 -0
- package/services/torrent-worker/pool-adapter.js +15 -0
- package/services/torrent-worker/protocol.js +9 -0
- package/services/torrent-worker/worker.js +8 -1
- package/services/viewer/positions.js +48 -0
- package/test/audio-inventory.test.js +176 -176
- package/test/auto-quality-step.test.js +514 -514
- package/test/concurrent-cost.test.js +138 -138
- package/test/coverage-follows-the-disk.test.js +191 -191
- package/test/coverage-map.test.js +195 -195
- package/test/declared-tracks.test.js +35 -35
- package/test/disk-space.test.js +138 -0
- package/test/encode-orchestrator.test.js +0 -3
- package/test/encode-run.test.js +5 -12
- package/test/held-request-width.test.js +155 -155
- package/test/helpers/encode-run.js +2 -2
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cues-track.test.js +192 -192
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +281 -220
- package/test/orchestrator-wired.test.js +199 -199
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +267 -0
- package/test/piece-reader.test.js +4 -4
- package/test/piece-store-eviction.test.js +17 -17
- package/test/piece-store-reservations.test.js +20 -1
- package/test/piece-store-slow-disk.test.js +16 -1
- package/test/read-window.test.js +6 -6
- package/test/run-intervals.test.js +100 -100
- package/test/seek-landing.test.js +109 -109
- package/test/segment-store-eviction.test.js +232 -0
- package/test/segments-are-shared.test.js +1 -1
- package/test/shared-piece-store.test.js +12 -12
- package/test/sidecar-naming.test.js +142 -142
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-walk.test.js +369 -369
- package/test/subtitle-defaults.test.js +97 -97
- package/test/subtitle-track-numbering.test.js +370 -370
- package/test/tail-duplication.test.js +167 -167
- package/test/tracks-begin-together.test.js +195 -195
- package/test/two-viewers-one-picture.test.js +374 -374
- package/test/video-facts.test.js +102 -102
- package/test/wedge-certainty.test.js +131 -131
- package/services/piece-store/disk-tier.js +0 -151
|
@@ -1,220 +1,281 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One place decides which encoders exist.
|
|
3
|
-
*
|
|
4
|
-
* Not a check of behaviour but of shape, and it is here because the shape is
|
|
5
|
-
* what failed. Three separate places used to decide where an encoder should
|
|
6
|
-
* work and whether it should go on living, and they disagreed on every pass:
|
|
7
|
-
* measured in the field on 2026-09-05, 684 starts and 660 stops in 482 seconds,
|
|
8
|
-
* of which 294 were one place killing what another had just decided to keep,
|
|
9
|
-
* while the viewer's own segment went unmade for 32.3 seconds.
|
|
10
|
-
*
|
|
11
|
-
* Every rule below is one that was broken then. A reader who needs to add a
|
|
12
|
-
* fourth place should read this file first and then not.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import test from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// building
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
const manager = source("services/hls-session-manager.js");
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
assert.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file One place decides which encoders exist.
|
|
3
|
+
*
|
|
4
|
+
* Not a check of behaviour but of shape, and it is here because the shape is
|
|
5
|
+
* what failed. Three separate places used to decide where an encoder should
|
|
6
|
+
* work and whether it should go on living, and they disagreed on every pass:
|
|
7
|
+
* measured in the field on 2026-09-05, 684 starts and 660 stops in 482 seconds,
|
|
8
|
+
* of which 294 were one place killing what another had just decided to keep,
|
|
9
|
+
* while the viewer's own segment went unmade for 32.3 seconds.
|
|
10
|
+
*
|
|
11
|
+
* Every rule below is one that was broken then. A reader who needs to add a
|
|
12
|
+
* fourth place should read this file first and then not.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import test from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { EventEmitter } from "node:events";
|
|
21
|
+
import { EncodeRun } from "../services/encode/EncodeRun.js";
|
|
22
|
+
|
|
23
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} relative
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function source(relative) {
|
|
30
|
+
return readFileSync(path.join(HERE, "..", relative), "utf8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Lines of code, without comments or blanks: a rule about what the code does
|
|
35
|
+
* must not be answered by what a comment says about it.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} text
|
|
38
|
+
* @returns {string[]}
|
|
39
|
+
*/
|
|
40
|
+
function statements(text) {
|
|
41
|
+
return text
|
|
42
|
+
.split("\n")
|
|
43
|
+
.map((line) => line.trim())
|
|
44
|
+
.filter((line) => line.length > 0 && !line.startsWith("//") && !line.startsWith("*") && !line.startsWith("/*"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("an encoder is stopped for scheduling reasons in exactly one place", () => {
|
|
48
|
+
// The orchestrator decides; nobody else may. What is left in the session
|
|
49
|
+
// manager is teardown — the session is going away and its encoders with it —
|
|
50
|
+
// which is not a decision about which encoders should exist.
|
|
51
|
+
const orchestrator = statements(source("services/orchestrators/EncodeOrchestrator.js"));
|
|
52
|
+
const stopsInOrchestrator = orchestrator.filter((line) => line.includes("run.stop("));
|
|
53
|
+
assert.equal(stopsInOrchestrator.length, 1, "the orchestrator stops runs in one place");
|
|
54
|
+
|
|
55
|
+
const manager = statements(source("services/hls-session-manager.js"));
|
|
56
|
+
const stopsInManager = manager.filter((line) => line.includes(".stop("));
|
|
57
|
+
assert.equal(
|
|
58
|
+
stopsInManager.length,
|
|
59
|
+
2,
|
|
60
|
+
"the session manager stops runs only when a session is torn down: " +
|
|
61
|
+
stopsInManager.join(" / ")
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("nothing outside the encoding layer starts an encoder", () => {
|
|
66
|
+
// A run is built in one place. Two places building them is how a start came
|
|
67
|
+
// to kill what the plan had decided to keep — the killing lived in the
|
|
68
|
+
// building.
|
|
69
|
+
const manager = statements(source("services/hls-session-manager.js"));
|
|
70
|
+
const builds = manager.filter((line) => line.includes("new EncodeRun("));
|
|
71
|
+
assert.equal(builds.length, 1, "one place builds an encoder");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a run exists means its process is running, so nothing can start one twice", () => {
|
|
75
|
+
// WHAT THIS IS FOR, and why the test above did not catch it. That one counts
|
|
76
|
+
// where a run is BUILT and asks nothing about where it is STARTED, and until
|
|
77
|
+
// 2026-09-10 those were two acts: the session manager built a run and started
|
|
78
|
+
// it, handed it back, and the orchestrator started it again. Every run of
|
|
79
|
+
// every session therefore had two ffmpeg processes on one output writing one
|
|
80
|
+
// set of names — 207 runs against 414 spawns in the field logs of 08-10
|
|
81
|
+
// September, without a single exception — and only the second was reachable,
|
|
82
|
+
// so a stop killed one and the other ran on, measured 105 seconds past its
|
|
83
|
+
// own run's death.
|
|
84
|
+
//
|
|
85
|
+
// Asserted over the source because the guarantee IS a property of the source:
|
|
86
|
+
// there is no second act for a second owner to perform.
|
|
87
|
+
const files = [
|
|
88
|
+
"services/encode/EncodeRun.js",
|
|
89
|
+
"services/orchestrators/EncodeOrchestrator.js",
|
|
90
|
+
"services/hls-session-manager.js"
|
|
91
|
+
];
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
const starts = statements(source(file)).filter(
|
|
94
|
+
(line) =>
|
|
95
|
+
/(?:^|[^a-zA-Z#])start\(/.test(line) &&
|
|
96
|
+
!/startsWith|startPosition|startIndex|startSeconds|startAt|startNumber/.test(line)
|
|
97
|
+
);
|
|
98
|
+
assert.deepEqual(starts, [], "an encoder is started by hand in " + file + ": " + starts.join(" / "));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("starting an encoder stops nothing", () => {
|
|
103
|
+
// The rule that broke it: the start path looked for a live run whose own
|
|
104
|
+
// start was not below the new one's and killed it. It is not enough that the
|
|
105
|
+
// line is gone — the words it was written with must not come back.
|
|
106
|
+
const manager = source("services/hls-session-manager.js");
|
|
107
|
+
assert.equal(
|
|
108
|
+
manager.includes("previousRun"),
|
|
109
|
+
false,
|
|
110
|
+
"there is no such thing as the previous run: a session holds several"
|
|
111
|
+
);
|
|
112
|
+
assert.equal(manager.includes("a new run is taking its place"), false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("a seek moves the viewer and nothing else", () => {
|
|
116
|
+
// It used to do eleven things and write the position into five places. What
|
|
117
|
+
// follows from a viewer moving is the map's business, and the orchestrators
|
|
118
|
+
// read the map.
|
|
119
|
+
const manager = source("services/hls-session-manager.js");
|
|
120
|
+
const seek = manager.slice(
|
|
121
|
+
manager.indexOf("requestSeek(sessionId, positionSeconds"),
|
|
122
|
+
manager.indexOf("requestSeek(sessionId, positionSeconds") + 2000
|
|
123
|
+
);
|
|
124
|
+
const body = seek.slice(0, seek.indexOf("\n }\n"));
|
|
125
|
+
assert.equal(body.includes("#startEncodeRun"), false, "a seek starts no encoder");
|
|
126
|
+
assert.equal(body.includes("setTimeout"), false, "and waits for nothing");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("how far an encoder may work is answered once", () => {
|
|
130
|
+
// The plan computes the stretch and it reaches ffmpeg. A second computation
|
|
131
|
+
// somewhere else is what made the first one pointless: it was passed and then
|
|
132
|
+
// dropped by a parameter list that did not name it.
|
|
133
|
+
const orchestrator = source("services/orchestrators/EncodeOrchestrator.js");
|
|
134
|
+
assert.match(
|
|
135
|
+
orchestrator,
|
|
136
|
+
/makeRun\(\{ address, from, to, because \}\)/,
|
|
137
|
+
"the stretch is handed over, and so are the plan's own words for why"
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("only the plan places an encoder", () => {
|
|
142
|
+
// The count is the whole of this item. There were eight other places: the
|
|
143
|
+
// first run of a session, a viewer joining it further in, a rung or a
|
|
144
|
+
// soundtrack being warmed, one being switched to, a hardware encoder falling
|
|
145
|
+
// back to software, an input coming back, a cut table correcting itself, and
|
|
146
|
+
// a settled seek. Each of them chose a position by a rule of its own, and the
|
|
147
|
+
// plan — which is arithmetic over what is made, what is being made and what is
|
|
148
|
+
// wanted — was left to compare its answer against theirs.
|
|
149
|
+
const manager = source("services/hls-session-manager.js");
|
|
150
|
+
const starts = [...manager.matchAll(/this\.#startEncodeRun\(/g)].length;
|
|
151
|
+
assert.equal(starts, 1, "one caller, and it is the one the plan asks through");
|
|
152
|
+
assert.match(
|
|
153
|
+
manager,
|
|
154
|
+
/#makeRunAt\(address, from, to, because\)[\s\S]{0,3000}this\.#startEncodeRun\(base, from, because, \{ to \}\)/,
|
|
155
|
+
"and that caller is what the plan is given to build runs with"
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("nobody stops an encoder for being unwatched", () => {
|
|
160
|
+
// Whether an encoder is still wanted is the same question as where one should
|
|
161
|
+
// be, and the plan answers it: an output with nobody on it has a priority map
|
|
162
|
+
// with nothing in it. Answered here as well, it was answered twice by two
|
|
163
|
+
// rules — and since a viewer moving between steps announces itself, the plan
|
|
164
|
+
// started again what this class had just killed, several times a second.
|
|
165
|
+
const manager = source("services/hls-session-manager.js");
|
|
166
|
+
assert.equal(
|
|
167
|
+
manager.includes("no viewer is watching"),
|
|
168
|
+
false,
|
|
169
|
+
"a rung nobody is on is a fact, not an act"
|
|
170
|
+
);
|
|
171
|
+
assert.equal(manager.includes("no viewer is listening to audio track"), false);
|
|
172
|
+
assert.equal(
|
|
173
|
+
manager.includes("warmed for a switch the viewer did not make"),
|
|
174
|
+
false,
|
|
175
|
+
"an abandoned warm-up is a viewer leaving an output"
|
|
176
|
+
);
|
|
177
|
+
assert.equal(manager.includes("prepared for a track change the viewer did not make"), false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("the seek settle machinery is gone, whole", () => {
|
|
181
|
+
// A viewer's position is applied the moment they state it. The settle was a
|
|
182
|
+
// second debounce on a signal the browser had already debounced, and every
|
|
183
|
+
// millisecond of it was dead time in front of the viewer; the cooldown behind
|
|
184
|
+
// it existed because segment REQUESTS once steered the encoder.
|
|
185
|
+
const manager = source("services/hls-session-manager.js");
|
|
186
|
+
for (const gone of [
|
|
187
|
+
"seekSettleTimer",
|
|
188
|
+
"seekTarget",
|
|
189
|
+
"seekFirstFarAt",
|
|
190
|
+
"SEEK_SETTLE_MS",
|
|
191
|
+
"SEEK_SETTLE_MAX_MS",
|
|
192
|
+
"RESTART_COOLDOWN_MS",
|
|
193
|
+
"SEEK_BACKOFF_SEGMENTS",
|
|
194
|
+
"#fireSettledSeek",
|
|
195
|
+
"#seekSession"
|
|
196
|
+
]) {
|
|
197
|
+
assert.equal(manager.includes(gone), false, `${gone} is gone`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("where a soundtrack begins is read off the table, not handed in", () => {
|
|
202
|
+
// The instant a number really begins is a fact of the FILE's cutting, held in
|
|
203
|
+
// the live table every session of the file shares. Passed as an argument by
|
|
204
|
+
// the one caller that had measured it, only a run started by that caller ever
|
|
205
|
+
// had it, and a run the plan placed at the same number landed apart again.
|
|
206
|
+
const manager = source("services/hls-session-manager.js");
|
|
207
|
+
assert.match(
|
|
208
|
+
manager,
|
|
209
|
+
/const positionSecondsOverride = session\.audioOnly === true\s*\n?\s*\? trueStartOf\(session\.timeline, startIndex\)/,
|
|
210
|
+
"derived where the run is built"
|
|
211
|
+
);
|
|
212
|
+
assert.equal(
|
|
213
|
+
manager.includes("this.#startEncodeRun(member, index, trueStart)"),
|
|
214
|
+
false,
|
|
215
|
+
"and not carried in from the correction that measured it"
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("a changed bitrate cap stops the encoder carrying the old one and nothing more", () => {
|
|
220
|
+
// An argument list is fixed when a process starts, so a run carrying the
|
|
221
|
+
// previous cap cannot be told about the new one — that is what is known here.
|
|
222
|
+
// Where the replacement stands is a different question, and the old answer to
|
|
223
|
+
// it was neither where a viewer is nor a gap in the material: it was the
|
|
224
|
+
// segment the process being replaced happened to have reached.
|
|
225
|
+
const manager = source("services/hls-session-manager.js");
|
|
226
|
+
assert.equal(manager.includes("#restartAtViewer"), false, "the second answer is gone");
|
|
227
|
+
assert.match(
|
|
228
|
+
manager,
|
|
229
|
+
/#reencodeAtNewRate\(session\) \{\s*\n\s*this\.#stopEncodeRun\(session, "its bitrate cap changed"\);\s*\n\s*this\.planEncodersSoon\(\);\s*\n\s*\}/,
|
|
230
|
+
"stopped, and then decided again"
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("each output is handed its own priority map, and the plan is what reads it", () => {
|
|
235
|
+
// The map is one fact asked at two scopes, and both are right for what asks
|
|
236
|
+
// them: the swarm is asked for bytes of a FILE, which every output of it
|
|
237
|
+
// reads, and encoders are placed per OUTPUT, which a person watching 480p
|
|
238
|
+
// wants nothing of at 1080p.
|
|
239
|
+
const manager = source("services/hls-session-manager.js");
|
|
240
|
+
assert.match(
|
|
241
|
+
manager,
|
|
242
|
+
/notePriorityMap\([\s\S]{0,200}mapForOutput\(address\)/,
|
|
243
|
+
"the encoding reads the output's own map"
|
|
244
|
+
);
|
|
245
|
+
assert.equal(
|
|
246
|
+
manager.includes("this.priority.mapFor("),
|
|
247
|
+
false,
|
|
248
|
+
"and never the whole film's, which wanted an encoder on every output of it"
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("one process per run, and no way to ask for a second", () => {
|
|
253
|
+
const spawned = [];
|
|
254
|
+
const run = new EncodeRun({
|
|
255
|
+
address: "torrent:abc:video-only:v=0/copy",
|
|
256
|
+
encoder: { name: "libx264", kind: "software" },
|
|
257
|
+
from: 0,
|
|
258
|
+
to: 4,
|
|
259
|
+
buildArgs: () => [],
|
|
260
|
+
spawn: () => {
|
|
261
|
+
const child = new EventEmitter();
|
|
262
|
+
child.stdout = new EventEmitter();
|
|
263
|
+
child.stderr = new EventEmitter();
|
|
264
|
+
child.stdio = [null, child.stdout, child.stderr, new EventEmitter()];
|
|
265
|
+
child.kill = () => undefined;
|
|
266
|
+
child.pid = 1 + spawned.length;
|
|
267
|
+
spawned.push(child);
|
|
268
|
+
return child;
|
|
269
|
+
},
|
|
270
|
+
logger: { info: () => undefined, warn: () => undefined },
|
|
271
|
+
now: () => 1000,
|
|
272
|
+
because: "a test asked for it"
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
assert.equal(spawned.length, 1, "building a run did not put a process on the machine");
|
|
276
|
+
assert.equal(
|
|
277
|
+
Reflect.get(run, "start"),
|
|
278
|
+
undefined,
|
|
279
|
+
"a run still offers a way to start it a second time"
|
|
280
|
+
);
|
|
281
|
+
});
|