)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"10f1c2b98962058ea0c498d5624ac5525e18e76c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"7b09a5a3_b46cfb90","updated":"2026-02-27 12:08:32.000000000","message":"I have squashed this into https://review.opendev.org/c/openstack/swift/+/968740/33?usp\u003drelated-change, with the changes from my last comment","commit_id":"a5ab99ab51cc062268238598842ac797a6b0686d"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab9ef577c1dd9af00389ae3657201ab6dbfed838","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"7173f3a1_c534b33d","updated":"2026-02-26 17:34:33.000000000","message":"Thanks, I want to squash this but I ran into an issue with the test:\n\nA suggestion:\n\n```\ndiff --git a/test/unit/__init__.py b/test/unit/__init__.py\nindex af98ae740..d5e896a3d 100644\n--- a/test/unit/__init__.py\n+++ b/test/unit/__init__.py\n@@ -1154,7 +1154,8 @@ def make_normal_timestamp_iter():\n def mock_timestamp_now(now\u003dNone, klass\u003dTimestamp):\n     if now is None:\n         now \u003d klass.now()\n-    with mocklib.patch.object(klass, \u0027now\u0027, classmethod(lambda c: now)):\n+    with mocklib.patch.object(\n+            klass, \u0027now\u0027, classmethod(lambda *args, **kwargs: now)):\n         yield now\n \n \n@@ -1163,7 +1164,7 @@ def mock_normal_timestamp_now(now\u003dNone):\n     if now is None:\n         now \u003d NormalTimestamp.now()\n     with mocklib.patch.object(\n-            NormalTimestamp, \u0027now\u0027, classmethod(lambda c: now)):\n+            NormalTimestamp, \u0027now\u0027, classmethod(lambda *args, **kwargs: now)):\n         yield now\n \n \ndiff --git a/test/unit/obj/test_expirer.py b/test/unit/obj/test_expirer.py\nindex 722855703..4a016bcb5 100644\n--- a/test/unit/obj/test_expirer.py\n+++ b/test/unit/obj/test_expirer.py\n@@ -19,7 +19,8 @@ import itertools\n from time import time\n from unittest import main, TestCase\n from test.debug_logger import debug_logger\n-from test.unit import FakeRing, mocked_http_conn, make_timestamp_iter\n+from test.unit import FakeRing, mocked_http_conn, make_timestamp_iter, \\\n+    mock_normal_timestamp_now\n from tempfile import mkdtemp\n from shutil import rmtree\n from collections import defaultdict\n@@ -1298,9 +1299,7 @@ class TestObjectExpirer(TestCase):\n         ]\n         with mock.patch.object(self.expirer.swift, \u0027iter_containers\u0027,\n                                return_value\u003dcontainer_list), \\\n-                mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027) \\\n-                as mock_normal_now:\n-            mock_normal_now.return_value \u003d NormalTimestamp(mock_past)\n+                mock_normal_timestamp_now(NormalTimestamp(mock_past)):\n             result \u003d self.expirer.get_task_containers_to_expire(\n                 \u0027task_account\u0027)\n         # past_container (now-200) is before mock_past (now-100): included\n@@ -1422,9 +1421,7 @@ class TestObjectExpirer(TestCase):\n         fake_swift \u003d FakeInternalClient(aco_dict)\n         x \u003d expirer.ObjectExpirer(self.conf, logger\u003ddebug_logger(),\n                                   swift\u003dfake_swift)\n-        with mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027) \\\n-                as mock_normal_now:\n-            mock_normal_now.return_value \u003d NormalTimestamp(mock_past)\n+        with mock_normal_timestamp_now(NormalTimestamp(mock_past)):\n             tasks \u003d list(x._iter_task_container(\n                 \u0027.expiring_objects\u0027, past_time_container, 0, 1))\n         # past_time (now-200) is before mock_past (now-100): yielded\n@@ -1455,8 +1452,9 @@ class TestObjectExpirer(TestCase):\n         self.conf[\u0027delay_reaping_a1\u0027] \u003d delay\n         x \u003d expirer.ObjectExpirer(self.conf, logger\u003ddebug_logger(),\n                                   swift\u003dfake_swift)\n-        with mock.patch(\u0027swift.obj.expirer.time\u0027,\n-                        return_value\u003dfloat(mock_past)):\n+        times \u003d [NormalTimestamp(now), NormalTimestamp(mock_past - delay)]\n+        with mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027,\n+                        side_effect\u003dtimes):\n             tasks \u003d list(x._iter_task_container(\n                 \u0027.expiring_objects\u0027, task_container, 0, 1))\n         # delete_ts (now-350) \u003e NormalTimestamp(mock_past - delay \u003d now-400)\n\n```","commit_id":"a5ab99ab51cc062268238598842ac797a6b0686d"}],"swift/obj/expirer.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c1df296501d76b22e4e3c2a8b71cf4cebdf6722a","unresolved":true,"context_lines":[{"line_number":29,"context_line":"from swift.common.internal_client import InternalClient, UnexpectedResponse"},{"line_number":30,"context_line":"from swift.common import utils"},{"line_number":31,"context_line":"from swift.common.utils import get_logger, dump_recon_cache, split_path, \\"},{"line_number":32,"context_line":"    Timestamp, NormalTimestamp, config_true_value, \\"},{"line_number":33,"context_line":"    normalize_delete_at_timestamp, \\"},{"line_number":34,"context_line":"    RateLimitedIterator, md5, non_negative_float, non_negative_int, \\"},{"line_number":35,"context_line":"    parse_content_type, parse_options, config_positive_int_value"}],"source_content_type":"text/x-python","patch_set":1,"id":"51233386_b90704b2","line":32,"range":{"start_line":32,"start_character":4,"end_line":32,"end_character":13},"updated":"2026-02-24 14:43:55.000000000","message":"I think this is only used to parse expiry queue items that have normalized, or even rounded to int. If so they can be parsed using NormalTimestamp.\n\nBut unlike the patchset proposed, we\u0027d need to really sure there are no existing queue entries with offsets before changing the parsing class.","commit_id":"3ec185364a9d1cfbdef1c0e5888d4beca33b60e2"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c1df296501d76b22e4e3c2a8b71cf4cebdf6722a","unresolved":true,"context_lines":[{"line_number":496,"context_line":"                self.logger.increment(\u0027tasks.skipped\u0027)"},{"line_number":497,"context_line":"                continue"},{"line_number":498,"context_line":""},{"line_number":499,"context_line":"            if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":500,"context_line":"                    and not is_async:"},{"line_number":501,"context_line":"                # we shouldn\u0027t yield the object during the delay"},{"line_number":502,"context_line":"                self.logger.increment(\u0027tasks.delayed\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"18b8528b_090bb759","line":499,"range":{"start_line":499,"start_character":34,"end_line":499,"end_character":43},"updated":"2026-02-24 14:43:55.000000000","message":"also here","commit_id":"3ec185364a9d1cfbdef1c0e5888d4beca33b60e2"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"065311aef5f17df0f8671d8567ccae595e1c174e","unresolved":false,"context_lines":[{"line_number":496,"context_line":"                self.logger.increment(\u0027tasks.skipped\u0027)"},{"line_number":497,"context_line":"                continue"},{"line_number":498,"context_line":""},{"line_number":499,"context_line":"            if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":500,"context_line":"                    and not is_async:"},{"line_number":501,"context_line":"                # we shouldn\u0027t yield the object during the delay"},{"line_number":502,"context_line":"                self.logger.increment(\u0027tasks.delayed\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"af5cd1d6_e356d4c5","line":499,"range":{"start_line":499,"start_character":34,"end_line":499,"end_character":43},"in_reply_to":"18b8528b_090bb759","updated":"2026-02-25 06:00:21.000000000","message":"also found one walltime comparison in multi_upload.py, changed to NormalTimestamp too","commit_id":"3ec185364a9d1cfbdef1c0e5888d4beca33b60e2"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab9ef577c1dd9af00389ae3657201ab6dbfed838","unresolved":true,"context_lines":[{"line_number":496,"context_line":"                self.logger.increment(\u0027tasks.skipped\u0027)"},{"line_number":497,"context_line":"                continue"},{"line_number":498,"context_line":""},{"line_number":499,"context_line":"            if delete_timestamp \u003e NormalTimestamp(time() - delay_reaping) \\"},{"line_number":500,"context_line":"                    and not is_async:"},{"line_number":501,"context_line":"                # we shouldn\u0027t yield the object during the delay"},{"line_number":502,"context_line":"                self.logger.increment(\u0027tasks.delayed\u0027)"}],"source_content_type":"text/x-python","patch_set":2,"id":"b9497853_fcf9a413","line":499,"updated":"2026-02-26 17:34:33.000000000","message":"this can be written using ``Timestamp.now(delta\u003d-1e5 * delay_reaping)``","commit_id":"a5ab99ab51cc062268238598842ac797a6b0686d"}],"test/unit/obj/test_expirer.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"c1df296501d76b22e4e3c2a8b71cf4cebdf6722a","unresolved":true,"context_lines":[{"line_number":1298,"context_line":"                               return_value\u003dcontainer_list), \\"},{"line_number":1299,"context_line":"                mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027) \\"},{"line_number":1300,"context_line":"                as mock_normal_now:"},{"line_number":1301,"context_line":"            mock_normal_now.return_value \u003d NormalTimestamp(now)"},{"line_number":1302,"context_line":"            result \u003d self.expirer.get_task_containers_to_expire("},{"line_number":1303,"context_line":"                \u0027task_account\u0027)"},{"line_number":1304,"context_line":"        self.assertTrue(mock_normal_now.called)"}],"source_content_type":"text/x-python","patch_set":1,"id":"a10321a3_78e21453","line":1301,"updated":"2026-02-24 14:43:55.000000000","message":"this seems very \"white box testing\" - I wonder if there is way to validate based on the behavior e.g. shift times into the past, mock_normal_timestamp_now(past) and the result would only be correct if NormalTimestamp was called","commit_id":"3ec185364a9d1cfbdef1c0e5888d4beca33b60e2"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"065311aef5f17df0f8671d8567ccae595e1c174e","unresolved":false,"context_lines":[{"line_number":1298,"context_line":"                               return_value\u003dcontainer_list), \\"},{"line_number":1299,"context_line":"                mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027) \\"},{"line_number":1300,"context_line":"                as mock_normal_now:"},{"line_number":1301,"context_line":"            mock_normal_now.return_value \u003d NormalTimestamp(now)"},{"line_number":1302,"context_line":"            result \u003d self.expirer.get_task_containers_to_expire("},{"line_number":1303,"context_line":"                \u0027task_account\u0027)"},{"line_number":1304,"context_line":"        self.assertTrue(mock_normal_now.called)"}],"source_content_type":"text/x-python","patch_set":1,"id":"125fb2a2_30539f35","line":1301,"in_reply_to":"a10321a3_78e21453","updated":"2026-02-25 06:00:21.000000000","message":"Done","commit_id":"3ec185364a9d1cfbdef1c0e5888d4beca33b60e2"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab9ef577c1dd9af00389ae3657201ab6dbfed838","unresolved":true,"context_lines":[{"line_number":1299,"context_line":"        with mock.patch.object(self.expirer.swift, \u0027iter_containers\u0027,"},{"line_number":1300,"context_line":"                               return_value\u003dcontainer_list), \\"},{"line_number":1301,"context_line":"                mock.patch(\u0027swift.obj.expirer.NormalTimestamp.now\u0027) \\"},{"line_number":1302,"context_line":"                as mock_normal_now:"},{"line_number":1303,"context_line":"            mock_normal_now.return_value \u003d NormalTimestamp(mock_past)"},{"line_number":1304,"context_line":"            result \u003d self.expirer.get_task_containers_to_expire("},{"line_number":1305,"context_line":"                \u0027task_account\u0027)"}],"source_content_type":"text/x-python","patch_set":2,"id":"09e1e781_ae1c3ce5","line":1302,"updated":"2026-02-26 17:34:33.000000000","message":"we have a mock_normal_timestamp_now helper function","commit_id":"a5ab99ab51cc062268238598842ac797a6b0686d"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab9ef577c1dd9af00389ae3657201ab6dbfed838","unresolved":true,"context_lines":[{"line_number":1456,"context_line":"        x \u003d expirer.ObjectExpirer(self.conf, logger\u003ddebug_logger(),"},{"line_number":1457,"context_line":"                                  swift\u003dfake_swift)"},{"line_number":1458,"context_line":"        with mock.patch(\u0027swift.obj.expirer.time\u0027,"},{"line_number":1459,"context_line":"                        return_value\u003dfloat(mock_past)):"},{"line_number":1460,"context_line":"            tasks \u003d list(x._iter_task_container("},{"line_number":1461,"context_line":"                \u0027.expiring_objects\u0027, task_container, 0, 1))"},{"line_number":1462,"context_line":"        # delete_ts (now-350) \u003e NormalTimestamp(mock_past - delay \u003d now-400)"}],"source_content_type":"text/x-python","patch_set":2,"id":"6c3b3b95_ecbe6e96","line":1459,"updated":"2026-02-26 17:34:33.000000000","message":"this didn\u0027t fail when I reverted the change back to use Timestamp, I think because the mocked time is being used by whichever timestamp class","commit_id":"a5ab99ab51cc062268238598842ac797a6b0686d"}]}
