)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"c79fc70f_d59e8c48","updated":"2024-08-01 13:35:15.000000000","message":"I\u0027d like to add these metrics.\n\nfollow-up-squash patch with suggested changes: https://review.opendev.org/c/openstack/swift/+/925478\n\nBut -1 because I think the ordering or delayed vs skipped case should be swapped for the stats to make more sense (see ``test_iter_task_to_expire_with_skipped_and_delayed_tasks`` in my follow-up-squash patch). \n\nAlso I\u0027m not convinced that ``future`` is useful and possibly confusing given that all other ``list_tasks`` stats are actual counts of listed objects whereas that stat is a count of some of the listed containers.\n\nI think the code change is independent of the parent patches and with the follow-up we have enough test coverage that this patch could be made against master and maybe merged.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"d2a8754d_8001e755","updated":"2024-08-01 13:21:21.000000000","message":"LGTM, I think we could rebase this on master and just merge it.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"642a6d02_2920bf12","updated":"2024-08-01 21:16:12.000000000","message":"thanks all for the reviews.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"b377d91a_debc7d4c","updated":"2024-08-02 10:35:39.000000000","message":"LGTM\n\nI\u0027ll follow up on some of the off-topic/nits stuff","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"a8653c8cf8b1c21d10635b127a85003d7153f9af","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"b64d6d14_6947395a","updated":"2024-08-02 10:51:09.000000000","message":"follow up here https://review.opendev.org/c/openstack/swift/+/925575 expirer stats: add test coverage for \u0027errors\u0027 stat","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ca758c493cc77659537c6bc52d07e90cdead3fc3","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"dd5222be_4b389454","updated":"2024-08-02 17:31:07.000000000","message":"recheck\n\nunrelated probe test failure, see last comment","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"154d518aa304d32e9a581dfda2bf60a9545dc6fc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"6bd00b99_a6b5df44","updated":"2024-08-02 23:41:06.000000000","message":"recheck\nunrelated failures.","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"1c9f46ceeb7b30c74c9609179843c3a2a83230eb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"04c6fc06_86460322","updated":"2024-08-02 13:43:40.000000000","message":"recheck \n\nTask \nCollect logs, artifacts and docs\n  failed running on host \ndebian-bookworm\nssh: connect to host 104.239.145.177 port 22: Connection timed out\nrsync: connection unexpectedly closed (0 bytes received so far) [Receiver]\nrsync error: unexplained error (code 255) at io.c(231) [Receiver\u003d3.2.7]\n Task \nCollect logs, artifacts and docs\n  failed running on host \ndebian-bookworm\nssh: connect to host 104.239.145.177 port 22: Connection timed out\nrsync: connection unexpectedly closed (0 bytes received so far) [Receiver]\nrsync error: unexplained error (code 255) at io.c(231) [Receiver\u003d3.2.7]","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"62b361586739294f4de3d18acbdce1d1eac51882","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"e6c3b801_82aa2db0","updated":"2024-08-02 17:30:32.000000000","message":"this failed in the gate again, but zuul has yet to record its vote here\n\nfailed job is \n probe tests https://zuul.opendev.org/t/openstack/build/fa1c55d78b1344a3910b894ac7801c86\n\nI think the failed probe test is unrelated and spurious:\n```\nswiftclient.exceptions.ClientException: Object GET failed: http://127.0.0.1:8080/v1/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf 503 Service Unavailable\n```\n\nthe test hasn\u0027t done much at this point: uploaded an object and trying to get it\n\nproxy logs the 503:\n```\nAug  2 15:47:08 np0038115858 proxy-server[53718]: Object returning 503 for [] (txn: txd9494dc2639d440d94db4-0066acff7b) (client_ip: 127.0.0.1)\n```\n\nobject servers returned 200 and 5 x 404 (did backend requests span the x-delete-at time?)\n```\nAug  2 15:47:08 np0038115858 object-server[53693]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb5/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 404 251 0.002334 (txn: txd9494dc2639d440d94db4-0066acff7b)\nAug  2 15:47:08 np0038115858 object-server[53693]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb1/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 200 918623 0.008530 (txn: txd9494dc2639d440d94db4-0066acff7b)\nAug  2 15:47:08 np0038115858 object-server[53707]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb3/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 404 251 0.002247 (txn: txd9494dc2639d440d94db4-0066acff7b)\nAug  2 15:47:08 np0038115858 object-server[53707]: STDERR: (53707) accepted (\u0027127.0.0.1\u0027, 53378)\nAug  2 15:47:08 np0038115858 object-server[53707]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb7/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 404 212 0.001224 (txn: txd9494dc2639d440d94db4-0066acff7b)\nAug  2 15:47:08 np0038115858 object-server[53706]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb4/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 404 251 0.001650 (txn: txd9494dc2639d440d94db4-0066acff7b)\nAug  2 15:47:08 np0038115858 object-server[53706]: STDERR: (53706) accepted (\u0027127.0.0.1\u0027, 35554)\nAug  2 15:47:08 np0038115858 object-server[53706]: STDERR: 127.0.0.1 - - [02/Aug/2024 15:47:08] \"GET /sdb8/400/AUTH_test/container-ee842c57-4f93-49cb-baf4-bd92dcd7c400/object-88b6c485-d2a1-4e88-af19-3fc0f645b6cf HTTP/1.1\" 404 212 0.001674 (txn: txd9494dc2639d440d94db4-0066acff7b)\n\n```","commit_id":"b400a1fdb3ae609d5f86d4d0f1b590640337b8f0"}],"swift/obj/expirer.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":679,"context_line":"                except ValueError:"},{"line_number":680,"context_line":"                    self.logger.exception(\u0027Unexcepted error handling task %r\u0027 %"},{"line_number":681,"context_line":"                                          task_object)"},{"line_number":682,"context_line":"                    continue"},{"line_number":683,"context_line":"                is_async \u003d o.get(\u0027content_type\u0027) \u003d\u003d ASYNC_DELETE_TYPE"},{"line_number":684,"context_line":"                delay_reaping \u003d self.get_delay_reaping(target_account,"},{"line_number":685,"context_line":"                                                       target_container)"}],"source_content_type":"text/x-python","patch_set":1,"id":"ca77fad6_bd4b5cef","line":682,"updated":"2024-08-01 13:21:21.000000000","message":"this is another continue through the loop that might be interesting to instrument.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":679,"context_line":"                except ValueError:"},{"line_number":680,"context_line":"                    self.logger.exception(\u0027Unexcepted error handling task %r\u0027 %"},{"line_number":681,"context_line":"                                          task_object)"},{"line_number":682,"context_line":"                    continue"},{"line_number":683,"context_line":"                is_async \u003d o.get(\u0027content_type\u0027) \u003d\u003d ASYNC_DELETE_TYPE"},{"line_number":684,"context_line":"                delay_reaping \u003d self.get_delay_reaping(target_account,"},{"line_number":685,"context_line":"                                                       target_container)"}],"source_content_type":"text/x-python","patch_set":1,"id":"ab4eee23_69fb1f40","line":682,"in_reply_to":"ca77fad6_bd4b5cef","updated":"2024-08-01 21:16:12.000000000","message":"Added a new stat ``tasks.parse_errors``","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":687,"context_line":"                if delete_timestamp \u003e Timestamp.now():"},{"line_number":688,"context_line":"                    # we shouldn\u0027t yield ANY more objects that can\u0027t reach"},{"line_number":689,"context_line":"                    # the expiration date yet."},{"line_number":690,"context_line":"                    self.logger.increment(\u0027list_tasks.future\u0027)"},{"line_number":691,"context_line":"                    break"},{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"}],"source_content_type":"text/x-python","patch_set":1,"id":"b79710ca_88f17729","line":690,"updated":"2024-08-01 13:35:15.000000000","message":"I\u0027m not sure how useful this stat is. It will be emitted zero or one time per container, so it\u0027s not a count of how many future tasks there are in the queue.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":687,"context_line":"                if delete_timestamp \u003e Timestamp.now():"},{"line_number":688,"context_line":"                    # we shouldn\u0027t yield ANY more objects that can\u0027t reach"},{"line_number":689,"context_line":"                    # the expiration date yet."},{"line_number":690,"context_line":"                    self.logger.increment(\u0027list_tasks.future\u0027)"},{"line_number":691,"context_line":"                    break"},{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"}],"source_content_type":"text/x-python","patch_set":1,"id":"cda89879_06ba7fd2","line":690,"in_reply_to":"b79710ca_88f17729","updated":"2024-08-01 21:16:12.000000000","message":"ACK. I will have it removed.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":688,"context_line":"                    # we shouldn\u0027t yield ANY more objects that can\u0027t reach"},{"line_number":689,"context_line":"                    # the expiration date yet."},{"line_number":690,"context_line":"                    self.logger.increment(\u0027list_tasks.future\u0027)"},{"line_number":691,"context_line":"                    break"},{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"},{"line_number":694,"context_line":"                    # we shouldn\u0027t yield the object during the delay"}],"source_content_type":"text/x-python","patch_set":1,"id":"4eeb6e40_50b308a6","line":691,"updated":"2024-08-01 13:21:21.000000000","message":"this stat will be interesting... since it always emits \"one\" and then breaks","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":688,"context_line":"                    # we shouldn\u0027t yield ANY more objects that can\u0027t reach"},{"line_number":689,"context_line":"                    # the expiration date yet."},{"line_number":690,"context_line":"                    self.logger.increment(\u0027list_tasks.future\u0027)"},{"line_number":691,"context_line":"                    break"},{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"},{"line_number":694,"context_line":"                    # we shouldn\u0027t yield the object during the delay"}],"source_content_type":"text/x-python","patch_set":1,"id":"f86ee3a7_b806f148","line":691,"in_reply_to":"4eeb6e40_50b308a6","updated":"2024-08-01 21:16:12.000000000","message":"I agree, if we want to monitor how many containers are in future, that\u0027ll be in a different patch. I will have it removed.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"},{"line_number":694,"context_line":"                    # we shouldn\u0027t yield the object during the delay"},{"line_number":695,"context_line":"                    self.logger.increment(\u0027list_tasks.delayed\u0027)"},{"line_number":696,"context_line":"                    continue"},{"line_number":697,"context_line":""},{"line_number":698,"context_line":"                # Only one expirer daemon assigned for each task"}],"source_content_type":"text/x-python","patch_set":1,"id":"635bf039_d825d7f7","line":695,"range":{"start_line":695,"start_character":43,"end_line":695,"end_character":53},"updated":"2024-08-01 13:35:15.000000000","message":"just \u0027tasks.*\u0027 would probably be sufficient IMHO","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":692,"context_line":"                if delete_timestamp \u003e Timestamp(time() - delay_reaping) \\"},{"line_number":693,"context_line":"                        and not is_async:"},{"line_number":694,"context_line":"                    # we shouldn\u0027t yield the object during the delay"},{"line_number":695,"context_line":"                    self.logger.increment(\u0027list_tasks.delayed\u0027)"},{"line_number":696,"context_line":"                    continue"},{"line_number":697,"context_line":""},{"line_number":698,"context_line":"                # Only one expirer daemon assigned for each task"}],"source_content_type":"text/x-python","patch_set":1,"id":"b0d1c62c_8e6aa09f","line":695,"range":{"start_line":695,"start_character":43,"end_line":695,"end_character":53},"in_reply_to":"635bf039_d825d7f7","updated":"2024-08-01 21:16:12.000000000","message":"Acknowledged","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":698,"context_line":"                # Only one expirer daemon assigned for each task"},{"line_number":699,"context_line":"                if self.hash_mod(\u0027%s/%s\u0027 % (task_container, task_object),"},{"line_number":700,"context_line":"                                 divisor) !\u003d my_index:"},{"line_number":701,"context_line":"                    self.logger.increment(\u0027list_tasks.skipped\u0027)"},{"line_number":702,"context_line":"                    continue"},{"line_number":703,"context_line":""},{"line_number":704,"context_line":"                self.logger.increment(\u0027list_tasks.assigned\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"bb06e766_27225ffc","line":701,"updated":"2024-08-01 13:35:15.000000000","message":"The current order of the ``continue`` conditions will result in multiple counting of delayed tasks.\n\nI think that we should re-order the conditions so that tasks not assigned to this process are skipped before the check for delay reaping.\n\nskipped \u003d\u003d \"it\u0027s not my task\"\ndelayed \u003d\u003d \"it\u0027s my task but delayed\"\nassigned \u003d\u003d \"it\u0027s my task\"\n\nThat way the global sum of ``delayed`` and ``assigned`` should equal the number of queued tasks up to the present time. And, the ratio of ``assigned / (assigned + delayed)`` gives us an accurate measure of the inefficiency of listing delayed tasks.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":698,"context_line":"                # Only one expirer daemon assigned for each task"},{"line_number":699,"context_line":"                if self.hash_mod(\u0027%s/%s\u0027 % (task_container, task_object),"},{"line_number":700,"context_line":"                                 divisor) !\u003d my_index:"},{"line_number":701,"context_line":"                    self.logger.increment(\u0027list_tasks.skipped\u0027)"},{"line_number":702,"context_line":"                    continue"},{"line_number":703,"context_line":""},{"line_number":704,"context_line":"                self.logger.increment(\u0027list_tasks.assigned\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"68f34976_b2938edd","line":701,"in_reply_to":"bb06e766_27225ffc","updated":"2024-08-01 21:16:12.000000000","message":"Acknowledged","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"}],"test/unit/obj/test_expirer.py":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"bfaccb1f515cad65daceb5b81bbe65de4909a43c","unresolved":true,"context_lines":[{"line_number":2112,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2113,"context_line":"        )"},{"line_number":2114,"context_line":""},{"line_number":2115,"context_line":"    def test_iter_task_to_expire_with_skipped_tasks(self):"},{"line_number":2116,"context_line":"        # No task is assigned to the tested expirer"},{"line_number":2117,"context_line":"        my_index \u003d 0"},{"line_number":2118,"context_line":"        divisor \u003d 10"}],"source_content_type":"text/x-python","patch_set":1,"id":"602b6b05_3aa61d1a","line":2115,"updated":"2024-08-01 04:38:25.000000000","message":"no current test case would emit new stat ``list_tasks.skipped``. I had to add a new one.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2112,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2113,"context_line":"        )"},{"line_number":2114,"context_line":""},{"line_number":2115,"context_line":"    def test_iter_task_to_expire_with_skipped_tasks(self):"},{"line_number":2116,"context_line":"        # No task is assigned to the tested expirer"},{"line_number":2117,"context_line":"        my_index \u003d 0"},{"line_number":2118,"context_line":"        divisor \u003d 10"}],"source_content_type":"text/x-python","patch_set":1,"id":"c8334d89_ef18e3d0","line":2115,"in_reply_to":"30f35b2f_23c29a92","updated":"2024-08-01 21:16:12.000000000","message":"how did I miss ``test_process_based_concurrency``?! I have squashed your changes, thanks!","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":2112,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2113,"context_line":"        )"},{"line_number":2114,"context_line":""},{"line_number":2115,"context_line":"    def test_iter_task_to_expire_with_skipped_tasks(self):"},{"line_number":2116,"context_line":"        # No task is assigned to the tested expirer"},{"line_number":2117,"context_line":"        my_index \u003d 0"},{"line_number":2118,"context_line":"        divisor \u003d 10"}],"source_content_type":"text/x-python","patch_set":1,"id":"f7117ee9_796fe64b","line":2115,"in_reply_to":"602b6b05_3aa61d1a","updated":"2024-08-01 13:21:21.000000000","message":"how is that possible?  any test that uses process \u003d N should have to skip...\n\n```\ndiff --git a/test/unit/obj/test_expirer.py b/test/unit/obj/test_expirer.py\nindex 2f440fdf6..ed2ccb2c2 100644\n--- a/test/unit/obj/test_expirer.py\n+++ b/test/unit/obj/test_expirer.py\n@@ -1180,6 +1180,7 @@ class TestObjectExpirer(TestCase):\n                 self.deleted_objects[task_container].add(task_object)\n \n         x \u003d ObjectExpirer(self.conf, swift\u003dself.fake_swift)\n+        x.logger \u003d self.logger\n \n         deleted_objects \u003d defaultdict(set)\n         for i in range(3):\n@@ -1191,6 +1192,12 @@ class TestObjectExpirer(TestCase):\n                 self.assertFalse(deleted_objects[task_container] \u0026 deleted)\n                 deleted_objects[task_container] |\u003d deleted\n \n+        self.assertEqual({\n+            \u0027list_tasks.assigned\u0027: 10,\n+            \u0027list_tasks.future\u0027: 3,\n+            \u0027list_tasks.skipped\u0027: 20,\n+        }, x.logger.statsd_client.get_increment_counts())\n+\n         # sort for comparison\n         deleted_objects \u003d {\n             con: sorted(o_set) for con, o_set in deleted_objects.items()}\n```","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":2112,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2113,"context_line":"        )"},{"line_number":2114,"context_line":""},{"line_number":2115,"context_line":"    def test_iter_task_to_expire_with_skipped_tasks(self):"},{"line_number":2116,"context_line":"        # No task is assigned to the tested expirer"},{"line_number":2117,"context_line":"        my_index \u003d 0"},{"line_number":2118,"context_line":"        divisor \u003d 10"}],"source_content_type":"text/x-python","patch_set":1,"id":"30f35b2f_23c29a92","line":2115,"in_reply_to":"602b6b05_3aa61d1a","updated":"2024-08-01 13:35:15.000000000","message":"👍 I found that `test_process_based_concurrency` hits skipped case but this new test is a little more targeted","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":2121,"context_line":"        ]"},{"line_number":2122,"context_line":""},{"line_number":2123,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2124,"context_line":"            return 1"},{"line_number":2125,"context_line":""},{"line_number":2126,"context_line":"        with mock.patch.object(self.expirer, \"hash_mod\", mock_hash_mod):"},{"line_number":2127,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":1,"id":"0d36639e_7bf1124b","line":2124,"updated":"2024-08-01 13:35:15.000000000","message":"mock\u0027s `return_value` will take care of this without needing to define this function","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":2121,"context_line":"        ]"},{"line_number":2122,"context_line":""},{"line_number":2123,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2124,"context_line":"            return 1"},{"line_number":2125,"context_line":""},{"line_number":2126,"context_line":"        with mock.patch.object(self.expirer, \"hash_mod\", mock_hash_mod):"},{"line_number":2127,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":1,"id":"9181affa_fb23db66","line":2124,"updated":"2024-08-01 13:21:21.000000000","message":"this is a little on the nose to me, I\u0027d prefer:\n\n```\n    def test_iter_task_to_expire_with_skipped_tasks(self):\n        processes \u003d 10\n        task_account_container_list \u003d [\n            (\".expiring_objects\", self.past_time_container),\n            (\".expiring_objects\", self.just_past_time_container),\n            (\".expiring_objects\", self.future_time_container),\n        ]\n\n        total_tasks \u003d 0\n        for i in range(processes):\n            yielded_tasks \u003d list(\n                self.expirer.iter_task_to_expire(\n                    task_account_container_list, i, processes\n                ))\n            total_tasks +\u003d len(yielded_tasks)\n        # ten tasks, each process gets 1 on overage\n        self.assertEqual(10, total_tasks)\n\n        # each process was assigned 1 task and skipped 9\n        self.assertEqual({\n            \u0027list_tasks.assigned\u0027: 10,\n            \u0027list_tasks.future\u0027: 20,\n            \u0027list_tasks.skipped\u0027: 90,\n        }, self.expirer.logger.statsd_client.get_increment_counts())\n\n```\n\nbenifits:\n\n * less mocking, tests more real code\n * tests multiple interacting branches at once, more coverage\n * more setUp reuse, more likely to alert maintainers of unintended changes\n * shows tasks are processed (by someone) and why they\u0027re skipped (because someone else will process them)\n\ndisadvantages:\n\n * real hash_mod depends on name which is based on current time, can\u0027t easily make assertions about any *single* divisor\n * interacts with multiple branches in UUT - more difficult to pause a debugger in a specific iteration/branch\n * more setUp reuse, possibly more likely to require maintenance/churn\n * have to infer from math that skipped is getting incremented *when we skip*\n \nI\u0027m curious how you weigh these trade offs.\n\nObviously you wrote the test the way you did and not the way I would have - Do you see this as merely preference or are there any tradeoffs we would agree as maintainers that should be objectively prioritized when adding tests?  What the primary reasons we add tests?","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2121,"context_line":"        ]"},{"line_number":2122,"context_line":""},{"line_number":2123,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2124,"context_line":"            return 1"},{"line_number":2125,"context_line":""},{"line_number":2126,"context_line":"        with mock.patch.object(self.expirer, \"hash_mod\", mock_hash_mod):"},{"line_number":2127,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":1,"id":"b5ebf91a_e5e93270","line":2124,"in_reply_to":"0d36639e_7bf1124b","updated":"2024-08-01 21:16:12.000000000","message":"Done","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2121,"context_line":"        ]"},{"line_number":2122,"context_line":""},{"line_number":2123,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2124,"context_line":"            return 1"},{"line_number":2125,"context_line":""},{"line_number":2126,"context_line":"        with mock.patch.object(self.expirer, \"hash_mod\", mock_hash_mod):"},{"line_number":2127,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":1,"id":"a963321a_fecc81c9","line":2124,"in_reply_to":"9181affa_fb23db66","updated":"2024-08-01 21:16:12.000000000","message":"thanks for adding this test case! from maintainers prospective, I\u0027d like to have both two types of test cases. the first type to test specific logic or branch within the target function (perfect to use mock and etc), and the second type to test expected behaviors when the target function being called multiple times by different processes. the latter is kind of like an integration test for this function.\n\nI have been following this principle in the past, see the test cases (``test_concurrent_requests``, ``test_concurrent_requests_all_token_requests_fail`` and ``test_concurrent_requests_pass_token_ttl``, versus other test cases) for the cooperative token: https://review.opendev.org/c/openstack/swift/+/890174/33/test/unit/common/test_utils.py\n\nI see your new test case as the second type, and very happy to get it squashed!","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":2138,"context_line":"        )"},{"line_number":2139,"context_line":""},{"line_number":2140,"context_line":"        # Only one task is assigned to the tested expirer"},{"line_number":2141,"context_line":"        self.expirer.logger.statsd_client.clear()"},{"line_number":2142,"context_line":"        my_index \u003d 1"},{"line_number":2143,"context_line":"        call_count \u003d [0]"},{"line_number":2144,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"db43235a_c4b9b82b","line":2141,"updated":"2024-08-01 13:21:21.000000000","message":"normally when I have to clean in a test it\u0027s a smell that ideally it would be a new/fresh test.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2138,"context_line":"        )"},{"line_number":2139,"context_line":""},{"line_number":2140,"context_line":"        # Only one task is assigned to the tested expirer"},{"line_number":2141,"context_line":"        self.expirer.logger.statsd_client.clear()"},{"line_number":2142,"context_line":"        my_index \u003d 1"},{"line_number":2143,"context_line":"        call_count \u003d [0]"},{"line_number":2144,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"94e720b0_f9c71d31","line":2141,"in_reply_to":"db43235a_c4b9b82b","updated":"2024-08-01 21:16:12.000000000","message":"I have remove the first unrealistic case within this test case, so no need to use ``statsd_client.clear()`` anymore.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b15aa6efc73248aba7315e16b2650114262bacaf","unresolved":true,"context_lines":[{"line_number":2144,"context_line":""},{"line_number":2145,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2146,"context_line":"            call_count[0] +\u003d 1"},{"line_number":2147,"context_line":"            return call_count[0]"},{"line_number":2148,"context_line":""},{"line_number":2149,"context_line":"        expected \u003d ["},{"line_number":2150,"context_line":"            self.make_task("}],"source_content_type":"text/x-python","patch_set":1,"id":"b796d78a_f2bcf4bc","line":2147,"updated":"2024-08-01 13:35:15.000000000","message":"similar to above comment - use mock\u0027s side_effect","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2144,"context_line":""},{"line_number":2145,"context_line":"        def mock_hash_mod(name, divisor):"},{"line_number":2146,"context_line":"            call_count[0] +\u003d 1"},{"line_number":2147,"context_line":"            return call_count[0]"},{"line_number":2148,"context_line":""},{"line_number":2149,"context_line":"        expected \u003d ["},{"line_number":2150,"context_line":"            self.make_task("}],"source_content_type":"text/x-python","patch_set":1,"id":"519d9690_62bf2c50","line":2147,"in_reply_to":"b796d78a_f2bcf4bc","updated":"2024-08-01 21:16:12.000000000","message":"Done","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":true,"context_lines":[{"line_number":2753,"context_line":"        self.assertEqual("},{"line_number":2754,"context_line":"            {\u0027list_tasks.assigned\u0027: 1000,"},{"line_number":2755,"context_line":"                \u0027list_tasks.delayed\u0027: 2000,"},{"line_number":2756,"context_line":"                \u0027objects\u0027: 1000},"},{"line_number":2757,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2758,"context_line":"        )"},{"line_number":2759,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"7ad0d364_193cc780","line":2756,"updated":"2024-08-01 13:21:21.000000000","message":"I find the indentation on of this dict literal un-usual; but flake8 doesn\u0027t seem to complain.","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"0be518a7e6d42085386671d37341fcc4765cf371","unresolved":false,"context_lines":[{"line_number":2753,"context_line":"        self.assertEqual("},{"line_number":2754,"context_line":"            {\u0027list_tasks.assigned\u0027: 1000,"},{"line_number":2755,"context_line":"                \u0027list_tasks.delayed\u0027: 2000,"},{"line_number":2756,"context_line":"                \u0027objects\u0027: 1000},"},{"line_number":2757,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2758,"context_line":"        )"},{"line_number":2759,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"546f28f3_cbecd6c9","line":2756,"in_reply_to":"7ad0d364_193cc780","updated":"2024-08-01 21:16:12.000000000","message":"Done","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"bfaccb1f515cad65daceb5b81bbe65de4909a43c","unresolved":true,"context_lines":[{"line_number":2913,"context_line":"        self.assertEqual("},{"line_number":2914,"context_line":"            {\u0027objects\u0027: 5,"},{"line_number":2915,"context_line":"             \u0027list_tasks.assigned\u0027: 10,"},{"line_number":2916,"context_line":"             \u0027list_tasks.future\u0027: 1,"},{"line_number":2917,"context_line":"             \u0027errors\u0027: 5},"},{"line_number":2918,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2919,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":1,"id":"1e13527e_3b69d4b1","line":2916,"updated":"2024-08-01 04:38:25.000000000","message":"here is \"list_tasks.future\"","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1e4361e6b9461dfc0b1f18fe04c8136e88ec90c","unresolved":false,"context_lines":[{"line_number":2913,"context_line":"        self.assertEqual("},{"line_number":2914,"context_line":"            {\u0027objects\u0027: 5,"},{"line_number":2915,"context_line":"             \u0027list_tasks.assigned\u0027: 10,"},{"line_number":2916,"context_line":"             \u0027list_tasks.future\u0027: 1,"},{"line_number":2917,"context_line":"             \u0027errors\u0027: 5},"},{"line_number":2918,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":2919,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":1,"id":"5ab80ec2_f5f9beda","line":2916,"in_reply_to":"1e13527e_3b69d4b1","updated":"2024-08-01 13:21:21.000000000","message":"Acknowledged","commit_id":"6775db1ae59873a1a9f54f7d0d9e79172c237822"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":638,"context_line":""},{"line_number":639,"context_line":"            def __init__(self, conf, swift):"},{"line_number":640,"context_line":"                super(ObjectExpirer, self).__init__(conf, swift\u003dswift)"},{"line_number":641,"context_line":"                self.processes \u003d 3"},{"line_number":642,"context_line":"                self.deleted_objects \u003d {}"},{"line_number":643,"context_line":""},{"line_number":644,"context_line":"            def delete_object(self, target_path, delete_timestamp,"}],"source_content_type":"text/x-python","patch_set":2,"id":"d0edde58_a86490fe","line":641,"updated":"2024-08-02 10:35:39.000000000","message":"off-topic: processes is configurable, so why not pass it in the conf?","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":641,"context_line":"                self.processes \u003d 3"},{"line_number":642,"context_line":"                self.deleted_objects \u003d {}"},{"line_number":643,"context_line":""},{"line_number":644,"context_line":"            def delete_object(self, target_path, delete_timestamp,"},{"line_number":645,"context_line":"                              task_account, task_container, task_object,"},{"line_number":646,"context_line":"                              is_async_delete):"},{"line_number":647,"context_line":"                if task_container not in self.deleted_objects:"}],"source_content_type":"text/x-python","patch_set":2,"id":"88aaab6f_a355fb26","line":644,"updated":"2024-08-02 10:35:39.000000000","message":"off-topic: this subclassing is just to override a method to capture some call args. We usually use mock to do that.","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":645,"context_line":"                              task_account, task_container, task_object,"},{"line_number":646,"context_line":"                              is_async_delete):"},{"line_number":647,"context_line":"                if task_container not in self.deleted_objects:"},{"line_number":648,"context_line":"                    self.deleted_objects[task_container] \u003d set()"},{"line_number":649,"context_line":"                self.deleted_objects[task_container].add(task_object)"},{"line_number":650,"context_line":""},{"line_number":651,"context_line":"        x \u003d ObjectExpirer(self.conf, swift\u003dself.fake_swift)"}],"source_content_type":"text/x-python","patch_set":2,"id":"e72f67c3_7d535627","line":648,"updated":"2024-08-02 10:35:39.000000000","message":"off-topic: this is unnecessary if self.deleted_objects is a defaultdict(set) ... oh, and at line 658 it IS replaced by a defaultdict(set)","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":664,"context_line":"        self.assertEqual({"},{"line_number":665,"context_line":"            \u0027tasks.assigned\u0027: 10,"},{"line_number":666,"context_line":"            \u0027tasks.skipped\u0027: 20,"},{"line_number":667,"context_line":"        }, self.logger.statsd_client.get_increment_counts())"},{"line_number":668,"context_line":""},{"line_number":669,"context_line":"        # sort for comparison"},{"line_number":670,"context_line":"        deleted_objects \u003d {"}],"source_content_type":"text/x-python","patch_set":2,"id":"06dde8e4_af166d32","line":667,"updated":"2024-08-02 10:35:39.000000000","message":"ok, we don\u0027t get \u0027objects\u0027 stat because it is issued in ``delete_objects`` which was overridden. That\u0027s a shame, perhaps we could mock a little deeper in the call tree e.g. ``pop_queue``, or ``delete_actual_object``","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":1081,"context_line":"                task_account_container_list, my_index, divisor)),"},{"line_number":1082,"context_line":"            expected)"},{"line_number":1083,"context_line":"        self.assertEqual("},{"line_number":1084,"context_line":"            {\u0027tasks.assigned\u0027: 5, \u0027tasks.parse_errors\u0027: 2},"},{"line_number":1085,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":1086,"context_line":"        )"},{"line_number":1087,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"bdd0c08f_a2ab619d","line":1084,"updated":"2024-08-02 10:35:39.000000000","message":"nice, ``tasks.parse_errors`` has been added","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":1184,"context_line":"                    task_account_container_list, i, processes"},{"line_number":1185,"context_line":"                ))"},{"line_number":1186,"context_line":"            total_tasks +\u003d len(yielded_tasks)"},{"line_number":1187,"context_line":"        # Ten tasks, each process gets 1 on overage."},{"line_number":1188,"context_line":"        # N.B. each process may get 0 or multiple tasks, since hash_mod is"},{"line_number":1189,"context_line":"        # based on names of current time."},{"line_number":1190,"context_line":"        self.assertEqual(10, total_tasks)"}],"source_content_type":"text/x-python","patch_set":2,"id":"955ffcae_272e6ac2","line":1187,"range":{"start_line":1187,"start_character":44,"end_line":1187,"end_character":51},"updated":"2024-08-02 10:35:39.000000000","message":"nit: s/overage/average/","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"dce70fff99af9fd2f1b4cfff9fcf7421b42d469f","unresolved":true,"context_lines":[{"line_number":1571,"context_line":"            self.expirer.run_once()"},{"line_number":1572,"context_line":"        self.assertEqual(self.expirer.report_objects, 10)"},{"line_number":1573,"context_line":"        self.assertEqual("},{"line_number":1574,"context_line":"            {\u0027tasks.assigned\u0027: 10, \u0027objects\u0027: 10},"},{"line_number":1575,"context_line":"            self.expirer.logger.statsd_client.get_increment_counts()"},{"line_number":1576,"context_line":"        )"},{"line_number":1577,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"01071aa0_159227a7","line":1574,"updated":"2024-08-02 10:35:39.000000000","message":"here we get the previously untested \u0027objects\u0027 stat covered too 😊\n\noff-topic: I see no assertions for the \u0027errors\u0027 stats 😞","commit_id":"da16db41879d28556b2e04053935a80701bc4e2f"}]}
