Skip to content

Commit a8a5606

Browse files
committed
Warn when backslashes are used in paths/globs on Windows
Backslashes behave inconsistently between OSes in the version of glob that Jasmine currently uses, and the next major verison of glob will treat them as escape sequences rather than path separators on all OSes.
1 parent da0db7f commit a8a5606

File tree

5 files changed

+222
-10
lines changed

5 files changed

+222
-10
lines changed

bin/jasmine.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
#!/usr/bin/env node
22

33
const path = require('path');
4+
const os = require('os');
45
const Command = require('../lib/command');
56
const Jasmine = require('../lib/jasmine');
67

7-
const jasmine = new Jasmine({ projectBaseDir: path.resolve() });
8+
let projectBaseDir = path.resolve();
9+
10+
if (os.platform() === 'win32') {
11+
// Future versions of glob will interpret backslashes as escape sequences on
12+
// all platforms, and Jasmine warns about them. Convert to slashes to avoid
13+
// the warning and future behavior change.
14+
projectBaseDir = projectBaseDir.replace(/\\/g, '/');
15+
}
16+
17+
const jasmine = new Jasmine({ projectBaseDir });
818
const examplesDir = path.join(path.dirname(require.resolve('jasmine-core')), 'jasmine-core', 'example', 'node_example');
9-
const command = new Command(path.resolve(), examplesDir, console.log);
19+
const command = new Command(path.resolve(), examplesDir, {
20+
print: console.log,
21+
platform: os.platform,
22+
});
1023

1124
command.run(jasmine, process.argv.slice(2));

lib/command.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ const subCommands = {
2424
}
2525
};
2626

