Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit 04103e5

Browse files
authored
Fix bugs in findScripts (#223)
* Code was buggy when fileList had only one entry. * Fix bug in pathToRegExp that was not considering '.' as literal. * Make the code easier to unit test. * More unit tests.
1 parent cc29b29 commit 04103e5

File tree

2 files changed

+115
-27
lines changed

2 files changed

+115
-27
lines changed

src/agent/v8debugapi.js

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var formatInterval = function(msg, interval) {
5858
};
5959

6060
var singleton;
61-
module.exports.create = function(logger_, config_, jsFiles_, sourcemapper_) {
61+
function create(logger_, config_, jsFiles_, sourcemapper_) {
6262
if (singleton && !config_.forceNewAgent_) {
6363
return singleton;
6464
}
@@ -377,22 +377,6 @@ module.exports.create = function(logger_, config_, jsFiles_, sourcemapper_) {
377377
return null;
378378
}
379379

380-
/**
381-
* @param {!string} scriptPath path of a script
382-
*/
383-
function pathToRegExp(scriptPath) {
384-
// make sure the script path starts with a slash. This makes sure our
385-
// regexp doesn't match monkey.js when the user asks to set a breakpoint
386-
// in key.js
387-
if (path.sep === '/' || scriptPath.indexOf(':') === -1) {
388-
scriptPath = path.join(path.sep, scriptPath);
389-
}
390-
if (path.sep !== '/') {
391-
scriptPath = scriptPath.replace(new RegExp('\\\\', 'g'), '\\\\');
392-
}
393-
return new RegExp(scriptPath + '$');
394-
}
395-
396380
function setByRegExp(scriptPath, line, column) {
397381
var regexp = pathToRegExp(scriptPath);
398382
var num = v8.setScriptBreakPointByRegExp(regexp, line - 1, column - 1);
@@ -433,17 +417,12 @@ module.exports.create = function(logger_, config_, jsFiles_, sourcemapper_) {
433417
var regexp = pathToRegExp(scriptPath);
434418
// Next try to match path.
435419
var matches = Object.keys(fileStats).filter(regexp.test.bind(regexp));
436-
// Finally look for files with the same name regardless of path.
437-
if (matches.length !== 1) {
438-
matches = Object.keys(fileStats);
439-
var components = scriptPath.split(path.sep);
440-
for (var i = components.length - 1;
441-
i >= 0 && matches.length > 1; i--) {
442-
regexp = pathToRegExp(components.slice(i).join(path.sep));
443-
matches = matches.filter(regexp.test.bind(regexp));
444-
}
420+
if (matches.length === 1) {
421+
return matches;
445422
}
446-
return matches;
423+
424+
// Finally look for files with the same name regardless of path.
425+
return findScriptsFuzzy(scriptPath, Object.keys(fileStats));
447426
}
448427

449428
function onBreakpointHit(breakpoint, callback, execState) {
@@ -585,4 +564,66 @@ module.exports.create = function(logger_, config_, jsFiles_, sourcemapper_) {
585564
}
586565

587566
return singleton;
567+
}
568+
569+
/**
570+
* @param {!string} scriptPath path of a script
571+
*/
572+
function pathToRegExp(scriptPath) {
573+
// make sure the script path starts with a slash. This makes sure our
574+
// regexp doesn't match monkey.js when the user asks to set a breakpoint
575+
// in key.js
576+
if (path.sep === '/' || scriptPath.indexOf(':') === -1) {
577+
scriptPath = path.join(path.sep, scriptPath);
578+
}
579+
if (path.sep !== '/') {
580+
scriptPath = scriptPath.replace(new RegExp('\\\\', 'g'), '\\\\');
581+
}
582+
// Escape '.' characters.
583+
scriptPath = scriptPath.replace('.', '\\.');
584+
return new RegExp(scriptPath + '$');
585+
}
586+
587+
/**
588+
* Given an list of available files and a script path to match, this function
589+
* tries to resolve the script to a (hopefully unique) match in the file list
590+
* disregarding the full path to the script. This can be useful because repo
591+
* file paths (that the UI has) may not necessarily be suffixes of the absolute
592+
* paths of the deployed files. This happens when the user deploys a
593+
* subdirectory of the repo.
594+
*
595+
* For example consider a file named `a/b.js` in the repo. If the
596+
* directory contents of `a` are deployed rather than the whole repo, we are not
597+
* going to have any file named `a/b.js` in the running Node process.
598+
*
599+
* We incrementally consider more components of the path until we find a unique
600+
* match, or return all the potential matches.
601+
*
602+
* @example findScriptsFuzzy('a/b.js', ['/d/b.js']) // -> ['/d/b.js']
603+
* @example findScriptsFuzzy('a/b.js', ['/c/b.js', '/d/b.js']); // -> []
604+
* @example findScriptsFuzzy('a/b.js', ['/x/a/b.js', '/y/a/b.js'])
605+
* // -> ['x/a/b.js', 'y/a/b.js']
606+
*
607+
* @param {string} scriptPath partial path to the script.
608+
* @param {array<string>} fileList an array of absolute paths of filenames
609+
* available.
610+
* @return {array<string>} list of files that match.
611+
*/
612+
function findScriptsFuzzy(scriptPath, fileList) {
613+
var matches = fileList;
614+
var components = scriptPath.split(path.sep);
615+
for (var i = components.length - 1; i >= 0; i--) {
616+
var regexp = pathToRegExp(components.slice(i).join(path.sep));
617+
matches = matches.filter(regexp.test.bind(regexp));
618+
if (matches.length <= 1) {
619+
break; // Either no matches, or we found a unique match.
620+
}
621+
}
622+
return matches;
623+
}
624+
625+
module.exports = {
626+
create: create,
627+
// Exposed for unit testing.
628+
findScriptsFuzzy: findScriptsFuzzy
588629
};

test/test-v8debugapi.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,3 +1210,50 @@ describe('v8debugapi', function() {
12101210

12111211
it('should be possible to set deferred breakpoints');
12121212
});
1213+
1214+
describe('v8debugapi.findScriptsFuzzy', function() {
1215+
var fuzzy = v8debugapi.findScriptsFuzzy;
1216+
1217+
it('should not confuse . as a regexp pattern', function() {
1218+
assert.deepEqual(fuzzy('foo.js', ['/fooXjs']), []);
1219+
});
1220+
1221+
it('should do suffix matches correctly', function() {
1222+
1223+
var TESTS = [
1224+
// Exact match.
1225+
{scriptPath: 'foo.js', fileList: ['/foo.js'], result: ['/foo.js']},
1226+
// Non-exact but unique matches.
1227+
{scriptPath: 'a/foo.js', fileList: ['/foo.js'], result: ['/foo.js']},
1228+
{scriptPath: 'a/foo.js', fileList: ['/b/foo.js'], result: ['/b/foo.js']},
1229+
{
1230+
scriptPath: 'a/foo.js',
1231+
fileList: ['/a/b/foo.js'],
1232+
result: ['/a/b/foo.js']
1233+
},
1234+
// Resolve to a better match.
1235+
{
1236+
scriptPath: 'a/foo.js',
1237+
fileList: ['/b/a/foo.js', '/a/b/foo.js'],
1238+
result: ['/b/a/foo.js']
1239+
},
1240+
// Empty list on no matches.
1241+
{scriptPath: 'st-v8debugapi.js', fileList: ['/doc.js'], result: []},
1242+
// Return multiple exact matches.
1243+
{
1244+
scriptPath: 'a/foo.js',
1245+
fileList: ['x/a/foo.js', 'y/a/foo.js'],
1246+
result: ['x/a/foo.js', 'y/a/foo.js']
1247+
},
1248+
// Fail on multiple fuzzy matches.
1249+
{scriptPath: 'a/foo.js', fileList: ['b/foo.js', 'c/foo.js'], result: []}
1250+
];
1251+
1252+
TESTS.forEach(function(test) {
1253+
var scriptPath = path.normalize(test.scriptPath);
1254+
var fileList = test.fileList.map(path.normalize);
1255+
var result = test.result.map(path.normalize);
1256+
assert.deepEqual(fuzzy(scriptPath, fileList), result);
1257+
});
1258+
});
1259+
});

0 commit comments

Comments
 (0)