|
22 | 22 | from django_tasks.backends.database.management.commands.db_worker import (
|
23 | 23 | logger as db_worker_logger,
|
24 | 24 | )
|
| 25 | +from django_tasks.backends.database.management.commands.prune_db_task_results import ( |
| 26 | + logger as prune_db_tasks_logger, |
| 27 | +) |
25 | 28 | from django_tasks.backends.database.models import DBTaskResult
|
26 | 29 | from django_tasks.backends.database.utils import (
|
27 | 30 | connection_requires_manual_exclusive_transaction,
|
@@ -921,3 +924,221 @@ def test_explicit_transaction(self) -> None:
|
921 | 924 | self.assertFalse(
|
922 | 925 | connection_requires_manual_exclusive_transaction(self.connection)
|
923 | 926 | )
|
| 927 | + |
| 928 | + |
| 929 | +@override_settings( |
| 930 | + TASKS={ |
| 931 | + "default": { |
| 932 | + "BACKEND": "django_tasks.backends.database.DatabaseBackend", |
| 933 | + "QUEUES": ["default", "queue-1"], |
| 934 | + }, |
| 935 | + "dummy": {"BACKEND": "django_tasks.backends.dummy.DummyBackend"}, |
| 936 | + } |
| 937 | +) |
| 938 | +class DatabaseBackendPruneTaskResultsTestCase(TransactionTestCase): |
| 939 | + prune_task_results = partial(call_command, "prune_db_task_results", verbosity=0) |
| 940 | + |
| 941 | + def tearDown(self) -> None: |
| 942 | + # Reset the logger after every run, to ensure the correct `stdout` is used |
| 943 | + for handler in prune_db_tasks_logger.handlers: |
| 944 | + prune_db_tasks_logger.removeHandler(handler) |
| 945 | + |
| 946 | + def test_prunes_tasks(self) -> None: |
| 947 | + result = test_tasks.noop_task.enqueue() |
| 948 | + |
| 949 | + DBTaskResult.objects.all().update( |
| 950 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() |
| 951 | + ) |
| 952 | + |
| 953 | + self.assertEqual(DBTaskResult.objects.finished().count(), 1) |
| 954 | + |
| 955 | + stdout = StringIO() |
| 956 | + |
| 957 | + with self.assertNumQueries(3): |
| 958 | + self.prune_task_results(min_age_days=0, stdout=stdout, verbosity=3) |
| 959 | + |
| 960 | + self.assertEqual(DBTaskResult.objects.finished().count(), 0) |
| 961 | + |
| 962 | + with self.assertRaises(ResultDoesNotExist): |
| 963 | + result.refresh() |
| 964 | + |
| 965 | + self.assertEqual(stdout.getvalue().strip(), "Deleted 1 task result(s)") |
| 966 | + |
| 967 | + def test_doesnt_prune_new_tasks(self) -> None: |
| 968 | + result = test_tasks.noop_task.enqueue() |
| 969 | + |
| 970 | + self.assertEqual(DBTaskResult.objects.ready().count(), 1) |
| 971 | + |
| 972 | + stdout = StringIO() |
| 973 | + with self.assertNumQueries(3): |
| 974 | + self.prune_task_results(min_age_days=0, stdout=stdout, verbosity=3) |
| 975 | + |
| 976 | + self.assertEqual(DBTaskResult.objects.ready().count(), 1) |
| 977 | + |
| 978 | + result.refresh() |
| 979 | + |
| 980 | + self.assertEqual(stdout.getvalue().strip(), "Deleted 0 task result(s)") |
| 981 | + |
| 982 | + def test_doesnt_prune_running_tasks(self) -> None: |
| 983 | + result = test_tasks.noop_task.enqueue() |
| 984 | + |
| 985 | + DBTaskResult.objects.all().update(status=ResultStatus.RUNNING) |
| 986 | + |
| 987 | + self.assertEqual(DBTaskResult.objects.running().count(), 1) |
| 988 | + |
| 989 | + with self.assertNumQueries(3): |
| 990 | + self.prune_task_results(min_age_days=0) |
| 991 | + |
| 992 | + self.assertEqual(DBTaskResult.objects.running().count(), 1) |
| 993 | + |
| 994 | + result.refresh() |
| 995 | + |
| 996 | + def test_only_prunes_specified_queue(self) -> None: |
| 997 | + result = test_tasks.noop_task.enqueue() |
| 998 | + queue_1_result = test_tasks.noop_task.using(queue_name="queue-1").enqueue() |
| 999 | + |
| 1000 | + DBTaskResult.objects.all().update( |
| 1001 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() |
| 1002 | + ) |
| 1003 | + |
| 1004 | + self.assertEqual(DBTaskResult.objects.complete().count(), 2) |
| 1005 | + |
| 1006 | + with self.assertNumQueries(3): |
| 1007 | + self.prune_task_results(queue_name="queue-1", min_age_days=0) |
| 1008 | + |
| 1009 | + self.assertEqual(DBTaskResult.objects.complete().count(), 1) |
| 1010 | + |
| 1011 | + result.refresh() |
| 1012 | + |
| 1013 | + with self.assertRaises(ResultDoesNotExist): |
| 1014 | + queue_1_result.refresh() |
| 1015 | + |
| 1016 | + def test_prune_all_queues(self) -> None: |
| 1017 | + test_tasks.noop_task.enqueue() |
| 1018 | + test_tasks.noop_task.using(queue_name="queue-1").enqueue() |
| 1019 | + |
| 1020 | + DBTaskResult.objects.all().update( |
| 1021 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() |
| 1022 | + ) |
| 1023 | + |
| 1024 | + self.assertEqual(DBTaskResult.objects.complete().count(), 2) |
| 1025 | + |
| 1026 | + with self.assertNumQueries(3): |
| 1027 | + self.prune_task_results(queue_name="*", min_age_days=0) |
| 1028 | + |
| 1029 | + self.assertEqual(DBTaskResult.objects.complete().count(), 0) |
| 1030 | + |
| 1031 | + def test_min_age(self) -> None: |
| 1032 | + one_day_result = test_tasks.noop_task.enqueue() |
| 1033 | + |
| 1034 | + DBTaskResult.objects.ready().update( |
| 1035 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() - timedelta(days=1) |
| 1036 | + ) |
| 1037 | + |
| 1038 | + three_day_result = test_tasks.noop_task.enqueue() |
| 1039 | + DBTaskResult.objects.ready().update( |
| 1040 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() - timedelta(days=3) |
| 1041 | + ) |
| 1042 | + |
| 1043 | + self.assertEqual(DBTaskResult.objects.complete().count(), 2) |
| 1044 | + |
| 1045 | + with self.assertNumQueries(3): |
| 1046 | + self.prune_task_results() |
| 1047 | + |
| 1048 | + self.assertEqual(DBTaskResult.objects.complete().count(), 2) |
| 1049 | + |
| 1050 | + with self.assertNumQueries(3): |
| 1051 | + self.prune_task_results(min_age_days=3) |
| 1052 | + |
| 1053 | + self.assertEqual(DBTaskResult.objects.complete().count(), 1) |
| 1054 | + |
| 1055 | + one_day_result.refresh() |
| 1056 | + |
| 1057 | + with self.assertRaises(ResultDoesNotExist): |
| 1058 | + three_day_result.refresh() |
| 1059 | + |
| 1060 | + with self.assertNumQueries(3): |
| 1061 | + self.prune_task_results(min_age_days=1) |
| 1062 | + |
| 1063 | + self.assertEqual(DBTaskResult.objects.complete().count(), 0) |
| 1064 | + |
| 1065 | + def test_failed_min_age(self) -> None: |
| 1066 | + completed_result = test_tasks.noop_task.enqueue() |
| 1067 | + |
| 1068 | + DBTaskResult.objects.ready().update( |
| 1069 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() - timedelta(days=3) |
| 1070 | + ) |
| 1071 | + |
| 1072 | + failed_result = test_tasks.noop_task.enqueue() |
| 1073 | + DBTaskResult.objects.ready().update( |
| 1074 | + status=ResultStatus.FAILED, finished_at=timezone.now() - timedelta(days=3) |
| 1075 | + ) |
| 1076 | + |
| 1077 | + self.assertEqual(DBTaskResult.objects.finished().count(), 2) |
| 1078 | + |
| 1079 | + with self.assertNumQueries(3): |
| 1080 | + self.prune_task_results() |
| 1081 | + |
| 1082 | + self.assertEqual(DBTaskResult.objects.finished().count(), 2) |
| 1083 | + |
| 1084 | + with self.assertNumQueries(3): |
| 1085 | + self.prune_task_results(min_age_days=3, failed_min_age_days=5) |
| 1086 | + |
| 1087 | + self.assertEqual(DBTaskResult.objects.finished().count(), 1) |
| 1088 | + |
| 1089 | + failed_result.refresh() |
| 1090 | + |
| 1091 | + with self.assertRaises(ResultDoesNotExist): |
| 1092 | + completed_result.refresh() |
| 1093 | + |
| 1094 | + with self.assertNumQueries(3): |
| 1095 | + self.prune_task_results(min_age_days=3, failed_min_age_days=1) |
| 1096 | + |
| 1097 | + with self.assertRaises(ResultDoesNotExist): |
| 1098 | + failed_result.refresh() |
| 1099 | + |
| 1100 | + def test_dry_run(self) -> None: |
| 1101 | + test_tasks.noop_task.enqueue() |
| 1102 | + |
| 1103 | + DBTaskResult.objects.all().update( |
| 1104 | + status=ResultStatus.COMPLETE, finished_at=timezone.now() |
| 1105 | + ) |
| 1106 | + |
| 1107 | + self.assertEqual(DBTaskResult.objects.count(), 1) |
| 1108 | + |
| 1109 | + stdout = StringIO() |
| 1110 | + with self.assertNumQueries(1): |
| 1111 | + self.prune_task_results( |
| 1112 | + min_age_days=0, dry_run=True, stdout=stdout, verbosity=3 |
| 1113 | + ) |
| 1114 | + |
| 1115 | + self.assertEqual(DBTaskResult.objects.count(), 1) |
| 1116 | + |
| 1117 | + self.assertEqual(stdout.getvalue().strip(), "Would delete 1 task result(s)") |
| 1118 | + |
| 1119 | + def test_unknown_backend(self) -> None: |
| 1120 | + output = StringIO() |
| 1121 | + with redirect_stderr(output): |
| 1122 | + with self.assertRaises(SystemExit): |
| 1123 | + execute_from_command_line( |
| 1124 | + ["django-admin", "prune_db_task_results", "--backend", "unknown"] |
| 1125 | + ) |
| 1126 | + self.assertIn("The connection 'unknown' doesn't exist.", output.getvalue()) |
| 1127 | + |
| 1128 | + def test_incorrect_backend(self) -> None: |
| 1129 | + output = StringIO() |
| 1130 | + with redirect_stderr(output): |
| 1131 | + with self.assertRaises(SystemExit): |
| 1132 | + execute_from_command_line( |
| 1133 | + ["django-admin", "prune_db_task_results", "--backend", "dummy"] |
| 1134 | + ) |
| 1135 | + self.assertIn("Backend 'dummy' is not a database backend", output.getvalue()) |
| 1136 | + |
| 1137 | + def test_negative_age(self) -> None: |
| 1138 | + output = StringIO() |
| 1139 | + with redirect_stderr(output): |
| 1140 | + with self.assertRaises(SystemExit): |
| 1141 | + execute_from_command_line( |
| 1142 | + ["django-admin", "prune_db_task_results", "--min-age-days", "-1"] |
| 1143 | + ) |
| 1144 | + self.assertIn("Must be greater than zero", output.getvalue()) |
0 commit comments