27-
function Command(projectBaseDir, examplesDir, print) {
28-
this.projectBaseDir = projectBaseDir;
29-
this.specDir = path.join(projectBaseDir, 'spec');
27+
function Command(projectBaseDir, examplesDir, deps) {
28+
const {print, platform} = deps;
29+
const isWindows = platform() === 'win32';
30+
31+
this.projectBaseDir = isWindows ? unWindows(projectBaseDir) : projectBaseDir;
32+
this.specDir = `${this.projectBaseDir}/spec`;
3033

3134
const command = this;
3235

@@ -46,7 +49,7 @@ function Command(projectBaseDir, examplesDir, print) {
4649
if (commandToRun) {
4750
commandToRun.action({jasmine: jasmine, projectBaseDir: command.projectBaseDir, specDir: command.specDir, examplesDir: examplesDir, print: print});
4851
} else {
49-
const env = parseOptions(commands);
52+
const env = parseOptions(commands, isWindows);
5053
if (env.unknownOptions.length > 0) {
5154
process.exitCode = 1;
5255
print('Unknown options: ' + env.unknownOptions.join(', '));
@@ -63,7 +66,7 @@ function isFileArg(arg) {
6366
return arg.indexOf('--') !== 0 && !isEnvironmentVariable(arg);
6467
}
6568

66-
function parseOptions(argv) {
69+
function parseOptions(argv, isWindows) {
6770
let files = [],
6871
helpers = [],
6972
requires = [],
@@ -100,7 +103,7 @@ function parseOptions(argv) {
100103
} else if (arg === '--') {
101104
break;
102105
} else if (isFileArg(arg)) {
103-
files.push(arg);
106+
files.push(isWindows ? unWindows(arg) : arg);
104107
} else if (!isEnvironmentVariable(arg)) {
105108
unknownOptions.push(arg);
106109
}
@@ -315,3 +318,11 @@ function setEnvironmentVariables(commands) {
315318
}
316319
});
317320
}
321+
322+
// Future versions of glob will interpret backslashes as escape sequences on
323+
// all platforms, and Jasmine warns about them. Convert to slashes to avoid
324+
// the warning and future behavior change. Should only be called when running
325+
// on Windows.
326+
function unWindows(projectBaseDir) {
327+
return projectBaseDir.replace(/\\/g, '/');
328+
}

lib/jasmine.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const os = require('os');
12
const path = require('path');
23
const util = require('util');
34
const glob = require('glob');
@@ -42,6 +43,7 @@ class Jasmine {
4243
constructor(options) {
4344
options = options || {};
4445
this.loader = options.loader || new Loader();
46+
this.isWindows_ = (options.platform || os.platform)() === 'win32';
4547
const jasmineCore = options.jasmineCore || require('jasmine-core');
4648

4749
if (options.globals === false) {
@@ -50,7 +52,13 @@ class Jasmine {
5052
this.jasmine = jasmineCore.boot(jasmineCore);
5153
}
5254

53-
this.projectBaseDir = options.projectBaseDir || path.resolve();
55+
if (options.projectBaseDir) {
56+
this.validatePath_(options.projectBaseDir);
57+
this.projectBaseDir = options.projectBaseDir;
58+
} else {
59+
this.projectBaseDir = (options.getcwd || path.resolve)();
60+
}
61+
5462
this.specDir = '';
5563
this.specFiles = [];
5664
this.helperFiles = [];
@@ -275,6 +283,7 @@ class Jasmine {
275283
* @type string | undefined
276284
*/
277285
this.specDir = config.spec_dir || this.specDir;
286+
this.validatePath_(this.specDir);
278287

279288
/**
280289
* Whether to fail specs that contain no expectations.
@@ -497,6 +506,16 @@ class Jasmine {
497506

498507
return overallResult;
499508
}
509+
510+
validatePath_(path) {
511+
if (this.isWindows_ && path.includes('\\')) {
512+
const fixed = path.replace(/\\/g, '/');
513+
console.warn('Backslashes in ' +
514+
'file paths behave inconsistently between platforms and might not be ' +
515+
'treated as directory separators in a future version. Consider ' +
516+
`changing ${path} to ${fixed}.`);
517+
}
518+
}
500519
}
501520

502521
/**
@@ -520,6 +539,10 @@ Jasmine.prototype.addMatchingHelperFiles = addFiles('helperFiles');
520539

521540
function addFiles(kind) {
522541
return function (files) {
542+
for (const f of files) {
543+
this.validatePath_(f);
544+
}
545+
523546
const jasmineRunner = this;
524547
const fileArr = this[kind];
525548

spec/command_spec.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ describe('command', function() {
5151
};
5252
}());
5353

54-
this.command = new Command(projectBaseDir, examplesDir, this.out.print);
54+
this.command = new Command(projectBaseDir, examplesDir, {
55+
print: this.out.print,
56+
platform: () => 'Oberon'
57+
});
5558

5659
this.fakeJasmine = jasmine.createSpyObj('jasmine', ['loadConfigFile', 'addMatchingHelperFiles', 'addRequires', 'showColors', 'execute',
5760
'randomizeTests', 'seed', 'coreVersion', 'clearReporters', 'addReporter']);
@@ -385,6 +388,50 @@ describe('command', function() {
385388
expect(this.fakeJasmine.seed).toHaveBeenCalledWith('12345');
386389
});
387390
});
391+
392+
describe('Path handling', function() {
393+
describe('On Windows', function () {
394+
beforeEach(function() {
395+
this.deps = {
396+
print: this.out.print,
397+
platform: () => 'win32'
398+
};
399+
});
400+
401+
it('replaces backslashes in the project base dir with slashes', function() {
402+
const subject = new Command('foo\\bar', '', this.deps);
403+
expect(subject.projectBaseDir).toEqual('foo/bar');
404+
expect(subject.specDir).toEqual('foo/bar/spec');
405+
});
406+
407+
it('replaces backslashes in spec file paths from the command line', async function() {
408+
const subject = new Command('arbitrary', '', this.deps);
409+
await subject.run(this.fakeJasmine, ['somedir\\somespec.js']);
410+
expect(this.fakeJasmine.execute).toHaveBeenCalledWith(['somedir/somespec.js'], undefined);
411+
});
412+
});
413+
414+
describe('On non-Windows systems', function () {
415+
beforeEach(function() {
416+
this.deps = {
417+
print: this.out.print,
418+
platform: () => 'BeOS'
419+
};
420+
});
421+
422+
it('does not replace backslashes in the project base dir', function() {
423+
const subject = new Command('foo\\bar', '', this.deps);
424+
expect(subject.projectBaseDir).toEqual('foo\\bar');
425+
expect(subject.specDir).toEqual('foo\\bar/spec');
426+
});
427+
428+
it('does not replace backslashes in spec file paths from the command line', async function() {
429+
const subject = new Command('arbitrary', '', this.deps);
430+
await subject.run(this.fakeJasmine, ['somedir\\somespec.js']);
431+
expect(this.fakeJasmine.execute).toHaveBeenCalledWith(['somedir\\somespec.js'], undefined);
432+
});
433+
});
434+
});
388435
});
389436

390437
// Adapted from Sindre Sorhus's escape-string-regexp (MIT license)

spec/jasmine_spec.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,122 @@ describe('Jasmine', function() {
630630
});
631631
});
632632
});
633+
634+
describe('When running on Windows', function() {
635+
beforeEach(function() {
636+
spyOn(console, 'warn');
637+
});
638+
639+
function windows() {
640+
return 'win32';
641+
}
642+
643+
it('warns about backslashes in the configured project base dir', function() {
644+
new Jasmine({
645+
projectBaseDir: 'c:\\foo\\bar',
646+
platform: windows,
647+
jasmineCore: this.fakeJasmineCore,
648+
});
649+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
650+
'file paths behave inconsistently between platforms and might not be ' +
651+
'treated as directory separators in a future version. Consider ' +
652+
'changing c:\\foo\\bar to c:/foo/bar.');
653+
});
654+
655+
it('does not warn about backslashes in the current working directory', function() {
656+
const jasmine = new Jasmine({
657+
getcwd: () => 'c:\\foo\\bar',
658+
platform: windows,
659+
jasmineCore: this.fakeJasmineCore,
660+
});
661+
expect(jasmine.projectBaseDir).toEqual('c:\\foo\\bar');
662+
expect(console.warn).not.toHaveBeenCalled();
663+
});
664+
665+
it('warns about backslashes in spec_dir', function() {
666+
const jasmine = new Jasmine({
667+
platform: windows,
668+
jasmineCore: this.fakeJasmineCore,
669+
});
670+
jasmine.loadConfig({
671+
spec_dir: 'foo\\bar',
672+
});
673+
674+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
675+
'file paths behave inconsistently between platforms and might not be ' +
676+
'treated as directory separators in a future version. Consider ' +
677+
'changing foo\\bar to foo/bar.');
678+
});
679+
680+
it('warns about backslashes in helpers', function() {
681+
const jasmine = new Jasmine({
682+
platform: windows,
683+
jasmineCore: this.fakeJasmineCore,
684+
});
685+
686+
jasmine.loadConfig({
687+
helpers: ['foo\\bar']
688+
});
689+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
690+
'file paths behave inconsistently between platforms and might not be ' +
691+
'treated as directory separators in a future version. Consider ' +
692+
'changing foo\\bar to foo/bar.');
693+
694+
jasmine.addMatchingHelperFiles(['foo\\baz']);
695+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
696+
'file paths behave inconsistently between platforms and might not be ' +
697+
'treated as directory separators in a future version. Consider ' +
698+
'changing foo\\baz to foo/baz.');
699+
});
700+
701+
it('warns about backslashes in spec_files', function() {
702+
const jasmine = new Jasmine({
703+
platform: windows,
704+
jasmineCore: this.fakeJasmineCore,
705+
});
706+
707+
jasmine.loadConfig({
708+
spec_files: ['foo\\bar']
709+
});
710+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
711+
'file paths behave inconsistently between platforms and might not be ' +
712+
'treated as directory separators in a future version. Consider ' +
713+
'changing foo\\bar to foo/bar.');
714+
715+
jasmine.addMatchingSpecFiles(['foo\\baz']);
716+
expect(console.warn).toHaveBeenCalledWith('Backslashes in ' +
717+
'file paths behave inconsistently between platforms and might not be ' +
718+
'treated as directory separators in a future version. Consider ' +
719+
'changing foo\\baz to foo/baz.');
720+
});
721+
722+
it('does not warn if no configured path contains backslashes', function() {
723+
const jasmine = new Jasmine({
724+
projectBaseDir: 'c:/foo/bar',
725+
platform: windows,
726+
jasmineCore: this.fakeJasmineCore,
727+
});
728+
jasmine.loadConfig({
729+
spec_dir: 'foo/bar',
730+
spec_files: ['baz/qux'],
731+
helpers: ['waldo/fred']
732+
});
733+
expect(console.warn).not.toHaveBeenCalled();
734+
});
735+
});
736+
737+
it('does not warn about backslashes when not running on Windows', function() {
738+
spyOn(console, 'warn');
739+
const jasmine = new Jasmine({
740+
projectBaseDir: 'foo\\bar',
741+
platform: () => 'NetWare',
742+
jasmineCore: this.fakeJasmineCore,
743+
});
744+
jasmine.loadConfig({
745+
spec_dir: 'foo\\bar',
746+
spec_files: ['baz\\qux'],
747+
helpers: ['waldo\\fred']
748+
});
749+
expect(console.warn).not.toHaveBeenCalled();
750+
});
633751
});

0 commit comments

Comments
 (0)