|
1 | 1 | import*ascommonfrom'../common/index.mjs'; |
2 | 2 | importtmpdirfrom'../common/tmpdir.js'; |
| 3 | +import{spawnSync}from'node:child_process'; |
3 | 4 | import{resolve,dirname,sep,relative,join,isAbsolute}from'node:path'; |
4 | 5 | import{mkdir,writeFile,symlink,globasasyncGlob}from'node:fs/promises'; |
5 | 6 | import{glob,globSync,Dirent,chmodSync,writeFileSync,rmSync}from'node:fs'; |
@@ -669,3 +670,78 @@ describe('globSync - ENOTDIR', function() { |
669 | 670 | } |
670 | 671 | }); |
671 | 672 | }); |
| 673 | + |
| 674 | +describe('glob - seen cache',function(){ |
| 675 | +// Refs: https://github.com/nodejs/node/issues/62897 |
| 676 | +test('does not skip siblings after a seen child path',()=>{ |
| 677 | +// The glob traversal used to return early from the children loop when a |
| 678 | +// child path had already been seen through a different pattern context, |
| 679 | +// silently dropping the remaining siblings. Whether the bug triggered |
| 680 | +// depended on directory iteration order, so the child process pins the |
| 681 | +// order by patching readdir before loading the glob implementation. |
| 682 | +constscript=` |
| 683 | + const assert = require('node:assert'); |
| 684 | + const fs = require('node:fs'); |
| 685 | + const fsPromises = require('node:fs/promises'); |
| 686 | + const path = require('node:path'); |
| 687 | +
|
| 688 | + const cwd = process.argv[1]; |
| 689 | + const a = path.join(cwd, 'a'); |
| 690 | + fs.mkdirSync(path.join(a, 'b', 'c', 'd'), { recursive: true }); |
| 691 | + fs.mkdirSync(path.join(a, 'c', 'd', 'c'), { recursive: true }); |
| 692 | + fs.writeFileSync(path.join(a, 'x'), ''); |
| 693 | + fs.writeFileSync(path.join(a, 'z'), ''); |
| 694 | +
|
| 695 | + const originalReaddirSync = fs.readdirSync; |
| 696 | + const originalReaddir = fsPromises.readdir; |
| 697 | +
|
| 698 | + const reorder = (target, entries) => { |
| 699 | + if (!Array.isArray(entries) || target !== a) return entries; |
| 700 | + const names = ['c', 'b', 'x', 'z']; |
| 701 | + return names.map((name) => entries.find((entry) => entry.name === name)) |
| 702 | + .filter(Boolean); |
| 703 | + }; |
| 704 | +
|
| 705 | + fs.readdirSync = function(target, options) { |
| 706 | + return reorder(target, originalReaddirSync.call(this, target, options)); |
| 707 | + }; |
| 708 | + fsPromises.readdir = async function(target, options) { |
| 709 | + return reorder(target, await originalReaddir.call(this, target, options)); |
| 710 | + }; |
| 711 | +
|
| 712 | + const { Glob } = require('internal/fs/glob'); |
| 713 | + const expected = ['a/b', 'a/c', 'a/x', 'a/z']; |
| 714 | + const normalize = (results) => |
| 715 | + results.map((item) => item.replaceAll(path.sep, '/')).sort(); |
| 716 | +
|
| 717 | + (async () => { |
| 718 | + const syncResults = normalize(new Glob('a/**/../*', { cwd }).globSync()); |
| 719 | + for (const item of expected) { |
| 720 | + assert.ok(syncResults.includes(item), |
| 721 | + \`missing \${item} from sync results: \${syncResults}\`); |
| 722 | + } |
| 723 | +
|
| 724 | + const asyncResults = []; |
| 725 | + for await (const item of new Glob('a/**/../*', { cwd }).glob()) { |
| 726 | + asyncResults.push(item); |
| 727 | + } |
| 728 | + const normalized = normalize(asyncResults); |
| 729 | + for (const item of expected) { |
| 730 | + assert.ok(normalized.includes(item), |
| 731 | + \`missing \${item} from async results: \${normalized}\`); |
| 732 | + } |
| 733 | + })().catch((err) => { |
| 734 | + console.error(err); |
| 735 | + process.exitCode = 1; |
| 736 | + }); |
| 737 | + `; |
| 738 | + |
| 739 | +constseenDir=tmpdir.resolve('glob-seen'); |
| 740 | +constchild=spawnSync( |
| 741 | +process.execPath, |
| 742 | +['--expose-internals','-e',script,seenDir], |
| 743 | +{encoding: 'utf8'}, |
| 744 | +); |
| 745 | +assert.strictEqual(child.status,0,child.stderr||child.stdout); |
| 746 | +}); |
| 747 | +}); |
0 commit comments