|
1 | 1 | import * as assert from 'assert'; |
2 | 2 | import * as path from 'path'; |
| 3 | +import * as fs from 'fs'; |
| 4 | +import * as os from 'os'; |
3 | 5 | import * as vscode from 'vscode'; |
4 | 6 | import { DCollection } from '../../../src/diagnostic'; |
5 | 7 | import { SanyData } from '../../../src/parsers/sany'; |
@@ -65,4 +67,70 @@ suite('SANY Tool cancellation handling', () => { |
65 | 67 | parseModuleMutable.parseSpec = originalParseSpec; |
66 | 68 | } |
67 | 69 | }); |
| 70 | + |
| 71 | + test('ParseModuleTool uses a temp copy and leaves the original file untouched', async () => { |
| 72 | + const parseModuleMutable = parseModule as unknown as { |
| 73 | + transpilePlusCal: typeof parseModule.transpilePlusCal; |
| 74 | + parseSpec: typeof parseModule.parseSpec; |
| 75 | + }; |
| 76 | + const originalTranspile = parseModuleMutable.transpilePlusCal; |
| 77 | + const originalParseSpec = parseModuleMutable.parseSpec; |
| 78 | + |
| 79 | + const originalDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'tlaplus-sany-orig-')); |
| 80 | + const originalPath = path.join(originalDir, 'Spec.tla'); |
| 81 | + const originalContent = '---- MODULE Spec ----\n====\n'; |
| 82 | + await fs.promises.writeFile(originalPath, originalContent, 'utf8'); |
| 83 | + |
| 84 | + let transpilePath: string | undefined; |
| 85 | + let transpileDiagPath: string | undefined; |
| 86 | + let parseSpecPath: string | undefined; |
| 87 | + |
| 88 | + parseModuleMutable.transpilePlusCal = async (uri: vscode.Uri, _token?: vscode.CancellationToken, options?: { diagnosticFilePath?: string }) => { |
| 89 | + transpilePath = uri.fsPath; |
| 90 | + transpileDiagPath = options?.diagnosticFilePath; |
| 91 | + const dc = new DCollection(); |
| 92 | + dc.addMessage(options?.diagnosticFilePath ?? uri.fsPath, new vscode.Range(0, 0, 0, 0), 'pluscal message'); |
| 93 | + return dc; |
| 94 | + }; |
| 95 | + |
| 96 | + parseModuleMutable.parseSpec = async (uri: vscode.Uri) => { |
| 97 | + parseSpecPath = uri.fsPath; |
| 98 | + const sd = new SanyData(); |
| 99 | + sd.dCollection.addMessage(uri.fsPath, new vscode.Range(1, 0, 1, 0), 'sany message'); |
| 100 | + return sd; |
| 101 | + }; |
| 102 | + |
| 103 | + try { |
| 104 | + const tool = new ParseModuleTool(); |
| 105 | + const options = { |
| 106 | + toolInvocationToken: undefined, |
| 107 | + input: { fileName: originalPath } |
| 108 | + } as unknown as vscode.LanguageModelToolInvocationOptions<FileParameter>; |
| 109 | + |
| 110 | + const result = await tool.invoke(options, new vscode.CancellationTokenSource().token); |
| 111 | + |
| 112 | + assert.ok(transpilePath, 'transpilePlusCal should have been called'); |
| 113 | + assert.ok(parseSpecPath, 'parseSpec should have been called'); |
| 114 | + assert.notStrictEqual(transpilePath, originalPath, 'transpilePlusCal must run on a temp copy'); |
| 115 | + assert.notStrictEqual(parseSpecPath, originalPath, 'parseSpec must parse the temp copy'); |
| 116 | + assert.strictEqual(transpileDiagPath, originalPath, 'diagnostics should be attributed to the original file'); |
| 117 | + |
| 118 | + // Returned messages should reference the original path, not the temp copy |
| 119 | + assert.strictEqual(result.content.length, 2, 'Should surface both PlusCal and SANY diagnostics'); |
| 120 | + result.content.forEach(part => { |
| 121 | + assert.ok(part instanceof vscode.LanguageModelTextPart, 'Each result should be a text part'); |
| 122 | + const val = (part as vscode.LanguageModelTextPart).value; |
| 123 | + assert.ok(val.includes(originalPath), 'Diagnostics should point to the original file path'); |
| 124 | + assert.ok(!val.includes(transpilePath!), 'Diagnostics should not leak temp file paths'); |
| 125 | + }); |
| 126 | + |
| 127 | + // Original file should remain unchanged on disk |
| 128 | + const after = await fs.promises.readFile(originalPath, 'utf8'); |
| 129 | + assert.strictEqual(after, originalContent, 'Original file content must not be modified'); |
| 130 | + } finally { |
| 131 | + parseModuleMutable.transpilePlusCal = originalTranspile; |
| 132 | + parseModuleMutable.parseSpec = originalParseSpec; |
| 133 | + await fs.promises.rm(originalDir, { recursive: true, force: true }); |
| 134 | + } |
| 135 | + }); |
68 | 136 | }); |
0 commit comments