diff --git a/tests/python/pants_test/pantsd/BUILD b/tests/python/pants_test/pantsd/BUILD index 116d92c4b236..d8e13be3b56a 100644 --- a/tests/python/pants_test/pantsd/BUILD +++ b/tests/python/pants_test/pantsd/BUILD @@ -66,6 +66,7 @@ python_tests( name = 'pantsd_integration', sources = ['test_pantsd_integration.py'], dependencies = [ + '3rdparty/python:futures', 'src/python/pants/pantsd:process_manager', 'src/python/pants/util:collections', 'tests/python/pants_test:int-test', diff --git a/tests/python/pants_test/pantsd/test_pantsd_integration.py b/tests/python/pants_test/pantsd/test_pantsd_integration.py index 429d5249bfad..98c003ae367f 100644 --- a/tests/python/pants_test/pantsd/test_pantsd_integration.py +++ b/tests/python/pants_test/pantsd/test_pantsd_integration.py @@ -9,11 +9,15 @@ import itertools import os import signal +import threading import time from contextlib import contextmanager +from concurrent.futures import ThreadPoolExecutor + from pants.pantsd.process_manager import ProcessManager from pants.util.collections import combined_dict +from pants.util.dirutil import touch from pants_test.pants_run_integration_test import PantsRunIntegrationTest from pants_test.testutils.process_test_util import check_process_exists_by_command @@ -55,6 +59,25 @@ def read_pantsd_log(workdir): yield line.strip() +def launch_file_toucher(f): + """Launch a loop to touch the given file, and return a function to call to stop and join it.""" + executor = ThreadPoolExecutor(max_workers=1) + halt = threading.Event() + + def file_toucher(): + while not halt.isSet(): + touch(f) + time.sleep(1) + + future = executor.submit(file_toucher) + + def join(): + halt.set() + future.result(timeout=10) + + return join + + class TestPantsDaemonIntegration(PantsRunIntegrationTest): @contextmanager def pantsd_test_context(self, log_level='info'): @@ -189,3 +212,20 @@ def test_pantsd_lifecycle_invalidation(self): # Run with an env var. pantsd_run(cmd[1:], {'GLOBAL': {'level': cmd[0]}}) checker.assert_running() + + def test_pantsd_filesystem_invalidation(self): + """Runs with pantsd enabled, in a loop, while another thread invalidates files.""" + with self.pantsd_successful_run_context() as (pantsd_run, checker, workdir): + cmd = ['list', '::'] + pantsd_run(cmd) + checker.await_pantsd() + + # Launch a separate thread to poke files in 3rdparty. + join = launch_file_toucher('3rdparty/BUILD') + + # Repeatedly re-list 3rdparty while the file is being invalidated. + for _ in range(0, 8): + pantsd_run(cmd) + checker.assert_running() + + join()