@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
22import {
33 isAbortError ,
44 isBenignUncaughtExceptionError ,
5+ isTransientFileWatchError ,
56 isTransientNetworkError ,
67 isTransientSqliteError ,
78 isTransientUnhandledRejectionError ,
@@ -258,6 +259,104 @@ describe("isTransientSqliteError", () => {
258259 } ) ;
259260} ) ;
260261
262+ describe ( "isTransientFileWatchError" , ( ) => {
263+ it ( "returns true for ENOSPC with inotify message" , ( ) => {
264+ const error = Object . assign ( new Error ( "inotify watches exhausted" ) , { code : "ENOSPC" } ) ;
265+ expect ( isTransientFileWatchError ( error ) ) . toBe ( true ) ;
266+ } ) ;
267+
268+ it ( "returns true for ENOSPC with file watcher message" , ( ) => {
269+ const error = Object . assign ( new Error ( "System limit for number of file watchers reached" ) , {
270+ code : "ENOSPC" ,
271+ } ) ;
272+ expect ( isTransientFileWatchError ( error ) ) . toBe ( true ) ;
273+ } ) ;
274+
275+ it ( "returns true for ENOSPC with watcher error message" , ( ) => {
276+ const error = Object . assign ( new Error ( "watcher error: ENOSPC" ) , { code : "ENOSPC" } ) ;
277+ expect ( isTransientFileWatchError ( error ) ) . toBe ( true ) ;
278+ } ) ;
279+
280+ it ( "returns false for ENOSPC without watch indicator (general disk full)" , ( ) => {
281+ const error = Object . assign ( new Error ( "write failed: no space left on device" ) , {
282+ code : "ENOSPC" ,
283+ } ) ;
284+ expect ( isTransientFileWatchError ( error ) ) . toBe ( false ) ;
285+ } ) ;
286+
287+ it ( "returns false for ENOSPC with only 'disk full' message" , ( ) => {
288+ const error = Object . assign ( new Error ( "ENOSPC: disk full" ) , { code : "ENOSPC" } ) ;
289+ expect ( isTransientFileWatchError ( error ) ) . toBe ( false ) ;
290+ } ) ;
291+
292+ it ( "returns false for message-only disk full without watch indicator" , ( ) => {
293+ expect ( isTransientFileWatchError ( new Error ( "write failed: no space left on device" ) ) ) . toBe (
294+ false ,
295+ ) ;
296+ expect ( isTransientFileWatchError ( new Error ( "ENOSPC: no space left on device" ) ) ) . toBe ( false ) ;
297+ } ) ;
298+
299+ it ( "returns true for 'no space left on device' message with watcher context" , ( ) => {
300+ const error = new Error ( "file watcher: no space left on device" ) ;
301+ expect ( isTransientFileWatchError ( error ) ) . toBe ( true ) ;
302+ } ) ;
303+
304+ it ( "returns true for inotify-related error messages" , ( ) => {
305+ expect ( isTransientFileWatchError ( new Error ( "inotify watches exhausted" ) ) ) . toBe ( true ) ;
306+ expect (
307+ isTransientFileWatchError ( new Error ( "System limit for number of file watchers reached" ) ) ,
308+ ) . toBe ( true ) ;
309+ } ) ;
310+
311+ it ( "returns true for watcher-related no-space messages" , ( ) => {
312+ expect ( isTransientFileWatchError ( new Error ( "file watcher: no space left on device" ) ) ) . toBe (
313+ true ,
314+ ) ;
315+ } ) ;
316+
317+ it ( "returns false for generic code-less watcher messages" , ( ) => {
318+ expect ( isTransientFileWatchError ( new Error ( "file watcher failed" ) ) ) . toBe ( false ) ;
319+ expect ( isTransientFileWatchError ( new Error ( "watcher error: boom" ) ) ) . toBe ( false ) ;
320+ expect ( isTransientFileWatchError ( new Error ( "watcher error: ENOSPC" ) ) ) . toBe ( false ) ;
321+ expect ( isTransientUnhandledRejectionError ( new Error ( "file watcher failed" ) ) ) . toBe ( false ) ;
322+ expect ( isTransientUnhandledRejectionError ( new Error ( "watcher error: boom" ) ) ) . toBe ( false ) ;
323+ expect ( isTransientUnhandledRejectionError ( new Error ( "watcher error: ENOSPC" ) ) ) . toBe ( false ) ;
324+ } ) ;
325+
326+ it ( "returns true for ENOSPC with cause chain containing watch indicator" , ( ) => {
327+ const cause = Object . assign ( new Error ( "inotify watches exhausted" ) , { code : "ENOSPC" } ) ;
328+ const error = Object . assign ( new Error ( "watcher failed" ) , { cause } ) ;
329+ expect ( isTransientFileWatchError ( error ) ) . toBe ( true ) ;
330+ } ) ;
331+
332+ it ( "returns false for 'watchdog timeout' (unrelated watch error)" , ( ) => {
333+ expect ( isTransientFileWatchError ( new Error ( "watchdog timeout" ) ) ) . toBe ( false ) ;
334+ expect ( isTransientFileWatchError ( new Error ( "cannot watch process" ) ) ) . toBe ( false ) ;
335+ } ) ;
336+
337+ it ( "returns false for regular errors without file watch indicators" , ( ) => {
338+ expect ( isTransientFileWatchError ( new Error ( "Something went wrong" ) ) ) . toBe ( false ) ;
339+ expect ( isTransientFileWatchError ( new TypeError ( "Cannot read property" ) ) ) . toBe ( false ) ;
340+ expect ( isTransientFileWatchError ( new RangeError ( "Invalid array length" ) ) ) . toBe ( false ) ;
341+ } ) ;
342+
343+ it ( "returns false for other disk errors without ENOSPC" , ( ) => {
344+ expect ( isTransientFileWatchError ( new Error ( "disk quota exceeded" ) ) ) . toBe ( false ) ;
345+ expect (
346+ isTransientFileWatchError (
347+ Object . assign ( new Error ( "read only file system" ) , { code : "EROFS" } ) ,
348+ ) ,
349+ ) . toBe ( false ) ;
350+ } ) ;
351+
352+ it . each ( [ null , undefined , "string error" , 42 , { message : "plain object" } ] ) (
353+ "returns false for non-file-watch input %#" ,
354+ ( value ) => {
355+ expect ( isTransientFileWatchError ( value ) ) . toBe ( false ) ;
356+ } ,
357+ ) ;
358+ } ) ;
359+
261360describe ( "isTransientUnhandledRejectionError" , ( ) => {
262361 it ( "treats raw pre-connect network uncaught exceptions as benign" , ( ) => {
263362 const epipe = Object . assign ( new Error ( "write EPIPE" ) , { code : "EPIPE" } ) ;
@@ -287,4 +386,30 @@ describe("isTransientUnhandledRejectionError", () => {
287386
288387 expect ( isTransientUnhandledRejectionError ( error ) ) . toBe ( true ) ;
289388 } ) ;
389+
390+ it ( "returns true for transient file watcher errors (ENOSPC + inotify)" , ( ) => {
391+ const error = Object . assign ( new Error ( "inotify watches exhausted" ) , { code : "ENOSPC" } ) ;
392+ expect ( isTransientUnhandledRejectionError ( error ) ) . toBe ( true ) ;
393+ } ) ;
394+
395+ it ( "returns true for file watcher errors with message only" , ( ) => {
396+ const error = new Error ( "System limit for number of file watchers reached" ) ;
397+ expect ( isTransientUnhandledRejectionError ( error ) ) . toBe ( true ) ;
398+ } ) ;
399+
400+ it ( "returns false for ENOSPC without watch indicator (general disk full)" , ( ) => {
401+ const error = Object . assign ( new Error ( "write failed: no space left on device" ) , {
402+ code : "ENOSPC" ,
403+ } ) ;
404+ expect ( isTransientUnhandledRejectionError ( error ) ) . toBe ( false ) ;
405+ } ) ;
406+
407+ it ( "returns false for code-less disk full messages without watch indicator" , ( ) => {
408+ expect (
409+ isTransientUnhandledRejectionError ( new Error ( "write failed: no space left on device" ) ) ,
410+ ) . toBe ( false ) ;
411+ expect ( isTransientUnhandledRejectionError ( new Error ( "ENOSPC: no space left on device" ) ) ) . toBe (
412+ false ,
413+ ) ;
414+ } ) ;
290415} ) ;
0 commit comments