Skip to content

Commit 953f011

Browse files
committed
feat: add cross-platform test utilities for filesystem detection
- Add getPlatformInfo() to detect OS and filesystem capabilities - Add conditional test helpers for platform-specific features - Add path utilities for cross-platform compatibility - Support environment variable overrides for CI testing - Include case sensitivity and symlink detection
1 parent 2df36f1 commit 953f011

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

src/utils/test-helpers.ts

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/**
2+
* Cross-platform test utilities for handling filesystem differences
3+
*/
4+
5+
import { platform } from 'node:os';
6+
import { join, sep, win32, posix } from 'node:path';
7+
import { accessSync, constants, lstatSync } from 'node:fs';
8+
9+
export interface PlatformInfo {
10+
isWindows: boolean;
11+
isMacOS: boolean;
12+
isLinux: boolean;
13+
pathSeparator: string;
14+
caseSensitive: boolean;
15+
supportsSymlinks: boolean;
16+
}
17+
18+
/**
19+
* Get current platform information
20+
*/
21+
export function getPlatformInfo(): PlatformInfo {
22+
const currentPlatform = platform();
23+
const isWindows = currentPlatform === 'win32';
24+
const isMacOS = currentPlatform === 'darwin';
25+
const isLinux = currentPlatform === 'linux';
26+
27+
return {
28+
isWindows,
29+
isMacOS,
30+
isLinux,
31+
pathSeparator: sep,
32+
caseSensitive: getCaseSensitivity(),
33+
supportsSymlinks: getSymlinkSupport(),
34+
};
35+
}
36+
37+
/**
38+
* Detect filesystem case sensitivity
39+
*/
40+
function getCaseSensitivity(): boolean {
41+
// Check environment variable first (from CI)
42+
const envVar = process.env.MARKMV_TEST_FILESYSTEM_CASE_SENSITIVE;
43+
if (envVar !== undefined) {
44+
return envVar === 'true';
45+
}
46+
47+
// Default assumptions based on platform
48+
const currentPlatform = platform();
49+
switch (currentPlatform) {
50+
case 'win32':
51+
return false; // Windows is typically case-insensitive
52+
case 'darwin':
53+
return false; // macOS is typically case-insensitive (default APFS/HFS+)
54+
case 'linux':
55+
return true; // Linux is typically case-sensitive
56+
default:
57+
return true; // Default to case-sensitive for unknown platforms
58+
}
59+
}
60+
61+
/**
62+
* Detect symbolic link support
63+
*/
64+
function getSymlinkSupport(): boolean {
65+
// Check environment variable first (from CI)
66+
const envVar = process.env.MARKMV_TEST_SUPPORTS_SYMLINKS;
67+
if (envVar !== undefined) {
68+
return envVar === 'true';
69+
}
70+
71+
// Default assumptions based on platform
72+
const currentPlatform = platform();
73+
switch (currentPlatform) {
74+
case 'win32':
75+
return false; // Windows has limited symlink support
76+
case 'darwin':
77+
case 'linux':
78+
return true; // Unix-like systems generally support symlinks
79+
default:
80+
return false; // Default to no symlink support for unknown platforms
81+
}
82+
}
83+
84+
/**
85+
* Normalize path for the current platform
86+
*/
87+
export function normalizePath(path: string): string {
88+
const platformInfo = getPlatformInfo();
89+
90+
if (platformInfo.isWindows) {
91+
return win32.normalize(path);
92+
} else {
93+
return posix.normalize(path);
94+
}
95+
}
96+
97+
/**
98+
* Create a path using the appropriate separator for the current platform
99+
*/
100+
export function createPath(...segments: string[]): string {
101+
const platformInfo = getPlatformInfo();
102+
103+
if (platformInfo.isWindows) {
104+
return win32.join(...segments);
105+
} else {
106+
return posix.join(...segments);
107+
}
108+
}
109+
110+
/**
111+
* Convert path separators to the current platform
112+
*/
113+
export function convertPathSeparators(path: string): string {
114+
const platformInfo = getPlatformInfo();
115+
116+
if (platformInfo.isWindows) {
117+
return path.replace(/\//g, '\\');
118+
} else {
119+
return path.replace(/\\/g, '/');
120+
}
121+
}
122+
123+
/**
124+
* Test if two filenames would conflict on the current filesystem
125+
*/
126+
export function wouldFilenamesConflict(filename1: string, filename2: string): boolean {
127+
const platformInfo = getPlatformInfo();
128+
129+
if (platformInfo.caseSensitive) {
130+
return filename1 === filename2;
131+
} else {
132+
return filename1.toLowerCase() === filename2.toLowerCase();
133+
}
134+
}
135+
136+
/**
137+
* Skip test if the current platform doesn't support the required feature
138+
*/
139+
export function skipIfUnsupported(feature: 'symlinks' | 'case-sensitivity'): boolean {
140+
const platformInfo = getPlatformInfo();
141+
142+
switch (feature) {
143+
case 'symlinks':
144+
return !platformInfo.supportsSymlinks;
145+
case 'case-sensitivity':
146+
return !platformInfo.caseSensitive;
147+
default:
148+
return false;
149+
}
150+
}
151+
152+
/**
153+
* Create test helper that conditionally runs based on platform capabilities
154+
*/
155+
export function conditionalTest(
156+
name: string,
157+
requirement: 'symlinks' | 'case-sensitivity' | 'windows' | 'unix',
158+
testFn: () => void | Promise<void>
159+
): void {
160+
const platformInfo = getPlatformInfo();
161+
162+
let shouldSkip = false;
163+
let skipReason = '';
164+
165+
switch (requirement) {
166+
case 'symlinks':
167+
shouldSkip = !platformInfo.supportsSymlinks;
168+
skipReason = 'symbolic links not supported on this platform';
169+
break;
170+
case 'case-sensitivity':
171+
shouldSkip = !platformInfo.caseSensitive;
172+
skipReason = 'filesystem is not case-sensitive';
173+
break;
174+
case 'windows':
175+
shouldSkip = !platformInfo.isWindows;
176+
skipReason = 'test requires Windows';
177+
break;
178+
case 'unix':
179+
shouldSkip = platformInfo.isWindows;
180+
skipReason = 'test requires Unix-like system';
181+
break;
182+
}
183+
184+
if (shouldSkip) {
185+
test.skip(`${name} (skipped: ${skipReason})`, testFn);
186+
} else {
187+
test(name, testFn);
188+
}
189+
}
190+
191+
/**
192+
* Check if a file exists and is accessible
193+
*/
194+
export function fileExists(filePath: string): boolean {
195+
try {
196+
accessSync(filePath, constants.F_OK);
197+
return true;
198+
} catch {
199+
return false;
200+
}
201+
}
202+
203+
/**
204+
* Check if a path is a symbolic link
205+
*/
206+
export function isSymbolicLink(filePath: string): boolean {
207+
try {
208+
const stats = lstatSync(filePath);
209+
return stats.isSymbolicLink();
210+
} catch {
211+
return false;
212+
}
213+
}
214+
215+
/**
216+
* Get OS-specific temporary directory patterns
217+
*/
218+
export function getTempDirPatterns(): string[] {
219+
const platformInfo = getPlatformInfo();
220+
221+
if (platformInfo.isWindows) {
222+
return ['C:\\temp', 'C:\\tmp', '%TEMP%', '%TMP%'];
223+
} else {
224+
return ['/tmp', '/var/tmp', '$TMPDIR'];
225+
}
226+
}
227+
228+
/**
229+
* Platform-specific test data for path testing
230+
*/
231+
export const PLATFORM_TEST_PATHS = {
232+
windows: {
233+
absolute: ['C:\\Users\\test\\file.txt', 'D:\\projects\\readme.md'],
234+
relative: ['..\\parent\\file.txt', 'subfolder\\document.md'],
235+
invalid: ['C:', 'C:\\con', 'C:\\prn', 'C:\\aux'],
236+
},
237+
unix: {
238+
absolute: ['/home/test/file.txt', '/usr/local/bin/script'],
239+
relative: ['../parent/file.txt', 'subfolder/document.md'],
240+
invalid: ['/dev/null/../file', ''],
241+
},
242+
};
243+
244+
/**
245+
* Get platform-appropriate test paths
246+
*/
247+
export function getTestPaths(): typeof PLATFORM_TEST_PATHS.windows | typeof PLATFORM_TEST_PATHS.unix {
248+
const platformInfo = getPlatformInfo();
249+
250+
if (platformInfo.isWindows) {
251+
return PLATFORM_TEST_PATHS.windows;
252+
} else {
253+
return PLATFORM_TEST_PATHS.unix;
254+
}
255+
}

0 commit comments

Comments
 (0)