diff --git a/.github/workflows/freethreading_tests.yml b/.github/workflows/freethreading_tests.yml deleted file mode 100644 index 8742d83c..00000000 --- a/.github/workflows/freethreading_tests.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Free Threading Tests -on: [push, pull_request] -jobs: - freethreading_tests: - name: Free Threaded Build ${{ matrix.os.emoji }} ${{ matrix.os.name }} ${{ matrix.python }} - runs-on: ${{ matrix.os.runs-on }} - strategy: - fail-fast: false - matrix: - os: - - name: Linux - matrix: linux - emoji: 🐧 - runs-on: [ubuntu-latest] - - name: macOS intel - matrix: macos - emoji: 🍎 - runs-on: [macos-13] - - name: macOS silicon - matrix: macos - emoji: 🍎 - runs-on: [macos-14] - - name: Windows - matrix: windows - emoji: 🪟 - runs-on: [windows-latest] - steps: - - uses: actions/checkout@v5 - - name: Install uv and set the python version - uses: astral-sh/setup-uv@v5 - with: - version: "0.7.2" - python-version: 3.13t - enable-cache: false - - name: Disable Coverage - run: | - perl -i -ne 'print unless /--cov/' pyproject.toml - - name: Set up Python - run: uv python install - - name: Install Dependencies - run: | - uv pip install pytest-timeout pytest-run-parallel flaky - - name: Editable build - run: | - uv pip install -v -e . - - name: Run Tests - run: | - uv run --no-project python -m pytest --parallel-threads=2 --iterations=2 -v -s --timeout=600 --durations=10 -m "not thread_unsafe" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f3328af..0dda3ba7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,6 +60,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.13t" - "pypy-3.9" exclude: - os: diff --git a/requirements-tests.txt b/requirements-tests.txt index ee635fa0..1439205e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -2,7 +2,6 @@ eventlet==0.37.0; python_version < "3.13" flaky==3.8.1 pytest==8.3.3 pytest-cov==6.0.0 -pytest-run-parallel==0.4.2 pytest-timeout==2.3.1 ruff==0.7.1 sphinx==7.4.7; python_version <= "3.9" diff --git a/tests/test_delayed_queue.py b/tests/test_delayed_queue.py index 65ee1d9d..da4c5588 100644 --- a/tests/test_delayed_queue.py +++ b/tests/test_delayed_queue.py @@ -8,7 +8,6 @@ @pytest.mark.flaky(max_runs=5, min_passes=1) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delayed_get(): q = DelayedQueue[str](2) q.put("", delay=True) diff --git a/tests/test_emitter.py b/tests/test_emitter.py index 7783530d..5d0aa28a 100644 --- a/tests/test_emitter.py +++ b/tests/test_emitter.py @@ -45,7 +45,6 @@ def rerun_filter(exc, *args): @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_create(p: P, event_queue: TestEventQueue, start_watching: StartWatching, expect_event: ExpectEvent) -> None: start_watching() open(p("a"), "a").close() @@ -66,7 +65,6 @@ def test_create(p: P, event_queue: TestEventQueue, start_watching: StartWatching @pytest.mark.skipif(not platform.is_linux(), reason="FileClosed*Event only supported in GNU/Linux") @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_closed(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: with open(p("a"), "a"): start_watching() @@ -99,7 +97,6 @@ def test_closed(p: P, event_queue: TestEventQueue, start_watching: StartWatching platform.is_darwin() or platform.is_windows(), reason="Windows and macOS enforce proper encoding", ) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_create_wrong_encoding(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: start_watching() open(p("a_\udce4"), "a").close() @@ -115,7 +112,6 @@ def test_create_wrong_encoding(p: P, event_queue: TestEventQueue, start_watching @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delete(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkfile(p("a")) @@ -129,7 +125,6 @@ def test_delete(p: P, start_watching: StartWatching, expect_event: ExpectEvent) @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_modify(p: P, event_queue: TestEventQueue, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkfile(p("a")) start_watching() @@ -150,7 +145,6 @@ def test_modify(p: P, event_queue: TestEventQueue, start_watching: StartWatching @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_chmod(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkfile(p("a")) start_watching() @@ -166,7 +160,6 @@ def test_chmod(p: P, start_watching: StartWatching, expect_event: ExpectEvent) - @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move(p: P, event_queue: TestEventQueue, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkdir(p("dir1")) mkdir(p("dir2")) @@ -196,7 +189,6 @@ def test_move(p: P, event_queue: TestEventQueue, start_watching: StartWatching, @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_case_change( p: P, event_queue: TestEventQueue, @@ -231,7 +223,6 @@ def test_case_change( @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_to(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkdir(p("dir1")) mkdir(p("dir2")) @@ -247,7 +238,6 @@ def test_move_to(p: P, start_watching: StartWatching, expect_event: ExpectEvent) @pytest.mark.skipif(not platform.is_linux(), reason="InotifyFullEmitter only supported in Linux") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_to_full(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: mkdir(p("dir1")) mkdir(p("dir2")) @@ -262,7 +252,6 @@ def test_move_to_full(p: P, event_queue: TestEventQueue, start_watching: StartWa @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_from(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkdir(p("dir1")) mkdir(p("dir2")) @@ -278,7 +267,6 @@ def test_move_from(p: P, start_watching: StartWatching, expect_event: ExpectEven @pytest.mark.skipif(not platform.is_linux(), reason="InotifyFullEmitter only supported in Linux") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_from_full(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: mkdir(p("dir1")) mkdir(p("dir2")) @@ -293,7 +281,6 @@ def test_move_from_full(p: P, event_queue: TestEventQueue, start_watching: Start @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_separate_consecutive_moves(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkdir(p("dir1")) mkfile(p("dir1", "a")) @@ -322,7 +309,6 @@ def test_separate_consecutive_moves(p: P, start_watching: StartWatching, expect_ @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) @pytest.mark.skipif(platform.is_bsd(), reason="BSD create another set of events for this test") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delete_self(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: mkdir(p("dir1")) emitter = start_watching(path=p("dir1")) @@ -336,7 +322,6 @@ def test_delete_self(p: P, start_watching: StartWatching, expect_event: ExpectEv platform.is_windows() or platform.is_bsd(), reason="Windows|BSD create another set of events for this test", ) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_fast_subdirectory_creation_deletion(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: root_dir = p("dir1") sub_dir = p("dir1", "subdir1") @@ -369,7 +354,6 @@ def test_fast_subdirectory_creation_deletion(p: P, event_queue: TestEventQueue, @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_passing_unicode_should_give_unicode(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: start_watching(path=str(p())) mkfile(p("a")) @@ -389,7 +373,6 @@ def test_passing_bytes_should_give_bytes(p: P, event_queue: TestEventQueue, star @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_recursive_on(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: mkdir(p("dir1", "dir2", "dir3"), parents=True) start_watching() @@ -416,7 +399,6 @@ def test_recursive_on(p: P, event_queue: TestEventQueue, start_watching: StartWa @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_recursive_off( p: P, event_queue: TestEventQueue, @@ -461,7 +443,6 @@ def test_recursive_off( @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_renaming_top_level_directory( p: P, event_queue: TestEventQueue, @@ -514,7 +495,6 @@ def test_renaming_top_level_directory( @pytest.mark.skipif(platform.is_windows(), reason="Windows create another set of events for this test") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_nested_subdirectories( p: P, event_queue: TestEventQueue, @@ -559,7 +539,6 @@ def test_move_nested_subdirectories( not platform.is_windows(), reason="Non-Windows create another set of events for this test", ) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_nested_subdirectories_on_windows( p: P, event_queue: TestEventQueue, @@ -605,7 +584,6 @@ def test_move_nested_subdirectories_on_windows( @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) @pytest.mark.skipif(platform.is_bsd(), reason="BSD create another set of events for this test") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_file_lifecyle(p: P, start_watching: StartWatching, expect_event: ExpectEvent) -> None: start_watching() diff --git a/tests/test_fsevents.py b/tests/test_fsevents.py index 2ecfed0d..47185ebe 100644 --- a/tests/test_fsevents.py +++ b/tests/test_fsevents.py @@ -228,7 +228,6 @@ def on_thread_stop(self): observer.unschedule(w) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_converting_cfstring_to_pyunicode(p: P, start_watching: StartWatching, event_queue: TestEventQueue) -> None: """See https://github.com/gorakhargosh/watchdog/issues/762""" @@ -293,7 +292,6 @@ def done(self): observer.join() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_watchdog_recursive(p: P) -> None: """See https://github.com/gorakhargosh/watchdog/issues/706""" import os.path diff --git a/tests/test_inotify_c.py b/tests/test_inotify_c.py index 51113c83..220e11ee 100644 --- a/tests/test_inotify_c.py +++ b/tests/test_inotify_c.py @@ -41,7 +41,6 @@ def struct_inotify(wd, mask, cookie=0, length=0, name=b"") -> bytes: return struct.pack(struct_format, wd, mask, cookie, length, name) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_late_double_deletion(helper: Helper, p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: inotify_fd = type("FD", (object,), {})() inotify_fd.last = 0 @@ -170,7 +169,6 @@ def test_raise_error(error, pattern): assert exc.value.errno == error -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_non_ascii_path(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: """ Inotify can construct an event for a path containing non-ASCII. @@ -206,7 +204,6 @@ def test_event_equality(p: P) -> None: assert event2 != event3 -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_select_fd(p: P, event_queue: TestEventQueue, start_watching: StartWatching) -> None: # We open a file 2048 times to ensure that we exhaust 1024 file # descriptors, the limit of a select() call. diff --git a/tests/test_inotify_watch_group.py b/tests/test_inotify_watch_group.py index 5506cc04..8c28fadb 100644 --- a/tests/test_inotify_watch_group.py +++ b/tests/test_inotify_watch_group.py @@ -37,7 +37,6 @@ def create_inotify_watch(path: bytes, *, recursive: bool = False, follow_symlink @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_from(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -50,7 +49,6 @@ def test_move_from(p): @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_to(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -63,7 +61,6 @@ def test_move_to(p): @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_internal(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -78,7 +75,6 @@ def test_move_internal(p): @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_internal_symlink_followed(p): mkdir(p("dir", "dir1"), parents=True) mkdir(p("dir", "dir2")) @@ -94,7 +90,6 @@ def test_move_internal_symlink_followed(p): @pytest.mark.timeout(10) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_internal_batch(p): n = 100 mkdir(p("dir1")) @@ -132,7 +127,6 @@ def test_delete_watched_directory(p): @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delete_watched_directory_symlink_followed(p): mkdir(p("dir", "dir2"), parents=True) symlink(p("dir"), p("symdir"), target_is_directory=True) @@ -152,7 +146,6 @@ def test_delete_watched_directory_symlink_followed(p): @pytest.mark.timeout(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delete_watched_directory_symlink_followed_recursive(p): mkdir(p("dir"), parents=True) mkdir(p("dir2", "dir3", "dir4"), parents=True) @@ -174,7 +167,6 @@ def test_delete_watched_directory_symlink_followed_recursive(p): @pytest.mark.timeout(5) @pytest.mark.skipif("GITHUB_REF" not in os.environ, reason="sudo password prompt") -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_unmount_watched_directory_filesystem(p): mkdir(p("dir1")) mount_tmpfs(p("dir1")) diff --git a/tests/test_observer.py b/tests/test_observer.py index d35b9d9e..b66c2ff2 100644 --- a/tests/test_observer.py +++ b/tests/test_observer.py @@ -32,7 +32,6 @@ def observer2(): obs.join() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_schedule_should_start_emitter_if_running(observer): observer.start() observer.schedule(None, "") @@ -46,7 +45,6 @@ def test_schedule_should_not_start_emitter_if_not_running(observer): assert not emitter.is_alive() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_start_should_start_emitter(observer): observer.schedule(None, "") observer.start() @@ -54,7 +52,6 @@ def test_start_should_start_emitter(observer): assert emitter.is_alive() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_stop_should_stop_emitter(observer): observer.schedule(None, "") observer.start() @@ -66,7 +63,6 @@ def test_stop_should_stop_emitter(observer): assert not emitter.is_alive() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_unschedule_self(observer): """ Tests that unscheduling a watch from within an event handler correctly @@ -89,7 +85,6 @@ def on_modified(self, event): assert len(observer.emitters) == 0 -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_schedule_after_unschedule_all(observer): observer.start() observer.schedule(None, "") @@ -112,7 +107,6 @@ def test_2_observers_on_the_same_path(observer, observer2): assert len(observer2.emitters) == 1 -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_start_failure_should_not_prevent_further_try(observer): observer.schedule(None, "") emitters = observer.emitters @@ -138,7 +132,6 @@ def mocked_start(): assert len(observer.emitters) == 1 -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_schedule_failure_should_not_prevent_future_schedules(observer): observer.start() diff --git a/tests/test_observers_polling.py b/tests/test_observers_polling.py index 71bd00d3..48e3b777 100644 --- a/tests/test_observers_polling.py +++ b/tests/test_observers_polling.py @@ -47,7 +47,6 @@ def emitter(event_queue): em.stop() em.join(5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test___init__(event_queue, emitter): sleep(SLEEP_TIME) mkdir(p("project")) @@ -130,7 +129,6 @@ def test___init__(event_queue, emitter): assert expected == got -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_delete_watched_dir(event_queue, emitter): rm(p(""), recursive=True) diff --git a/tests/test_observers_winapi.py b/tests/test_observers_winapi.py index 152636ad..1cddd842 100644 --- a/tests/test_observers_winapi.py +++ b/tests/test_observers_winapi.py @@ -47,7 +47,6 @@ def emitter(event_queue): em.stop() -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test___init__(event_queue, emitter): emitter.start() sleep(SLEEP_TIME) @@ -82,7 +81,6 @@ def test___init__(event_queue, emitter): assert expected == got -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_root_deleted(event_queue, emitter): r"""Test the event got when removing the watched folder. The regression to prevent is: diff --git a/tests/test_snapshot_diff.py b/tests/test_snapshot_diff.py index f7114948..e5c6c2c9 100644 --- a/tests/test_snapshot_diff.py +++ b/tests/test_snapshot_diff.py @@ -25,7 +25,6 @@ def wait(): time.sleep(0.5) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_pickle(p): """It should be possible to pickle a snapshot.""" mkdir(p("dir1")) @@ -33,7 +32,6 @@ def test_pickle(p): pickle.dumps(snasphot) -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_to(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -44,7 +42,6 @@ def test_move_to(p): assert diff.files_created == [p("dir2", "b")] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_to_with_context_manager(p): mkdir(p("dir1")) touch(p("dir1", "a")) @@ -59,7 +56,6 @@ def test_move_to_with_context_manager(p): assert dir2_cm.diff.files_created == [p("dir2", "b")] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_from(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -70,7 +66,6 @@ def test_move_from(p): assert diff.files_deleted == [p("dir1", "a")] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_internal(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -83,7 +78,6 @@ def test_move_internal(p): assert diff.files_deleted == [] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_move_replace(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -97,7 +91,6 @@ def test_move_replace(p): assert diff.files_created == [] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_dir_modify_on_create(p): ref = DirectorySnapshot(p("")) wait() @@ -106,7 +99,6 @@ def test_dir_modify_on_create(p): assert diff.dirs_modified == [p("")] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_dir_modify_on_move(p): mkdir(p("dir1")) mkdir(p("dir2")) @@ -129,7 +121,6 @@ def test_detect_modify_for_moved_files(p): assert diff.files_modified == [p("a")] -@pytest.mark.thread_unsafe(reason="Uses recwarn") def test_replace_dir_with_file(p): # Replace a dir with a file of the same name just before the normal listdir # call and ensure it doesn't cause an exception