Coverage for tests / cli / supervisors / test_watchfiles.py: 90%

30 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-08 01:48 +0000

1import os 

2import signal 

3import time 

4from multiprocessing.context import SpawnProcess 

5from pathlib import Path 

6from unittest.mock import MagicMock, patch 

7 

8import pytest 

9 

10from faststream._internal.cli.dto import RunArgs 

11from faststream._internal.cli.supervisors.utils import get_subprocess 

12from faststream._internal.cli.supervisors.watchfiles import WatchReloader 

13from tests.cli import interfaces 

14from tests.marks import skip_windows 

15 

16DIR = Path(__file__).resolve().parent 

17 

18 

19class PatchedWatchReloader(WatchReloader): 

20 def start_process(self, worker_id: int | None = None) -> SpawnProcess: 

21 process = get_subprocess(target=self._target, args=(self._args,)) 

22 process.start() 

23 return process 

24 

25 

26@pytest.mark.slow() 

27@skip_windows 

28def test_base(generate_template: interfaces.GenerateTemplateFactory) -> None: 

29 with generate_template("") as file_path: 

30 processor = PatchedWatchReloader( 

31 target=exit, 

32 args=RunArgs(app=""), 

33 reload_dirs=[str(file_path.parent)], 

34 ) 

35 

36 processor._args.extra_options = {"parent_id": processor.pid} 

37 processor.run() 

38 

39 code = abs(processor._process.exitcode or 0) 

40 

41 assert code in {signal.SIGTERM.value, 0}, code 

42 

43 

44@pytest.mark.slow() 

45@skip_windows 

46def test_restart( 

47 mock: MagicMock, generate_template: interfaces.GenerateTemplateFactory 

48) -> None: 

49 with generate_template("") as file_path: 

50 processor = PatchedWatchReloader( 

51 target=touch_file, 

52 args=RunArgs(app=str(file_path)), 

53 reload_dirs=[file_path.parent], 

54 ) 

55 

56 mock.side_effect = lambda: exit( 

57 RunArgs(app="", extra_options={"parent_id": processor.pid}) 

58 ) 

59 

60 with patch.object(processor, "restart", mock): 

61 processor.run() 

62 

63 mock.assert_called_once() 

64 

65 

66def touch_file(args: RunArgs) -> None: 

67 while True: 

68 time.sleep(0.1) 

69 Path(args.app).write_text("hello", encoding="utf-8") 

70 

71 

72def exit(args: RunArgs) -> None: 

73 os.kill(int(args.extra_options["parent_id"]), signal.SIGINT)