// check maintained zc sources through the editor document protocol, one file at a time. // interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const fs = require('fs'); const cp = require('child_process'); const path = require('path'); const target = process.argv[2]; // the compiler is the files listed in src/compiler/parts; the server checks them as the one document they make const compilerSource = () => fs.readFileSync('src/compiler/parts', 'utf8').split(/\r?\n/).filter(Boolean).map(n => fs.readFileSync(path.join('src/compiler', n), 'utf8')).join(''); const files = ['src/core/core.zc', 'src/runtime/rt.zc', 'src/compiler/compiler.zc', 'src/runtime/run.zc', 'src/lib/std.zc', 'src/lib/icon.zc', 'src/lsp/server.zc', 'src/tools/build.zc']; const frame = message => { const body = Buffer.from(JSON.stringify(message)); return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); }; for (const file of files) { const uri = 'file://' + path.resolve(file); const text = file === 'src/compiler/compiler.zc' ? compilerSource() : fs.readFileSync(file, 'utf8'); const messages = [ { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, { jsonrpc: '2.0', method: 'initialized', params: {} }, { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, { jsonrpc: '2.0', id: 2, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }, { jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: text + '\n' }] } }, { jsonrpc: '2.0', id: 4, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri } } }, { jsonrpc: '2.0', method: 'textDocument/didClose', params: { textDocument: { uri } } }, { jsonrpc: '2.0', id: 3, method: 'shutdown', params: null }, { jsonrpc: '2.0', method: 'exit' }, ]; const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 32 << 20 }); assert.strictEqual(result.status, 0, file + ': ' + result.stderr); let data = result.stdout; const replies = []; while (data.length) { const end = data.indexOf('\r\n\r\n'); assert.ok(end >= 0, 'complete protocol header'); const length = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]); replies.push(JSON.parse(data.subarray(end + 4, end + 4 + length))); data = data.subarray(end + 4 + length); } const diagnostics = replies.filter(m => m.method === 'textDocument/publishDiagnostics'); assert.ok(diagnostics.length >= 3, file + ': open and change are checked before close, with streamed publications allowed'); const errors = diagnostics.flatMap(m => m.params.diagnostics).filter(d => d.severity === 1); assert.deepStrictEqual(errors, [], file + ': ' + errors.map(d => (d.range.start.line + 1) + ': ' + d.message).join('\n')); assert.ok(replies.find(m => m.id === 2).result.data.length, file + ': semantic tokens are available'); assert.ok(replies.find(m => m.id === 4).result.data.length, file + ': the changed source also has semantic tokens'); assert.deepStrictEqual(diagnostics.at(-1).params.diagnostics, [], 'close clears diagnostics'); console.log('ok editor open, highlight, change, and close: ' + file); }