diff --git a/changedetectionio/run_basic_tests.sh b/changedetectionio/run_basic_tests.sh index 5906e5ba..33b40cec 100755 --- a/changedetectionio/run_basic_tests.sh +++ b/changedetectionio/run_basic_tests.sh @@ -66,7 +66,7 @@ echo "-------------------- Running rest of tests in parallel ------------------- # REMOVE_REQUESTS_OLD_SCREENSHOTS disabled so that we can write a screenshot and send it in test_notifications.py without a real browser REMOVE_REQUESTS_OLD_SCREENSHOTS=false \ pytest tests/test_*.py \ - -n 30 \ + -n 16 \ --dist=load \ -vvv \ -s \ diff --git a/changedetectionio/store/__init__.py b/changedetectionio/store/__init__.py index 70198a37..ff7685dd 100644 --- a/changedetectionio/store/__init__.py +++ b/changedetectionio/store/__init__.py @@ -166,6 +166,9 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore): """ logger.info(f"Datastore path is '{datastore_path}'") + # CRITICAL: Update datastore_path (was using old path from __init__) + self.datastore_path = datastore_path + # Initialize data structure self.__data = App.model() self.json_store_path = os.path.join(self.datastore_path, "changedetection.json") diff --git a/changedetectionio/tests/conftest.py b/changedetectionio/tests/conftest.py index 67e41c52..672860c6 100644 --- a/changedetectionio/tests/conftest.py +++ b/changedetectionio/tests/conftest.py @@ -175,31 +175,55 @@ def prepare_test_function(live_server, datastore_path): # CRITICAL: Get datastore and stop it from writing stale data datastore = live_server.app.config.get('DATASTORE') - # Clear the queue before starting the test to prevent state leakage - from changedetectionio.flask_app import update_q + # CRITICAL: Clear ALL global state that persists between tests + from changedetectionio.flask_app import update_q, notification_q, notification_debug_log + + # Clear update queue while not update_q.empty(): try: update_q.get_nowait() except: break + # Clear notification queue (was missing!) + while not notification_q.empty(): + try: + notification_q.get_nowait() + except: + break + + # Clear notification debug log (accumulates across tests!) + notification_debug_log.clear() + + # Clear SALTED_PASS env var if set by previous test + if 'SALTED_PASS' in os.environ: + del os.environ['SALTED_PASS'] + + # Stop background thread with longer timeout + datastore.stop_thread = True + if hasattr(datastore, 'save_data_thread') and datastore.save_data_thread: + datastore.save_data_thread.join(timeout=2.0) + # Prevent background thread from writing during cleanup/reload datastore.needs_write = False datastore.needs_write_urgent = False + # Reset dirty tracking + datastore._dirty_watches = set() + datastore._dirty_settings = False + datastore._watch_hashes = {} + # CRITICAL: Clean up any files from previous tests - # This ensures a completely clean directory cleanup(datastore_path) - # CRITICAL: Reload the EXISTING datastore instead of creating a new one - # This keeps blueprint references valid (they capture datastore at construction) - # reload_state() completely resets the datastore to a clean state + # Reset stop_thread so reload_state can start new thread + datastore.stop_thread = False # Reload state with clean data (no default watches) datastore.reload_state( datastore_path=datastore_path, include_default_watches=False, - version_tag=datastore.data.get('version_tag', '0.0.0') + version_tag='0.0.0' ) live_server.app.secret_key = init_app_secret(datastore_path) logger.debug(f"prepare_test_function: Reloaded datastore at {hex(id(datastore))}") diff --git a/changedetectionio/tests/test_group.py b/changedetectionio/tests/test_group.py index 08c72e82..1d4441b5 100644 --- a/changedetectionio/tests/test_group.py +++ b/changedetectionio/tests/test_group.py @@ -142,7 +142,7 @@ def test_tag_import_singular(client, live_server, measure_memory_usage, datastor ) # Should be only 1 tag because they both had the same assert len(live_server.app.config['DATASTORE'].data['settings']['application'].get('tags')) ==1 - + wait_for_all_checks() delete_all_watches(client) def test_tag_add_in_ui(client, live_server, measure_memory_usage, datastore_path): diff --git a/changedetectionio/tests/test_request.py b/changedetectionio/tests/test_request.py index 907ea76b..7091146b 100644 --- a/changedetectionio/tests/test_request.py +++ b/changedetectionio/tests/test_request.py @@ -213,6 +213,7 @@ def test_method_in_request(client, live_server, measure_memory_usage, datastore_ follow_redirects=True ) assert b"Updated watch." in res.data + res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True) # Give the thread time to pick up the first version wait_for_all_checks(client) diff --git a/changedetectionio/tests/test_restock_itemprop.py b/changedetectionio/tests/test_restock_itemprop.py index c9639e02..1fb7e8ae 100644 --- a/changedetectionio/tests/test_restock_itemprop.py +++ b/changedetectionio/tests/test_restock_itemprop.py @@ -236,22 +236,17 @@ def test_restock_itemprop_with_tag(client, live_server, measure_memory_usage, da def test_itemprop_percent_threshold(client, live_server, measure_memory_usage, datastore_path): - delete_all_watches(client) - test_url = url_for('test_endpoint', _external=True) set_original_response(props_markup=instock_props[0], price="950.95", datastore_path=datastore_path) - client.post( - url_for("ui.ui_views.form_quick_watch_add"), - data={"url": test_url, "tags": 'restock tests', 'processor': 'restock_diff'}, - follow_redirects=True - ) + uuid = client.application.config.get('DATASTORE').add_watch(url=test_url, tag="restock tests", extras={'processor':"restock_diff"}) + res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True) # A change in price, should trigger a change by default wait_for_all_checks(client) res = client.post( - url_for("ui.ui_edit.edit_page", uuid="first"), + url_for("ui.ui_edit.edit_page", uuid=uuid), data={"restock_settings-follow_price_changes": "y", "restock_settings-price_change_threshold_percent": 5.0, "url": test_url, @@ -295,7 +290,6 @@ def test_itemprop_percent_threshold(client, live_server, measure_memory_usage, d # Re #2600 - Switch the mode to normal type and back, and see if the values stick.. ################################################################################### - uuid = next(iter(live_server.app.config['DATASTORE'].data['watching'])) res = client.post( url_for("ui.ui_edit.edit_page", uuid=uuid), @@ -314,9 +308,6 @@ def test_itemprop_percent_threshold(client, live_server, measure_memory_usage, d res = client.get(url_for("ui.ui_edit.edit_page", uuid=uuid)) assert b'type="text" value="5.05"' in res.data - delete_all_watches(client) - - def test_change_with_notification_values(client, live_server, measure_memory_usage, datastore_path): @@ -390,16 +381,11 @@ def test_change_with_notification_values(client, live_server, measure_memory_usa def test_data_sanity(client, live_server, measure_memory_usage, datastore_path): - delete_all_watches(client) - test_url = url_for('test_endpoint', _external=True) test_url2 = url_for('test_endpoint2', _external=True) set_original_response(props_markup=instock_props[0], price="950.95", datastore_path=datastore_path) - client.post( - url_for("ui.ui_views.form_quick_watch_add"), - data={"url": test_url, "tags": 'restock tests', 'processor': 'restock_diff'}, - follow_redirects=True - ) + uuid = client.application.config.get('DATASTORE').add_watch(url=test_url, tag="restock tests", extras={'processor':"restock_diff"}) + res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True) wait_for_all_checks(client) @@ -418,16 +404,12 @@ def test_data_sanity(client, live_server, measure_memory_usage, datastore_path): ## different test, check the edit page works on an empty request result delete_all_watches(client) - - client.post( - url_for("ui.ui_views.form_quick_watch_add"), - data={"url": test_url2, "tags": 'restock tests', 'processor': 'restock_diff'}, - follow_redirects=True - ) + uuid = client.application.config.get('DATASTORE').add_watch(url=test_url2, tag="restock tests", extras={'processor':"restock_diff"}) + res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True) wait_for_all_checks(client) res = client.get( - url_for("ui.ui_edit.edit_page", uuid="first")) + url_for("ui.ui_edit.edit_page", uuid=uuid)) assert test_url2.encode('utf-8') in res.data delete_all_watches(client) diff --git a/changedetectionio/tests/util.py b/changedetectionio/tests/util.py index b5cb793a..96510ed6 100644 --- a/changedetectionio/tests/util.py +++ b/changedetectionio/tests/util.py @@ -144,7 +144,7 @@ def wait_for_all_checks(client=None): """ from changedetectionio.flask_app import update_q as global_update_q from changedetectionio import worker_pool - time.sleep(0.05) + time.sleep(0.15) # Use the shared wait logic from worker_handler return worker_pool.wait_for_all_checks(global_update_q, timeout=150)