| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const { createFsFromVolume, Volume } = require("memfs"); | 
					
						
							|  |  |  | const util = require("util"); | 
					
						
							|  |  |  | const FileSystemInfo = require("../lib/FileSystemInfo"); | 
					
						
							|  |  |  | const { buffersSerializer } = require("../lib/util/serialization"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe("FileSystemInfo", () => { | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 	afterEach(() => { | 
					
						
							|  |  |  | 		// restore the spy created with spyOn
 | 
					
						
							|  |  |  | 		jest.restoreAllMocks(); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 	const files = [ | 
					
						
							|  |  |  | 		"/path/file.txt", | 
					
						
							|  |  |  | 		"/path/nested/deep/file.txt", | 
					
						
							|  |  |  | 		"/path/nested/deep/ignored.txt", | 
					
						
							|  |  |  | 		"/path/context+files/file.txt", | 
					
						
							|  |  |  | 		"/path/context+files/sub/file.txt", | 
					
						
							|  |  |  | 		"/path/context+files/sub/ignored.txt", | 
					
						
							|  |  |  | 		"/path/node_modules/package/file.txt", | 
					
						
							|  |  |  | 		"/path/cache/package-1234/file.txt", | 
					
						
							|  |  |  | 		"/path/circular/circular/file2.txt", | 
					
						
							|  |  |  | 		"/path/nested/deep/symlink/file.txt", | 
					
						
							|  |  |  | 		"/path/context+files/sub/symlink/file.txt", | 
					
						
							| 
									
										
										
										
											2021-08-19 19:17:18 +08:00
										 |  |  | 		"/path/context/sub/symlink/file.txt", | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 		"/path/missing.txt", | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package1/index.js", | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package2/index.js", | 
					
						
							|  |  |  | 		"/path/node_modules/bar-package3/index.js" | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 	]; | 
					
						
							|  |  |  | 	const directories = [ | 
					
						
							|  |  |  | 		"/path/context+files", | 
					
						
							|  |  |  | 		"/path/context", | 
					
						
							| 
									
										
										
										
											2021-08-19 19:17:18 +08:00
										 |  |  | 		"/path/missing", | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 		"/path/node_modules/package", | 
					
						
							| 
									
										
										
										
											2021-08-19 19:17:18 +08:00
										 |  |  | 		"/path/node_modules/missing", | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 		"/path/node_modules/@foo", | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package1", | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package2", | 
					
						
							|  |  |  | 		"/path/node_modules/bar-package3", | 
					
						
							| 
									
										
										
										
											2021-08-19 19:17:18 +08:00
										 |  |  | 		"/path/cache/package-1234", | 
					
						
							|  |  |  | 		"/path/cache/package-missing" | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 	]; | 
					
						
							|  |  |  | 	const missing = [ | 
					
						
							|  |  |  | 		"/path/package.json", | 
					
						
							|  |  |  | 		"/path/file2.txt", | 
					
						
							|  |  |  | 		"/path/context+files/file2.txt", | 
					
						
							|  |  |  | 		"/path/node_modules/package.txt", | 
					
						
							|  |  |  | 		"/path/node_modules/package/missing.txt", | 
					
						
							|  |  |  | 		"/path/cache/package-2345", | 
					
						
							|  |  |  | 		"/path/cache/package-1234/missing.txt", | 
					
						
							|  |  |  | 		"/path/ignored.txt" | 
					
						
							|  |  |  | 	]; | 
					
						
							|  |  |  | 	const ignored = [ | 
					
						
							|  |  |  | 		"/path/nested/deep/ignored.txt", | 
					
						
							|  |  |  | 		"/path/context+files/sub/ignored.txt", | 
					
						
							|  |  |  | 		"/path/context/sub/ignored.txt", | 
					
						
							|  |  |  | 		"/path/ignored.txt", | 
					
						
							|  |  |  | 		"/path/node_modules/package/ignored.txt", | 
					
						
							|  |  |  | 		"/path/cache/package-1234/ignored.txt" | 
					
						
							|  |  |  | 	]; | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 	const unmanagedPaths = [ | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package1", | 
					
						
							|  |  |  | 		"/path/node_modules/@foo/package2", | 
					
						
							|  |  |  | 		"/path/node_modules/bar-package3" | 
					
						
							|  |  |  | 	]; | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 	const managedPaths = ["/path/node_modules"]; | 
					
						
							|  |  |  | 	const immutablePaths = ["/path/cache"]; | 
					
						
							|  |  |  | 	const createFs = () => { | 
					
						
							|  |  |  | 		const fs = createFsFromVolume(new Volume()); | 
					
						
							| 
									
										
										
										
											2024-03-13 22:00:12 +08:00
										 |  |  | 		fs.mkdirSync("/path/context+files/sub", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/context/sub", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/nested/deep", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/node_modules/package", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/node_modules/@foo", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/node_modules/@foo/package1", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/node_modules/@foo/package2", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/node_modules/bar-package3", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/cache/package-1234", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/folder/context", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/folder/context+files", { recursive: true }); | 
					
						
							|  |  |  | 		fs.mkdirSync("/path/folder/nested", { recursive: true }); | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 		fs.writeFileSync("/path/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/file2.txt", "Hello World2"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/nested/deep/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/nested/deep/ignored.txt", "Ignored"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/file2.txt", "Hello World2"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/sub/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/sub/file2.txt", "Hello World2"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/sub/file3.txt", "Hello World3"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context+files/sub/ignored.txt", "Ignored"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/file2.txt", "Hello World2"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/sub/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/sub/file2.txt", "Hello World2"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/sub/file3.txt", "Hello World3"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/context/sub/ignored.txt", "Ignored"); | 
					
						
							|  |  |  | 		fs.writeFileSync( | 
					
						
							|  |  |  | 			"/path/node_modules/package/package.json", | 
					
						
							|  |  |  | 			JSON.stringify({ name: "package", version: "1.0.0" }) | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/node_modules/package/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/node_modules/package/ignored.txt", "Ignored"); | 
					
						
							|  |  |  | 		fs.writeFileSync( | 
					
						
							|  |  |  | 			"/path/cache/package-1234/package.json", | 
					
						
							|  |  |  | 			JSON.stringify({ name: "package", version: "1.0.0" }) | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/cache/package-1234/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/cache/package-1234/ignored.txt", "Ignored"); | 
					
						
							|  |  |  | 		fs.symlinkSync("/path", "/path/circular", "dir"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/folder/context/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/folder/context+files/file.txt", "Hello World"); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/folder/nested/file.txt", "Hello World"); | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 		fs.writeFileSync( | 
					
						
							|  |  |  | 			"/path/node_modules/@foo/package1/index.js", | 
					
						
							|  |  |  | 			"Hello World" | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.writeFileSync( | 
					
						
							|  |  |  | 			"/path/node_modules/@foo/package2/index.js", | 
					
						
							|  |  |  | 			"Hello World" | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.writeFileSync("/path/node_modules/bar-package3/index.js", "Hello World"); | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 		fs.symlinkSync("/path/folder/context", "/path/context/sub/symlink", "dir"); | 
					
						
							|  |  |  | 		fs.symlinkSync( | 
					
						
							|  |  |  | 			"/path/folder/context+files", | 
					
						
							|  |  |  | 			"/path/context+files/sub/symlink", | 
					
						
							|  |  |  | 			"dir" | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.symlinkSync("/path/folder/nested", "/path/nested/deep/symlink", "dir"); | 
					
						
							|  |  |  | 		return fs; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const createFsInfo = fs => { | 
					
						
							|  |  |  | 		const logger = { | 
					
						
							|  |  |  | 			error: (...args) => { | 
					
						
							|  |  |  | 				throw new Error(util.format(...args)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		const fsInfo = new FileSystemInfo(fs, { | 
					
						
							|  |  |  | 			logger, | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 			unmanagedPaths, | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 			managedPaths, | 
					
						
							| 
									
										
										
										
											2021-09-22 18:12:46 +08:00
										 |  |  | 			immutablePaths, | 
					
						
							|  |  |  | 			hashFunction: "sha256" | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 		for (const method of ["warn", "info", "log", "debug"]) { | 
					
						
							|  |  |  | 			fsInfo.logs = []; | 
					
						
							|  |  |  | 			fsInfo[method] = []; | 
					
						
							|  |  |  | 			logger[method] = (...args) => { | 
					
						
							|  |  |  | 				const msg = util.format(...args); | 
					
						
							|  |  |  | 				fsInfo[method].push(msg); | 
					
						
							|  |  |  | 				fsInfo.logs.push(`[${method}] ${msg}`); | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		fsInfo.addFileTimestamps(new Map(ignored.map(i => [i, "ignore"]))); | 
					
						
							|  |  |  | 		return fsInfo; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const createSnapshot = (fs, options, callback) => { | 
					
						
							|  |  |  | 		const fsInfo = createFsInfo(fs); | 
					
						
							|  |  |  | 		fsInfo.createSnapshot( | 
					
						
							|  |  |  | 			Date.now() + 10000, | 
					
						
							|  |  |  | 			files, | 
					
						
							|  |  |  | 			directories, | 
					
						
							|  |  |  | 			missing, | 
					
						
							|  |  |  | 			options, | 
					
						
							|  |  |  | 			(err, snapshot) => { | 
					
						
							|  |  |  | 				if (err) return callback(err); | 
					
						
							|  |  |  | 				snapshot.name = "initial snapshot"; | 
					
						
							|  |  |  | 				// create another one to test the caching
 | 
					
						
							|  |  |  | 				fsInfo.createSnapshot( | 
					
						
							|  |  |  | 					Date.now() + 10000, | 
					
						
							|  |  |  | 					files, | 
					
						
							|  |  |  | 					directories, | 
					
						
							|  |  |  | 					missing, | 
					
						
							|  |  |  | 					options, | 
					
						
							|  |  |  | 					(err, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return callback(err); | 
					
						
							|  |  |  | 						snapshot2.name = "cached snapshot"; | 
					
						
							|  |  |  | 						callback(null, snapshot, snapshot2); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const clone = object => { | 
					
						
							|  |  |  | 		const serialized = buffersSerializer.serialize(object, {}); | 
					
						
							|  |  |  | 		return buffersSerializer.deserialize(serialized, {}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const expectSnapshotsState = ( | 
					
						
							|  |  |  | 		fs, | 
					
						
							|  |  |  | 		snapshot, | 
					
						
							|  |  |  | 		snapshot2, | 
					
						
							|  |  |  | 		expected, | 
					
						
							|  |  |  | 		callback | 
					
						
							|  |  |  | 	) => { | 
					
						
							|  |  |  | 		expectSnapshotState(fs, snapshot, expected, err => { | 
					
						
							|  |  |  | 			if (err) return callback(err); | 
					
						
							|  |  |  | 			if (!snapshot2) return callback(); | 
					
						
							|  |  |  | 			expectSnapshotState(fs, snapshot2, expected, callback); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const expectSnapshotState = (fs, snapshot, expected, callback) => { | 
					
						
							|  |  |  | 		const fsInfo = createFsInfo(fs); | 
					
						
							|  |  |  | 		const details = snapshot => `${fsInfo.logs.join("\n")}
 | 
					
						
							|  |  |  | ${util.inspect(snapshot, false, Infinity, true)}`;
 | 
					
						
							|  |  |  | 		fsInfo.checkSnapshotValid(snapshot, (err, valid) => { | 
					
						
							|  |  |  | 			if (err) return callback(err); | 
					
						
							|  |  |  | 			if (valid !== expected) { | 
					
						
							|  |  |  | 				return callback( | 
					
						
							|  |  |  | 					new Error(`Expected snapshot to be ${ | 
					
						
							|  |  |  | 						expected ? "valid" : "invalid" | 
					
						
							|  |  |  | 					} but it is ${valid ? "valid" : "invalid"}: | 
					
						
							|  |  |  | ${details(snapshot)}`)
 | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Another try to check if direct caching works
 | 
					
						
							|  |  |  | 			fsInfo.checkSnapshotValid(snapshot, (err, valid) => { | 
					
						
							|  |  |  | 				if (err) return callback(err); | 
					
						
							|  |  |  | 				if (valid !== expected) { | 
					
						
							|  |  |  | 					return callback( | 
					
						
							|  |  |  | 						new Error(`Expected snapshot lead to the same result when directly cached:
 | 
					
						
							|  |  |  | ${details(snapshot)}`)
 | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				// Another try to check if indirect caching works
 | 
					
						
							|  |  |  | 				fsInfo.checkSnapshotValid(clone(snapshot), (err, valid) => { | 
					
						
							|  |  |  | 					if (err) return callback(err); | 
					
						
							|  |  |  | 					if (valid !== expected) { | 
					
						
							|  |  |  | 						return callback( | 
					
						
							|  |  |  | 							new Error(`Expected snapshot lead to the same result when indirectly cached:
 | 
					
						
							|  |  |  | ${details(snapshot)}`)
 | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					callback(); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const updateFile = (fs, filename) => { | 
					
						
							|  |  |  | 		const oldContent = fs.readFileSync(filename, "utf-8"); | 
					
						
							|  |  |  | 		if (filename.endsWith(".json")) { | 
					
						
							|  |  |  | 			const data = JSON.parse(oldContent); | 
					
						
							|  |  |  | 			fs.writeFileSync( | 
					
						
							|  |  |  | 				filename, | 
					
						
							|  |  |  | 				JSON.stringify({ | 
					
						
							|  |  |  | 					...data, | 
					
						
							| 
									
										
										
										
											2024-07-31 10:39:30 +08:00
										 |  |  | 					version: `${data.version}.1` | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 				}) | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2024-07-31 10:39:30 +08:00
										 |  |  | 			fs.writeFileSync(filename, `${oldContent}!`); | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (const [name, options] of [ | 
					
						
							|  |  |  | 		["timestamp", { timestamp: true }], | 
					
						
							|  |  |  | 		["hash", { hash: true }], | 
					
						
							|  |  |  | 		["tsh", { timestamp: true, hash: true }] | 
					
						
							|  |  |  | 	]) { | 
					
						
							|  |  |  | 		describe(`${name} mode`, () => { | 
					
						
							|  |  |  | 			it("should always accept an empty snapshot", done => { | 
					
						
							|  |  |  | 				const fs = createFs(); | 
					
						
							|  |  |  | 				const fsInfo = createFsInfo(fs); | 
					
						
							|  |  |  | 				fsInfo.createSnapshot( | 
					
						
							|  |  |  | 					Date.now() + 10000, | 
					
						
							|  |  |  | 					[], | 
					
						
							|  |  |  | 					[], | 
					
						
							|  |  |  | 					[], | 
					
						
							|  |  |  | 					options, | 
					
						
							|  |  |  | 					(err, snapshot) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						const fs = createFs(); | 
					
						
							|  |  |  | 						expectSnapshotState(fs, snapshot, true, done); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			it("should accept a snapshot when fs is unchanged", done => { | 
					
						
							|  |  |  | 				const fs = createFs(); | 
					
						
							|  |  |  | 				createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 					if (err) return done(err); | 
					
						
							|  |  |  | 					expectSnapshotsState(fs, snapshot, snapshot2, true, done); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const ignoredFileChanges = [ | 
					
						
							|  |  |  | 				"/path/nested/deep/ignored.txt", | 
					
						
							|  |  |  | 				"/path/context+files/sub/ignored.txt" | 
					
						
							|  |  |  | 			]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (const fileChange of [ | 
					
						
							|  |  |  | 				"/path/file.txt", | 
					
						
							|  |  |  | 				"/path/file2.txt", | 
					
						
							|  |  |  | 				"/path/nested/deep/file.txt", | 
					
						
							|  |  |  | 				"/path/context+files/file.txt", | 
					
						
							|  |  |  | 				"/path/context+files/file2.txt", | 
					
						
							|  |  |  | 				"/path/context+files/sub/file.txt", | 
					
						
							|  |  |  | 				"/path/context+files/sub/file2.txt", | 
					
						
							|  |  |  | 				"/path/context+files/sub/file3.txt", | 
					
						
							|  |  |  | 				"/path/context/file.txt", | 
					
						
							|  |  |  | 				"/path/context/file2.txt", | 
					
						
							|  |  |  | 				"/path/context/sub/file.txt", | 
					
						
							|  |  |  | 				"/path/context/sub/file2.txt", | 
					
						
							|  |  |  | 				"/path/context/sub/file3.txt", | 
					
						
							|  |  |  | 				"/path/node_modules/package/package.json", | 
					
						
							|  |  |  | 				"/path/folder/context/file.txt", | 
					
						
							|  |  |  | 				"/path/folder/context+files/file.txt", | 
					
						
							|  |  |  | 				"/path/folder/nested/file.txt", | 
					
						
							| 
									
										
										
										
											2023-10-05 04:57:30 +08:00
										 |  |  | 				"/path/node_modules/@foo/package1/index.js", | 
					
						
							|  |  |  | 				"/path/node_modules/@foo/package2/index.js", | 
					
						
							|  |  |  | 				"/path/node_modules/bar-package3/index.js", | 
					
						
							| 
									
										
										
										
											2021-08-17 14:19:01 +08:00
										 |  |  | 				...(name !== "timestamp" ? ignoredFileChanges : []), | 
					
						
							|  |  |  | 				...(name === "hash" ? ["/path/context/sub/ignored.txt"] : []) | 
					
						
							|  |  |  | 			]) { | 
					
						
							|  |  |  | 				it(`should invalidate the snapshot when ${fileChange} is changed`, done => { | 
					
						
							|  |  |  | 					const fs = createFs(); | 
					
						
							|  |  |  | 					createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						updateFile(fs, fileChange); | 
					
						
							|  |  |  | 						expectSnapshotsState(fs, snapshot, snapshot2, false, done); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (const fileChange of [ | 
					
						
							|  |  |  | 				"/path/node_modules/package/file.txt", | 
					
						
							|  |  |  | 				"/path/node_modules/package/ignored.txt", | 
					
						
							|  |  |  | 				"/path/cache/package-1234/package.json", | 
					
						
							|  |  |  | 				"/path/cache/package-1234/file.txt", | 
					
						
							|  |  |  | 				"/path/cache/package-1234/ignored.txt", | 
					
						
							|  |  |  | 				...(name === "timestamp" ? ignoredFileChanges : []), | 
					
						
							|  |  |  | 				...(name !== "hash" ? ["/path/context/sub/ignored.txt"] : []) | 
					
						
							|  |  |  | 			]) { | 
					
						
							|  |  |  | 				it(`should not invalidate the snapshot when ${fileChange} is changed`, done => { | 
					
						
							|  |  |  | 					const fs = createFs(); | 
					
						
							|  |  |  | 					createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						updateFile(fs, fileChange); | 
					
						
							|  |  |  | 						expectSnapshotsState(fs, snapshot, snapshot2, true, done); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (const newFile of [ | 
					
						
							|  |  |  | 				"/path/package.json", | 
					
						
							|  |  |  | 				"/path/file2.txt", | 
					
						
							|  |  |  | 				"/path/context+files/file2.txt", | 
					
						
							|  |  |  | 				"/path/node_modules/package.txt" | 
					
						
							|  |  |  | 			]) { | 
					
						
							|  |  |  | 				it(`should invalidate the snapshot when ${newFile} is created`, done => { | 
					
						
							|  |  |  | 					const fs = createFs(); | 
					
						
							|  |  |  | 					createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						fs.writeFileSync(newFile, "New file"); | 
					
						
							|  |  |  | 						expectSnapshotsState(fs, snapshot, snapshot2, false, done); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (const newFile of [ | 
					
						
							|  |  |  | 				"/path/node_modules/package/missing.txt", | 
					
						
							|  |  |  | 				"/path/cache/package-1234/missing.txt", | 
					
						
							|  |  |  | 				"/path/cache/package-2345", | 
					
						
							|  |  |  | 				"/path/ignored.txt" | 
					
						
							|  |  |  | 			]) { | 
					
						
							|  |  |  | 				it(`should not invalidate the snapshot when ${newFile} is created`, done => { | 
					
						
							|  |  |  | 					const fs = createFs(); | 
					
						
							|  |  |  | 					createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						fs.writeFileSync(newFile, "New file"); | 
					
						
							|  |  |  | 						expectSnapshotsState(fs, snapshot, snapshot2, true, done); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (name !== "timestamp") { | 
					
						
							|  |  |  | 				it("should not invalidate snapshot when only timestamps have changed", done => { | 
					
						
							|  |  |  | 					const fs = createFs(); | 
					
						
							|  |  |  | 					createSnapshot(fs, options, (err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 						if (err) return done(err); | 
					
						
							|  |  |  | 						const fs = createFs(); | 
					
						
							|  |  |  | 						expectSnapshotsState(fs, snapshot, snapshot2, true, done); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-07-02 00:57:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	describe("stable iterables identity", () => { | 
					
						
							|  |  |  | 		const options = { timestamp: true }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  | 		 * @param {(err?: WebpackError | null, snapshot?: Snapshot | null) => void} callback callback function | 
					
						
							| 
									
										
										
										
											2022-07-02 00:57:36 +08:00
										 |  |  | 		 */ | 
					
						
							|  |  |  | 		function getSnapshot(callback) { | 
					
						
							|  |  |  | 			const fs = createFs(); | 
					
						
							|  |  |  | 			const fsInfo = createFsInfo(fs); | 
					
						
							|  |  |  | 			fsInfo.createSnapshot( | 
					
						
							|  |  |  | 				Date.now() + 10000, | 
					
						
							|  |  |  | 				files, | 
					
						
							|  |  |  | 				directories, | 
					
						
							|  |  |  | 				missing, | 
					
						
							|  |  |  | 				options, | 
					
						
							|  |  |  | 				callback | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it("should return same iterable for getFileIterable()", done => { | 
					
						
							|  |  |  | 			getSnapshot((err, snapshot) => { | 
					
						
							|  |  |  | 				if (err) done(err); | 
					
						
							|  |  |  | 				expect(snapshot.getFileIterable()).toEqual(snapshot.getFileIterable()); | 
					
						
							|  |  |  | 				done(); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it("should return same iterable for getContextIterable()", done => { | 
					
						
							|  |  |  | 			getSnapshot((err, snapshot) => { | 
					
						
							|  |  |  | 				if (err) done(err); | 
					
						
							|  |  |  | 				expect(snapshot.getContextIterable()).toEqual( | 
					
						
							|  |  |  | 					snapshot.getContextIterable() | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				done(); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it("should return same iterable for getMissingIterable()", done => { | 
					
						
							|  |  |  | 			getSnapshot((err, snapshot) => { | 
					
						
							|  |  |  | 				if (err) done(err); | 
					
						
							|  |  |  | 				expect(snapshot.getFileIterable()).toEqual(snapshot.getFileIterable()); | 
					
						
							|  |  |  | 				done(); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}); | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 	describe("symlinks", () => { | 
					
						
							|  |  |  | 		it("should work with symlinks with errors", done => { | 
					
						
							|  |  |  | 			const fs = createFs(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			fs.symlinkSync( | 
					
						
							|  |  |  | 				"/path/folder/context", | 
					
						
							|  |  |  | 				"/path/context/sub/symlink-error", | 
					
						
							|  |  |  | 				"dir" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const originalReadlink = fs.readlink; | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 			let i = 0; | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 			jest.spyOn(fs, "readlink").mockImplementation((path, callback) => { | 
					
						
							|  |  |  | 				if (path === "/path/context/sub/symlink-error" && i < 2) { | 
					
						
							|  |  |  | 					i += 1; | 
					
						
							|  |  |  | 					callback(new Error("test")); | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				originalReadlink(path, callback); | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 			createSnapshot( | 
					
						
							|  |  |  | 				fs, | 
					
						
							|  |  |  | 				["timestamp", { timestamp: true }], | 
					
						
							|  |  |  | 				(err, snapshot, snapshot2) => { | 
					
						
							|  |  |  | 					if (err) return done(err); | 
					
						
							|  |  |  | 					expectSnapshotsState(fs, snapshot, snapshot2, true, done); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		it("should work with symlinks with errors #1", done => { | 
					
						
							|  |  |  | 			const fs = createFs(); | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 			fs.symlinkSync( | 
					
						
							|  |  |  | 				"/path/folder/context", | 
					
						
							|  |  |  | 				"/path/context/sub/symlink-error", | 
					
						
							|  |  |  | 				"dir" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			jest.spyOn(fs, "readlink").mockImplementation((path, callback) => { | 
					
						
							|  |  |  | 				callback(new Error("test")); | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-22 23:31:35 +08:00
										 |  |  | 			const fsInfo = createFsInfo(fs); | 
					
						
							|  |  |  | 			fsInfo.createSnapshot( | 
					
						
							|  |  |  | 				Date.now() + 10000, | 
					
						
							|  |  |  | 				files, | 
					
						
							|  |  |  | 				directories, | 
					
						
							|  |  |  | 				missing, | 
					
						
							|  |  |  | 				["timestamp", { timestamp: true }], | 
					
						
							|  |  |  | 				(err, snapshot) => { | 
					
						
							|  |  |  | 					expect(snapshot).toBe(null); | 
					
						
							|  |  |  | 					done(); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2024-08-22 11:03:22 +08:00
										 |  |  | 	}); | 
					
						
							|  |  |  | }); |