@@ -5510,3 +5510,82 @@ def test_plan_environment_statements_doesnt_cause_extra_diff(tmp_path: Path):
55105510
55115511 # second plan - nothing has changed so should report no changes
55125512 assert not ctx .plan (auto_apply = True , no_prompts = True ).has_changes
5513+
5514+
5515+ def test_janitor_cleanup_order (mocker : MockerFixture , tmp_path : Path ):
5516+ def setup_scenario ():
5517+ models_dir = tmp_path / "models"
5518+
5519+ if not models_dir .exists ():
5520+ models_dir .mkdir ()
5521+
5522+ model1_path = models_dir / "model1.sql"
5523+
5524+ with open (model1_path , "w" ) as f :
5525+ f .write ("MODEL(name test.model1, kind FULL); SELECT 1 AS col" )
5526+
5527+ config = Config (
5528+ model_defaults = ModelDefaultsConfig (dialect = "duckdb" ),
5529+ )
5530+ ctx = Context (paths = [tmp_path ], config = config )
5531+
5532+ ctx .plan ("dev" , no_prompts = True , auto_apply = True )
5533+
5534+ model1_snapshot = ctx .get_snapshot ("test.model1" )
5535+
5536+ # Delete the model file to cause a snapshot expiration
5537+ model1_path .unlink ()
5538+
5539+ ctx .load ()
5540+
5541+ ctx .plan ("dev" , no_prompts = True , auto_apply = True )
5542+
5543+ # Invalidate the environment to cause an environment cleanup
5544+ ctx .invalidate_environment ("dev" )
5545+
5546+ try :
5547+ ctx ._run_janitor (ignore_ttl = True )
5548+ except :
5549+ pass
5550+
5551+ return ctx , model1_snapshot
5552+
5553+ # Case 1: Assume that the snapshot cleanup yields an error, the snapshot records
5554+ # should still exist in the state sync so the next janitor can retry
5555+ mocker .patch (
5556+ "sqlmesh.core.snapshot.evaluator.SnapshotEvaluator.cleanup" ,
5557+ side_effect = Exception ("snapshot cleanup error" ),
5558+ )
5559+ ctx , model1_snapshot = setup_scenario ()
5560+
5561+ # - Check that the snapshot record exists in the state sync
5562+ state_snapshot = ctx .state_sync .state_sync .get_snapshots ([model1_snapshot .snapshot_id ])
5563+ assert state_snapshot
5564+
5565+ # - Run the janitor again, this time it should succeed
5566+ mocker .patch ("sqlmesh.core.snapshot.evaluator.SnapshotEvaluator.cleanup" )
5567+ ctx ._run_janitor (ignore_ttl = True )
5568+
5569+ # - Check that the snapshot record does not exist in the state sync anymore
5570+ state_snapshot = ctx .state_sync .state_sync .get_snapshots ([model1_snapshot .snapshot_id ])
5571+ assert not state_snapshot
5572+
5573+ # Case 2: Assume that the view cleanup yields an error, the enviroment
5574+ # record should still exist
5575+ mocker .patch (
5576+ "sqlmesh.core.context.cleanup_expired_views" , side_effect = Exception ("view cleanup error" )
5577+ )
5578+ ctx , model1_snapshot = setup_scenario ()
5579+
5580+ views = ctx .fetchdf ("FROM duckdb_views() SELECT * EXCLUDE(sql) WHERE NOT internal" )
5581+ assert views .empty
5582+
5583+ # - Check that the environment record exists in the state sync
5584+ assert ctx .state_sync .get_environment ("dev" )
5585+
5586+ # - Run the janitor again, this time it should succeed
5587+ mocker .patch ("sqlmesh.core.context.cleanup_expired_views" )
5588+ ctx ._run_janitor (ignore_ttl = True )
5589+
5590+ # - Check that the environment record does not exist in the state sync anymore
5591+ assert not ctx .state_sync .get_environment ("dev" )
0 commit comments