aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/include_test.js
diff options
context:
space:
mode:
Diffstat (limited to 'tests/drivers/include_test.js')
-rw-r--r--tests/drivers/include_test.js94
1 files changed, 94 insertions, 0 deletions
diff --git a/tests/drivers/include_test.js b/tests/drivers/include_test.js
new file mode 100644
index 0000000..6ba4e5f
--- /dev/null
+++ b/tests/drivers/include_test.js
@@ -0,0 +1,94 @@
+// include: a path is found next to the file that has it, then in the workspace folder, then as written; the
+// error for one that cannot be read is on its string; include std; needs no file; a server with no files of its own
+// is sent the text of each (zc/file). usage: node tests/drivers/include_test.js path/to/zclsp [path/to/lsp.wasm]
+const assert = require('assert');
+const cp = require('child_process');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const server = path.resolve(process.argv[2]);
+const wasm = process.argv[3];
+const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); };
+function parse(out) {
+ const res = [];
+ let d = out;
+ while (d.length) {
+ const e = d.indexOf('\r\n\r\n'), n = Number(/Content-Length:\s*(\d+)/i.exec(d.subarray(0, e).toString())[1]);
+ res.push(JSON.parse(d.subarray(e + 4, e + 4 + n))); d = d.subarray(e + 4 + n);
+ }
+ return res;
+}
+// runs one session on the native server (cwd is where includes are read as written) or the wasm host
+function check(uri, text, { files = [], root = null, cwd = os.tmpdir(), host = 'native', navigation = false } = {}) {
+ const msgs = [{ jsonrpc: '2.0', id: 1, method: 'initialize', params: { rootUri: root } },
+ ...files.map(([p, t]) => ({ jsonrpc: '2.0', method: 'zc/file', params: { path: p, text: t } })),
+ { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } },
+ ...(navigation ? [2, 3].map(id => ({ jsonrpc: '2.0', id, method: 'textDocument/definition', params: { textDocument: { uri }, position: { line: 2, character: 20 } } })) : []),
+ { jsonrpc: '2.0', method: 'exit' }];
+ let out;
+ if (host === 'native') out = cp.spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), cwd, maxBuffer: 1 << 26 }).stdout;
+ else {
+ const { wasmProcess } = require(path.join(__dirname, '..', '..', 'editors', 'vscode', 'client', 'wasmproc.js'));
+ const p = wasmProcess(path.resolve(wasm), { log: () => {} });
+ const chunks = [];
+ p.stdout.on('data', d => chunks.push(d));
+ for (const m of msgs) p.stdin.write(frame(m));
+ out = Buffer.concat(chunks);
+ }
+ const replies = parse(out);
+ if (navigation) {
+ for (const id of [2, 3]) assert.deepStrictEqual(replies.find(m => m.id === id).result, [{ uri: 'file:///nowhere/proj/lib/virt.zc', range: { start: { line: 0, character: 4 }, end: { line: 0, character: 10 } } }], host + ': navigation uses client-provided includes repeatedly');
+ }
+ const note = replies.filter(m => m.method === 'textDocument/publishDiagnostics').pop();
+ return note ? note.params.diagnostics : null;
+}
+const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'zcinc-'));
+const w = (f, t) => { fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true }); fs.writeFileSync(path.join(dir, f), t); };
+w('src/lib/util.zc', 'int twice(int v) { return v * 2; }\n');
+w('src/lib/deep.zc', 'include "util.zc";\nint quad(int v) { return twice(twice(v)); }\n');
+w('src/tools/main.zc', 'include "../lib/deep.zc";\nfn main() { return quad(1); }\n');
+w('top.zc', 'int fromtop() { return 7; }\n');
+const uri = f => 'file://' + path.join(dir, f);
+const read = f => fs.readFileSync(path.join(dir, f), 'utf8');
+const errors = d => d.filter(x => x.severity === 1);
+// next to the file, and nested includes next to the file that has them (the cwd is not the document's directory)
+assert.deepStrictEqual(errors(check(uri('src/tools/main.zc'), read('src/tools/main.zc'))), [], 'a relative include, nested');
+// the original build.zc reproduction, checked from an unrelated working directory
+const buildFile = path.resolve(__dirname, '../../src/tools/build.zc');
+const buildText = fs.readFileSync(buildFile, 'utf8').replace('include std;', 'include "../lib/std.zc";');
+assert.deepStrictEqual(errors(check('file://' + buildFile, buildText)), [], 'build.zc finds ../lib/std.zc');
+// the workspace folder
+assert.deepStrictEqual(errors(check(uri('src/tools/a.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { root: 'file://' + dir })), [], 'found in the workspace folder');
+// as written, from the working directory
+assert.deepStrictEqual(errors(check(uri('src/tools/b.zc'), 'include "top.zc";\nfn main() { return fromtop(); }\n', { cwd: dir })), [], 'found from the working directory');
+// an include that is not there: the error is on its string
+let d = check(uri('src/tools/c.zc'), 'include "../lib/nothere.zc";\nfn main() { return 0; }\n');
+assert.strictEqual(errors(d).length, 1);
+assert.deepStrictEqual(errors(d)[0].range, { start: { line: 0, character: 8 }, end: { line: 0, character: 27 } }, 'the error is on the string: ' + JSON.stringify(errors(d)[0].range));
+assert.match(errors(d)[0].message, /can't read that include/);
+// include std; and the same twice
+assert.deepStrictEqual(errors(check(uri('src/tools/d.zc'), 'include std;\ninclude std;\nfn main() { return std.Text.atoi("4"); }\n')), [], 'include std;');
+d = check(uri('src/tools/e.zc'), 'include std\nfn main() { return 0; }\n');
+assert.ok(errors(d).length >= 1, 'include std without a semicolon is an error');
+d = check(uri('src/tools/f.zc'), 'include other;\nfn main() { return 0; }\n');
+assert.match(errors(d)[0].message, /can't read that module include/);
+assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 }, 'an unknown library is reported at its name');
+// findings inside an included file: hints are left out, an error is shown on the include with where it is
+w('src/lib/loud.zc', 'int a() { int unused = 1; return 2 * 2; }\nint b() { return 1 }\n');
+d = check(uri('src/tools/g.zc'), 'include "../lib/loud.zc";\nfn main() { return a(); }\n');
+assert.strictEqual(d.filter(x => x.severity !== 1).length, 0, 'no hints from a library: ' + JSON.stringify(d));
+assert.strictEqual(errors(d).length, 1);
+assert.match(errors(d)[0].message, /^in .*loud\.zc, line 2: .*expected ';'/);
+assert.deepStrictEqual(errors(d)[0].range.start, { line: 0, character: 8 });
+// a server that cannot read files is sent them
+const virtual = '/nowhere/proj/lib/virt.zc';
+const vdoc = 'file:///nowhere/proj/app/main.zc';
+const vtext = 'include "../lib/virt.zc";\ninclude "../lib/gone.zc";\nfn main() { return thrice(2); }\n';
+const files = [[virtual, 'int thrice(int v) { return v * 3; }\n']];
+for (const host of wasm ? ['native', 'wasm'] : ['native']) {
+ d = check(vdoc, vtext, { files, host, navigation: true });
+ assert.strictEqual(errors(d).length, 1, host + ': ' + JSON.stringify(d));
+ assert.deepStrictEqual(errors(d)[0].range.start, { line: 1, character: 8 }, host + ': only the missing file is an error');
+}
+fs.rmSync(dir, { recursive: true });
+console.log('ok includes: found next to the file, in the workspace and as written; errors on the string; include std; files sent by the client');