Skip to content

Commit cb598da

Browse files
committed
Fixed: FileSystem.waitForFile: do not crash if directory does not exist yet (#5420)
fs.watch() throws for a missing directory, and the timeout callback then accesses the uninitialized watcher, which kills the process with a ReferenceError. Poll with fileExists() instead, like seeFile() does.
1 parent 744dba8 commit cb598da

2 files changed

Lines changed: 34 additions & 37 deletions

File tree

‎lib/helper/FileSystem.js‎

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,18 @@ class FileSystem extends Helper {
100100
*/
101101
async waitForFile(name, sec = 1) {
102102
if (sec === 0) assert.fail('Use `seeFile` instead of waiting 0 seconds!')
103-
const waitTimeout = sec * 1000
104103
this.file = path.join(this.dir, name)
105104
try {
106105
this.debugSection('File', this.file)
107106
} catch (e) {
108107
// Fallback debug for ESM transition
109108
console.log(`[File] ${this.file}`)
110109
}
111-
return isFileExists(this.file, waitTimeout).catch(() => {
112-
throw new Error(`file (${name}) still not present in directory ${this.dir} after ${waitTimeout / 1000} sec`)
113-
})
110+
const endTime = Date.now() + sec * 1000
111+
while (!fileExists(this.file)) {
112+
if (endTime < Date.now()) throw new Error(`file (${name}) still not present in directory ${this.dir} after ${sec} sec`)
113+
await new Promise(resolve => setTimeout(resolve, 200))
114+
}
114115
}
115116

116117
/**
@@ -213,36 +214,3 @@ function getFileContents(file, encoding = 'utf8') {
213214
if (encoding === '') assert.fail('Encoding is an empty string, please set a valid encoding')
214215
return fs.readFileSync(file, encoding)
215216
}
216-
217-
/**
218-
* @param {string} file
219-
* @param {number} timeout
220-
* @private
221-
* @returns {Promise<any>}
222-
*/
223-
function isFileExists(file, timeout) {
224-
return new Promise((resolve, reject) => {
225-
const timer = setTimeout(() => {
226-
watcher.close()
227-
reject(new Error('File did not exists and was not created during the timeout.'))
228-
}, timeout)
229-
230-
const dir = path.dirname(file)
231-
const basename = path.basename(file)
232-
const watcher = fs.watch(dir, (eventType, filename) => {
233-
if (eventType === 'rename' && filename === basename) {
234-
clearTimeout(timer)
235-
watcher.close()
236-
resolve()
237-
}
238-
})
239-
240-
fs.access(file, fs.constants.R_OK, err => {
241-
if (!err) {
242-
clearTimeout(timer)
243-
watcher.close()
244-
resolve()
245-
}
246-
})
247-
})
248-
}

‎test/unit/helper/FileSystem_test.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,33 @@ describe('FileSystem', () => {
7777
fs.seeFile(outputFilePath)
7878
fs.seeInThisFile(text)
7979
})
80+
81+
describe('#waitForFile', () => {
82+
const waitDir = path.join('data', 'output', 'wait_for_file')
83+
84+
beforeEach(() => {
85+
nodeFs.rmSync(path.join(global.codecept_dir, waitDir), { recursive: true, force: true })
86+
fs.amInPath(waitDir)
87+
})
88+
89+
it('should wait for a file in a directory that does not exist yet', async () => {
90+
setTimeout(() => {
91+
nodeFs.mkdirSync(fs.dir, { recursive: true })
92+
nodeFs.writeFileSync(path.join(fs.dir, 'file.txt'), 'content')
93+
}, 300)
94+
await fs.waitForFile('file.txt', 3)
95+
})
96+
97+
it('should fail after the timeout if the directory does not exist', async () => {
98+
const start = Date.now()
99+
let error
100+
try {
101+
await fs.waitForFile('file.txt', 1)
102+
} catch (err) {
103+
error = err
104+
}
105+
expect(error?.message).to.include('file (file.txt) still not present')
106+
expect(Date.now() - start).to.be.at.least(1000)
107+
})
108+
})
80109
})

0 commit comments

Comments
 (0)