From 030913b68746a548b8b3bc43c1e9091a16d8e82c Mon Sep 17 00:00:00 2001 From: connectdotz Date: Mon, 9 Sep 2024 20:38:45 -0400 Subject: [PATCH] fix windows testNamePattern not found issue --- src/DebugConfigurationProvider.ts | 3 +- src/helpers.ts | 16 ++--- src/test-provider/test-item-data.ts | 1 - tests/DebugConfigurationProvider.test.ts | 3 + tests/helpers.test.ts | 92 ++++++++++++++---------- webpack/webpack.config.js | 12 ++-- 6 files changed, 74 insertions(+), 53 deletions(-) diff --git a/src/DebugConfigurationProvider.ts b/src/DebugConfigurationProvider.ts index c0d7ffc2d..9634ab191 100755 --- a/src/DebugConfigurationProvider.ts +++ b/src/DebugConfigurationProvider.ts @@ -9,6 +9,7 @@ import { escapeRegExp, parseCmdLine, toAbsoluteRootPath, + escapeQuotes, } from './helpers'; import { platform } from 'os'; import { PluginResourceSettings } from './Settings'; @@ -118,7 +119,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv return arg .replace(testFileRegex, toFilePath(this.fileNameToRun)) .replace(testFilePatternRegex, escapeRegExp(this.fileNameToRun)) - .replace(testNamePatternRegex, this.testToRun); + .replace(testNamePatternRegex, escapeQuotes(this.testToRun)); }); debugConfiguration.args = args; diff --git a/src/helpers.ts b/src/helpers.ts index 6efac5c18..25302ee53 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -238,6 +238,8 @@ export const emptyTestStats = (): TestStats => { return { success: 0, fail: 0, unknown: 0 }; }; +export const escapeQuotes = (str: string): string => str.replace(/(['"])/g, '\\$1'); + const getShellPath = (shell?: string | LoginShell): string | undefined => { if (!shell) { return; @@ -276,19 +278,13 @@ export const shellQuote = (str: string, shell?: string | LoginShell): string => switch (shellType) { case 'powershell': { const s = str.replace(/(['"])/g, '$1$1'); - if (s.length > 2 && s.slice(-2) === '\\\\') { - return `'${s}\\\\'`; - } - return `'${s}'`; + return s.endsWith('\\') ? `'${s}'\\` : `'${s}'`; } case 'cmd': { - let s = str.replace(/"/g, '""'); - s = s.replace(/([> 2 && s.slice(-2) === '\\\\') { - s = `${s}\\\\`; - } - return s.indexOf(' ') >= 0 || s.indexOf('"') >= 0 || s.length === 0 ? `"${s}"` : s; + // Escape double quotes by doubling them and always quote the string + // no need to escape special cmd characters (such as > void): void { - console.log(`${this.item.id} has ${this.item.children.size} children`); this.item.children.forEach((childItem) => { const child = this.context.getData(childItem); if (child) { diff --git a/tests/DebugConfigurationProvider.test.ts b/tests/DebugConfigurationProvider.test.ts index f28082a2d..25093182e 100755 --- a/tests/DebugConfigurationProvider.test.ts +++ b/tests/DebugConfigurationProvider.test.ts @@ -11,6 +11,7 @@ import { escapeRegExp, parseCmdLine, toAbsoluteRootPath, + escapeQuotes, } from '../src/helpers'; import * as fs from 'fs'; import { makeWorkspaceFolder } from './test-helper'; @@ -118,6 +119,7 @@ describe('DebugConfigurationProvider', () => { `('will only translate known variables: $args', ({ args, expected }) => { (toFilePath as unknown as jest.Mock<{}>).mockReturnValueOnce(fileName); (escapeRegExp as unknown as jest.Mock<{}>).mockReturnValueOnce(fileNamePattern); + (escapeQuotes as unknown as jest.Mock<{}>).mockImplementation((s) => s); let configuration: any = { name: 'vscode-jest-tests.v2', args }; @@ -133,6 +135,7 @@ describe('DebugConfigurationProvider', () => { it('will translate multiple variables in a single arg', () => { (toFilePath as unknown as jest.Mock<{}>).mockReturnValueOnce(fileName); (escapeRegExp as unknown as jest.Mock<{}>).mockReturnValueOnce(fileNamePattern); + (escapeQuotes as unknown as jest.Mock<{}>).mockImplementation((s) => s); let configuration: any = { name: 'vscode-jest-tests.v2', diff --git a/tests/helpers.test.ts b/tests/helpers.test.ts index 6e3af5c3f..c777f8806 100644 --- a/tests/helpers.test.ts +++ b/tests/helpers.test.ts @@ -231,43 +231,46 @@ describe('toUpperCaseDriveLetter', () => { describe('shellQuote', () => { it.each` - platform | shell | str | expected - ${'win32'} | ${undefined} | ${'plain text'} | ${'"plain text"'} - ${'linux'} | ${undefined} | ${'plain text'} | ${'plain\\ text'} - ${'win32'} | ${'powershell'} | ${"with 'single quote'"} | ${"'with ''single quote'''"} - ${'win32'} | ${'cmd.exe'} | ${"with 'single quote'"} | ${'"with \'single quote\'"'} - ${'linux'} | ${'/bin/bash'} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} - ${'darwin'} | ${'/bin/zsh'} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} - ${'darwin'} | ${{ path: '/bin/zsh', args: ['-l'] }} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} - ${'win32'} | ${undefined} | ${"with 'single quote'"} | ${'"with \'single quote\'"'} - ${'linux'} | ${undefined} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} - ${'win32'} | ${'powershell'} | ${'with "double quote"'} | ${'\'with ""double quote""\''} - ${'win32'} | ${'cmd.exe'} | ${'with "double quote"'} | ${'"with ""double quote"""'} - ${'linux'} | ${'bash'} | ${'with "double quote"'} | ${'with\\ \\"double\\ quote\\"'} - ${'win32'} | ${'powershell'} | ${'with $name.txt'} | ${"'with $name.txt'"} - ${'win32'} | ${'cmd.exe'} | ${'with $name.txt'} | ${'"with $name.txt"'} - ${'linux'} | ${'bash'} | ${'with $name.txt'} | ${'with\\ \\$name.txt'} - ${'win32'} | ${'powershell'} | ${'with \\$name\\.txt'} | ${"'with \\$name\\.txt'"} - ${'win32'} | ${'cmd.exe'} | ${'with \\$name\\.txt'} | ${'"with \\$name\\.txt"'} - ${'linux'} | ${'bash'} | ${'with \\$name\\.txt'} | ${'with\\ \\\\\\$name\\\\.txt'} - ${'linux'} | ${{ path: '/bin/sh', args: ['--login'] }} | ${'with \\$name\\.txt'} | ${'with\\ \\\\\\$name\\\\.txt'} - ${'win32'} | ${'powershell'} | ${''} | ${"''"} - ${'win32'} | ${undefined} | ${''} | ${'""'} - ${'darwin'} | ${undefined} | ${''} | ${'""'} - ${'win32'} | ${'powershell'} | ${'with \\ and \\\\'} | ${"'with \\ and \\\\\\\\'"} - ${'win32'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\ and \\\\\\\\"'} - ${'linux'} | ${undefined} | ${'with \\ and \\\\'} | ${'with\\ \\\\\\ and\\ \\\\\\\\'} - ${'win32'} | ${'powershell'} | ${'something\\'} | ${"'something\\'"} - ${'win32'} | ${undefined} | ${'something\\'} | ${'something\\'} - ${'darwin'} | ${undefined} | ${'something\\'} | ${'something\\\\'} - ${'win32'} | ${'powershell'} | ${'with `backtick'} | ${"'with `backtick'"} - ${'win32'} | ${undefined} | ${'with `backtick'} | ${'"with `backtick"'} - ${'darwin'} | ${undefined} | ${'with `backtick'} | ${'with\\ \\`backtick'} - `('can quote "$str" for $shell on $platform', ({ platform, shell, str, expected }) => { - jest.resetAllMocks(); - mockPlatform.mockReturnValueOnce(platform); - expect(shellQuote(str, shell)).toEqual(expected); - }); + case | platform | shell | str | expected + ${1} | ${'win32'} | ${undefined} | ${'plain text'} | ${'"plain text"'} + ${2} | ${'linux'} | ${undefined} | ${'plain text'} | ${'plain\\ text'} + ${3} | ${'win32'} | ${'powershell'} | ${"with 'single quote'"} | ${"'with ''single quote'''"} + ${4} | ${'win32'} | ${'cmd.exe'} | ${"with 'single quote'"} | ${'"with \'single quote\'"'} + ${5} | ${'linux'} | ${'/bin/bash'} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} + ${6} | ${'darwin'} | ${'/bin/zsh'} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} + ${7} | ${'darwin'} | ${{ path: '/bin/zsh', args: ['-l'] }} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} + ${8} | ${'win32'} | ${undefined} | ${"with 'single quote'"} | ${'"with \'single quote\'"'} + ${9} | ${'linux'} | ${undefined} | ${"with 'single quote'"} | ${"with\\ \\'single\\ quote\\'"} + ${10} | ${'win32'} | ${'powershell'} | ${'with "double quote"'} | ${'\'with ""double quote""\''} + ${11} | ${'win32'} | ${'cmd.exe'} | ${'with "double quote"'} | ${'"with ""double quote"""'} + ${12} | ${'linux'} | ${'bash'} | ${'with "double quote"'} | ${'with\\ \\"double\\ quote\\"'} + ${13} | ${'win32'} | ${'powershell'} | ${'with $name.txt'} | ${"'with $name.txt'"} + ${14} | ${'win32'} | ${'cmd.exe'} | ${'with $name.txt'} | ${'"with $name.txt"'} + ${15} | ${'linux'} | ${'bash'} | ${'with $name.txt'} | ${'with\\ \\$name.txt'} + ${16} | ${'win32'} | ${'powershell'} | ${'with \\$name\\.txt'} | ${"'with \\$name\\.txt'"} + ${17} | ${'win32'} | ${'cmd.exe'} | ${'with \\$name\\.txt'} | ${'"with \\$name\\.txt"'} + ${18} | ${'linux'} | ${'bash'} | ${'with \\$name\\.txt'} | ${'with\\ \\\\\\$name\\\\.txt'} + ${19} | ${'linux'} | ${{ path: '/bin/sh', args: ['--login'] }} | ${'with \\$name\\.txt'} | ${'with\\ \\\\\\$name\\\\.txt'} + ${20} | ${'win32'} | ${'powershell'} | ${''} | ${"''"} + ${21} | ${'win32'} | ${undefined} | ${''} | ${'""'} + ${22} | ${'darwin'} | ${undefined} | ${''} | ${'""'} + ${23} | ${'win32'} | ${'powershell'} | ${'with \\ and \\\\'} | ${"'with \\ and \\\\'\\"} + ${24} | ${'win32'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\ and \\\\"'} + ${25} | ${'linux'} | ${undefined} | ${'with \\ and \\\\'} | ${'with\\ \\\\\\ and\\ \\\\\\\\'} + ${26} | ${'win32'} | ${'powershell'} | ${'something\\'} | ${"'something\\'\\"} + ${27} | ${'win32'} | ${undefined} | ${'something\\'} | ${'"something\\"'} + ${28} | ${'darwin'} | ${undefined} | ${'something\\'} | ${'something\\\\'} + ${29} | ${'win32'} | ${'powershell'} | ${'with `backtick'} | ${"'with `backtick'"} + ${30} | ${'win32'} | ${undefined} | ${'with `backtick'} | ${'"with `backtick"'} + ${31} | ${'darwin'} | ${undefined} | ${'with `backtick'} | ${'with\\ \\`backtick'} + `( + 'case $case: can quote "$str" for $shell on $platform', + ({ platform, shell, str, expected }) => { + jest.resetAllMocks(); + mockPlatform.mockReturnValueOnce(platform); + expect(shellQuote(str, shell)).toEqual(expected); + } + ); }); it.each` name | e | matchString @@ -481,3 +484,18 @@ describe('getValidJestCommand', () => { ]); }); }); + +describe('escapeQuotes', () => { + it.each` + case | inputString | expected + ${'no quotes'} | ${'no quotes'} | ${'no quotes'} + ${'single quotes'} | ${"with 'single quotes'"} | ${"with \\'single quotes\\'"} + ${'double quotes'} | ${'with "double quotes"'} | ${'with \\"double quotes\\"'} + ${'mixed quotes'} | ${'with "double" and \'single\''} | ${'with \\"double\\" and \\\'single\\\''} + ${'escaped quotes'} | ${'with \\"escaped\\" quotes'} | ${'with \\\\"escaped\\\\" quotes'} + ${'escaped quotes 2'} | ${"with \\'escaped\\' quotes"} | ${"with \\\\'escaped\\\\' quotes"} + ${'escaped quotes 3'} | ${'with \\\'escaped\\\' and "quotes"'} | ${'with \\\\\'escaped\\\\\' and \\"quotes\\"'} + `('$case', ({ inputString, expected }) => { + expect(helper.escapeQuotes(inputString)).toEqual(expected); + }); +}); diff --git a/webpack/webpack.config.js b/webpack/webpack.config.js index ba3495c7a..e8b32de9f 100644 --- a/webpack/webpack.config.js +++ b/webpack/webpack.config.js @@ -16,15 +16,19 @@ module.exports = (env) => { // Function to find files matching a pattern within a specific package function addMatchingFiles(packageName, filePattern) { const files = glob.sync(`node_modules/**/${packageName}/${filePattern}`, { absolute: true }); - return files; + const normalizedFiles = files.map((file) => path.normalize(file)); + return normalizedFiles; } + // this path should always use forward slashes. On windows, this requires replacing backslashes with forward slashes + const dummyModulePath = path.resolve(__dirname, 'dummy-module.js').replace(/\\/g, '/'); + const replacements = [ - { packageName: '@babel/generator', replacement: path.resolve(__dirname, './dummy-module.js') }, - { packageName: '@babel/core', replacement: path.resolve(__dirname, './dummy-module.js') }, + { packageName: '@babel/generator', replacement: dummyModulePath }, + { packageName: '@babel/core', replacement: dummyModulePath }, { packageName: './src/InlineSnapshots.ts', - replacement: path.resolve(__dirname, './dummy-module.js'), + replacement: dummyModulePath, }, ];