|
20 | 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. |
21 | 21 |
|
22 | 22 | 'use strict'; |
| 23 | + |
23 | 24 | const common = require('../common'); |
24 | 25 | const ArrayStream = require('../common/arraystream'); |
25 | | -const assert = require('assert'); |
26 | | -const fs = require('fs'); |
| 26 | + |
| 27 | +const assert = require('node:assert'); |
| 28 | +const fs = require('node:fs'); |
| 29 | +const repl = require('node:repl'); |
| 30 | +const path = require('node:path'); |
| 31 | + |
27 | 32 | const tmpdir = require('../common/tmpdir'); |
28 | 33 | tmpdir.refresh(); |
29 | 34 |
|
30 | | -// TODO: the following async IIFE and the completePromise function are necessary because |
31 | | -// the reply tests are all run against the same repl instance (testMe) and thus coordination |
32 | | -// needs to be in place for the tests not to interfere with each other, this is really |
33 | | -// not ideal, the tests in this file should be refactored so that each use its own isolated |
34 | | -// repl instance, making sure that no special coordination needs to be in place for them |
35 | | -// and also allowing the tests to all be run in parallel |
36 | | -(async () => { |
37 | | - const repl = require('repl'); |
38 | | - |
39 | | - const works = [['inner.one'], 'inner.o']; |
40 | | - |
41 | | - const putIn = new ArrayStream(); |
42 | | - const testMe = repl.start('', putIn); |
43 | | - |
44 | | - // Some errors might be passed to the domain. |
45 | | - testMe._domain.on('error', function(reason) { |
46 | | - const err = new Error('Test failed'); |
47 | | - err.reason = reason; |
48 | | - throw err; |
| 35 | +function prepareREPL(replOpts = {}) { |
| 36 | + const input = new ArrayStream(); |
| 37 | + const output = new ArrayStream(); |
| 38 | + |
| 39 | + const replServer = repl.start({ |
| 40 | + prompt: '', |
| 41 | + input, |
| 42 | + output, |
| 43 | + allowBlockingCompletions: true, |
| 44 | + ...replOpts, |
49 | 45 | }); |
50 | 46 |
|
51 | | - async function completePromise(query, callback) { |
52 | | - return new Promise((resolve) => { |
53 | | - testMe.complete(query, (...args) => { |
54 | | - callback(...args); |
55 | | - resolve(); |
56 | | - }); |
57 | | - }); |
58 | | - } |
| 47 | + // Some errors are passed to the domain, but do not callback |
| 48 | + replServer._domain.on('error', assert.ifError); |
| 49 | + |
| 50 | + return { replServer, input, output }; |
| 51 | +} |
59 | 52 |
|
60 | | - const testFile = [ |
| 53 | +// The tests in this file test the REPL saving and loading session data to/from a file |
| 54 | + |
| 55 | + |
| 56 | +// The REPL can save a session's data to a file and load it back |
| 57 | +{ |
| 58 | + const { replServer, input } = prepareREPL(); |
| 59 | + const tmpDirPath = tmpdir.random(); |
| 60 | + |
| 61 | + const filePath = path.resolve(tmpDirPath, 'test.save.js'); |
| 62 | + |
| 63 | + const testFileContents = [ |
61 | 64 | 'let inner = (function() {', |
62 | 65 | ' return {one:1};', |
63 | 66 | '})()', |
64 | 67 | ]; |
65 | | - const saveFileName = tmpdir.resolve('test.save.js'); |
66 | 68 |
|
67 | | - // Add some data. |
68 | | - putIn.run(testFile); |
| 69 | + input.run(testFileContents); |
| 70 | + input.run([`.save ${filePath}`]); |
69 | 71 |
|
70 | | - // Save it to a file. |
71 | | - putIn.run([`.save ${saveFileName}`]); |
| 72 | + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), |
| 73 | + testFileContents.join('\n')); |
72 | 74 |
|
73 | | - // The file should have what I wrote. |
74 | | - assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'), |
75 | | - testFile.join('\n')); |
| 75 | + const innerOCompletions = [['inner.one'], 'inner.o']; |
76 | 76 |
|
77 | | - // Make sure that the REPL data is "correct". |
78 | | - await completePromise('inner.o', common.mustSucceed((data) => { |
79 | | - assert.deepStrictEqual(data, works); |
| 77 | + // Double check that the data is still present in the repl after the save |
| 78 | + replServer.completer('inner.o', common.mustSucceed((data) => { |
| 79 | + assert.deepStrictEqual(data, innerOCompletions); |
80 | 80 | })); |
81 | 81 |
|
82 | | - // Clear the REPL. |
83 | | - putIn.run(['.clear']); |
| 82 | + // Clear the repl context |
| 83 | + input.run(['.clear']); |
| 84 | + |
| 85 | + // Double check that the data is no longer present in the repl |
| 86 | + replServer.completer('inner.o', common.mustSucceed((data) => { |
| 87 | + assert.deepStrictEqual(data, [[], 'inner.o']); |
| 88 | + })); |
84 | 89 |
|
85 | | - testMe._sawKeyPress = true; |
86 | 90 | // Load the file back in. |
87 | | - putIn.run([`.load ${saveFileName}`]); |
| 91 | + input.run([`.load ${filePath}`]); |
88 | 92 |
|
89 | 93 | // Make sure loading doesn't insert extra indentation |
90 | 94 | // https://github.com/nodejs/node/issues/47673 |
91 | | - assert.strictEqual(testMe.line, ''); |
| 95 | + assert.strictEqual(replServer.line, ''); |
92 | 96 |
|
93 | | - // Make sure that the REPL data is "correct". |
94 | | - await completePromise('inner.o', common.mustSucceed((data) => { |
95 | | - assert.deepStrictEqual(data, works); |
| 97 | + // Make sure that the loaded data is present |
| 98 | + replServer.complete('inner.o', common.mustSucceed((data) => { |
| 99 | + assert.deepStrictEqual(data, innerOCompletions); |
96 | 100 | })); |
97 | 101 |
|
98 | | - // Clear the REPL. |
99 | | - putIn.run(['.clear']); |
| 102 | + replServer.close(); |
| 103 | +} |
100 | 104 |
|
101 | | - let loadFile = tmpdir.resolve('file.does.not.exist'); |
| 105 | +// An appropriate error is displayed if .load is called without a filename |
| 106 | +{ |
| 107 | + const { replServer, input, output } = prepareREPL(); |
102 | 108 |
|
103 | | - // Should not break. |
104 | | - putIn.write = common.mustCall(function(data) { |
105 | | - // Make sure I get a failed to load message and not some crazy error. |
106 | | - assert.strictEqual(data, `Failed to load: ${loadFile}\n`); |
107 | | - // Eat me to avoid work. |
108 | | - putIn.write = () => {}; |
| 109 | + output.write = common.mustCall(function(data) { |
| 110 | + assert.strictEqual(data, 'The "file" argument must be specified\n'); |
| 111 | + output.write = () => {}; |
109 | 112 | }); |
110 | | - putIn.run([`.load ${loadFile}`]); |
111 | 113 |
|
112 | | - // Throw error on loading directory. |
113 | | - loadFile = tmpdir.path; |
114 | | - putIn.write = common.mustCall(function(data) { |
115 | | - assert.strictEqual(data, `Failed to load: ${loadFile} is not a valid file\n`); |
116 | | - putIn.write = () => {}; |
| 114 | + input.run(['.load']); |
| 115 | + |
| 116 | + replServer.close(); |
| 117 | +} |
| 118 | + |
| 119 | +// An appropriate error is displayed if .save is called without a filename |
| 120 | +{ |
| 121 | + const { replServer, input, output } = prepareREPL(); |
| 122 | + |
| 123 | + output.write = common.mustCall(function(data) { |
| 124 | + assert.strictEqual(data, 'The "file" argument must be specified\n'); |
| 125 | + output.write = () => {}; |
117 | 126 | }); |
118 | | - putIn.run([`.load ${loadFile}`]); |
119 | 127 |
|
120 | | - // Clear the REPL. |
121 | | - putIn.run(['.clear']); |
| 128 | + input.run(['.save']); |
| 129 | + |
| 130 | + replServer.close(); |
| 131 | +} |
| 132 | + |
| 133 | +// The case in which the user tries to load a non existing file is appropriately handled |
| 134 | +{ |
| 135 | + const { replServer, input, output } = prepareREPL(); |
| 136 | + |
| 137 | + const filePath = tmpdir.resolve('file.does.not.exist'); |
| 138 | + |
| 139 | + output.write = common.mustCall(function(data) { |
| 140 | + assert.strictEqual(data, `Failed to load: ${filePath}\n`); |
| 141 | + output.write = () => {}; |
| 142 | + }); |
| 143 | + |
| 144 | + input.run([`.load ${filePath}`]); |
| 145 | + |
| 146 | + replServer.close(); |
| 147 | +} |
| 148 | + |
| 149 | +// The case in which the user tries to load a directory instead of a file is appropriately handled |
| 150 | +{ |
| 151 | + const { replServer, input, output } = prepareREPL(); |
| 152 | + |
| 153 | + const dirPath = tmpdir.path; |
| 154 | + |
| 155 | + output.write = common.mustCall(function(data) { |
| 156 | + assert.strictEqual(data, `Failed to load: ${dirPath} is not a valid file\n`); |
| 157 | + output.write = () => {}; |
| 158 | + }); |
| 159 | + |
| 160 | + input.run([`.load ${dirPath}`]); |
| 161 | + |
| 162 | + replServer.close(); |
| 163 | +} |
| 164 | + |
| 165 | +// The case in which a file save fails is appropriately handled |
| 166 | +{ |
| 167 | + const { replServer, input, output } = prepareREPL(); |
122 | 168 |
|
123 | 169 | // NUL (\0) is disallowed in filenames in UNIX-like operating systems and |
124 | 170 | // Windows so we can use that to test failed saves. |
125 | | - const invalidFileName = tmpdir.resolve('\0\0\0\0\0'); |
126 | | - |
127 | | - // Should not break. |
128 | | - putIn.write = common.mustCall(function(data) { |
129 | | - // Make sure I get a failed to save message and not some other error. |
130 | | - assert.strictEqual(data, `Failed to save: ${invalidFileName}\n`); |
131 | | - // Reset to no-op. |
132 | | - putIn.write = () => {}; |
| 171 | + const invalidFilePath = tmpdir.resolve('\0\0\0\0\0'); |
| 172 | + |
| 173 | + output.write = common.mustCall(function(data) { |
| 174 | + assert.strictEqual(data, `Failed to save: ${invalidFilePath}\n`); |
| 175 | + output.write = () => {}; |
133 | 176 | }); |
134 | 177 |
|
135 | | - // Save it to a file. |
136 | | - putIn.run([`.save ${invalidFileName}`]); |
| 178 | + input.run([`.save ${invalidFilePath}`]); |
137 | 179 |
|
138 | | - { |
139 | | - // Save .editor mode code. |
140 | | - const cmds = [ |
141 | | - 'function testSave() {', |
142 | | - 'return "saved";', |
143 | | - '}', |
144 | | - ]; |
145 | | - const putIn = new ArrayStream(); |
146 | | - const replServer = repl.start({ terminal: true, stream: putIn }); |
| 180 | + replServer.close(); |
| 181 | +} |
147 | 182 |
|
148 | | - putIn.run(['.editor']); |
149 | | - putIn.run(cmds); |
150 | | - replServer.write('', { ctrl: true, name: 'd' }); |
| 183 | +// Saving in editor mode works |
| 184 | +{ |
| 185 | + const { replServer, input } = prepareREPL({ terminal: true }); |
| 186 | + const tmpDirPath = tmpdir.random(); |
151 | 187 |
|
152 | | - putIn.run([`.save ${saveFileName}`]); |
153 | | - replServer.close(); |
154 | | - assert.strictEqual(fs.readFileSync(saveFileName, 'utf8'), |
155 | | - `${cmds.join('\n')}\n`); |
156 | | - } |
| 188 | + input.run(['.editor']); |
157 | 189 |
|
158 | | - // Check if the file is present when using save |
| 190 | + const commands = [ |
| 191 | + 'function testSave() {', |
| 192 | + 'return "saved";', |
| 193 | + '}', |
| 194 | + ]; |
159 | 195 |
|
160 | | - // Clear the REPL. |
161 | | - putIn.run(['.clear']); |
| 196 | + input.run(commands); |
162 | 197 |
|
163 | | - // Error message when using save without a file |
164 | | - putIn.write = common.mustCall(function(data) { |
165 | | - assert.strictEqual(data, 'The "file" argument must be specified\n'); |
166 | | - putIn.write = () => {}; |
167 | | - }); |
168 | | - putIn.run(['.save']); |
| 198 | + replServer.write('', { ctrl: true, name: 'd' }); |
169 | 199 |
|
170 | | - // Check if the file is present when using load |
| 200 | + const filePath = path.resolve(tmpDirPath, 'test.save.js'); |
171 | 201 |
|
172 | | - // Clear the REPL. |
173 | | - putIn.run(['.clear']); |
| 202 | + input.run([`.save ${filePath}`]); |
174 | 203 |
|
175 | | - // Error message when using load without a file |
176 | | - putIn.write = common.mustCall(function(data) { |
177 | | - assert.strictEqual(data, 'The "file" argument must be specified\n'); |
178 | | - putIn.write = () => {}; |
179 | | - }); |
180 | | - putIn.run(['.load']); |
181 | | -})().then(common.mustCall()); |
| 204 | + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), |
| 205 | + `${commands.join('\n')}\n`); |
| 206 | + |
| 207 | + replServer.close(); |
| 208 | +} |
0 commit comments