aboutsummaryrefslogtreecommitdiff
path: root/plugin/test/runner.test.js
blob: a22d1032b9898c3c23f28bb329bb252af6e5f277 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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

'use strict';

const assert = require('node:assert/strict');
const { test } = require('node:test');
const { spawnSync } = require('node:child_process');
const { buildRunCommand, registerRunner } = require('../src/runner');

test('commands quote paths and only run after changing directory', () => {
	assert.equal(buildRunCommand('/tools/my cpsc', '/work/my file.cps', '/work/my project', 'posix'),
		"cd '/work/my project' && '/tools/my cpsc' '/work/my file.cps' -r");
	assert.equal(buildRunCommand('C:\\My Tools\\cpsc.exe', 'D:\\My Files\\test.cps', 'D:\\My Files', 'cmd'),
		'cd /d "D:\\My Files" && "C:\\My Tools\\cpsc.exe" "D:\\My Files\\test.cps" -r');
	assert.equal(buildRunCommand('C:\\My Tools\\cpsc.exe', "D:\\Pat's Files\\test.cps", "D:\\Pat's Files", 'powershell'),
		"Set-Location -LiteralPath 'D:\\Pat''s Files'; if ($?) { & 'C:\\My Tools\\cpsc.exe' 'D:\\Pat''s Files\\test.cps' -r }");
	assert.throws(() => buildRunCommand('cpsc\nother', 'test.cps', '.', 'posix'), /line breaks/);
	if (process.platform !== 'win32') {
		const file = "/tmp/Pat's file $(echo unexpected) `echo unexpected`.cps";
		const result = spawnSync('/bin/sh', ['-c', buildRunCommand('/usr/bin/printf', file, '/tmp', 'posix')], { encoding: 'utf8' });
		assert.equal(result.status, 0);
		assert.equal(result.stdout, file);
		const failed = spawnSync('/bin/sh', ['-c', buildRunCommand('/usr/bin/printf', file, '/missing-cie-run-directory', 'posix')], { encoding: 'utf8' });
		assert.notEqual(failed.status, 0);
		assert.equal(failed.stdout, '');
	}
});

function harness(platform = 'linux') {
	let run, close;
	const calls = { saved: 0, terminals: [], errors: [], settings: [] };
	const document = {
		languageId: 'cie-pseudocode', isUntitled: false,
		uri: { scheme: 'file', fsPath: '/project/sub/my file.cps' },
		async save() { calls.saved++; return true; }
	};
	const vscode = {
		env: { shell: 'C:\\Windows\\System32\\cmd.exe' },
		commands: { registerCommand(id, callback) { assert.equal(id, 'cie-pseudocode.runFile'); run = callback; return { dispose() {} }; } },
		workspace: {
			async openTextDocument() { return document; },
			getWorkspaceFolder() { return { uri: { fsPath: '/project' } }; },
			getConfiguration(section, uri) {
				calls.settings.push({ section, uri });
				return { get(key, fallback) { return section === 'cie-pseudocode' ? '/tools/my cpsc' : fallback; } };
			}
		},
		window: {
			activeTextEditor: { document },
			async showTextDocument(doc) { this.activeTextEditor = { document: doc }; },
			showErrorMessage(message) { calls.errors.push(message); },
			onDidCloseTerminal(callback) { close = callback; return { dispose() {} }; },
			createTerminal(name) {
				const terminal = { name, sent: [], show(focus) { this.preserveFocus = focus; }, sendText(text) { this.sent.push(text); }, dispose() {} };
				calls.terminals.push(terminal);
				return terminal;
			}
		}
	};
	registerRunner(vscode, { subscriptions: [] }, platform);
	return { vscode, document, calls, run: uri => run(uri), close: terminal => close(terminal) };
}

test('save before running the whole file, scope settings, reuse and recreate the terminal', async () => {
	const h = harness();
	await h.run(h.document.uri);
	assert.equal(h.calls.saved, 1);
	assert.equal(h.calls.terminals[0].preserveFocus, false);
	assert.equal(h.calls.terminals[0].sent[0], "cd '/project' && '/tools/my cpsc' '/project/sub/my file.cps' -r");
	assert.equal(h.calls.settings[0].uri, h.document.uri);
	h.close({});
	await h.run();
	assert.equal(h.calls.terminals.length, 1);
	h.close(h.calls.terminals[0]);
	h.vscode.workspace.getWorkspaceFolder = () => undefined;
	await h.run();
	assert.equal(h.calls.terminals.length, 2);
	assert.match(h.calls.terminals[1].sent[0], /^cd '\/project\/sub'/);
	assert.deepEqual(h.calls.errors, []);
});

test('save cancellation and failure do not create a terminal; untitled files use the saved URI', async () => {
	const h = harness();
	h.document.isUntitled = true;
	h.document.uri = { scheme: 'untitled', fsPath: 'Untitled-1' };
	h.document.save = async () => false;
	await h.run();
	assert.equal(h.calls.terminals.length, 0);
	h.document.save = async () => { throw new Error('Save failed'); };
	await h.run();
	assert.match(h.calls.errors[0], /Save failed/);
	assert.equal(h.calls.terminals.length, 0);
	h.document.save = async () => {
		h.vscode.window.activeTextEditor = { document: { ...h.document, isUntitled: false,
			uri: { scheme: 'file', fsPath: '/project/new file.cps' } } };
		return true;
	};
	await h.run();
	assert.match(h.calls.terminals[0].sent[0], /'\/project\/new file.cps' -r$/);
});

test('Windows recognizes custom pwsh and cmd profiles and retains the reused terminal shell', async () => {
	const h = harness('win32');
	let profile = 'My Shell';
	h.vscode.workspace.getConfiguration = section => ({ get(key, fallback) {
		if (section === 'cie-pseudocode') return 'cpsc';
		if (key === 'integrated.defaultProfile.windows') return profile;
		if (key === 'integrated.profiles.windows') return { 'My Shell': { path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe' } };
		return fallback;
	} });
	await h.run();
	assert.match(h.calls.terminals[0].sent[0], /^Set-Location.*& 'cpsc'/);
	profile = 'Command Prompt';
	await h.run();
	assert.match(h.calls.terminals[0].sent[1], /^Set-Location/);
	h.close(h.calls.terminals[0]);
	await h.run();
	assert.match(h.calls.terminals[1].sent[0], /^cd \/d .*&& "cpsc"/);
});